diff --git a/src/bin/bin.cpp b/src/bin/bin.cpp index c4efb0441..684bc4e24 100644 --- a/src/bin/bin.cpp +++ b/src/bin/bin.cpp @@ -1,3175 +1,3176 @@ /* 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 "mainwindow.h" #include "projectitemmodel.h" #include "projectclip.h" #include "projectsubclip.h" #include "projectfolder.h" #include "projectfolderup.h" #include "kdenlivesettings.h" #include "project/projectmanager.h" #include "project/clipmanager.h" #include "project/dialogs/slideshowclip.h" #include "project/jobs/jobmanager.h" #include "monitor/monitor.h" #include "doc/kdenlivedoc.h" #include "dialogs/clipcreationdialog.h" #include "ui_qtextclip_ui.h" #include "titler/titlewidget.h" #include "core.h" #include "utils/KoIconUtils.h" #include "mltcontroller/clipcontroller.h" #include "mltcontroller/clippropertiescontroller.h" #include "project/projectcommands.h" #include "project/invaliddialog.h" #include "projectsortproxymodel.h" #include "bincommands.h" #include "mlt++/Mlt.h" #include #include #include #include #include #include #include #include #include #include #include #include #include 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) {} void MyTreeView::mousePressEvent(QMouseEvent *event) { if (event->button() == Qt::LeftButton) { m_startPos = event->pos(); } QTreeView::mousePressEvent(event); } 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) { int distance = (event->pos() - m_startPos).manhattanLength(); if (distance >= QApplication::startDragDistance()) dragged = performDrag(); } if (!dragged) QTreeView::mouseMoveEvent(event); } 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; QDrag *drag = new QDrag(this); drag->setMimeData(model()->mimeData(indexes)); QModelIndex ix = indexes.first(); 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; } BinMessageWidget::BinMessageWidget(QWidget *parent) : KMessageWidget(parent) {} BinMessageWidget::BinMessageWidget(const QString &text, QWidget *parent) : KMessageWidget(text, parent) {} bool BinMessageWidget::event(QEvent* ev) { if (ev->type() == QEvent::Hide || ev->type() == QEvent::Close) emit messageClosing(); return KMessageWidget::event(ev); } SmallJobLabel::SmallJobLabel(QWidget *parent) : QPushButton(parent) , m_action(NULL) { setFixedWidth(0); setFlat(true); m_timeLine = new QTimeLine(500, this); QObject::connect(m_timeLine, SIGNAL(valueChanged(qreal)), this, SLOT(slotTimeLineChanged(qreal))); QObject::connect(m_timeLine, SIGNAL(finished()), this, SLOT(slotTimeLineFinished())); hide(); } const QString SmallJobLabel::getStyleSheet(const QPalette &p) { KColorScheme scheme(p.currentColorGroup(), KColorScheme::Window, KSharedConfig::openConfig(KdenliveSettings::colortheme())); 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, 0, this)) { 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, 0, this)) { 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) { if (event->type() == QEvent::ShortcutOverride) { if (((QKeyEvent*)event)->key() == Qt::Key_Escape) { emit clearSearchLine(); } } return QObject::eventFilter(obj, event); } Bin::Bin(QWidget* parent) : QWidget(parent) , isLoading(false) , m_itemModel(NULL) , m_itemView(NULL) , m_rootFolder(NULL) , m_folderUp(NULL) , m_jobManager(NULL) , m_doc(NULL) , m_extractAudioAction(NULL) , m_transcodeAction(NULL) , m_clipsActionsMenu(NULL) , m_inTimelineAction(NULL) , m_listType((BinViewType) KdenliveSettings::binMode()) , m_iconSize(160, 90) , m_propertiesPanel(NULL) , m_blankThumb() , m_invalidClipDialog(NULL) , m_gainedFocus(false) { QVBoxLayout *layout = new QVBoxLayout(this); // Create toolbar for buttons m_toolbar = new KToolBar(this); m_toolbar->setToolButtonStyle(Qt::ToolButtonIconOnly); layout->addWidget(m_toolbar); // Search line m_proxyModel = new ProjectSortProxyModel(this); m_proxyModel->setDynamicSortFilter(true); QLineEdit *searchLine = new QLineEdit(this); searchLine->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred); searchLine->setClearButtonEnabled(true); searchLine->setPlaceholderText(i18n("Search")); searchLine->setFocusPolicy(Qt::ClickFocus); connect(searchLine, SIGNAL(textChanged(const QString &)), m_proxyModel, SLOT(slotSetSearchString(const QString &))); LineEventEater *leventEater = new LineEventEater(this); searchLine->installEventFilter(leventEater); connect(leventEater, SIGNAL(clearSearchLine()), searchLine, SLOT(clear())); setFocusPolicy(Qt::ClickFocus); // Build item view model m_itemModel = new ProjectItemModel(this); // Connect models m_proxyModel->setSourceModel(m_itemModel); connect(m_itemModel, SIGNAL(dataChanged(const QModelIndex&,const QModelIndex&)), m_proxyModel, SLOT(slotDataChanged(const QModelIndex&,const QModelIndex&))); connect(m_itemModel, SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(rowsInserted(QModelIndex,int,int))); connect(m_itemModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SLOT(rowsRemoved(QModelIndex,int,int))); connect(m_proxyModel, SIGNAL(selectModel(QModelIndex)), this, SLOT(selectProxyModel(QModelIndex))); connect(m_itemModel, SIGNAL(itemDropped(QStringList, const QModelIndex &)), this, SLOT(slotItemDropped(QStringList, const QModelIndex &))); connect(m_itemModel, SIGNAL(itemDropped(const QList&, const QModelIndex &)), this, SLOT(slotItemDropped(const QList&, const QModelIndex &))); connect(m_itemModel, SIGNAL(effectDropped(QString, const QModelIndex &)), this, SLOT(slotEffectDropped(QString, const QModelIndex &))); connect(m_itemModel, SIGNAL(dataChanged(QModelIndex,QModelIndex,QVector)), this, SLOT(slotItemEdited(QModelIndex,QModelIndex,QVector))); connect(m_itemModel, SIGNAL(addClipCut(QString,int,int)), this, SLOT(slotAddClipCut(QString,int,int))); connect(this, &Bin::refreshPanel, this, &Bin::doRefreshPanel); // Zoom slider 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, SIGNAL(valueChanged(int)), this, SLOT(slotSetIconSize(int))); QWidgetAction * widgetslider = new QWidgetAction(this); widgetslider->setDefaultWidget(m_slider); // View type KSelectAction *listType = new KSelectAction(KoIconUtils::themedIcon(QStringLiteral("view-list-tree")), i18n("View Mode"), this); pCore->window()->actionCollection()->addAction(QStringLiteral("bin_view_mode"), listType); QAction *treeViewAction = listType->addAction(KoIconUtils::themedIcon(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(KoIconUtils::themedIcon(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, SIGNAL(triggered(bool)), this, SLOT(slotDisableEffects(bool))); disableEffects->setIcon(KoIconUtils::themedIcon(QStringLiteral("favorite"))); disableEffects->setData("disable_bin_effects"); disableEffects->setCheckable(true); disableEffects->setChecked(false); pCore->window()->actionCollection()->addAction(QStringLiteral("disable_bin_effects"), disableEffects); m_renameFolderAction = new QAction(i18n("Rename Folder"), this); connect(m_renameFolderAction, SIGNAL(triggered(bool)), this, SLOT(slotRenameFolder())); m_renameFolderAction->setData("rename_folder"); pCore->window()->actionCollection()->addAction("rename_folder", m_renameFolderAction); listType->setToolBarMode(KSelectAction::MenuMode); connect(listType, SIGNAL(triggered(QAction*)), this, SLOT(slotInitView(QAction*))); // Settings menu QMenu *settingsMenu = new QMenu(i18n("Settings"), this); settingsMenu->addAction(listType); QMenu *sliderMenu = new QMenu(i18n("Zoom"), this); sliderMenu->setIcon(KoIconUtils::themedIcon(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, SIGNAL(triggered(bool)), this, SLOT(slotShowDateColumn(bool))); m_showDesc = new QAction(i18n("Show description"), this); m_showDesc->setCheckable(true); connect(m_showDesc, SIGNAL(triggered(bool)), this, SLOT(slotShowDescColumn(bool))); settingsMenu->addAction(m_showDate); settingsMenu->addAction(m_showDesc); settingsMenu->addAction(disableEffects); QToolButton *button = new QToolButton; button->setIcon(KoIconUtils::themedIcon(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())); QAction *infoAction = m_toolbar->addWidget(m_infoLabel); m_jobsMenu = new QMenu(this); connect(m_jobsMenu, SIGNAL(aboutToShow()), this, SLOT(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_jobsMenu->addAction(m_cancelJobs); m_jobsMenu->addAction(m_discardCurrentClipJobs); 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(searchLine); m_binTreeViewDelegate = new BinItemDelegate(this); //connect(pCore->projectManager(), SIGNAL(projectOpened(Project*)), this, SLOT(setProject(Project*))); m_splitter = new QSplitter(this); m_headerInfo = QByteArray::fromBase64(KdenliveSettings::treeviewheaders().toLatin1()); layout->addWidget(m_splitter); m_propertiesPanel = new QWidget(this); m_splitter->addWidget(m_propertiesPanel); // Info widget for failed jobs, other errors m_infoMessage = new BinMessageWidget; layout->addWidget(m_infoMessage); m_infoMessage->setCloseButtonVisible(false); connect(m_infoMessage, SIGNAL(messageClosing()), this, SLOT(slotResetInfoMessage())); //m_infoMessage->setWordWrap(true); m_infoMessage->hide(); m_logAction = new QAction(i18n("Show Log"), this); m_logAction->setCheckable(false); connect(m_logAction, SIGNAL(triggered()), this, SLOT(slotShowJobLog())); connect(this, SIGNAL(requesteInvalidRemoval(QString,QUrl,QString)), this, SLOT(slotQueryRemoval(QString,QUrl,QString))); connect(this, &Bin::refreshAudioThumbs, this, &Bin::doRefreshAudioThumbs); connect(this, SIGNAL(displayBinMessage(QString,KMessageWidget::MessageType)), this, SLOT(doDisplayMessage(QString,KMessageWidget::MessageType))); } Bin::~Bin() { blockSignals(true); setEnabled(false); abortAudioThumbs(); foreach (QWidget * w, m_propertiesPanel->findChildren()) { delete w; } if (m_rootFolder) { while (!m_rootFolder->isEmpty()) { AbstractProjectItem *child = m_rootFolder->at(0); m_rootFolder->removeChild(child); delete child; } } delete m_rootFolder; delete m_itemView; delete m_jobManager; delete m_infoMessage; } void Bin::slotAbortAudioThumb(const QString &id) { if (!m_audioThumbsThread.isRunning()) return; QMutexLocker aMutex(&m_audioThumbMutex); m_audioThumbsList.removeAll(id); } void Bin::requestAudioThumbs(const QString &id) { if (!m_audioThumbsList.contains(id) && m_processingAudioThumb != id) { m_audioThumbMutex.lock(); m_audioThumbsList.append(id); m_audioThumbMutex.unlock(); processAudioThumbs(); } } void Bin::processAudioThumbs() { if (m_audioThumbsThread.isRunning()) return; m_audioThumbsThread = QtConcurrent::run(this, &Bin::slotCreateAudioThumbs); } void Bin::abortAudioThumbs() { if (!m_audioThumbsThread.isRunning()) return; if (!m_processingAudioThumb.isEmpty()) { ProjectClip *clip = m_rootFolder->clip(m_processingAudioThumb); if (clip) clip->abortAudioThumbs(); } m_audioThumbMutex.lock(); m_audioThumbsList.clear(); m_audioThumbMutex.unlock(); m_audioThumbsThread.waitForFinished(); } void Bin::slotCreateAudioThumbs() { int max = m_audioThumbsList.count(); int count = 0; while (!m_audioThumbsList.isEmpty()) { m_audioThumbMutex.lock(); max = qMax(max, m_audioThumbsList.count()); m_processingAudioThumb = m_audioThumbsList.takeFirst(); count++; m_audioThumbMutex.unlock(); emitMessage(i18n("Creating audio thumbnails") + QString(" (%1/%2)").arg(count).arg(max), ProcessingJobMessage); ProjectClip *clip = m_rootFolder->clip(m_processingAudioThumb); if (clip) clip->slotCreateAudioThumbs(); } m_audioThumbMutex.lock(); m_processingAudioThumb.clear(); m_audioThumbMutex.unlock(); emitMessage(i18n("Audio thumbnails done"), OperationCompletedMessage); } bool Bin::eventFilter(QObject *obj, QEvent *event) { if (event->type() == QEvent::MouseButtonRelease) { 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()); ClipController *ctl = NULL; if (idx.isValid()) { AbstractProjectItem *item = static_cast(m_proxyModel->mapToSource(idx).internalPointer()); if (item) { ProjectClip *clip = qobject_cast(item); if (clip) { ctl = clip->controller(); } } } m_gainedFocus = false; editMasterEffect(ctl); } // 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 { qDebug()<<" +++++++ NO VIEW-------!!"; } return true; } else if (event->type() == QEvent::Wheel) { QWheelEvent * e = static_cast(event); if (e && 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 = KoIconUtils::themedIcon(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 = KoIconUtils::themedIcon(ic.name()); m->setIcon(newIcon); } } void Bin::slotSaveHeaders() { if (m_itemView && 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 == true) ? 1 : -1; m_slider->setValue(m_slider->value() + progress); } Monitor *Bin::monitor() { return m_monitor; } const QStringList Bin::getFolderInfo(QModelIndex selectedIx) { QStringList folderInfo; QModelIndexList indexes; if (selectedIx.isValid()) { indexes << selectedIx; } else { indexes = m_proxyModel->selectionModel()->selectedIndexes(); } if (indexes.isEmpty()) { return folderInfo; } QModelIndex ix = indexes.first(); if (ix.isValid() && (m_proxyModel->selectionModel()->isSelected(ix) || selectedIx.isValid())) { AbstractProjectItem *currentItem = static_cast(m_proxyModel->mapToSource(ix).internalPointer()); while (currentItem->itemType() != AbstractProjectItem::FolderItem) { currentItem = currentItem->parent(); } if (currentItem == m_rootFolder) { // clip was added to root folder, leave folder info empty } else { folderInfo << currentItem->clipId(); folderInfo << currentItem->name(); } } return folderInfo; } void Bin::slotAddClip() { // Check if we are in a folder QStringList folderInfo = getFolderInfo(); ClipCreationDialog::createClipsCommand(m_doc, folderInfo, this); } void Bin::deleteClip(const QString &id) { if (m_monitor->activeClipId() == id) { emit openClip(NULL); } ProjectClip *clip = m_rootFolder->clip(id); if (!clip) { qWarning()<<"Cannot bin find clip to delete: "<discardJobs(id); clip->setClipStatus(AbstractProjectItem::StatusDeleting); AbstractProjectItem *parent = clip->parent(); parent->removeChild(clip); delete clip; } ProjectClip *Bin::getFirstSelectedClip() { QModelIndexList indexes = m_proxyModel->selectionModel()->selectedIndexes(); if (indexes.isEmpty()) { return NULL; } foreach (const QModelIndex &ix, indexes) { AbstractProjectItem *item = static_cast(m_proxyModel->mapToSource(ix).internalPointer()); ProjectClip *clip = qobject_cast(item); if (clip) { return clip; } } return NULL; } void Bin::slotDeleteClip() { QModelIndexList indexes = m_proxyModel->selectionModel()->selectedIndexes(); QStringList clipIds; QStringList subClipIds; QStringList foldersIds; ProjectSubClip *sub; QString subId; QPoint zone; // check folders, remove child folders if there is any QList topFolders; foreach (const QModelIndex &ix, indexes) { if (!ix.isValid() || ix.column() != 0) continue; AbstractProjectItem *item = static_cast(m_proxyModel->mapToSource(ix).internalPointer()); if (!item) continue; if (item->itemType() == AbstractProjectItem::SubClipItem) { QString subId = item->clipId(); sub = static_cast(item); zone = sub->zone(); subId.append(":" + QString::number(zone.x()) + ":" + QString::number(zone.y())); subClipIds << subId; continue; } if (item->itemType() != AbstractProjectItem::FolderItem) continue; ProjectFolder *current = static_cast(item); if (topFolders.isEmpty()) { topFolders << current; continue; } // parse all folders to check for children bool isChild = false; foreach (ProjectFolder *f, topFolders) { if (f->folder(current->clipId())) { // Current is a child, no need to take it into account isChild = true; break; } } if (isChild) continue; QList childFolders; // parse all folders to check for children foreach (ProjectFolder *f, topFolders) { if (current->folder(f->clipId())) { childFolders << f; } } if (!childFolders.isEmpty()) { // children are in the list, remove from foreach (ProjectFolder *f, childFolders) { topFolders.removeAll(f); } } topFolders << current; } foreach (const ProjectFolder *f, topFolders) { foldersIds << f->clipId(); } QList topClips; // Check if clips are in already selected folders foreach (const QModelIndex &ix, indexes) { if (!ix.isValid() || ix.column() != 0) continue; AbstractProjectItem *item = static_cast(m_proxyModel->mapToSource(ix).internalPointer()); if (!item || item->itemType() != AbstractProjectItem::ClipItem) continue; ProjectClip *current = static_cast(item); bool isChild = false; foreach (const ProjectFolder *f, topFolders) { if (current->hasParent(f->clipId())) { isChild = true; break; } } if (!isChild) clipIds << current->clipId(); } m_doc->clipManager()->deleteProjectItems(clipIds, foldersIds, subClipIds); } void Bin::slotReloadClip() { QModelIndexList indexes = m_proxyModel->selectionModel()->selectedIndexes(); foreach (const QModelIndex &ix, indexes) { if (!ix.isValid() || ix.column() != 0) { continue; } AbstractProjectItem *item = static_cast(m_proxyModel->mapToSource(ix).internalPointer()); ProjectClip *currentItem = qobject_cast(item); if (currentItem) { emit openClip(NULL); QDomDocument doc; QDomElement xml = currentItem->toXml(doc); if (!xml.isNull()) { currentItem->setClipStatus(AbstractProjectItem::StatusWaiting); // We need to set a temporary id before all outdated producers are replaced; m_doc->getFileProperties(xml, currentItem->clipId(), 150, true); } } } } void Bin::slotDuplicateClip() { QModelIndexList indexes = m_proxyModel->selectionModel()->selectedIndexes(); foreach (const QModelIndex &ix, indexes) { if (!ix.isValid() || ix.column() != 0) { continue; } AbstractProjectItem *item = static_cast(m_proxyModel->mapToSource(ix).internalPointer()); ProjectClip *currentItem = qobject_cast(item); if (currentItem) { QStringList folderInfo = getFolderInfo(ix); QDomDocument doc; QDomElement xml = currentItem->toXml(doc); if (!xml.isNull()) { QString currentName = EffectsList::property(xml, "kdenlive:clipname"); if (currentName.isEmpty()) { QUrl url = QUrl::fromLocalFile(EffectsList::property(xml, "resource")); if (url.isValid()) { QString currentName = url.fileName(); } } if (!currentName.isEmpty()) { currentName.append(i18nc("append to clip name to indicate a copied idem", " (copy)")); EffectsList::setProperty(xml, "kdenlive:clipname", currentName); } ClipCreationDialog::createClipFromXml(m_doc, xml, folderInfo, this); } } } } ProjectFolder *Bin::rootFolder() { return m_rootFolder; } QUrl Bin::projectFolder() const { return m_doc->projectFolder(); } void Bin::setMonitor(Monitor *monitor) { m_monitor = monitor; connect(m_monitor, SIGNAL(addClipToProject(QUrl)), this, SLOT(slotAddClipToProject(QUrl))); connect(m_monitor, SIGNAL(requestAudioThumb(QString)), this, SLOT(slotSendAudioThumb(QString))); connect(m_monitor, SIGNAL(refreshCurrentClip()), this, SLOT(slotOpenCurrent())); connect(m_monitor, SIGNAL(updateClipMarker(QString, QList)), this, SLOT(slotAddClipMarker(QString,QList))); connect(this, SIGNAL(openClip(ClipController*,int,int)), m_monitor, SLOT(slotOpenClip(ClipController*,int,int))); } int Bin::getFreeFolderId() { return m_folderCounter++; } int Bin::getFreeClipId() { return m_clipCounter++; } int Bin::lastClipId() const { return qMax(0, m_clipCounter - 1); } void Bin::setDocument(KdenliveDoc* project) { // Remove clip from Bin's monitor if (m_doc) emit openClip(NULL); m_infoMessage->hide(); setEnabled(false); // Cleanup previous project if (m_rootFolder) { while (!m_rootFolder->isEmpty()) { AbstractProjectItem *child = m_rootFolder->at(0); m_rootFolder->removeChild(child); delete child; } } delete m_rootFolder; delete m_itemView; m_itemView = NULL; delete m_jobManager; m_clipCounter = 1; m_folderCounter = 1; m_doc = project; int iconHeight = QFontInfo(font()).pixelSize() * 3.5; m_iconSize = QSize(iconHeight * m_doc->dar(), iconHeight); m_jobManager = new JobManager(this); m_rootFolder = new ProjectFolder(this); setEnabled(true); connect(m_jobManager, SIGNAL(addClip(QString)), this, SLOT(slotAddUrl(QString))); connect(m_proxyAction, SIGNAL(toggled(bool)), m_doc, SLOT(slotProxyCurrentItem(bool))); connect(m_jobManager, SIGNAL(jobCount(int)), m_infoLabel, SLOT(slotSetJobCount(int))); connect(m_discardCurrentClipJobs, SIGNAL(triggered()), m_jobManager, SLOT(slotDiscardClipJobs())); connect(m_cancelJobs, SIGNAL(triggered()), m_jobManager, SLOT(slotCancelJobs())); connect(m_jobManager, SIGNAL(updateJobStatus(QString,int,int,QString,QString,QString)), this, SLOT(slotUpdateJobStatus(QString,int,int,QString,QString,QString))); connect(m_jobManager, SIGNAL(gotFilterJobResults(QString,int,int,stringMap,stringMap)), this, SLOT(slotGotFilterJobResults(QString,int,int,stringMap,stringMap))); //connect(m_itemModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), m_itemView //connect(m_itemModel, SIGNAL(updateCurrentItem()), this, SLOT(autoSelect())); slotInitView(NULL); bool binEffectsDisabled = getDocumentProperty(QStringLiteral("disablebineffects")).toInt() == 1; setBinEffectsDisabledStatus(binEffectsDisabled); autoSelect(); } void Bin::slotAddUrl(QString url, QMap data) { QList urls; urls << QUrl::fromLocalFile(url); QStringList folderInfo = getFolderInfo(); ClipCreationDialog::createClipsCommand(m_doc, urls, folderInfo, this, data); } void Bin::createClip(QDomElement xml) { // Check if clip should be in a folder QString groupId = ProjectClip::getXmlProperty(xml, QStringLiteral("kdenlive:folderid")); ProjectFolder *parentFolder = m_rootFolder; if (!groupId.isEmpty()) { parentFolder = m_rootFolder->folder(groupId); if (!parentFolder) { // parent folder does not exist, put in root folder parentFolder = m_rootFolder; } } new ProjectClip(xml, m_blankThumb, parentFolder); } QString Bin::slotAddFolder(const QString &folderName) { // Check parent item QModelIndex ix = m_proxyModel->selectionModel()->currentIndex(); ProjectFolder *parentFolder = m_rootFolder; if (ix.isValid() && m_proxyModel->selectionModel()->isSelected(ix)) { AbstractProjectItem *currentItem = static_cast(m_proxyModel->mapToSource(ix).internalPointer()); while (currentItem->itemType() != AbstractProjectItem::FolderItem) { currentItem = currentItem->parent(); } if (currentItem->itemType() == AbstractProjectItem::FolderItem) { parentFolder = qobject_cast(currentItem); } } QString newId = QString::number(getFreeFolderId()); AddBinFolderCommand *command = new AddBinFolderCommand(this, newId, folderName.isEmpty() ? i18n("Folder") : folderName, parentFolder->clipId()); m_doc->commandStack()->push(command); // Edit folder name if (!folderName.isEmpty()) { // We already have a name, no need to edit return newId; } ix = getIndexForId(newId, true); if (ix.isValid()) { 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_rootFolder->supportedDataCount() - 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); } else 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->slotZoneMoved(zone.x(), zone.y()); } return; } QModelIndex ix = getIndexForId(clipId, false); if (ix.isValid()) { 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_rootFolder->supportedDataCount() - 1, ix.parent()); if (id.isValid() && id2.isValid()) { m_proxyModel->selectionModel()->select(QItemSelection(m_proxyModel->mapFromSource(id), m_proxyModel->mapFromSource(id2)), QItemSelectionModel::Select); } selectProxyModel(m_proxyModel->mapFromSource(ix)); m_itemView->scrollTo(m_proxyModel->mapFromSource(ix)); if (frame > -1) m_monitor->slotSeek(frame); if (!zone.isNull()) { m_monitor->slotZoneMoved(zone.x(), zone.y()); } } } void Bin::doAddFolder(const QString &id, const QString &name, const QString &parentId) { ProjectFolder *parentFolder = m_rootFolder->folder(parentId); if (!parentFolder) { qDebug()<<" / / ERROR IN PARENT FOLDER"; return; } //FIXME(style): constructor actually adds the new pointer to parent's children new ProjectFolder(id, name, parentFolder); emit storeFolder(id, parentId, QString(), name); } void Bin::renameFolder(const QString &id, const QString &name) { ProjectFolder *folder = m_rootFolder->folder(id); if (!folder || !folder->parent()) { qDebug()<<" / / ERROR IN PARENT FOLDER"; return; } folder->setName(name); emit itemUpdated(folder); emit storeFolder(id, folder->parent()->clipId(), QString(), name); } void Bin::slotLoadFolders(QMap foldersData) { // Folder parent is saved in folderId, separated by a dot. for example "1.3" means parent folder id is "1" and new folder id is "3". ProjectFolder *parentFolder; QStringList folderIds = foldersData.keys(); int maxIterations = folderIds.count() * folderIds.count(); int iterations = 0; while(!folderIds.isEmpty()) { QString id = folderIds.takeFirst(); QString parentId = id.section(QLatin1Char('.'), 0, 0); if (parentId == QLatin1String("-1")) { parentFolder = m_rootFolder; } else { // This is a sub-folder parentFolder = m_rootFolder->folder(parentId); if (parentFolder == m_rootFolder) { // parent folder not yet created, create unnamed placeholder parentFolder = new ProjectFolder(parentId, QString(), parentFolder); } else if (parentFolder == NULL) { // Parent folder not yet created in hierarchy if (iterations > maxIterations) { // Give up, place folder in root parentFolder = new ProjectFolder(parentId, i18n("Folder"), m_rootFolder); } else { // Try to process again at end of queue folderIds.append(id); iterations ++; continue; } } } // parent was found, create our folder QString folderId = id.section(QLatin1Char('.'), 1, 1); int numericId = folderId.toInt(); if (numericId >= m_folderCounter) m_folderCounter = numericId + 1; // Check if placeholder folder was created ProjectFolder *placeHolder = parentFolder->folder(folderId); if (placeHolder) { // Rename placeholder placeHolder->setName(foldersData.value(id)); } else { // Create new folder //FIXME(style): constructor actually adds the new pointer to parent's children new ProjectFolder(folderId, foldersData.value(id), parentFolder); } } } void Bin::removeFolder(const QString &id, QUndoCommand *deleteCommand) { // Check parent item ProjectFolder *folder = m_rootFolder->folder(id); AbstractProjectItem *parent = folder->parent(); if (folder->count() > 0) { // Folder has clips inside, warn user if (KMessageBox::warningContinueCancel(this, i18np("Folder contains a clip, delete anyways ?", "Folder contains %1 clips, delete anyways ?", folder->count())) != KMessageBox::Continue) { return; } QStringList clipIds; QStringList folderIds; // TODO: manage subclips for (int i = 0; i < folder->count(); i++) { AbstractProjectItem *child = folder->at(i); switch (child->itemType()) { case AbstractProjectItem::ClipItem: clipIds << child->clipId(); break; case AbstractProjectItem::FolderItem: folderIds << child->clipId(); break; default: break; } } m_doc->clipManager()->deleteProjectItems(clipIds, folderIds, QStringList(), deleteCommand); } new AddBinFolderCommand(this, folder->clipId(), folder->name(), parent->clipId(), true, deleteCommand); } void Bin::removeSubClip(const QString &id, QUndoCommand *deleteCommand) { // Check parent item QString clipId = id; int in = clipId.section(QLatin1Char(':'), 1, 1).toInt(); int out = clipId.section(QLatin1Char(':'), 2, 2).toInt(); clipId = clipId.section(QLatin1Char(':'), 0, 0); new AddBinClipCutCommand(this, clipId, in, out, false, deleteCommand); } void Bin::doRemoveFolder(const QString &id) { ProjectFolder *folder = m_rootFolder->folder(id); if (!folder) { qDebug()<<" / / FOLDER not found"; return; } //TODO: warn user on non-empty folders AbstractProjectItem *parent = folder->parent(); parent->removeChild(folder); emit storeFolder(id, parent->clipId(), QString(), QString()); delete folder; } void Bin::emitAboutToAddItem(AbstractProjectItem* item) { m_itemModel->onAboutToAddItem(item); } void Bin::emitItemAdded(AbstractProjectItem* item) { m_itemModel->onItemAdded(item); if (!m_proxyModel->selectionModel()->hasSelection()) { QModelIndex ix = getIndexForId(item->clipId(), item->itemType() == AbstractProjectItem::FolderItem); int row =ix.row(); if (row < 0) row = item->index(); const QModelIndex id = m_itemModel->index(row, 0, ix.parent()); const QModelIndex id2 = m_itemModel->index(row, m_rootFolder->supportedDataCount() - 1, ix.parent()); m_proxyModel->selectionModel()->select(QItemSelection(m_proxyModel->mapFromSource(id), m_proxyModel->mapFromSource(id2)), QItemSelectionModel::Select); selectProxyModel(m_proxyModel->mapFromSource(id)); } } void Bin::emitAboutToRemoveItem(AbstractProjectItem* item) { QModelIndex ix = m_proxyModel->mapFromSource(getIndexForId(item->clipId(), item->itemType() == AbstractProjectItem::FolderItem)); m_itemModel->onAboutToRemoveItem(item); int row =ix.row(); if (row > 0 && item->itemType() != AbstractProjectItem::SubClipItem) { // Go one level up to select upper item (not on subclip because ix is the parent index for subclips) row--; } if (!m_proxyModel->selectionModel()->hasSelection() || m_proxyModel->selectionModel()->isSelected(ix)) { // we have to select item above deletion QModelIndex id = m_proxyModel->index(row, 0, ix.parent()); QModelIndex id2 = m_proxyModel->index(row, m_rootFolder->supportedDataCount() - 1, ix.parent()); if (id.isValid() && id2.isValid()) { m_proxyModel->selectionModel()->select(QItemSelection(id, id2), QItemSelectionModel::Select); } } } void Bin::emitItemRemoved(AbstractProjectItem* item) { m_itemModel->onItemRemoved(item); } void Bin::rowsInserted(const QModelIndex &parent, int start, int end) { Q_UNUSED(parent) Q_UNUSED(start) Q_UNUSED(end) //Moved selection stuff to emitItemAdded otherwise selection is messaed up by sorting } void Bin::rowsRemoved(const QModelIndex &parent, int start, int end) { Q_UNUSED(parent) Q_UNUSED(start) Q_UNUSED(end) //Moved selection stuff to emitAboutToRemoveItem otherwise selection is messaed up by sorting } void Bin::selectProxyModel(const QModelIndex &id) { if (isLoading) { //return; } if (id.isValid()) { if (id.column() != 0) return; AbstractProjectItem *currentItem = static_cast(m_proxyModel->mapToSource(id).internalPointer()); if (currentItem) { // Set item as current so that it displays its content in clip monitor currentItem->setCurrent(true); if (currentItem->itemType() == AbstractProjectItem::ClipItem) { m_reloadAction->setEnabled(true); m_duplicateAction->setEnabled(true); ClipType type = static_cast(currentItem)->clipType(); m_openAction->setEnabled(type == Image || type == Audio); if (m_propertiesPanel->isVisible()) { // if info panel is displayed, update info showClipProperties(static_cast(currentItem), false, false); } m_deleteAction->setText(i18n("Delete Clip")); m_proxyAction->setText(i18n("Proxy Clip")); emit findInTimeline(currentItem->clipId()); } else if (currentItem->itemType() == AbstractProjectItem::FolderItem) { // A folder was selected, disable editing clip m_openAction->setEnabled(false); m_reloadAction->setEnabled(false); m_duplicateAction->setEnabled(false); m_deleteAction->setText(i18n("Delete Folder")); m_proxyAction->setText(i18n("Proxy Folder")); } else if (currentItem->itemType() == AbstractProjectItem::SubClipItem) { if (m_propertiesPanel->isVisible()) { // if info panel is displayed, update info showClipProperties(static_cast(currentItem->parent()), false, false); } m_openAction->setEnabled(false); m_reloadAction->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_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(NULL); emit findInTimeline(QString()); emit masterClipSelected(NULL, m_monitor); // Display black bg in clip monitor emit openClip(NULL); } } void Bin::autoSelect() { /*QModelIndex current = m_proxyModel->selectionModel()->currentIndex(); AbstractProjectItem *currentItem = static_cast(m_proxyModel->mapToSource(current).internalPointer()); if (!currentItem) { QModelIndex id = m_proxyModel->index(0, 0, QModelIndex()); //selectModel(id); //m_proxyModel->selectionModel()->select(m_proxyModel->mapFromSource(id), QItemSelectionModel::Select); }*/ } QList Bin::selectedClips() { //TODO: handle clips inside folders QModelIndexList indexes = m_proxyModel->selectionModel()->selectedIndexes(); QList list; foreach (const QModelIndex &ix, indexes) { if (!ix.isValid() || ix.column() != 0) { continue; } AbstractProjectItem *item = static_cast(m_proxyModel->mapToSource(ix).internalPointer()); ProjectClip *currentItem = qobject_cast(item); if (currentItem) { list << currentItem; } } return list; } 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); } delete m_folderUp; m_folderUp = NULL; } } m_listType = static_cast(viewType); } if (m_itemView) { delete m_itemView; } switch (m_listType) { case BinIconView: m_itemView = new MyListView(m_splitter); m_folderUp = new ProjectFolderUp(NULL); m_showDate->setEnabled(false); m_showDesc->setEnabled(false); break; default: m_itemView = new MyTreeView(m_splitter); 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_itemView->setModel(m_proxyModel); m_itemView->setSelectionModel(m_proxyModel->selectionModel()); m_splitter->addWidget(m_itemView); m_splitter->insertWidget(2, m_propertiesPanel); m_splitter->setSizes(QList () << 4 << 2); m_propertiesPanel->hide(); // 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, SIGNAL(layoutAboutToBeChanged()), this, SLOT(slotSetSorting())); m_proxyModel->setDynamicSortFilter(true); if (!m_headerInfo.isEmpty()) { view->header()->restoreState(m_headerInfo); } else { view->header()->resizeSections(QHeaderView::ResizeToContents); view->resizeColumnToContents(0); view->setColumnHidden(2, true); } m_showDate->setChecked(!view->isColumnHidden(1)); m_showDesc->setChecked(!view->isColumnHidden(2)); connect(view->header(), SIGNAL(sectionResized(int,int,int)), this, SLOT(slotSaveHeaders())); connect(view, SIGNAL(focusView()), this, SLOT(slotGotFocus())); } else if (m_listType == BinIconView) { MyListView *view = static_cast(m_itemView); connect(view, SIGNAL(focusView()), this, SLOT(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("clip_in_timeline", pCore->window()))); } void Bin::contextMenuEvent(QContextMenuEvent *event) { bool enableClipActions = false; ClipType type = Unknown; bool isFolder = 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 AbstractProjectItem *currentItem = static_cast(m_proxyModel->mapToSource(idx).internalPointer()); if (currentItem) { enableClipActions = true; if (currentItem->itemType() == AbstractProjectItem::FolderItem) { isFolder = true; } else { ProjectClip *clip = qobject_cast(currentItem); if (clip) { m_proxyAction->blockSignals(true); emit findInTimeline(clip->clipId()); clipService = clip->getProducerProperty(QStringLiteral("mlt_service")); m_proxyAction->setChecked(clip->hasProxy()); QList transcodeActions; if (m_transcodeAction) { transcodeActions = m_transcodeAction->actions(); } QStringList data; QString condition; audioCodec = clip->codec(true); QString videoCodec = clip->codec(false); type = clip->clipType(); bool noCodecInfo = false; if (audioCodec.isEmpty() && videoCodec.isEmpty()) { noCodecInfo = true; } for (int i = 0; i < transcodeActions.count(); ++i) { data = transcodeActions.at(i)->data().toStringList(); if (data.count() > 4) { condition = data.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("enableproxy").toInt() && enableClipActions); m_transcodeAction->setEnabled(enableClipActions); m_openAction->setEnabled(type == Image || type == Audio); m_reloadAction->setEnabled(enableClipActions); m_duplicateAction->setEnabled(enableClipActions); m_clipsActionsMenu->setEnabled(enableClipActions); m_extractAudioAction->setEnabled(enableClipActions); m_renameFolderAction->setVisible(isFolder); m_openAction->setVisible(!isFolder); m_reloadAction->setVisible(!isFolder); m_duplicateAction->setVisible(!isFolder); m_editAction->setVisible(!isFolder); m_inTimelineAction->setVisible(!isFolder); 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()); // Show menu if (enableClipActions) { m_menu->exec(event->globalPos()); } else { // Clicked in empty area m_addButton->menu()->exec(event->globalPos()); } } void Bin::slotRefreshClipProperties() { QModelIndexList indexes = m_proxyModel->selectionModel()->selectedIndexes(); foreach (const QModelIndex &ix, indexes) { if (!ix.isValid() || ix.column() != 0) { continue; } AbstractProjectItem *clip = static_cast(m_proxyModel->mapToSource(ix).internalPointer()); if (clip && clip->itemType() == AbstractProjectItem::ClipItem) { showClipProperties(qobject_cast(clip)); break; } } } void Bin::slotItemDoubleClicked(const QModelIndex &ix, const QPoint pos) { AbstractProjectItem *item = static_cast(m_proxyModel->mapToSource(ix).internalPointer()); if (m_listType == BinIconView) { if (item->count() > 0 || item->itemType() == AbstractProjectItem::FolderItem) { m_folderUp->setParent(item); m_itemView->setRootIndex(ix); return; } if (item == m_folderUp) { AbstractProjectItem *parentItem = item->parent(); QModelIndex parent = getIndexForId(parentItem->parent()->clipId(), parentItem->parent()->itemType() == AbstractProjectItem::FolderItem); if (parentItem->parent() != m_rootFolder) { // We are entering a parent folder m_folderUp->setParent(parentItem->parent()); } else m_folderUp->setParent(NULL); m_itemView->setRootIndex(m_proxyModel->mapFromSource(parent)); return; } } else { if (item->count() > 0) { QTreeView *view = static_cast(m_itemView); view->setExpanded(ix, !view->isExpanded(ix)); return; } } if (ix.isValid()) { QRect IconRect = m_itemView->visualRect(ix); IconRect.setSize(m_itemView->iconSize()); if (!pos.isNull() && ((ix.column() == 2 && item->itemType() == AbstractProjectItem::ClipItem) || !IconRect.contains(pos))) { // User clicked outside icon, trigger rename m_itemView->edit(ix); return; } m_editAction->trigger(); } } void Bin::slotSwitchClipProperties() { QModelIndex current = m_proxyModel->selectionModel()->currentIndex(); slotSwitchClipProperties(current); } void Bin::slotSwitchClipProperties(const QModelIndex &ix) { if (ix.isValid()) { // User clicked in the icon, open clip properties if (m_propertiesPanel->isHidden()) { AbstractProjectItem *item = static_cast(m_proxyModel->mapToSource(ix).internalPointer()); ProjectClip *clip = qobject_cast(item); if (clip && clip->clipType() == Text) { m_propertiesPanel->hide(); } else { m_propertiesPanel->setEnabled(true); m_propertiesPanel->show(); } showClipProperties(clip); } else m_propertiesPanel->hide(); } else { if (m_propertiesPanel->isHidden()) { showClipProperties(NULL); } else m_propertiesPanel->hide(); } } void Bin::doRefreshPanel(const QString &id) { if (m_editAction->isChecked()) { ProjectClip *currentItem = getFirstSelectedClip(); if (currentItem && currentItem->clipId() == id) { showClipProperties(currentItem, true); } } } void Bin::showClipProperties(ProjectClip *clip, bool forceRefresh, bool openExternalDialog ) { if (!m_editAction->isChecked()) return; if (!clip) { m_propertiesPanel->setEnabled(false); return; } if (!clip->isReady()) { m_propertiesPanel->setEnabled(false); return; } // Special case: text clips open title widget if (clip->clipType() == Text) { m_propertiesPanel->setEnabled(false); if (openExternalDialog) showTitleWidget(clip); return; } if (clip->clipType() == SlideShow) { // Cleanup widget for new content foreach (QWidget * w, m_propertiesPanel->findChildren()) { delete w; } m_propertiesPanel->setEnabled(false); showSlideshowWidget(clip); return; } if (clip->clipType() == QText) { // Cleanup widget for new content foreach (QWidget * w, m_propertiesPanel->findChildren()) { delete w; } m_propertiesPanel->setEnabled(false); ClipCreationDialog::createQTextClip(m_doc, getFolderInfo(), this, clip); return; } m_propertiesPanel->show(); QString panelId = m_propertiesPanel->property("clipId").toString(); if (!forceRefresh && panelId == clip->clipId()) { // the properties panel is already displaying current clip, do nothing m_propertiesPanel->setEnabled(true); return; } // Cleanup widget for new content foreach (QWidget * w, m_propertiesPanel->findChildren()) { delete w; } m_propertiesPanel->setEnabled(true); m_propertiesPanel->setProperty("clipId", clip->clipId()); QVBoxLayout *lay = static_cast(m_propertiesPanel->layout()); if (lay == 0) { lay = new QVBoxLayout(m_propertiesPanel); m_propertiesPanel->setLayout(lay); } ClipPropertiesController *panel = clip->buildProperties(m_propertiesPanel); connect(this, SIGNAL(refreshTimeCode()), panel, SLOT(slotRefreshTimeCode())); connect(this, SIGNAL(refreshPanelMarkers()), panel, SLOT(slotFillMarkers())); connect(panel, SIGNAL(updateClipProperties(const QString &, QMap, QMap)), this, SLOT(slotEditClipCommand(const QString &, QMap, QMap))); connect(panel, SIGNAL(seekToFrame(int)), m_monitor, SLOT(slotSeek(int))); connect(panel, SIGNAL(addMarkers(QString,QList)), this, SLOT(slotAddClipMarker(QString,QList))); connect(panel, SIGNAL(editAnalysis(QString,QString,QString)), this, SLOT(slotAddClipExtraData(QString,QString,QString))); connect(panel, SIGNAL(loadMarkers(QString)), this, SLOT(slotLoadClipMarkers(QString))); connect(panel, SIGNAL(saveMarkers(QString)), this, SLOT(slotSaveClipMarkers(QString))); lay->addWidget(panel); } void Bin::slotEditClipCommand(const QString &id, QMapoldProps, QMapnewProps) { EditClipCommand *command = new EditClipCommand(this, id, oldProps, newProps, true); m_doc->commandStack()->push(command); } void Bin::reloadClip(const QString &id) { ProjectClip *clip = m_rootFolder->clip(id); if (!clip) return; QDomDocument doc; QDomElement xml = clip->toXml(doc); if (!xml.isNull()) m_doc->getFileProperties(xml, id, 150, true); } void Bin::slotThumbnailReady(const QString &id, const QImage &img, bool fromFile) { ProjectClip *clip = m_rootFolder->clip(id); if (clip) { clip->setThumbnail(img); // Save thumbnail for later reuse if (!fromFile) img.save(m_doc->projectFolder().path() + "/thumbs/" + clip->hash() + ".png"); } } QStringList Bin::getBinFolderClipIds(const QString &id) const { QStringList ids; ProjectFolder *folder = m_rootFolder->folder(id); if (folder) { for (int i = 0; i < folder->count(); i++) { AbstractProjectItem *child = folder->at(i); if (child->itemType() == AbstractProjectItem::ClipItem) { ids << child->clipId(); } } } return ids; } ProjectClip *Bin::getBinClip(const QString &id) { ProjectClip *clip = NULL; if (id.contains(QLatin1Char('_'))) { clip = m_rootFolder->clip(id.section(QLatin1Char('_'), 0, 0)); } else { clip = m_rootFolder->clip(id); } return clip; } void Bin::setWaitingStatus(const QString &id) { ProjectClip *clip = m_rootFolder->clip(id); if (clip) clip->setClipStatus(AbstractProjectItem::StatusWaiting); } void Bin::slotRemoveInvalidClip(const QString &id, bool replace, const QString &errorMessage) { Q_UNUSED(replace) ProjectClip *clip = m_rootFolder->clip(id); if (!clip) return; emit requesteInvalidRemoval(id, clip->url(), errorMessage); } void Bin::slotProducerReady(requestClipInfo info, ClipController *controller) { ProjectClip *clip = m_rootFolder->clip(info.clipId); if (clip) { if (clip->setProducer(controller, info.replaceProducer) && !clip->hasProxy()) { emit producerReady(info.clipId); // Check for file modifications ClipType t = clip->clipType(); if (t == AV || t == Audio || t == Image || t == Video || t == Playlist) { m_doc->watchFile(clip->url()); } if (m_doc->useProxy()) { if (t == AV || t == Video || t == Playlist) { int width = clip->getProducerIntProperty(QStringLiteral("meta.media.width")); if (m_doc->autoGenerateProxy(width)) { // Start proxy m_doc->slotProxyCurrentItem(true, QList () << clip); } } else if (t == Image && m_doc->autoGenerateImageProxy(clip->getProducerIntProperty(QStringLiteral("meta.media.width")))) { // Start proxy m_doc->slotProxyCurrentItem(true, QList () << clip); } } } else emit producerReady(info.clipId); QString currentClip = m_monitor->activeClipId(); if (currentClip.isEmpty()) { //No clip displayed in monitor, check if item is selected QModelIndexList indexes = m_proxyModel->selectionModel()->selectedIndexes(); foreach (const QModelIndex &ix, indexes) { if (!ix.isValid() || ix.column() != 0) { continue; } AbstractProjectItem *item = static_cast(m_proxyModel->mapToSource(ix).internalPointer()); if (item && item->clipId() == info.clipId) { // Item was selected, show it in monitor item->setCurrent(true); break; } } } else if (currentClip == info.clipId) { emit openClip(NULL); clip->setCurrent(true); } } else { // Clip not found, create it QString groupId = controller->property(QStringLiteral("kdenlive:folderid")); ProjectFolder *parentFolder; if (!groupId.isEmpty()) { parentFolder = m_rootFolder->folder(groupId); if (!parentFolder) { // parent folder does not exist, put in root folder parentFolder = m_rootFolder; } if (groupId.toInt() >= m_folderCounter) m_folderCounter = groupId.toInt() + 1; } else parentFolder = m_rootFolder; //FIXME(style): constructor actually adds the new pointer to parent's children ProjectClip *clip = new ProjectClip(info.clipId, m_blankThumb, controller, parentFolder); emit producerReady(info.clipId); ClipType t = clip->clipType(); if (t == AV || t == Audio || t == Image || t == Video || t == Playlist) { m_doc->watchFile(clip->url()); } if (info.clipId.toInt() >= m_clipCounter) m_clipCounter = info.clipId.toInt() + 1; } } void Bin::slotOpenCurrent() { ProjectClip *currentItem = getFirstSelectedClip(); if (currentItem) { emit openClip(currentItem->controller()); } } void Bin::openProducer(ClipController *controller) { emit openClip(controller); } void Bin::openProducer(ClipController *controller, int in, int out) { emit openClip(controller, in, out); } void Bin::emitItemUpdated(AbstractProjectItem* item) { emit itemUpdated(item); } void Bin::emitRefreshPanel(const QString &id) { emit refreshPanel(id); } void Bin::setupGeneratorMenu() { if (!m_menu) { qDebug()<<"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("clip_in_timeline", pCore->window())); if (addMenu) { m_inTimelineAction = m_menu->addMenu(addMenu); m_inTimelineAction->setEnabled(!addMenu->isEmpty()); } 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_renameFolderAction); m_menu->addAction(m_deleteAction); m_menu->insertSeparator(m_deleteAction); } void Bin::setupMenu(QMenu *addMenu, QAction *defaultAction, QHash actions) { // Setup actions QAction *first = m_toolbar->actions().at(0); m_deleteAction = actions.value(QStringLiteral("delete")); m_toolbar->insertAction(first, m_deleteAction); m_editAction = actions.value(QStringLiteral("properties")); m_toolbar->insertAction(m_deleteAction, m_editAction); QAction *folder = actions.value(QStringLiteral("folder")); m_toolbar->insertAction(m_editAction, folder); m_openAction = actions.value(QStringLiteral("open")); m_reloadAction = actions.value(QStringLiteral("reload")); m_duplicateAction = actions.value(QStringLiteral("duplicate")); m_proxyAction = actions.value(QStringLiteral("proxy")); QMenu *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(); //m_menu->addActions(addMenu->actions()); } const QString Bin::getDocumentProperty(const QString &key) { return m_doc->getDocumentProperty(key); } const QSize Bin::getRenderSize() { return m_doc->getRenderSize(); } void Bin::slotUpdateJobStatus(const QString&id, int jobType, int status, const QString &label, const QString &actionName, const QString &details) { ProjectClip *clip = m_rootFolder->clip(id); if (clip) { clip->setJobStatus((AbstractClipJob::JOBTYPE) jobType, (ClipJobStatus) status); } if (status == JobCrashed) { QList actions = m_infoMessage->actions(); if (m_infoMessage->isHidden()) { m_infoMessage->setText(label); m_infoMessage->setWordWrap(m_infoMessage->text().length() > 35); m_infoMessage->setMessageType(KMessageWidget::Warning); } if (!actionName.isEmpty()) { QAction *action = NULL; QList< KActionCollection * > 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 && !actions.contains(action)) m_infoMessage->addAction(action); } if (!details.isEmpty()) { m_errorLog.append(details); if (!actions.contains(m_logAction)) m_infoMessage->addAction(m_logAction); } m_infoMessage->animatedShow(); } } void Bin::doDisplayMessage(const QString &text, KMessageWidget::MessageType type, QList actions) { m_infoMessage->setText(text); m_infoMessage->setWordWrap(m_infoMessage->text().length() > 35); foreach(QAction *action, actions) { m_infoMessage->addAction(action); connect(action, SIGNAL(triggered(bool)), this, SLOT(slotMessageActionTriggered())); } m_infoMessage->setCloseButtonVisible(actions.isEmpty()); m_infoMessage->setMessageType(type); if (m_infoMessage->isHidden()) { m_infoMessage->animatedShow(); } } void Bin::slotShowJobLog() { QDialog d(this); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Close); QWidget *mainWidget = new QWidget(this); QVBoxLayout *l = new QVBoxLayout; QTextEdit t(&d); for (int i = 0; i < m_errorLog.count(); ++i) { if (i > 0) t.insertHtml(QStringLiteral("


")); t.insertPlainText(m_errorLog.at(i)); } t.setReadOnly(true); l->addWidget(&t); mainWidget->setLayout(l); QVBoxLayout *mainLayout = new QVBoxLayout; d.setLayout(mainLayout); mainLayout->addWidget(mainWidget); mainLayout->addWidget(buttonBox); d.connect(buttonBox, SIGNAL(rejected()), &d, SLOT(accept())); d.exec(); } void Bin::gotProxy(const QString &id) { ProjectClip *clip = m_rootFolder->clip(id); if (clip) { QDomDocument doc; QDomElement xml = clip->toXml(doc, true); if (!xml.isNull()) m_doc->getFileProperties(xml, id, 150, true); } } void Bin::reloadProducer(const QString &id, QDomElement xml) { m_doc->getFileProperties(xml, id, 150, true); } void Bin::refreshClip(const QString &id) { emit clipNeedsReload(id, false); if (m_monitor->activeClipId() == id) m_monitor->refreshMonitorIfActive(); } void Bin::emitRefreshAudioThumbs(const QString &id) { emit refreshAudioThumbs(id); } void Bin::doRefreshAudioThumbs(const QString &id) { if (m_monitor->activeClipId() == id) { slotSendAudioThumb(id); } } void Bin::refreshClipMarkers(const QString &id) { if (m_monitor->activeClipId() == id) m_monitor->updateMarkers(); if (m_propertiesPanel) { QString panelId = m_propertiesPanel->property("clipId").toString(); if (panelId == id) emit refreshPanelMarkers(); } } void Bin::discardJobs(const QString &id, AbstractClipJob::JOBTYPE type) { m_jobManager->discardJobs(id, type); } void Bin::slotStartCutJob(const QString &id) { startJob(id, AbstractClipJob::CUTJOB); } void Bin::startJob(const QString &id, AbstractClipJob::JOBTYPE type) { QList clips; ProjectClip *clip = getBinClip(id); if (clip && !hasPendingJob(id, type)) { // Launch job clips << clip; m_jobManager->prepareJobs(clips, m_doc->fps(), type); } } bool Bin::hasPendingJob(const QString &id, AbstractClipJob::JOBTYPE type) { return m_jobManager->hasPendingJob(id, type); } void Bin::slotCreateProjectClip() { QAction* act = qobject_cast(sender()); if (act == 0) { // Cannot access triggering action, something is wrong qDebug()<<"// Error in clip creation action"; return; } ClipType type = (ClipType) act->data().toInt(); QStringList folderInfo = getFolderInfo(); switch (type) { case Color: ClipCreationDialog::createColorClip(m_doc, folderInfo, this); break; case SlideShow: ClipCreationDialog::createSlideshowClip(m_doc, folderInfo, this); break; case Text: ClipCreationDialog::createTitleClip(m_doc, folderInfo, QString(), this); break; case TextTemplate: ClipCreationDialog::createTitleTemplateClip(m_doc, folderInfo, this); break; case QText: ClipCreationDialog::createQTextClip(m_doc, folderInfo, this); break; default: break; } } void Bin::slotItemDropped(QStringList ids, const QModelIndex &parent) { AbstractProjectItem *parentItem; if (parent.isValid()) { parentItem = static_cast(parent.internalPointer()); while (parentItem->itemType() != AbstractProjectItem::FolderItem) { parentItem = parentItem->parent(); } } else { parentItem = m_rootFolder; } QUndoCommand *moveCommand = new QUndoCommand(); moveCommand->setText(i18np("Move Clip", "Move Clips", ids.count())); QStringList folderIds; foreach(const QString &id, ids) { if (id.contains(QStringLiteral("/"))) { // trying to move clip zone, not allowed. Ignore continue; } if (id.startsWith(QLatin1String("#"))) { // moving a folder, keep it for later folderIds << id; continue; } ProjectClip *currentItem = m_rootFolder->clip(id); AbstractProjectItem *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()) { foreach(QString id, folderIds) { id.remove(0, 1); ProjectFolder *currentItem = m_rootFolder->folder(id); AbstractProjectItem *currentParent = currentItem->parent(); if (currentParent != parentItem) { // Item was dropped on a different folder new MoveBinFolderCommand(this, id, currentParent->clipId(), parentItem->clipId(), moveCommand); } } } m_doc->commandStack()->push(moveCommand); } void Bin::slotEffectDropped(QString id, QDomElement effect) { if (id.isEmpty()) id = m_monitor->activeClipId(); if (id.isEmpty()) return; AddBinEffectCommand *command = new AddBinEffectCommand(this, id, effect); m_doc->commandStack()->push(command); } void Bin::slotEffectDropped(QString effect, const QModelIndex &parent) { if (parent.isValid()) { AbstractProjectItem *parentItem; parentItem = static_cast(parent.internalPointer()); 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_rootFolder->supportedDataCount() - 1, parent.parent()); if (id.isValid() && id2.isValid()) { m_proxyModel->selectionModel()->select(QItemSelection(m_proxyModel->mapFromSource(id), m_proxyModel->mapFromSource(id2)), QItemSelectionModel::Select); } parentItem->setCurrent(true); QDomDocument doc; doc.setContent(effect); QDomElement e = doc.documentElement(); AddBinEffectCommand *command = new AddBinEffectCommand(this, parentItem->clipId(), e); m_doc->commandStack()->push(command); } } void Bin::slotDeleteEffect(const QString &id, QDomElement effect) { RemoveBinEffectCommand *command = new RemoveBinEffectCommand(this, id, effect); m_doc->commandStack()->push(command); } void Bin::removeEffect(const QString &id, const QDomElement &effect) { if (effect.isNull()) { qWarning()<<" / /ERROR, trying to remove empty effect"; return; } ProjectClip *currentItem = m_rootFolder->clip(id); if (!currentItem) return; currentItem->removeEffect(effect.attribute(QStringLiteral("kdenlive_ix")).toInt()); m_monitor->refreshMonitorIfActive(); } void Bin::addEffect(const QString &id, QDomElement &effect) { ProjectClip *currentItem = m_rootFolder->clip(id); if (!currentItem) return; currentItem->addEffect(m_monitor->profileInfo(), effect); emit masterClipUpdated(currentItem->controller(), m_monitor); m_monitor->refreshMonitorIfActive(); } void Bin::editMasterEffect(ClipController *ctl) { if (m_gainedFocus) { // Widget just gained focus, updating stack is managed in the eventfilter event, not from item return; } emit masterClipSelected(ctl, m_monitor); } void Bin::updateMasterEffect(ClipController *ctl) { if (m_gainedFocus) { // Widget just gained focus, updating stack is managed in the eventfilter event, not from item return; } emit masterClipUpdated(ctl, m_monitor); } void Bin::slotGotFocus() { m_gainedFocus = true; } void Bin::doMoveClip(const QString &id, const QString &newParentId) { ProjectClip *currentItem = m_rootFolder->clip(id); if (!currentItem) return; AbstractProjectItem *currentParent = currentItem->parent(); ProjectFolder *newParent = m_rootFolder->folder(newParentId); currentParent->removeChild(currentItem); currentItem->setParent(newParent); currentItem->updateParentInfo(newParentId, newParent->name()); } void Bin::doMoveFolder(const QString &id, const QString &newParentId) { ProjectFolder *currentItem = m_rootFolder->folder(id); AbstractProjectItem *currentParent = currentItem->parent(); ProjectFolder *newParent = m_rootFolder->folder(newParentId); currentParent->removeChild(currentItem); currentItem->setParent(newParent); emit storeFolder(id, newParent->clipId(), currentParent->clipId(), currentItem->name()); } void Bin::droppedUrls(QList urls) { QModelIndex current = m_proxyModel->mapToSource(m_proxyModel->selectionModel()->currentIndex()); slotItemDropped(urls, current); } void Bin::slotAddClipToProject(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) { QStringList folderInfo; if (parent.isValid()) { // Check if drop occured on a folder AbstractProjectItem *parentItem = static_cast(parent.internalPointer()); while (parentItem->itemType() != AbstractProjectItem::FolderItem) { parentItem = parentItem->parent(); } if (parentItem != m_rootFolder) { folderInfo << parentItem->clipId(); } } //TODO: verify if urls exist QList clipsToAdd = urls; QMimeDatabase db; foreach(const QUrl & file, clipsToAdd) { // Check there is no folder here QMimeType type = db.mimeTypeForUrl(file); if (type.inherits(QStringLiteral("inode/directory"))) { // user dropped a folder, import its files clipsToAdd.removeAll(file); QDir dir(file.path()); QStringList result = dir.entryList(QDir::Files); QList folderFiles; foreach(const QString & path, result) { folderFiles.append(QUrl::fromLocalFile(dir.absoluteFilePath(path))); } if (folderFiles.count() > 0) { QString folderId = slotAddFolder(dir.dirName()); QModelIndex ind = getIndexForId(folderId, true); QStringList newFolderInfo; if (ind.isValid()) { newFolderInfo = getFolderInfo(m_proxyModel->mapFromSource(ind)); } ClipCreationDialog::createClipsCommand(m_doc, folderFiles, newFolderInfo, this); } } } if (!clipsToAdd.isEmpty()) ClipCreationDialog::createClipsCommand(m_doc, clipsToAdd, folderInfo, this); } void Bin::slotExpandUrl(ItemInfo info, QUrl url, QUndoCommand *command) { QStringList folderInfo; // Create folder to hold imported clips QString folderName = url.fileName().section(QLatin1Char('.'), 0, 0); QString newId = QString::number(getFreeFolderId()); new AddBinFolderCommand(this, newId, folderName.isEmpty() ? i18n("Folder") : folderName, m_rootFolder->clipId(), false, command); // Parse playlist clips QDomDocument doc; QFile file(url.path()); doc.setContent(&file, false); file.close(); bool invalid = false; if (doc.documentElement().isNull()) { invalid = true; } QDomNodeList producers = doc.documentElement().elementsByTagName("producer"); QDomNodeList tracks = doc.documentElement().elementsByTagName("track"); if (invalid || producers.isEmpty()) { doDisplayMessage(i18n("Playlist clip %1 is invalid.", 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.", url.fileName(), tracks.count()), KMessageWidget::Warning); delete command; return; } QMap processedUrl; QMap idMaps; for (int i = 0; i < producers.count(); i++) { QDomElement prod = producers.at(i).toElement(); QString resource = EffectsList::property(prod, QStringLiteral("resource")); QString service = EffectsList::property(prod, QStringLiteral("mlt_service")); if (service == QLatin1String("timewarp")) { resource = EffectsList::property(prod, QStringLiteral("warp_resource")); } else if (service == QLatin1String("framebuffer")) { resource = resource.section(QLatin1Char('?'), 0, -2); } if (!resource.isEmpty() && processedUrl.contains(resource)) { // This is a sub-clip (track producer or slowmotion, ignore continue; } // Add clip QDomElement clone = prod.cloneNode(true).toElement(); EffectsList::setProperty(clone, QStringLiteral("kdenlive:folderid"), newId); QString id = QString::number(getFreeClipId()); idMaps.insert(prod.attribute(QStringLiteral("id")), id); processedUrl.insert(resource, id); ClipCreationDialog::createClipsCommand(m_doc, clone, id, command); } pCore->projectManager()->currentTimeline()->importPlaylist(info, processedUrl, idMaps, doc, command); } void Bin::slotItemEdited(QModelIndex ix,QModelIndex,QVector) { if (ix.isValid()) { // User clicked in the icon, open clip properties AbstractProjectItem *item = static_cast(ix.internalPointer()); ProjectClip *clip = qobject_cast(item); if (clip) emit clipNameChanged(clip->clipId()); } } void Bin::renameFolderCommand(const QString &id, const QString &newName, const QString &oldName) { RenameBinFolderCommand *command = new RenameBinFolderCommand(this, id, newName, oldName); m_doc->commandStack()->push(command); } void Bin::renameSubClipCommand(const QString &id, const QString &newName, const QString oldName, int in, int out) { RenameBinSubClipCommand *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) { ProjectClip *clip = m_rootFolder->clip(id); if (!clip) return; ProjectSubClip *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) + ";" + QString::number(out)); emit itemUpdated(sub); } void Bin::slotStartClipJob(bool enable) { Q_UNUSED(enable) QAction* act = qobject_cast(sender()); if (act == 0) { // Cannot access triggering action, something is wrong qDebug()<<"// Error in clip job action"; return; } startClipJob(act->data().toStringList()); } void Bin::startClipJob(const QStringList ¶ms) { QStringList data = params; if (data.isEmpty()) { qDebug()<<"// Error in clip job action"; return; } AbstractClipJob::JOBTYPE jobType = (AbstractClipJob::JOBTYPE) data.takeFirst().toInt(); QList clips = selectedClips(); m_jobManager->prepareJobs(clips, m_doc->fps(), jobType, data); } void Bin::slotCancelRunningJob(const QString &id, const QMap &newProps) { if (newProps.isEmpty()) return; ProjectClip *clip = getBinClip(id); if (!clip) return; QMap oldProps; QMapIterator i(newProps); while (i.hasNext()) { i.next(); QString value = newProps.value(i.key()); oldProps.insert(i.key(), value); } if (newProps == oldProps) return; EditClipCommand *command = new EditClipCommand(this, id, oldProps, newProps, true); m_doc->commandStack()->push(command); } void Bin::slotPrepareJobsMenu() { ProjectClip *item = getFirstSelectedClip(); if (item) { QString id = item->clipId(); m_discardCurrentClipJobs->setData(id); QStringList jobs = m_jobManager->getPendingJobs(id); m_discardCurrentClipJobs->setEnabled(!jobs.isEmpty()); } else { m_discardCurrentClipJobs->setData(QString()); m_discardCurrentClipJobs->setEnabled(false); } } void Bin::slotAddClipCut(const QString&id, int in, int out) { AddBinClipCutCommand *command = new AddBinClipCutCommand(this, id, in, out, true); m_doc->commandStack()->push(command); } void Bin::loadSubClips(const QString&id, const QMap data) { ProjectClip *clip = getBinClip(id); if (!clip) return; QMapIterator i(data); QDir thumbsFolder(projectFolder().path() + "/thumbs/"); QList missingThumbs; while (i.hasNext()) { i.next(); if (!i.value().contains(QStringLiteral(";"))) { // Problem, the zone has no in/out points continue; } QImage img; int in = i.value().section(QLatin1Char(';'), 0, 0).toInt(); int out = i.value().section(QLatin1Char(';'), 1, 1).toInt(); missingThumbs << in; new ProjectSubClip(clip, in, out, m_doc->timecode().getDisplayTimecodeFromFrames(in, KdenliveSettings::frametimecode()), i.key()); } if (!missingThumbs.isEmpty()) { // generate missing subclip thumbnails QtConcurrent::run(clip, &ProjectClip::slotExtractSubImage, missingThumbs); } } void Bin::addClipCut(const QString&id, int in, int out) { ProjectClip *clip = getBinClip(id); if (!clip) return; // Check that we don't already have that subclip ProjectSubClip *sub = clip->getSubClip(in, out); if (sub) { // A subclip with same zone already exists return; } sub = new ProjectSubClip(clip, in, out, m_doc->timecode().getDisplayTimecodeFromFrames(in, KdenliveSettings::frametimecode())); QStringList markersComment = clip->markersText(GenTime(in, m_doc->fps()), GenTime(out, m_doc->fps())); sub->setDescription(markersComment.join(";")); QtConcurrent::run(clip, &ProjectClip::slotExtractSubImage, QList () << in); } void Bin::removeClipCut(const QString&id, int in, int out) { ProjectClip *clip = getBinClip(id); if (!clip) return; ProjectSubClip *sub = clip->getSubClip(in, out); if (sub) { clip->removeChild(sub); sub->discard(); delete sub; } } Timecode Bin::projectTimecode() const { return m_doc->timecode(); } void Bin::slotStartFilterJob(const ItemInfo &info, const QString&id, QMap &filterParams, QMap &consumerParams, QMap &extraParams) { ProjectClip *clip = getBinClip(id); if (!clip) return; QMap producerParams = QMap (); producerParams.insert(QStringLiteral("producer"), clip->url().path()); if (info.cropDuration != GenTime()) { producerParams.insert(QStringLiteral("in"), QString::number((int) info.cropStart.frames(m_doc->fps()))); producerParams.insert(QStringLiteral("out"), QString::number((int) (info.cropStart + info.cropDuration).frames(m_doc->fps()))); extraParams.insert(QStringLiteral("clipStartPos"), QString::number((int) info.startPos.frames(m_doc->fps()))); 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)); } m_jobManager->prepareJobFromTimeline(clip, producerParams, filterParams, consumerParams, extraParams); } void Bin::focusBinView() const { m_itemView->setFocus(); } void Bin::slotOpenClip() { ProjectClip *clip = getFirstSelectedClip(); if (!clip) return; if (clip->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().path()); } if (clip->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().path()); } } void Bin::updateTimecodeFormat() { emit refreshTimeCode(); } void Bin::slotGotFilterJobResults(QString id, int startPos, int track, stringMap results, stringMap filterInfo) { if (filterInfo.contains("finalfilter")) { if (filterInfo.contains(QStringLiteral("storedata"))) { // Store returned data as clip extra data ProjectClip *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 ProjectClip *currentItem = m_rootFolder->clip(id); if (!currentItem) return; ClipController *ctl = currentItem->controller(); EffectsList list = ctl->effectList(); QDomElement effect = list.effectById(filterInfo.value("finalfilter")); QDomDocument doc; QDomElement e = doc.createElement("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(pCore->monitorManager()->projectMonitor()->profileInfo(), newEffect, effect.attribute("kdenlive_ix").toInt()); emit masterClipUpdated(ctl, m_monitor); // 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; } else { // This is a timeline filter, forward results emit gotFilterJobResults(id, startPos, track, results, filterInfo); return; } } // Currently, only the first value of results is used ProjectClip *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(';', QString::SkipEmptyParts); //qDebug()<<"// RESULT; "<setText(i18n("Auto Split Clip")); foreach (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; QUndoCommand *command = new QUndoCommand(); command->setText(i18n("Add Markers")); QList markersList; int index = 1; bool simpleList = false; double sourceFps = clip->getOriginalFps(); if (sourceFps == 0) { sourceFps = m_doc->fps(); } if (filterInfo.contains(QStringLiteral("simplelist"))) { // simple list simpleList = true; } foreach (const QString &pos, value) { if (simpleList) { CommentedTime m(GenTime((int) (pos.toInt() * m_doc->fps() / sourceFps), m_doc->fps()), label + pos, markersType); markersList << m; index++; continue; } if (!pos.contains(QStringLiteral("="))) continue; int newPos = pos.section('=', 0, 0).toInt(); // Don't use scenes shorter than 1 second if (newPos - cutPos < 24) continue; CommentedTime m(GenTime(newPos + offset, m_doc->fps()), label + QString::number(index), markersType); markersList << m; index++; cutPos = newPos; } slotAddClipMarker(id, markersList); } 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::slotAddClipMarker(const QString &id, QList newMarkers, QUndoCommand *groupCommand) { ProjectClip *clip = getBinClip(id); if (!clip) return; if (groupCommand == NULL) { groupCommand = new QUndoCommand; groupCommand->setText(i18np("Add marker", "Add markers", newMarkers.count())); } clip->addClipMarker(newMarkers, groupCommand); if (groupCommand->childCount() > 0) m_doc->commandStack()->push(groupCommand); else delete groupCommand; } void Bin::slotLoadClipMarkers(const QString &id) { KComboBox *cbox = new KComboBox; for (int i = 0; i < 5; ++i) { cbox->insertItem(i, i18n("Category %1", i)); cbox->setItemData(i, CommentedTime::markerColor(i), Qt::DecorationRole); } cbox->setCurrentIndex(KdenliveSettings::default_marker_type()); //TODO KF5 how to add custom cbox to Qfiledialog QPointer fd = new QFileDialog(this, i18n("Load Clip Markers"), m_doc->projectFolder().path()); fd->setMimeTypeFilters(QStringList()<setFileMode(QFileDialog::ExistingFile); if (fd->exec() != QDialog::Accepted) return; QStringList selection = fd->selectedFiles(); QString url; if (!selection.isEmpty()) url = selection.first(); delete fd; //QUrl url = KFileDialog::getOpenUrl(QUrl("kfiledialog:///projectfolder"), "text/plain", this, i18n("Load marker file")); if (url.isEmpty()) return; int category = cbox->currentIndex(); delete cbox; QFile file(url); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { emit displayBinMessage(i18n("Cannot open file %1", QUrl::fromLocalFile(url).fileName()), KMessageWidget::Warning); return; } QString data = QString::fromUtf8(file.readAll()); file.close(); QStringList lines = data.split('\n', QString::SkipEmptyParts); QStringList values; bool ok; QUndoCommand *command = new QUndoCommand(); command->setText(QStringLiteral("Load markers")); QString markerText; QList markersList; foreach(const QString &line, lines) { markerText.clear(); values = line.split('\t', QString::SkipEmptyParts); double time1 = values.at(0).toDouble(&ok); double time2 = -1; if (!ok) continue; if (values.count() >1) { time2 = values.at(1).toDouble(&ok); if (values.count() == 2) { // Check if second value is a number or text if (!ok) { time2 = -1; markerText = values.at(1); } else markerText = i18n("Marker"); } else { // We assume 3 values per line: in out name if (!ok) { // 2nd value is not a number, drop } else { markerText = values.at(2); } } } if (!markerText.isEmpty()) { // Marker found, add it //TODO: allow user to set a marker category CommentedTime marker1(GenTime(time1), markerText, category); markersList << marker1; if (time2 > 0 && time2 != time1) { CommentedTime marker2(GenTime(time2), markerText, category); markersList << marker2; } } } if (!markersList.isEmpty()) slotAddClipMarker(id, markersList, command); } void Bin::slotSaveClipMarkers(const QString &id) { ProjectClip *clip = getBinClip(id); if (!clip) return; QList < CommentedTime > markers = clip->commentedSnapMarkers(); if (!markers.isEmpty()) { // Set up categories KComboBox *cbox = new KComboBox; cbox->insertItem(0, i18n("All categories")); for (int i = 0; i < 5; ++i) { cbox->insertItem(i + 1, i18n("Category %1", i)); cbox->setItemData(i + 1, CommentedTime::markerColor(i), Qt::DecorationRole); } cbox->setCurrentIndex(0); //TODO KF5 how to add custom cbox to Qfiledialog QPointer fd = new QFileDialog(this, i18n("Save Clip Markers"), m_doc->projectFolder().path()); fd->setMimeTypeFilters(QStringList() << QStringLiteral("text/plain")); fd->setFileMode(QFileDialog::AnyFile); fd->setAcceptMode(QFileDialog::AcceptSave); if (fd->exec() != QDialog::Accepted) return; QStringList selection = fd->selectedFiles(); QString url; if (!selection.isEmpty()) url = selection.first(); delete fd; //QString url = KFileDialog::getSaveFileName(QUrl("kfiledialog:///projectfolder"), "text/plain", this, i18n("Save markers")); if (url.isEmpty()) return; QString data; int category = cbox->currentIndex() - 1; for (int i = 0; i < markers.count(); ++i) { if (category >= 0) { // Save only the markers in selected category if (markers.at(i).markerType() != category) continue; } data.append(QString::number(markers.at(i).time().seconds())); data.append("\t"); data.append(QString::number(markers.at(i).time().seconds())); data.append("\t"); data.append(markers.at(i).comment()); data.append("\n"); } delete cbox; QFile file(url); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { emit displayBinMessage(i18n("Cannot open file %1", url), KMessageWidget::Error); return; } file.write(data.toUtf8()); file.close(); } } void Bin::deleteClipMarker(const QString &comment, const QString &id, const GenTime &position) { ProjectClip *clip = getBinClip(id); if (!clip) return; QUndoCommand *command = new QUndoCommand; command->setText(i18n("Delete marker")); CommentedTime marker(position, comment); marker.setMarkerType(-1); QList markers; markers << marker; clip->addClipMarker(markers, command); if (command->childCount() > 0) m_doc->commandStack()->push(command); else delete command; } void Bin::deleteAllClipMarkers(const QString &id) { ProjectClip *clip = getBinClip(id); if (!clip) return; QUndoCommand *command = new QUndoCommand; command->setText(i18n("Delete clip markers")); if (!clip->deleteClipMarkers(command)) { doDisplayMessage(i18n("Clip has no markers"), KMessageWidget::Warning); } if (command->childCount() > 0) m_doc->commandStack()->push(command); else delete command; } void Bin::slotGetCurrentProjectImage() { pCore->monitorManager()->projectMonitor()->slotGetCurrentImage(); } // TODO: move title editing into a better place... void Bin::showTitleWidget(ProjectClip *clip) { QString path = clip->getProducerProperty(QStringLiteral("resource")); QString titlepath = m_doc->projectFolder().path() + QDir::separator() + "titles/"; TitleWidget dia_ui(QUrl(), m_doc->timecode(), titlepath, pCore->monitorManager()->projectMonitor()->render, pCore->window()); connect(&dia_ui, SIGNAL(requestBackgroundFrame()), pCore->monitorManager()->projectMonitor(), SLOT(slotGetCurrentImage())); QDomDocument doc; doc.setContent(clip->getProducerProperty(QStringLiteral("xmldata"))); dia_ui.setXml(doc); if (dia_ui.exec() == QDialog::Accepted) { QMap newprops; newprops.insert(QStringLiteral("xmldata"), dia_ui.xml().toString()); if (dia_ui.duration() != clip->duration().frames(m_doc->fps())) { // duration changed, we need to update duration newprops.insert(QStringLiteral("out"), QString::number(dia_ui.duration() - 1)); int currentLength = clip->getProducerIntProperty(QStringLiteral("length")); if (currentLength <= dia_ui.duration()) { newprops.insert(QStringLiteral("length"), QString::number(dia_ui.duration())); } else { newprops.insert(QStringLiteral("length"), clip->getProducerProperty(QStringLiteral("length"))); } } // 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->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, MessageType type) { emit displayMessage(text, type); } void Bin::slotCreateAudioThumb(const QString &id) { ProjectClip *clip = m_rootFolder->clip(id); if (!clip) return; clip->createAudioThumbs(); } 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, QUrl url, const QString &errorMessage) { if (m_invalidClipDialog) { if (!url.isEmpty()) m_invalidClipDialog->addClip(id, url.toLocalFile()); 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.toLocalFile()); int result = m_invalidClipDialog->exec(); if (result == QDialog::Accepted) { QStringList ids = m_invalidClipDialog->getIds(); foreach(const QString &i, ids) { deleteClip(i); } } delete m_invalidClipDialog; m_invalidClipDialog = NULL; } void Bin::slotRefreshClipThumbnail(const QString &id) { ProjectClip *clip = m_rootFolder->clip(id); if (!clip) return; clip->reloadProducer(true); } void Bin::slotAddClipExtraData(const QString &id, const QString &key, const QString &data, QUndoCommand *groupCommand) { ProjectClip *clip = m_rootFolder->clip(id); if (!clip) return; QString oldValue = clip->getProducerProperty(key); QMap oldProps; oldProps.insert(key, oldValue); QMap newProps; newProps.insert(key, data); EditClipCommand *command = new EditClipCommand(this, id, oldProps, newProps, true, groupCommand); if (!groupCommand) m_doc->commandStack()->push(command); } void Bin::slotUpdateClipProperties(const QString &id, QMap properties, bool refreshPropertiesPanel) { ProjectClip *clip = m_rootFolder->clip(id); if (clip) { clip->setProperties(properties, refreshPropertiesPanel); } } void Bin::updateTimelineProducers(const QString &id, QMap passProperties) { pCore->projectManager()->currentTimeline()->updateClipProperties(id, passProperties); m_doc->renderer()->updateSlowMotionProducers(id, passProperties); } void Bin::showSlideshowWidget(ProjectClip *clip) { QString folder = clip->url().adjusted(QUrl::RemoveFilename).path(); SlideshowClip *dia = new SlideshowClip(m_doc->timecode(), folder, clip, 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("length"), 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(dia->loop())); properties.insert(QStringLiteral("crop"), QString::number(dia->crop())); properties.insert(QStringLiteral("fade"), QString::number(dia->fade())); properties.insert(QStringLiteral("luma_duration"), 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("length"), clip->getProducerProperty(QStringLiteral("length"))); 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->clipId(), oldProperties, properties); } } void Bin::slotDisableEffects(bool disable) { m_rootFolder->disableEffects(disable); pCore->projectManager()->disableBinEffects(disable); m_monitor->refreshMonitorIfActive(); } void Bin::setBinEffectsDisabledStatus(bool disabled) { QAction *disableEffects = pCore->window()->actionCollection()->action(QStringLiteral("disable_bin_effects")); if (disableEffects) { if (disabled == disableEffects->isChecked()) return; disableEffects->blockSignals(true); disableEffects->setChecked(disabled); disableEffects->blockSignals(false); } pCore->projectManager()->disableBinEffects(disabled); } void Bin::slotRenameFolder() { QModelIndexList indexes = m_proxyModel->selectionModel()->selectedIndexes(); foreach (const QModelIndex &ix, indexes) { if (!ix.isValid() || ix.column() != 0) { continue; } AbstractProjectItem *item = static_cast(m_proxyModel->mapToSource(ix).internalPointer()); ProjectFolder *currentItem = qobject_cast(item); if (currentItem) { m_itemView->edit(ix); return; } } } void Bin::refreshProxySettings() { QList clipList = m_rootFolder->childClips(); if (!m_doc->useProxy()) { // Disable all proxies m_doc->slotProxyCurrentItem(false, clipList); } else { QList toProxy; foreach (ProjectClip *clp, clipList) { ClipType t = clp->clipType(); if ((t == AV || t == Video || t == Playlist) && m_doc->autoGenerateProxy(clp->getProducerIntProperty(QStringLiteral("meta.media.width")))) { // Start proxy toProxy << clp; continue; } else if (t == Image && m_doc->autoGenerateImageProxy(clp->getProducerIntProperty(QStringLiteral("meta.media.width")))) { // Start proxy toProxy << clp; continue; } } if (!toProxy.isEmpty()) m_doc->slotProxyCurrentItem(true, toProxy); } } void Bin::slotSendAudioThumb(QString id) { ProjectClip *clip = m_rootFolder->clip(id); if (clip && 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_clipCounter == 1 || m_rootFolder == NULL) return true; return m_rootFolder->isEmpty(); } void Bin::reloadAllProducers() { if (m_rootFolder == NULL || m_rootFolder->isEmpty() || !isEnabled()) return; QList clipList = m_rootFolder->childClips(); emit openClip(NULL); foreach(ProjectClip *clip, clipList) { QDomDocument doc; QDomElement xml = clip->toXml(doc); // Make sure we reload clip length xml.removeAttribute("out"); EffectsList::removeProperty(xml, "length"); if (!xml.isNull()) { clip->setClipStatus(AbstractProjectItem::StatusWaiting); + clip->discardAudioThumb(); // We need to set a temporary id before all outdated producers are replaced; m_doc->getFileProperties(xml, clip->clipId(), 150, true); } } } void Bin::slotMessageActionTriggered() { m_infoMessage->animatedHide(); } void Bin::resetUsageCount() { QList clipList = m_rootFolder->childClips(); foreach(ProjectClip *clip, clipList) { clip->setRefCount(0); } } void Bin::cleanup() { QList clipList = m_rootFolder->childClips(); QStringList ids; QStringList subIds; foreach(ProjectClip *clip, clipList) { if (clip->refCount() == 0) { ids << clip->clipId(); subIds << clip->subClipIds(); } } QUndoCommand *command = new QUndoCommand(); command->setText(i18n("Clean Project")); m_doc->clipManager()->slotDeleteClips(ids, QStringList(), subIds, command, true); } void Bin::getBinStats(uint *used, uint *unused, qint64 *usedSize, qint64 *unusedSize) { QList clipList = m_rootFolder->childClips(); foreach(ProjectClip *clip, clipList) { if (clip->refCount() == 0) { *unused += 1; *unusedSize += clip->getProducerInt64Property("kdenlive:file_size"); } else { *used += 1; *usedSize += clip->getProducerInt64Property("kdenlive:file_size"); } } } diff --git a/src/bin/projectclip.cpp b/src/bin/projectclip.cpp index d62bbee8b..dd2a91588 100644 --- a/src/bin/projectclip.cpp +++ b/src/bin/projectclip.cpp @@ -1,1177 +1,1196 @@ /* 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 "projectclip.h" #include "projectfolder.h" #include "projectsubclip.h" #include "bin.h" #include "timecode.h" #include "doc/kthumb.h" #include "kdenlivesettings.h" #include "timeline/clip.h" #include "project/projectcommands.h" #include "mltcontroller/clipcontroller.h" #include "lib/audio/audioStreamInfo.h" #include "mltcontroller/clippropertiescontroller.h" #include #include #include #include #include #include #include #include ProjectClip::ProjectClip(const QString &id, QIcon thumb, ClipController *controller, ProjectFolder* parent) : AbstractProjectItem(AbstractProjectItem::ClipItem, id, parent) , m_abortAudioThumb(false) , m_controller(controller) , m_thumbsProducer(NULL) { m_clipStatus = StatusReady; m_thumbnail = thumb; m_name = m_controller->clipName(); m_duration = m_controller->getStringDuration(); m_date = m_controller->date; m_description = m_controller->description(); m_type = m_controller->clipType(); // Make sure we have a hash for this clip hash(); setParent(parent); connect(this, &ProjectClip::updateJobStatus, this, &ProjectClip::setJobStatus); bin()->loadSubClips(id, m_controller->getPropertiesFromPrefix(QStringLiteral("kdenlive:clipzone."))); createAudioThumbs(); } ProjectClip::ProjectClip(const QDomElement& description, QIcon thumb, ProjectFolder* parent) : AbstractProjectItem(AbstractProjectItem::ClipItem, description, parent) , m_abortAudioThumb(false) , m_controller(NULL) , m_type(Unknown) , m_thumbsProducer(NULL) { Q_ASSERT(description.hasAttribute("id")); m_clipStatus = StatusWaiting; m_thumbnail = thumb; if (description.hasAttribute(QStringLiteral("type"))) { m_type = (ClipType) description.attribute(QStringLiteral("type")).toInt(); } m_temporaryUrl = QUrl::fromLocalFile(getXmlProperty(description, QStringLiteral("resource"))); QString clipName = getXmlProperty(description, QStringLiteral("kdenlive:clipname")); if (!clipName.isEmpty()) { m_name = clipName; } else if (m_temporaryUrl.isValid()) { m_name = m_temporaryUrl.fileName(); } else m_name = i18n("Untitled"); connect(this, &ProjectClip::updateJobStatus, this, &ProjectClip::setJobStatus); setParent(parent); } ProjectClip::~ProjectClip() { // controller is deleted in bincontroller abortAudioThumbs(); bin()->slotAbortAudioThumb(m_id); QMutexLocker audioLock(&m_audioMutex); m_thumbMutex.lock(); m_requestedThumbs.clear(); m_thumbMutex.unlock(); m_thumbThread.waitForFinished(); delete m_thumbsProducer; audioFrameCache.clear(); } void ProjectClip::abortAudioThumbs() { m_abortAudioThumb = true; emit doAbortAudioThumbs(); } QString ProjectClip::getToolTip() const { return url().toLocalFile(); } QString ProjectClip::getXmlProperty(const QDomElement &producer, const QString &propertyName, const QString &defaultValue) { QString value = defaultValue; QDomNodeList props = producer.elementsByTagName(QStringLiteral("property")); for (int i = 0; i < props.count(); ++i) { if (props.at(i).toElement().attribute(QStringLiteral("name")) == propertyName) { value = props.at(i).firstChild().nodeValue(); break; } } return value; } void ProjectClip::updateAudioThumbnail(QVariantList audioLevels) { audioFrameCache = audioLevels; m_controller->audioThumbCreated = true; bin()->emitRefreshAudioThumbs(m_id); emit gotAudioData(); } QList < CommentedTime > ProjectClip::commentedSnapMarkers() const { if (m_controller) return m_controller->commentedSnapMarkers(); return QList < CommentedTime > (); } QStringList ProjectClip::markersText(GenTime in, GenTime out) const { if (m_controller) return m_controller->markerComments(in, out); return QStringList(); } bool ProjectClip::audioThumbCreated() const { return (m_controller && m_controller->audioThumbCreated); } ClipType ProjectClip::clipType() const { return m_type; } bool ProjectClip::hasParent(const QString &id) const { AbstractProjectItem *par = parent(); while (par) { if (par->clipId() == id) { return true; } par = par->parent(); } return false; } ProjectClip* ProjectClip::clip(const QString &id) { if (id == m_id) { return this; } return NULL; } ProjectFolder* ProjectClip::folder(const QString &id) { Q_UNUSED(id) return NULL; } void ProjectClip::disableEffects(bool disable) { if (m_controller) m_controller->disableEffects(disable); } ProjectSubClip* ProjectClip::getSubClip(int in, int out) { for (int i = 0; i < count(); ++i) { ProjectSubClip *clip = static_cast(at(i))->subClip(in, out); if (clip) { return clip; } } return NULL; } QStringList ProjectClip::subClipIds() const { QStringList subIds; for (int i = 0; i < count(); ++i) { AbstractProjectItem *clip = at(i); if (clip) { subIds << clip->clipId(); } } return subIds; } ProjectClip* ProjectClip::clipAt(int ix) { if (ix == index()) { return this; } return NULL; } /*bool ProjectClip::isValid() const { return m_controller->isValid(); }*/ QUrl ProjectClip::url() const { if (m_controller) return m_controller->clipUrl(); return m_temporaryUrl; } bool ProjectClip::hasLimitedDuration() const { if (m_controller) { return m_controller->hasLimitedDuration(); } return true; } GenTime ProjectClip::duration() const { if (m_controller) { return m_controller->getPlaytime(); } return GenTime(); } void ProjectClip::reloadProducer(bool thumbnailOnly) { QDomDocument doc; QDomElement xml = toXml(doc); if (thumbnailOnly) { // set a special flag to request thumbnail only xml.setAttribute(QStringLiteral("thumbnailOnly"), QStringLiteral("1")); } bin()->reloadProducer(m_id, xml); } void ProjectClip::setCurrent(bool current, bool notify) { Q_UNUSED(notify) if (current && m_controller) { bin()->openProducer(m_controller); bin()->editMasterEffect(m_controller); } } QDomElement ProjectClip::toXml(QDomDocument& document, bool includeMeta) { if (m_controller) { m_controller->getProducerXML(document, includeMeta); return document.documentElement().firstChildElement(QStringLiteral("producer")); } else { // We only have very basic infos, ike id and url, pass them QDomElement prod = document.createElement(QStringLiteral("producer")); prod.setAttribute(QStringLiteral("id"), m_id); EffectsList::setProperty(prod, QStringLiteral("resource"), m_temporaryUrl.path()); if (m_type != Unknown) { prod.setAttribute(QStringLiteral("type"), (int) m_type); } document.appendChild(prod); return prod; } } void ProjectClip::setThumbnail(QImage img) { QPixmap thumb = roundedPixmap(QPixmap::fromImage(img)); if (hasProxy() && !thumb.isNull()) { // Overlay proxy icon QPainter p(&thumb); QColor c(220, 220, 10, 200); QRect r(0, 0, thumb.height() / 2.5, thumb.height() / 2.5); p.fillRect(r, c); QFont font = p.font(); font.setPixelSize(r.height()); font.setBold(true); p.setFont(font); p.setPen(Qt::black); p.drawText(r, Qt::AlignCenter, i18nc("The first letter of Proxy, used as abbreviation", "P")); } m_thumbnail = QIcon(thumb); emit thumbUpdated(img); bin()->emitItemUpdated(this); } QPixmap ProjectClip::thumbnail(int width, int height) { return m_thumbnail.pixmap(width, height); } bool ProjectClip::setProducer(ClipController *controller, bool replaceProducer) { if (!replaceProducer && m_controller) { qDebug()<<"// RECEIVED PRODUCER BUT WE ALREADY HAVE ONE\n----------"; return false; } bool isNewProducer = true; if (m_controller) { // Replace clip for this controller resetProducerProperty("kdenlive:file_hash"); isNewProducer = false; } else if (controller) { // We did not yet have the controller, update info m_controller = controller; if (m_name.isEmpty()) m_name = m_controller->clipName(); m_date = m_controller->date; m_description = m_controller->description(); m_temporaryUrl.clear(); if (m_type == Unknown) m_type = m_controller->clipType(); } if (m_controller) m_duration = m_controller->getStringDuration(); m_clipStatus = StatusReady; if (!hasProxy()) bin()->emitRefreshPanel(m_id); bin()->emitItemUpdated(this); // Make sure we have a hash for this clip hash(); createAudioThumbs(); return isNewProducer; } void ProjectClip::createAudioThumbs() { if (KdenliveSettings::audiothumbnails() && (m_type == AV || m_type == Audio || m_type == Playlist)) { bin()->requestAudioThumbs(m_id); } } Mlt::Producer *ProjectClip::originalProducer() { if (!m_controller) { return NULL; } return &m_controller->originalProducer(); } Mlt::Producer *ProjectClip::thumbProducer() { QMutexLocker locker(&m_producerMutex); if (m_thumbsProducer) { return m_thumbsProducer; } if (!m_controller) { return NULL; } Mlt::Producer prod = m_controller->originalProducer(); Clip clip(prod); m_thumbsProducer = clip.softClone(ClipController::getPassPropertiesList()); // Check if we are using GPU accel, then we need to use alternate producer if (KdenliveSettings::gpu_accel()) { Mlt::Filter scaler(*prod.profile(), "swscale"); Mlt::Filter converter(*prod.profile(), "avcolor_space"); m_thumbsProducer->attach(scaler); m_thumbsProducer->attach(converter); } return m_thumbsProducer; } ClipController *ProjectClip::controller() { return m_controller; } bool ProjectClip::isReady() const { return m_controller != NULL && m_clipStatus == StatusReady; } /*void ProjectClip::setZone(const QPoint &zone) { m_zone = zone; }*/ QPoint ProjectClip::zone() const { int x = getProducerIntProperty(QStringLiteral("kdenlive:zone_in")); int y = getProducerIntProperty(QStringLiteral("kdenlive:zone_out")); return QPoint(x, y); } void ProjectClip::resetProducerProperty(const QString &name) { if (m_controller) { m_controller->resetProperty(name); } } void ProjectClip::setProducerProperty(const QString &name, int data) { if (m_controller) { m_controller->setProperty(name, data); } } void ProjectClip::setProducerProperty(const QString &name, double data) { if (m_controller) { m_controller->setProperty(name, data); } } void ProjectClip::setProducerProperty(const QString &name, const QString &data) { if (m_controller) { m_controller->setProperty(name, data); } } QMap ProjectClip::currentProperties(const QMap &props) { QMap currentProps; if (!m_controller) { return currentProps; } QMap::const_iterator i = props.constBegin(); while (i != props.constEnd()) { currentProps.insert(i.key(), m_controller->property(i.key())); ++i; } return currentProps; } QColor ProjectClip::getProducerColorProperty(const QString &key) const { if (m_controller) { return m_controller->color_property(key); } return QColor(); } int ProjectClip::getProducerIntProperty(const QString &key) const { int value = 0; if (m_controller) { value = m_controller->int_property(key); } return value; } qint64 ProjectClip::getProducerInt64Property(const QString &key) const { qint64 value = 0; if (m_controller) { value = m_controller->int64_property(key); } return value; } double ProjectClip::getDoubleProducerProperty(const QString &key) const { double value = 0; if (m_controller) { value = m_controller->double_property(key); } return value; } QString ProjectClip::getProducerProperty(const QString &key) const { QString value; if (m_controller) { value = m_controller->property(key); } return value; } const QString ProjectClip::hash() { if (m_controller) { QString clipHash = m_controller->property(QStringLiteral("kdenlive:file_hash")); if (!clipHash.isEmpty()) { return clipHash; } } return getFileHash(); } const QString ProjectClip::getFileHash() const { QByteArray fileData; QByteArray fileHash; switch (m_type) { case SlideShow: fileData = m_controller ? m_controller->clipUrl().toLocalFile().toUtf8() : m_temporaryUrl.toLocalFile().toUtf8(); fileHash = QCryptographicHash::hash(fileData, QCryptographicHash::Md5); break; case Text: fileData = m_controller ? m_controller->property(QStringLiteral("xmldata")).toUtf8() : name().toUtf8(); fileHash = QCryptographicHash::hash(fileData, QCryptographicHash::Md5); break; case QText: fileData = m_controller ? m_controller->property(QStringLiteral("text")).toUtf8() : name().toUtf8(); fileHash = QCryptographicHash::hash(fileData, QCryptographicHash::Md5); break; case Color: fileData = m_controller ? m_controller->property(QStringLiteral("resource")).toUtf8() : name().toUtf8(); fileHash = QCryptographicHash::hash(fileData, QCryptographicHash::Md5); break; default: QFile file(m_controller ? m_controller->clipUrl().toLocalFile() : m_temporaryUrl.toLocalFile()); if (file.open(QIODevice::ReadOnly)) { // write size and hash only if resource points to a file /* * 1 MB = 1 second per 450 files (or faster) * 10 MB = 9 seconds per 450 files (or faster) */ if (file.size() > 2000000) { fileData = file.read(1000000); if (file.seek(file.size() - 1000000)) fileData.append(file.readAll()); } else fileData = file.readAll(); file.close(); if (m_controller) m_controller->setProperty(QStringLiteral("kdenlive:file_size"), QString::number(file.size())); fileHash = QCryptographicHash::hash(fileData, QCryptographicHash::Md5); } break; } if (fileHash.isEmpty()) return QString(); QString result = fileHash.toHex(); if (m_controller) { m_controller->setProperty(QStringLiteral("kdenlive:file_hash"), result); } return result; } double ProjectClip::getOriginalFps() const { if (!m_controller) return 0; return m_controller->originalFps(); } bool ProjectClip::hasProxy() const { QString proxy = getProducerProperty(QStringLiteral("kdenlive:proxy")); if (proxy.isEmpty() || proxy == QLatin1String("-")) return false; return true; } void ProjectClip::setProperties(QMap properties, bool refreshPanel) { QMapIterator i(properties); QMap passProperties; bool refreshAnalysis = false; bool reload = false; // Some properties also need to be passed to track producers QStringList timelineProperties; if (properties.contains(QLatin1String("templatetext"))) { m_description = properties.value(QLatin1String("templatetext")); bin()->emitItemUpdated(this); refreshPanel = true; } timelineProperties << QStringLiteral("force_aspect_ratio") << QStringLiteral("video_index") << QStringLiteral("audio_index") << QStringLiteral("set.force_full_luma")<< QStringLiteral("full_luma") <hasPendingJob(m_id, AbstractClipJob::PROXYJOB)) { bin()->discardJobs(m_id, AbstractClipJob::PROXYJOB); } else { reloadProducer(); } } else { // A proxy was requested, make sure to keep original url setProducerProperty(QStringLiteral("kdenlive:originalurl"), url().toLocalFile()); bin()->startJob(m_id, AbstractClipJob::PROXYJOB); } } else if (properties.contains(QStringLiteral("resource")) || properties.contains(QStringLiteral("templatetext"))) { // Clip resource changed, update thumbnail if (m_type != Color) { reloadProducer(); } else reload = true; } if (properties.contains(QStringLiteral("xmldata")) || !passProperties.isEmpty()) { reload = true; } if (refreshAnalysis) emit refreshAnalysisPanel(); if (properties.contains(QStringLiteral("length"))) { m_duration = m_controller->getStringDuration(); bin()->emitItemUpdated(this); } if (properties.contains(QStringLiteral("kdenlive:clipname"))) { m_name = properties.value(QStringLiteral("kdenlive:clipname")); bin()->emitItemUpdated(this); } if (refreshPanel) { // Some of the clip properties have changed through a command, update properties panel emit refreshPropertiesPanel(); } if (reload) { // producer has changed, refresh monitor and thumbnail reloadProducer(true); bin()->refreshClip(m_id); } if (!passProperties.isEmpty()) { bin()->updateTimelineProducers(m_id, passProperties); } } void ProjectClip::setJobStatus(int jobType, int status, int progress, const QString &statusMessage) { m_jobType = (AbstractClipJob::JOBTYPE) jobType; if (progress > 0) { if (m_jobProgress == progress) return; m_jobProgress = progress; } else { m_jobProgress = (ClipJobStatus) status; if ((status == JobAborted || status == JobCrashed || status == JobDone) && !statusMessage.isEmpty()) { m_jobMessage = statusMessage; bin()->emitMessage(statusMessage, OperationCompletedMessage); } } bin()->emitItemUpdated(this); } ClipPropertiesController *ProjectClip::buildProperties(QWidget *parent) { ClipPropertiesController *panel = new ClipPropertiesController(bin()->projectTimecode(), m_controller, parent); connect(this, SIGNAL(refreshPropertiesPanel()), panel, SLOT(slotReloadProperties())); connect(this, SIGNAL(refreshAnalysisPanel()), panel, SLOT(slotFillAnalysisData())); return panel; } void ProjectClip::updateParentInfo(const QString &folderid, const QString &foldername) { Q_UNUSED(foldername) m_controller->setProperty(QStringLiteral("kdenlive:folderid"), folderid); } bool ProjectClip::matches(QString condition) { //TODO Q_UNUSED(condition) return true; } const QString ProjectClip::codec(bool audioCodec) const { if (!m_controller) return QString(); return m_controller->codec(audioCodec); } bool ProjectClip::rename(const QString &name, int column) { QMap newProperites; QMap oldProperites; bool edited = false; switch (column) { case 0: if (m_name == name) return false; // Rename clip oldProperites.insert(QStringLiteral("kdenlive:clipname"), m_name); newProperites.insert(QStringLiteral("kdenlive:clipname"), name); m_name = name; edited = true; break; case 2: if (m_description == name) return false; // Rename clip if (m_type == TextTemplate) { oldProperites.insert(QStringLiteral("templatetext"), m_description); newProperites.insert(QStringLiteral("templatetext"), name); } else { oldProperites.insert(QStringLiteral("kdenlive:description"), m_description); newProperites.insert(QStringLiteral("kdenlive:description"), name); } m_description = name; edited = true; break; } if (edited) { bin()->slotEditClipCommand(m_id, oldProperites, newProperites); } return edited; } void ProjectClip::addClipMarker(QList newMarkers, QUndoCommand *groupCommand) { if (!m_controller) return; QList oldMarkers; for (int i = 0; i < newMarkers.count(); ++i) { CommentedTime oldMarker = m_controller->markerAt(newMarkers.at(i).time()); if (oldMarker == CommentedTime()) { oldMarker = newMarkers.at(i); oldMarker.setMarkerType(-1); } oldMarkers << oldMarker; } (void) new AddMarkerCommand(this, oldMarkers, newMarkers, groupCommand); } bool ProjectClip::deleteClipMarkers(QUndoCommand *command) { QList markers = commentedSnapMarkers(); if (markers.isEmpty()) { return false; } QList newMarkers; for (int i = 0; i < markers.size(); ++i) { CommentedTime marker = markers.at(i); marker.setMarkerType(-1); newMarkers << marker; } new AddMarkerCommand(this, markers, newMarkers, command); return true; } void ProjectClip::addMarkers(QList &markers) { if (!m_controller) return; for (int i = 0; i < markers.count(); ++i) { if (markers.at(i).markerType() < 0) m_controller->deleteSnapMarker(markers.at(i).time()); else m_controller->addSnapMarker(markers.at(i)); } // refresh markers in clip monitor bin()->refreshClipMarkers(m_id); // refresh markers in timeline clips emit refreshClipDisplay(); } void ProjectClip::addEffect(const ProfileInfo &pInfo, QDomElement &effect) { m_controller->addEffect(pInfo, effect); bin()->updateMasterEffect(m_controller); bin()->emitItemUpdated(this); } void ProjectClip::removeEffect(int ix) { m_controller->removeEffect(ix); bin()->updateMasterEffect(m_controller); bin()->emitItemUpdated(this); } QVariant ProjectClip::data(DataType type) const { switch (type) { case AbstractProjectItem::IconOverlay: return m_controller != NULL ? (m_controller->hasEffects() ? QVariant("kdenlive-track_has_effect") : QVariant()) : QVariant(); break; default: break; } return AbstractProjectItem::data(type); } void ProjectClip::slotExtractImage(QList frames) { QMutexLocker lock(&m_thumbMutex); for (int i = 0; i < frames.count(); i++) { if (!m_requestedThumbs.contains(frames.at(i))) { m_requestedThumbs << frames.at(i); } } qSort(m_requestedThumbs); if (!m_thumbThread.isRunning()) { m_thumbThread = QtConcurrent::run(this, &ProjectClip::doExtractImage); } } void ProjectClip::doExtractImage() { Mlt::Producer *prod = thumbProducer(); if (prod == NULL || !prod->is_valid()) return; int fullWidth = (int)((double) 150 * prod->profile()->dar() + 0.5); QDir thumbFolder(bin()->projectFolder().path() + "/thumbs/"); int max = prod->get_length(); int pos; while (!m_requestedThumbs.isEmpty()) { m_thumbMutex.lock(); pos = m_requestedThumbs.takeFirst(); m_thumbMutex.unlock(); if (thumbFolder.exists(hash() + '#' + QString::number(pos) + ".png")) { emit thumbReady(pos, QImage(thumbFolder.absoluteFilePath(hash() + '#' + QString::number(pos) + ".png"))); continue; } if (pos >= max) pos = max - 1; prod->seek(pos); Mlt::Frame *frame = prod->get_frame(); if (frame && frame->is_valid()) { QImage img = KThumb::getFrame(frame, fullWidth, 150); emit thumbReady(pos, img); } delete frame; } } void ProjectClip::slotExtractSubImage(QList frames) { Mlt::Producer *prod = thumbProducer(); if (prod == NULL || !prod->is_valid()) return; int fullWidth = (int)((double) 150 * prod->profile()->dar() + 0.5); QDir thumbFolder(bin()->projectFolder().path() + "/thumbs/"); int max = prod->get_length(); for (int i = 0; i < frames.count(); i++) { int pos = frames.at(i); QString path = thumbFolder.absoluteFilePath(hash() + "#" + QString::number(pos) + ".png"); QImage img(path); if (!img.isNull()) { for (int i = 0; i < count(); ++i) { ProjectSubClip *clip = static_cast(at(i)); if (clip && clip->zone().x() == pos) { clip->setThumbnail(img); } } continue; } pos = qBound(0, pos, max - 1); prod->seek(pos); Mlt::Frame *frame = prod->get_frame(); if (frame && frame->is_valid()) { QImage img = KThumb::getFrame(frame, fullWidth, 150); if (!img.isNull()) { img.save(path); for (int i = 0; i < count(); ++i) { ProjectSubClip *clip = static_cast(at(i)); if (clip && clip->zone().x() == pos) { clip->setThumbnail(img); } } } } delete frame; } } int ProjectClip::audioChannels() const { if (!m_controller || !m_controller->audioInfo()) return 0; return m_controller->audioInfo()->channels(); } +void ProjectClip::discardAudioThumb() +{ + if (!m_controller) return; + abortAudioThumbs(); + AudioStreamInfo *audioInfo = m_controller->audioInfo(); + if (audioInfo == NULL) return; + int audioStream = audioInfo->ffmpeg_audio_index(); + QString clipHash = hash(); + if (clipHash.isEmpty()) return; + QString audioPath = bin()->projectFolder().path() + "/thumbs/" + clipHash; + if (audioStream > 0) { + audioPath.append("_" + QString::number(audioInfo->audio_index())); + } + audioPath.append("_audio.png"); + QFile::remove(audioPath); + audioFrameCache.clear(); + m_controller->audioThumbCreated = false; +} + void ProjectClip::slotCreateAudioThumbs() { QMutexLocker lock(&m_audioMutex); Mlt::Producer *prod = originalProducer(); if (!prod || !prod->is_valid()) return; AudioStreamInfo *audioInfo = m_controller->audioInfo(); if (audioInfo == NULL) return; int audioStream = audioInfo->ffmpeg_audio_index(); QString clipHash = hash(); if (clipHash.isEmpty()) return; QString audioPath = bin()->projectFolder().path() + "/thumbs/" + clipHash; if (audioStream > 0) { audioPath.append("_" + QString::number(audioInfo->audio_index())); } audioPath.append("_audio.png"); int lengthInFrames = prod->get_length(); int frequency = audioInfo->samplingRate(); if (frequency <= 0) frequency = 48000; int channels = audioInfo->channels(); if (channels <= 0) channels = 2; QVariantList audioLevels; QImage image(audioPath); if (!image.isNull()) { // convert cached image int n = image.width() * image.height(); for (int i = 0; i < n; i++) { QRgb p = image.pixel(i / 2, i % channels); audioLevels << qRed(p); audioLevels << qGreen(p); audioLevels << qBlue(p); audioLevels << qAlpha(p); } } if (audioLevels.size() > 0) { updateAudioThumbnail(audioLevels); return; } if (KdenliveSettings::ffmpegaudiothumbnails() && m_type != Playlist) { QStringList args; QTemporaryFile tmpfile; if (!tmpfile.open()) { bin()->emitMessage(i18n("Cannot create temporary file, check disk space and permissions"), ErrorMessage); return; } QTemporaryFile tmpfile2; if (!tmpfile2.open()) { bin()->emitMessage(i18n("Cannot create temporary file, check disk space and permissions"), ErrorMessage); return; } tmpfile.close(); tmpfile2.close(); args << QStringLiteral("-i") << QUrl::fromLocalFile(prod->get("resource")).path(); bool isFFmpeg = KdenliveSettings::ffmpegpath().contains("ffmpeg"); if (channels == 1) { if (isFFmpeg) { args << QStringLiteral("-ac") << QString::number(channels); args << QStringLiteral("-filter_complex:a") << QStringLiteral("aformat=channel_layouts=mono,aresample=async=100"); args << QStringLiteral("-map") << QStringLiteral("0:a%1").arg(audioStream > 0 ? ":" + QString::number(audioStream) : "") << QStringLiteral("-c:a") << QStringLiteral("pcm_s16le") << QStringLiteral("-y") << QStringLiteral("-f") << QStringLiteral("data")<< tmpfile.fileName(); } else { args << QStringLiteral("-filter_complex:a") << QStringLiteral("aformat=channel_layouts=mono:sample_rates=100"); args << QStringLiteral("-map") << QStringLiteral("0:a%1").arg(audioStream > 0 ? ":" + QString::number(audioStream) : "") << QStringLiteral("-c:a") << QStringLiteral("pcm_s16le") << QStringLiteral("-y") << QStringLiteral("-f") << QStringLiteral("s16le")<< tmpfile.fileName(); } } else { if (isFFmpeg) { //args << QStringLiteral("-ac") << QString::number(channels); args << QStringLiteral("-filter_complex:a") << QStringLiteral("[0:a%1]aresample=async=100,channelsplit=channel_layout=stereo[0:0][0:1]").arg(audioStream > 0 ? ":" + QString::number(audioStream) : ""); // Channel 1 args << QStringLiteral("-map") << QStringLiteral("[0:1]") << QStringLiteral("-c:a") << QStringLiteral("pcm_s16le") << QStringLiteral("-y") << QStringLiteral("-f") << QStringLiteral("data")<< tmpfile.fileName(); // Channel 2 args << QStringLiteral("-map") << QStringLiteral("[0:0]") << QStringLiteral("-c:a") << QStringLiteral("pcm_s16le") << QStringLiteral("-y") << QStringLiteral("-f") << QStringLiteral("data")<< tmpfile2.fileName(); } else { args << QStringLiteral("-filter_complex:a") << QStringLiteral("[0:a%1]aformat=sample_rates=100,channelsplit=channel_layout=stereo[0:0][0:1]").arg(audioStream > 0 ? ":" + QString::number(audioStream) : ""); // Channel 1 args << QStringLiteral("-map") << QStringLiteral("[0:1]") << QStringLiteral("-c:a") << QStringLiteral("pcm_s16le") << QStringLiteral("-y") << QStringLiteral("-f") << QStringLiteral("s16le")<< tmpfile.fileName(); // Channel 2 args << QStringLiteral("-map") << QStringLiteral("[0:0]") << QStringLiteral("-c:a") << QStringLiteral("pcm_s16le") << QStringLiteral("-y") << QStringLiteral("-f") << QStringLiteral("s16le")<< tmpfile2.fileName(); } } emit updateJobStatus(AbstractClipJob::THUMBJOB, JobWaiting, 0); QProcess audioThumbsProcess; - connect(this, SIGNAL(doAbortAudioThumbs()), &audioThumbsProcess, SLOT(kill())); + connect(this, SIGNAL(doAbortAudioThumbs()), &audioThumbsProcess, SLOT(kill()), Qt::DirectConnection); audioThumbsProcess.start(KdenliveSettings::ffmpegpath(), args); bool ffmpegError = false; if (!audioThumbsProcess.waitForStarted()) { ffmpegError = true; } audioThumbsProcess.waitForFinished(); if (m_abortAudioThumb) { emit updateJobStatus(AbstractClipJob::THUMBJOB, JobDone, 0); m_abortAudioThumb = false; return; } if (ffmpegError || audioThumbsProcess.exitStatus() == QProcess::CrashExit) { emit updateJobStatus(AbstractClipJob::THUMBJOB, JobDone, 0); bin()->emitMessage(i18n("Crash in %1 - creating audio thumbnails", KdenliveSettings::ffmpegpath()), ErrorMessage); return; } tmpfile.open(); QByteArray res = tmpfile.readAll(); tmpfile.close(); if (res.size() == 0) { emit updateJobStatus(AbstractClipJob::THUMBJOB, JobDone, 0); bin()->emitMessage(i18n("Error reading audio thumbnail"), ErrorMessage); return; } const qint16* raw = (const qint16*) res.constData(); - const qint16* raw2 = raw; + const qint16* raw2; QByteArray res2; QList data2; if (channels > 1) { tmpfile2.open(); res2 = tmpfile2.readAll(); tmpfile2.close(); raw2 = (const qint16*) res2.constData(); } int progress = 0; double offset = (double) res.size() / (2 * lengthInFrames); int pos = 0; for (int i = 0; i < lengthInFrames; i++) { long c1 = 0; long c2 = 0; pos = (int) (i * offset); int steps = 0; for (int j = 0; j < (int) offset && (pos + j < res.size()); j++) { steps ++; c1 += abs(raw[pos + j]); if (channels > 1) { c2 += abs(raw2[pos + j]); } } if (steps) c1 /= steps; c1 = c1 * 800 / 32768.0; audioLevels << (double) c1; if (channels > 1) { if (steps) c2 /= steps; c2 = c2 * 800 / 32768.0; audioLevels << (double)c2; } int p = i * 100 / lengthInFrames; if (p != progress) { emit updateJobStatus(AbstractClipJob::THUMBJOB, JobWorking, p); progress = p; } if (m_abortAudioThumb) break; } } else { QString service = prod->get("mlt_service"); if (service == QLatin1String("avformat-novalidate")) service = QStringLiteral("avformat"); else if (service.startsWith(QLatin1String("xml"))) service = QStringLiteral("xml-nogl"); QScopedPointer audioProducer(new Mlt::Producer(*prod->profile(), service.toUtf8().constData(), prod->get("resource"))); if (!audioProducer->is_valid()) { return; } audioProducer->set("video_index", "-1"); Mlt::Filter chans(*prod->profile(), "audiochannels"); Mlt::Filter converter(*prod->profile(), "audioconvert"); Mlt::Filter levels(*prod->profile(), "audiolevel"); audioProducer->attach(chans); audioProducer->attach(converter); audioProducer->attach(levels); int last_val = 0; emit updateJobStatus(AbstractClipJob::THUMBJOB, JobWaiting, 0); double framesPerSecond = audioProducer->get_fps(); mlt_audio_format audioFormat = mlt_audio_s16; QStringList keys; for (int i = 0; i < channels; i++) { keys << "meta.media.audio_level." + QString::number(i); } int val = 0; for (int z = 0;z < lengthInFrames && !m_abortAudioThumb; ++z) { val = (int)(100.0 * z / lengthInFrames); if (last_val != val) { emit updateJobStatus(AbstractClipJob::THUMBJOB, JobWorking, val); last_val = val; } QScopedPointer mlt_frame(audioProducer->get_frame()); if (mlt_frame && mlt_frame->is_valid() && !mlt_frame->get_int("test_audio")) { int samples = mlt_sample_calculator(framesPerSecond, frequency, z); mlt_frame->get_audio(audioFormat, frequency, channels, samples); for (int channel = 0; channel < channels; ++channel) { double level = 256 * qMin(mlt_frame->get_double(keys.at(channel).toUtf8().constData()) * 0.9, 1.0); audioLevels << level; } } else if (!audioLevels.isEmpty()) { for (int channel = 0; channel < channels; channel++) audioLevels << audioLevels.last(); } if (m_abortAudioThumb) break; } } emit updateJobStatus(AbstractClipJob::THUMBJOB, JobDone, 0); if (!m_abortAudioThumb) { updateAudioThumbnail(audioLevels); } if (!m_abortAudioThumb && audioLevels.size() > 0) { // Put into an image for caching. int count = audioLevels.size(); QImage image((count + 3) / 4 / channels, channels, QImage::Format_ARGB32); int n = image.width() * image.height(); for (int i = 0; i < n; i ++) { QRgb p; if ((4*i + 3) < count) { p = qRgba(audioLevels.at(4*i).toInt(), audioLevels.at(4*i+1).toInt(), audioLevels.at(4*i+2).toInt(), audioLevels.at(4*i+3).toInt()); } else { int last = audioLevels.last().toInt(); int r = (4*i+0) < count? audioLevels.at(4*i+0).toInt() : last; int g = (4*i+1) < count? audioLevels.at(4*i+1).toInt() : last; int b = (4*i+2) < count? audioLevels.at(4*i+2).toInt() : last; int a = last; p = qRgba(r, g, b, a); } image.setPixel(i / 2, i % channels, p); } image.save(audioPath); } m_abortAudioThumb = false; } bool ProjectClip::isTransparent() const { if (m_type == Text) return true; if (m_type == Image && m_controller->int_property(QStringLiteral("kdenlive:transparency")) == 1) return true; return false; } QStringList ProjectClip::updatedAnalysisData(const QString &name, const QString &data, int offset) { if (data.isEmpty()) { // Remove data return QStringList() << QString("kdenlive:clipanalysis." + name) << QString(); //m_controller->resetProperty("kdenlive:clipanalysis." + name); } else { QString current = m_controller->property("kdenlive:clipanalysis." + name); if (!current.isEmpty()) { if (KMessageBox::questionYesNo(QApplication::activeWindow(), i18n("Clip already contains analysis data %1", name), QString(), KGuiItem(i18n("Merge")), KGuiItem(i18n("Add"))) == KMessageBox::Yes) { // Merge data Mlt::Profile *profile = m_controller->profile(); Mlt::Geometry geometry(current.toUtf8().data(), duration().frames(profile->fps()), profile->width(), profile->height()); Mlt::Geometry newGeometry(data.toUtf8().data(), duration().frames(profile->fps()), profile->width(), profile->height()); Mlt::GeometryItem item; int pos = 0; while (!newGeometry.next_key(&item, pos)) { pos = item.frame(); item.frame(pos + offset); pos++; geometry.insert(item); } return QStringList() << QString("kdenlive:clipanalysis." + name) << geometry.serialise(); //m_controller->setProperty("kdenlive:clipanalysis." + name, geometry.serialise()); } else { // Add data with another name int i = 1; QString previous = m_controller->property("kdenlive:clipanalysis." + name + QString::number(i)); while (!previous.isEmpty()) { ++i; previous = m_controller->property("kdenlive:clipanalysis." + name + QString::number(i)); } return QStringList() << QString("kdenlive:clipanalysis." + name + QString::number(i)) << geometryWithOffset(data, offset); //m_controller->setProperty("kdenlive:clipanalysis." + name + ' ' + QString::number(i), geometryWithOffset(data, offset)); } } else { return QStringList() << QString("kdenlive:clipanalysis." + name) << geometryWithOffset(data, offset); //m_controller->setProperty("kdenlive:clipanalysis." + name, geometryWithOffset(data, offset)); } } } QMap ProjectClip::analysisData(bool withPrefix) { return m_controller->getPropertiesFromPrefix(QStringLiteral("kdenlive:clipanalysis."), withPrefix); } const QString ProjectClip::geometryWithOffset(const QString &data, int offset) { if (offset == 0) return data; Mlt::Profile *profile = m_controller->profile(); Mlt::Geometry geometry(data.toUtf8().data(), duration().frames(profile->fps()), profile->width(), profile->height()); Mlt::Geometry newgeometry(NULL, duration().frames(profile->fps()), profile->width(), profile->height()); Mlt::GeometryItem item; int pos = 0; while (!geometry.next_key(&item, pos)) { pos = item.frame(); item.frame(pos + offset); pos++; newgeometry.insert(item); } return newgeometry.serialise(); } diff --git a/src/bin/projectclip.h b/src/bin/projectclip.h index ca0278918..980566946 100644 --- a/src/bin/projectclip.h +++ b/src/bin/projectclip.h @@ -1,255 +1,257 @@ /* 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 PROJECTCLIP_H #define PROJECTCLIP_H #include "abstractprojectitem.h" #include "definitions.h" #include #include #include class ProjectFolder; class QDomElement; class ClipController; class ClipPropertiesController; class ProjectSubClip; class QUndoCommand; namespace Mlt { class Producer; class Properties; }; /** * @class ProjectClip * @brief Represents a clip in the project (not timeline). * */ class ProjectClip : public AbstractProjectItem { Q_OBJECT public: /** * @brief Constructor; used when loading a project and the producer is already available. */ ProjectClip(const QString &id, QIcon thumb, ClipController *controller, ProjectFolder* parent); /** * @brief Constructor. * @param description element describing the clip; the "id" attribute and "resource" property are used */ ProjectClip(const QDomElement &description, QIcon thumb, ProjectFolder *parent); virtual ~ProjectClip(); void reloadProducer(bool thumbnailOnly = false); /** @brief Returns a unique hash identifier used to store clip thumbnails. */ //virtual void hash() = 0; /** @brief Returns this if @param id matches the clip's id or NULL otherwise. */ ProjectClip *clip(const QString &id); ProjectFolder* folder(const QString &id); ProjectSubClip* getSubClip(int in, int out); /** @brief Returns this if @param ix matches the clip's index or NULL otherwise. */ ProjectClip* clipAt(int ix); /** @brief Recursively disable/enable bin effects. */ void disableEffects(bool disable); /** @brief Returns the clip type as defined in definitions.h */ ClipType clipType() const; /** @brief Check if clip has a parent folder with id id */ bool hasParent(const QString &id) const; ClipPropertiesController *buildProperties(QWidget *parent); QPoint zone() const; /** @brief Returns true if we want to add an affine transition in timeline when dropping this clip. */ bool isTransparent() const; /** @brief Returns whether this clip has a url (=describes a file) or not. */ bool hasUrl() const; /** @brief Returns the clip's url. */ QUrl url() const; /** @brief Returns whether this clip has a limited duration or whether it is resizable ad infinitum. */ virtual bool hasLimitedDuration() const; /** @brief Returns the clip's duration. */ GenTime duration() const; /** @brief Returns the original clip's fps. */ double getOriginalFps() const; /** @brief Calls AbstractProjectItem::setCurrent and sets the bin monitor to use the clip's producer. */ virtual void setCurrent(bool current, bool notify = true); virtual bool rename(const QString &name, int column); virtual QDomElement toXml(QDomDocument &document, bool includeMeta = false); QVariant data(DataType type) const; /** @brief Sets thumbnail for this clip. */ void setThumbnail(QImage); QPixmap thumbnail(int width, int height); /** @brief Sets the MLT producer associated with this clip * @param producer The producer * @param replaceProducer If true, we replace existing producer with this one * @returns true if producer was changed * . */ bool setProducer(ClipController *controller, bool replaceProducer); /** @brief Returns true if this clip already has a producer. */ bool isReady() const; /** @brief Returns this clip's producer. */ Mlt::Producer *originalProducer(); Mlt::Producer *thumbProducer(); ClipController *controller(); /** @brief Set properties on this clip. TODO: should we store all in MLT or use extra m_properties ?. */ void setProperties(QMap properties, bool refreshPanel = false); /** @brief Get an XML property from MLT produced xml. */ static QString getXmlProperty(const QDomElement &producer, const QString &propertyName, const QString &defaultValue = QString()); virtual QString getToolTip() const; /** @brief The clip hash created from the clip's resource. */ const QString hash(); /** @brief Set a property on the MLT producer. */ void setProducerProperty(const QString &name, int data); /** @brief Set a property on the MLT producer. */ void setProducerProperty(const QString &name, double data); /** @brief Set a property on the MLT producer. */ void setProducerProperty(const QString &name, const QString &data); /** @brief Reset a property on the MLT producer (=delete the property). */ void resetProducerProperty(const QString &name); /** @brief Get a property from the MLT producer. */ QMap currentProperties(const QMap &props); QString getProducerProperty(const QString &key) const; int getProducerIntProperty(const QString &key) const; qint64 getProducerInt64Property(const QString &key) const; QColor getProducerColorProperty(const QString &key) const; double getDoubleProducerProperty(const QString &key) const; QList < CommentedTime > commentedSnapMarkers() const; /** @brief Returns a list of all markers comments between in ant out frames. */ QStringList markersText(GenTime in, GenTime out) const; /** @brief Returns true if we are using a proxy for this clip. */ bool hasProxy() const; /** Cache for every audio Frame with 10 Bytes */ /** format is frame -> channel ->bytes */ QVariantList audioFrameCache; bool audioThumbCreated() const; void updateParentInfo(const QString &folderid, const QString &foldername); void setWaitingStatus(const QString &id); /** @brief Returns true if the clip matched a condition, for example vcodec=mpeg1video. */ bool matches(QString condition); /** @brief Returns true if the clip's video codec is equal to @param codec. * @param audioCodec set to true if you want to check audio codec. When false, this will check the video codec */ const QString codec(bool audioCodec) const; void addClipMarker(QList newMarkers, QUndoCommand *groupCommand); bool deleteClipMarkers(QUndoCommand *groupCommand); void addMarkers(QList &markers); /** @brief Add an effect to bin clip. */ void addEffect(const ProfileInfo &pInfo, QDomElement &effect); void removeEffect(int ix); /** @brief Create audio thumbnail for this clip. */ void createAudioThumbs(); /** @brief Returns the number of audio channels. */ int audioChannels() const; /** @brief get data analysis value. */ QStringList updatedAnalysisData(const QString &name, const QString &data, int offset); QMap analysisData(bool withPrefix = false); /** @brief Abort running audio thumb process if any. */ void abortAudioThumbs(); /** @brief Returns the list of this clip's subclip's ids. */ QStringList subClipIds() const; + /** @brief Delete cached audio thumb - needs to be recreated */ + void discardAudioThumb(); public slots: void updateAudioThumbnail(QVariantList audioLevels); /** @brief Extract image thumbnails for timeline. */ void slotExtractImage(QList frames); /** @brief Extract image thumbnails for clip's subclips. */ void slotExtractSubImage(QList frames); void slotCreateAudioThumbs(); /** @brief Set the Job status on a clip. * @param jobType The job type * @param status The job status (see definitions.h) * @param progress The job progress (in percents) * @param statusMessage The job info message */ void setJobStatus(int jobType, int status, int progress = 0, const QString &statusMessage = QString()); private: bool m_abortAudioThumb; /** @brief The Clip controller for this clip. */ ClipController *m_controller; /** @brief Generate and store file hash if not available. */ const QString getFileHash() const; /** @brief Store clip url temporarily while the clip controller has not been created. */ QUrl m_temporaryUrl; ClipType m_type; Mlt::Producer *m_thumbsProducer; QMutex m_producerMutex; QMutex m_thumbMutex; QMutex m_audioMutex; QFuture m_thumbThread; QList m_requestedThumbs; const QString geometryWithOffset(const QString &data, int offset); void doExtractImage(); signals: void gotAudioData(); void refreshPropertiesPanel(); void refreshAnalysisPanel(); void refreshClipDisplay(); void thumbReady(int, QImage); void thumbUpdated(QImage); void updateJobStatus(int jobType, int status, int progress = 0, const QString &statusMessage = QString()); /** @brief Clip is ready, load properties. */ void loadPropertiesPanel(); /** @brief Terminate running audio proxy job. */ void doAbortAudioThumbs(); }; #endif diff --git a/src/dvdwizard/dvdwizardmenu.cpp b/src/dvdwizard/dvdwizardmenu.cpp index 51361a05c..e800dc0d6 100644 --- a/src/dvdwizard/dvdwizardmenu.cpp +++ b/src/dvdwizard/dvdwizardmenu.cpp @@ -1,857 +1,857 @@ /*************************************************************************** * Copyright (C) 2009 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 "dvdwizardmenu.h" #include "kdenlivesettings.h" #include "utils/KoIconUtils.h" #include #include #include "klocalizedstring.h" #include #include "doc/kthumb.h" enum { DvdButtonItem = QGraphicsItem::UserType + 1, DvdButtonUnderlineItem = QGraphicsItem::UserType + 2 }; DvdScene::DvdScene(QObject * parent): QGraphicsScene(parent) , m_width(0) , m_height(0) , m_gridSize(1) { } void DvdScene::setProfile(int width, int height) { m_width = width; m_height = height; setSceneRect(0, 0, m_width, m_height); } int DvdScene::sceneWidth() const { return m_width; } int DvdScene::sceneHeight() const { return m_height; } int DvdScene::gridSize() const { return m_gridSize; } void DvdScene::setGridSize(int gridSize) { m_gridSize = gridSize; } void DvdScene::mouseReleaseEvent( QGraphicsSceneMouseEvent * mouseEvent ) { QGraphicsScene::mouseReleaseEvent(mouseEvent); emit sceneChanged(); } void DvdScene::drawForeground(QPainter *painter, const QRectF &rect) { // draw the grid if needed if (gridSize() <= 1) return; - QPen pen; + QPen pen(QColor(255, 0, 0, 100)); painter->setPen(pen); qreal left = int(rect.left()) - (int(rect.left()) % m_gridSize); qreal top = int(rect.top()) - (int(rect.top()) % m_gridSize); QVector points; for (qreal x = left; x < rect.right(); x += m_gridSize){ for (qreal y = top; y < rect.bottom(); y += m_gridSize){ points.append(QPointF(x,y)); } } painter->drawPoints(points.data(), points.size()); } DvdButton::DvdButton(const QString & text): QGraphicsTextItem(text) , m_target(0) , m_command(QStringLiteral("jump title 1")) , m_backToMenu(false) { setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable); setFlag(QGraphicsItem::ItemSendsGeometryChanges, true); } void DvdButton::setTarget(int t, const QString &c) { m_target = t; m_command = c; } int DvdButton::target() const { return m_target; } QString DvdButton::command() const { return m_command; } bool DvdButton::backMenu() const { return m_backToMenu; } int DvdButton::type() const { // Enable the use of qgraphicsitem_cast with this item. return UserType + 1; } void DvdButton::setBackMenu(bool back) { m_backToMenu = back; } QVariant DvdButton::itemChange(GraphicsItemChange change, const QVariant &value) { if (change == ItemPositionChange && scene()) { QPoint newPos = value.toPoint(); if(QApplication::mouseButtons() == Qt::LeftButton && qobject_cast (scene())){ DvdScene* customScene = qobject_cast (scene()); int gridSize = customScene->gridSize(); int xV = (newPos.x()/gridSize)*gridSize; int yV = (newPos.y()/gridSize)*gridSize; newPos = QPoint(xV, yV); } QRectF sceneShape = sceneBoundingRect(); DvdScene *sc = static_cast < DvdScene * >(scene()); newPos.setX(qMax(newPos.x(), 0)); newPos.setY(qMax(newPos.y(), 0)); if (newPos.x() + sceneShape.width() > sc->width()) newPos.setX(sc->width() - sceneShape.width()); if (newPos.y() + sceneShape.height() > sc->height()) newPos.setY(sc->height() - sceneShape.height()); sceneShape.translate(newPos - pos()); QList list = scene()->items(sceneShape, Qt::IntersectsItemShape); list.removeAll(this); if (!list.isEmpty()) { for (int i = 0; i < list.count(); ++i) { if (list.at(i)->type() == Type) return pos(); } } return newPos; } return QGraphicsItem::itemChange(change, value); } DvdWizardMenu::DvdWizardMenu(DVDFORMAT format, QWidget *parent) : QWizardPage(parent), m_color(NULL), m_safeRect(NULL), m_finalSize(720, 576), m_movieLength(-1) { m_view.setupUi(this); m_view.play_text->setText(i18n("Play")); m_scene = new DvdScene(this); m_view.menu_preview->setScene(m_scene); m_view.menu_preview->setMouseTracking(true); connect(m_view.create_menu, SIGNAL(toggled(bool)), m_view.menu_box, SLOT(setEnabled(bool))); connect(m_view.create_menu, SIGNAL(toggled(bool)), this, SIGNAL(completeChanged())); m_view.add_button->setIcon(KoIconUtils::themedIcon(QStringLiteral("document-new"))); m_view.delete_button->setIcon(KoIconUtils::themedIcon(QStringLiteral("trash-empty"))); m_view.zoom_button->setIcon(KoIconUtils::themedIcon(QStringLiteral("zoom-in"))); m_view.unzoom_button->setIcon(KoIconUtils::themedIcon(QStringLiteral("zoom-out"))); m_view.add_button->setToolTip(i18n("Add new button")); m_view.delete_button->setToolTip(i18n("Delete current button")); changeProfile(format); // Create color background m_color = new QGraphicsRectItem(0, 0, m_width, m_height); m_color->setBrush(m_view.background_color->color()); m_color->setZValue(2); m_scene->addItem(m_color); // create background image m_background = new QGraphicsPixmapItem(); m_background->setZValue(3); //m_scene->addItem(m_background); // create safe zone rect int safeW = m_width / 20; int safeH = m_height / 20; m_safeRect = new QGraphicsRectItem(safeW, safeH, m_width - 2 * safeW, m_height - 2 * safeH); QPen pen(Qt::red); pen.setStyle(Qt::DashLine); pen.setWidth(3); m_safeRect->setPen(pen); m_safeRect->setZValue(5); m_scene->addItem(m_safeRect); checkBackgroundType(0); // create menu button DvdButton *button = new DvdButton(m_view.play_text->text()); QFont font = m_view.font_family->currentFont(); font.setPixelSize(m_view.font_size->value()); //font.setStyleStrategy(QFont::NoAntialias); if (m_view.use_shadow->isChecked()) { QGraphicsDropShadowEffect *shadow = new QGraphicsDropShadowEffect(this); shadow->setBlurRadius(7); shadow->setOffset(4, 4); button->setGraphicsEffect(shadow); } connect(m_view.use_shadow, SIGNAL(stateChanged(int)), this, SLOT(slotEnableShadows(int))); button->setFont(font); button->setDefaultTextColor(m_view.text_color->color()); button->setZValue(4); QRectF r = button->sceneBoundingRect(); m_scene->addItem(button); button->setPos((m_width - r.width()) / 2, (m_height - r.height()) / 2); button->setSelected(true); if (m_view.use_grid->isChecked()) { m_scene->setGridSize(10); } connect(m_view.use_grid, SIGNAL(toggled(bool)), this, SLOT(slotUseGrid(bool))); //m_view.menu_preview->resizefitInView(0, 0, m_width, m_height); connect(m_view.play_text, SIGNAL(textChanged(QString)), this, SLOT(buildButton())); connect(m_view.text_color, SIGNAL(changed(QColor)), this, SLOT(updateColor())); connect(m_view.font_size, SIGNAL(valueChanged(int)), this, SLOT(buildButton())); connect(m_view.font_family, SIGNAL(currentFontChanged(QFont)), this, SLOT(buildButton())); connect(m_view.background_image, SIGNAL(textChanged(QString)), this, SLOT(buildImage())); connect(m_view.background_color, SIGNAL(changed(QColor)), this, SLOT(buildColor())); connect(m_view.background_list, SIGNAL(currentIndexChanged(int)), this, SLOT(checkBackgroundType(int))); connect(m_view.target_list, SIGNAL(activated(int)), this, SLOT(setButtonTarget(int))); connect(m_view.back_to_menu, SIGNAL(toggled(bool)), this, SLOT(setBackToMenu(bool))); connect(m_view.add_button, SIGNAL(pressed()), this, SLOT(addButton())); connect(m_view.delete_button, SIGNAL(pressed()), this, SLOT(deleteButton())); connect(m_view.zoom_button, SIGNAL(pressed()), this, SLOT(slotZoom())); connect(m_view.unzoom_button, SIGNAL(pressed()), this, SLOT(slotUnZoom())); connect(m_scene, SIGNAL(selectionChanged()), this, SLOT(buttonChanged())); connect(m_scene, SIGNAL(sceneChanged()), this, SIGNAL(completeChanged())); // red background for error message KColorScheme scheme(palette().currentColorGroup(), KColorScheme::Window, KSharedConfig::openConfig(KdenliveSettings::colortheme())); QPalette p = m_view.error_message->palette(); p.setColor(QPalette::Background, scheme.background(KColorScheme::NegativeBackground).color()); m_view.error_message->setAutoFillBackground(true); m_view.error_message->setPalette(p); m_view.menu_box->setEnabled(false); m_menuMessage = new KMessageWidget; QGridLayout *s = static_cast (layout()); s->addWidget(m_menuMessage, 7, 0, 1, -1); m_menuMessage->hide(); m_view.error_message->hide(); } DvdWizardMenu::~DvdWizardMenu() { delete m_color; delete m_safeRect; delete m_background; delete m_scene; } void DvdWizardMenu::slotEnableShadows(int enable) { QList list = m_scene->items(); for (int i = 0; i < list.count(); ++i) { if (list.at(i)->type() == DvdButtonItem) { if (enable) { QGraphicsDropShadowEffect *shadow = new QGraphicsDropShadowEffect(this); shadow->setBlurRadius(7); shadow->setOffset(4, 4); list.at(i)->setGraphicsEffect(shadow); } else list.at(i)->setGraphicsEffect(NULL); } } } void DvdWizardMenu::setButtonTarget(int ix) { QList list = m_scene->selectedItems(); for (int i = 0; i < list.count(); ++i) { if (list.at(i)->type() == DvdButtonItem) { DvdButton *button = static_cast < DvdButton* >(list.at(i)); button->setTarget(ix, m_view.target_list->itemData(ix).toString()); break; } } emit completeChanged(); } void DvdWizardMenu::setBackToMenu(bool backToMenu) { QList list = m_scene->selectedItems(); for (int i = 0; i < list.count(); ++i) { if (list.at(i)->type() == DvdButtonItem) { DvdButton *button = static_cast < DvdButton* >(list.at(i)); button->setBackMenu(backToMenu); break; } } emit completeChanged(); } void DvdWizardMenu::buttonChanged() { QList list = m_scene->selectedItems(); bool foundButton = false; for (int i = 0; i < list.count(); ++i) { if (list.at(i)->type() == DvdButtonItem) { m_view.play_text->blockSignals(true); m_view.font_size->blockSignals(true); m_view.font_family->blockSignals(true); m_view.target_list->blockSignals(true); m_view.back_to_menu->blockSignals(true); foundButton = true; m_view.tabWidget->widget(0)->setEnabled(true); DvdButton *button = static_cast < DvdButton* >(list.at(i)); m_view.target_list->setCurrentIndex(button->target()); m_view.play_text->setText(button->toPlainText()); m_view.back_to_menu->setChecked(button->backMenu()); QFont font = button->font(); m_view.font_size->setValue(font.pixelSize()); m_view.font_family->setCurrentFont(font); m_view.play_text->blockSignals(false); m_view.font_size->blockSignals(false); m_view.font_family->blockSignals(false); m_view.target_list->blockSignals(false); m_view.back_to_menu->blockSignals(false); break; } } if (!foundButton) m_view.tabWidget->widget(0)->setEnabled(false); } void DvdWizardMenu::addButton() { m_scene->clearSelection(); DvdButton *button = new DvdButton(m_view.play_text->text()); QFont font = m_view.font_family->currentFont(); font.setPixelSize(m_view.font_size->value()); if (m_view.use_shadow->isChecked()) { QGraphicsDropShadowEffect *shadow = new QGraphicsDropShadowEffect(this); shadow->setBlurRadius(7); shadow->setOffset(4, 4); button->setGraphicsEffect(shadow); } //font.setStyleStrategy(QFont::NoAntialias); button->setFont(font); button->setDefaultTextColor(m_view.text_color->color()); button->setZValue(4); QRectF r = button->sceneBoundingRect(); m_scene->addItem(button); button->setPos((m_width - r.width()) / 2, (m_height - r.height()) / 2); button->setSelected(true); } void DvdWizardMenu::deleteButton() { QList list = m_scene->selectedItems(); for (int i = 0; i < list.count(); ++i) { if (list.at(i)->type() == DvdButtonItem) { delete list.at(i); break; } } } void DvdWizardMenu::changeProfile(DVDFORMAT format) { m_format = format; switch (m_format) { case PAL_WIDE: m_finalSize = QSize(720, 576); m_width = 1024; m_height = 576; break; case NTSC_WIDE: m_finalSize = QSize(720, 480); m_width = 853; m_height = 480; break; case NTSC: m_finalSize = QSize(720, 480); m_width = 640; m_height = 480; break; default: m_finalSize = QSize(720, 576); m_width = 768; m_height = 576; } updatePreview(); } void DvdWizardMenu::updatePreview() { m_scene->setProfile(m_width, m_height); QMatrix matrix; matrix.scale(0.5, 0.5); m_view.menu_preview->setMatrix(matrix); m_view.menu_preview->setMinimumSize(m_width / 2 + 4, m_height / 2 + 8); if (m_color) m_color->setRect(0, 0, m_width, m_height); int safeW = m_width / 20; int safeH = m_height / 20; if (m_safeRect) m_safeRect->setRect(safeW, safeH, m_width - 2 * safeW, m_height - 2 * safeH); } void DvdWizardMenu::setTargets(const QStringList &list, const QStringList &targetlist) { m_view.target_list->clear(); m_view.target_list->addItem(i18n("Play All"), "jump title 1"); int movieCount = 0; for (int i = 0; i < list.count(); ++i) { if (targetlist.at(i).contains(QStringLiteral("chapter"))) m_view.target_list->addItem(list.at(i), targetlist.at(i)); else { m_view.target_list->addItem(KoIconUtils::themedIcon(QStringLiteral("video-x-generic")), list.at(i), targetlist.at(i)); movieCount++; } } m_view.back_to_menu->setHidden(movieCount == 1); } void DvdWizardMenu::checkBackgroundType(int ix) { if (ix == 0) { m_view.background_color->setVisible(true); m_view.background_image->setVisible(false); m_view.loop_movie->setVisible(false); if (m_background->scene() != 0) m_scene->removeItem(m_background); } else { m_view.background_color->setVisible(false); m_view.background_image->setVisible(true); if (ix == 1) { m_view.background_image->clear(); m_view.background_image->setFilter(QStringLiteral("*")); if (m_background->scene() != 0) m_scene->removeItem(m_background); m_view.loop_movie->setVisible(false); } else { if (m_background->scene() != 0) m_scene->removeItem(m_background); m_view.background_image->clear(); m_view.background_image->setFilter(QStringLiteral("video/mpeg")); m_view.loop_movie->setVisible(true); } } emit completeChanged(); } void DvdWizardMenu::buildColor() { m_color->setBrush(m_view.background_color->color()); } void DvdWizardMenu::slotUseGrid(bool useGrid) { if(useGrid) m_scene->setGridSize(10); else m_scene->setGridSize(1); m_scene->update(); } void DvdWizardMenu::buildImage() { emit completeChanged(); if (m_view.background_image->url().isEmpty()) { if (m_background->scene() != 0) m_scene->removeItem(m_background); return; } QPixmap pix; if (m_view.background_list->currentIndex() == 1) { // image background if (!pix.load(m_view.background_image->url().path())) { if (m_background->scene() != 0) m_scene->removeItem(m_background); return; } pix = pix.scaled(m_width, m_height); } else if (m_view.background_list->currentIndex() == 2) { // video background m_movieLength = -1; QString profileName = DvdWizardVob::getDvdProfile(m_format); Mlt::Profile profile(profileName.toUtf8().constData()); profile.set_explicit(true); Mlt::Producer *producer = new Mlt::Producer(profile, m_view.background_image->url().path().toUtf8().constData()); if (producer && producer->is_valid()) { pix = QPixmap::fromImage(KThumb::getFrame(producer, 0, m_width, m_height)); m_movieLength = producer->get_length(); } if (producer) delete producer; } m_background->setPixmap(pix); m_scene->addItem(m_background); } void DvdWizardMenu::buildButton() { DvdButton *button = NULL; QList list = m_scene->selectedItems(); for (int i = 0; i < list.count(); ++i) { if (list.at(i)->type() == DvdButtonItem) { button = static_cast < DvdButton* >(list.at(i)); break; } } if (button == NULL) return; button->setPlainText(m_view.play_text->text()); QFont font = m_view.font_family->currentFont(); font.setPixelSize(m_view.font_size->value()); //font.setStyleStrategy(QFont::NoAntialias); button->setFont(font); button->setDefaultTextColor(m_view.text_color->color()); // Check for button overlapping in case we changed text / size emit completeChanged(); } void DvdWizardMenu::updateColor() { updateColor(m_view.text_color->color()); m_view.menu_preview->viewport()->update(); } void DvdWizardMenu::prepareUnderLines() { QList list = m_scene->items(); for (int i = 0; i < list.count(); ++i) { if (list.at(i)->type() == DvdButtonItem) { QRectF r = list.at(i)->sceneBoundingRect(); int bottom = r.bottom() - 1; if (bottom % 2 == 1) bottom = bottom - 1; int underlineHeight = r.height() / 10; if (underlineHeight % 2 == 1) underlineHeight = underlineHeight - 1; underlineHeight = qMin(underlineHeight, 10); underlineHeight = qMax(underlineHeight, 2); r.setTop(bottom - underlineHeight); r.setBottom(bottom); r.adjust(2, 0, -2, 0); DvdButtonUnderline *underline = new DvdButtonUnderline(r); m_scene->addItem(underline); list.at(i)->setVisible(false); } } } void DvdWizardMenu::resetUnderLines() { QList list = m_scene->items(); QList toDelete; for (int i = 0; i < list.count(); ++i) { if (list.at(i)->type() == DvdButtonUnderlineItem) { toDelete.append(list.at(i)); } if (list.at(i)->type() == DvdButtonItem) { list.at(i)->setVisible(true); } } while (!toDelete.isEmpty()) { QGraphicsItem *item = toDelete.takeFirst(); delete item; } } void DvdWizardMenu::updateUnderlineColor(QColor c) { QList list = m_scene->items(); for (int i = 0; i < list.count(); ++i) { if (list.at(i)->type() == DvdButtonUnderlineItem) { DvdButtonUnderline *underline = static_cast < DvdButtonUnderline* >(list.at(i)); underline->setPen(Qt::NoPen); c.setAlpha(150); underline->setBrush(c); } } } void DvdWizardMenu::updateColor(const QColor &c) { DvdButton *button = NULL; QList list = m_scene->items(); for (int i = 0; i < list.count(); ++i) { if (list.at(i)->type() == DvdButtonItem) { button = static_cast < DvdButton* >(list.at(i)); button->setDefaultTextColor(c); } } } void DvdWizardMenu::createButtonImages(const QString &selected_image, const QString &highlighted_image, bool letterbox) { if (m_view.create_menu->isChecked()) { m_scene->clearSelection(); QRectF source(0, 0, m_width, m_height); QRectF target; if (!letterbox) target = QRectF(0, 0, m_finalSize.width(), m_finalSize.height()); else { // Scale the button images to fit a letterbox image double factor = (double) m_width / m_finalSize.width(); int letterboxHeight = m_height / factor; target = QRectF(0, (m_finalSize.height() - letterboxHeight) / 2, m_finalSize.width(), letterboxHeight); } if (m_safeRect->scene() != 0) m_scene->removeItem(m_safeRect); if (m_color->scene() != 0) m_scene->removeItem(m_color); if (m_background->scene() != 0) m_scene->removeItem(m_background); prepareUnderLines(); QImage img(m_finalSize.width(), m_finalSize.height(), QImage::Format_ARGB32); img.fill(Qt::transparent); updateUnderlineColor(m_view.highlighted_color->color()); QPainter p; p.begin(&img); //p.setRenderHints(QPainter::Antialiasing, false); //p.setRenderHints(QPainter::TextAntialiasing, false); m_scene->render(&p, target, source, Qt::IgnoreAspectRatio); p.end(); img.setColor(0, m_view.highlighted_color->color().rgb()); img.setColor(1, qRgba(0,0,0,0)); img.save(highlighted_image); img.fill(Qt::transparent); updateUnderlineColor(m_view.selected_color->color()); p.begin(&img); //p.setRenderHints(QPainter::Antialiasing, false); //p.setRenderHints(QPainter::TextAntialiasing, false); m_scene->render(&p, target, source, Qt::IgnoreAspectRatio); p.end(); img.setColor(0, m_view.selected_color->color().rgb()); img.setColor(1, qRgba(0,0,0,0)); img.save(selected_image); resetUnderLines(); m_scene->addItem(m_safeRect); m_scene->addItem(m_color); if (m_view.background_list->currentIndex() > 0) m_scene->addItem(m_background); } } void DvdWizardMenu::createBackgroundImage(const QString &img1, bool letterbox) { Q_UNUSED(letterbox) m_scene->clearSelection(); if (m_safeRect->scene() != 0) m_scene->removeItem(m_safeRect); bool showBg = false; QImage img(m_width, m_height, QImage::Format_ARGB32); //TODO: Should the image be scaled when letterboxing? /* QRectF source(0, 0, m_width, m_height); QRectF target; if (!letterbox) target = QRectF(0, 0, m_finalSize.width(), m_finalSize.height()); else { // Scale the button images to fit a letterbox image double factor = (double) m_width / m_finalSize.width(); int letterboxHeight = m_height / factor; target = QRectF(0, (m_finalSize.height() - letterboxHeight) / 2, m_finalSize.width(), letterboxHeight); }*/ if (menuMovie()) { showBg = true; if (m_background->scene() != 0) m_scene->removeItem(m_background); if (m_color->scene() != 0) m_scene->removeItem(m_color); img.fill(Qt::transparent); } updateColor(m_view.text_color->color()); QPainter p(&img); p.setRenderHints(QPainter::Antialiasing, true); p.setRenderHints(QPainter::TextAntialiasing, true); m_scene->render(&p, QRectF(0, 0, img.width(), img.height())); //m_scene->render(&p, target, source, Qt::IgnoreAspectRatio); p.end(); img.save(img1); m_scene->addItem(m_safeRect); if (showBg) { m_scene->addItem(m_background); m_scene->addItem(m_color); } } bool DvdWizardMenu::createMenu() const { return m_view.create_menu->isChecked(); } bool DvdWizardMenu::loopMovie() const { return m_view.loop_movie->isChecked(); } bool DvdWizardMenu::menuMovie() const { return m_view.background_list->currentIndex() == 2; } QString DvdWizardMenu::menuMoviePath() const { return m_view.background_image->url().path(); } int DvdWizardMenu::menuMovieLength() const { return m_movieLength; } QMap DvdWizardMenu::buttonsInfo(bool letterbox) { QMap info; QList list = m_scene->items(); double ratiox = (double) m_finalSize.width() / m_width; double ratioy = 1; int offset = 0; if (letterbox) { int letterboxHeight = m_height * ratiox; ratioy = (double) letterboxHeight / m_finalSize.height(); offset = (m_finalSize.height() - letterboxHeight) / 2; } for (int i = 0; i < list.count(); ++i) { if (list.at(i)->type() == DvdButtonItem) { DvdButton *button = static_cast < DvdButton* >(list.at(i)); QRectF r = button->sceneBoundingRect(); QRect adjustedRect(r.x() * ratiox, offset + r.y() * ratioy, r.width() * ratiox, r.height() * ratioy); // Make sure y1 is not odd (requested by spumux) if (adjustedRect.height() % 2 == 1) adjustedRect.setHeight(adjustedRect.height() + 1); if (adjustedRect.y() % 2 == 1) adjustedRect.setY(adjustedRect.y() - 1); QString command = button->command(); if (button->backMenu()) command.prepend("g1 = 999;"); info.insertMulti(command, adjustedRect); } } return info; } QDomElement DvdWizardMenu::toXml() const { QDomDocument doc; QDomElement xml = doc.createElement(QStringLiteral("menu")); doc.appendChild(xml); xml.setAttribute(QStringLiteral("enabled"), m_view.create_menu->isChecked()); if (m_view.background_list->currentIndex() == 0) { // Color bg xml.setAttribute(QStringLiteral("background_color"), m_view.background_color->color().name()); } else if (m_view.background_list->currentIndex() == 1) { // Image bg xml.setAttribute(QStringLiteral("background_image"), m_view.background_image->url().path()); } else { // Video bg xml.setAttribute(QStringLiteral("background_video"), m_view.background_image->url().path()); } xml.setAttribute(QStringLiteral("text_color"), m_view.text_color->color().name()); xml.setAttribute(QStringLiteral("selected_color"), m_view.selected_color->color().name()); xml.setAttribute(QStringLiteral("highlighted_color"), m_view.highlighted_color->color().name()); xml.setAttribute(QStringLiteral("text_shadow"), (int) m_view.use_shadow->isChecked()); QList list = m_scene->items(); int buttonCount = 0; for (int i = 0; i < list.count(); ++i) { if (list.at(i)->type() == DvdButtonItem) { buttonCount++; DvdButton *button = static_cast < DvdButton* >(list.at(i)); QDomElement xmlbutton = doc.createElement(QStringLiteral("button")); xmlbutton.setAttribute(QStringLiteral("target"), button->target()); xmlbutton.setAttribute(QStringLiteral("command"), button->command()); xmlbutton.setAttribute(QStringLiteral("backtomenu"), button->backMenu()); xmlbutton.setAttribute(QStringLiteral("posx"), (int) button->pos().x()); xmlbutton.setAttribute(QStringLiteral("posy"), (int) button->pos().y()); xmlbutton.setAttribute(QStringLiteral("text"), button->toPlainText()); QFont font = button->font(); xmlbutton.setAttribute(QStringLiteral("font_size"), font.pixelSize()); xmlbutton.setAttribute(QStringLiteral("font_family"), font.family()); xml.appendChild(xmlbutton); } } return doc.documentElement(); } void DvdWizardMenu::loadXml(DVDFORMAT format, const QDomElement &xml) { //qDebug() << "// LOADING MENU"; if (xml.isNull()) return; //qDebug() << "// LOADING MENU 1"; changeProfile(format); m_view.create_menu->setChecked(xml.attribute(QStringLiteral("enabled")).toInt()); if (xml.hasAttribute(QStringLiteral("background_color"))) { m_view.background_list->setCurrentIndex(0); m_view.background_color->setColor(xml.attribute(QStringLiteral("background_color"))); } else if (xml.hasAttribute(QStringLiteral("background_image"))) { m_view.background_list->setCurrentIndex(1); m_view.background_image->setUrl(QUrl(xml.attribute(QStringLiteral("background_image")))); } else if (xml.hasAttribute(QStringLiteral("background_video"))) { m_view.background_list->setCurrentIndex(2); m_view.background_image->setUrl(QUrl(xml.attribute(QStringLiteral("background_video")))); } m_view.text_color->setColor(xml.attribute(QStringLiteral("text_color"))); m_view.selected_color->setColor(xml.attribute(QStringLiteral("selected_color"))); m_view.highlighted_color->setColor(xml.attribute(QStringLiteral("highlighted_color"))); m_view.use_shadow->setChecked(xml.attribute(QStringLiteral("text_shadow")).toInt()); QDomNodeList buttons = xml.elementsByTagName(QStringLiteral("button")); //qDebug() << "// LOADING MENU 2" << buttons.count(); if (buttons.count() > 0) { // Clear existing buttons foreach (QGraphicsItem *item, m_scene->items()) { if (item->type() == DvdButtonItem) { m_scene->removeItem(item); delete item; } } } for (int i = 0; i < buttons.count(); ++i) { QDomElement e = buttons.at(i).toElement(); // create menu button DvdButton *button = new DvdButton(e.attribute(QStringLiteral("text"))); QFont font(e.attribute(QStringLiteral("font_family"))); font.setPixelSize(e.attribute(QStringLiteral("font_size")).toInt()); if (m_view.use_shadow->isChecked()) { QGraphicsDropShadowEffect *shadow = new QGraphicsDropShadowEffect(this); shadow->setBlurRadius(7); shadow->setOffset(4, 4); button->setGraphicsEffect(shadow); } //font.setStyleStrategy(QFont::NoAntialias); button->setFont(font); button->setTarget(e.attribute(QStringLiteral("target")).toInt(), e.attribute(QStringLiteral("command"))); button->setBackMenu(e.attribute(QStringLiteral("backtomenu")).toInt()); button->setDefaultTextColor(m_view.text_color->color()); button->setZValue(4); m_scene->addItem(button); button->setPos(e.attribute(QStringLiteral("posx")).toInt(), e.attribute(QStringLiteral("posy")).toInt()); } } void DvdWizardMenu::slotZoom() { m_view.menu_preview->scale(2.0, 2.0); } void DvdWizardMenu::slotUnZoom() { m_view.menu_preview->scale(0.5, 0.5); } diff --git a/src/effectslist/initeffects.cpp b/src/effectslist/initeffects.cpp index 2b688b711..88aa65a48 100644 --- a/src/effectslist/initeffects.cpp +++ b/src/effectslist/initeffects.cpp @@ -1,959 +1,962 @@ /*************************************************************************** initeffects.cpp - description ------------------- begin : Jul 2006 copyright : (C) 2006 by Jean-Baptiste Mardelle email : jb@ader.ch copyright : (C) 2008 Marco Gittler email : g.marco@freenet.de ***************************************************************************/ /*************************************************************************** * * * 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. * * * ***************************************************************************/ #include "initeffects.h" #include "effectslist.h" #include "kdenlivesettings.h" #include "mainwindow.h" #include #include #include #include #include #include #ifdef Q_OS_MAC #include #endif #include #ifdef Q_OS_MAC #include #endif // static void initEffects::refreshLumas() { // Check for Kdenlive installed luma files, add empty string at start for no luma QStringList imagenamelist = QStringList() << i18n("None"); QStringList imagefiles = QStringList() << QString(); QStringList filters; filters << QStringLiteral("*.pgm") << QStringLiteral("*.png"); QStringList customLumas = QStandardPaths::locateAll(QStandardPaths::DataLocation, QStringLiteral("lumas"), QStandardPaths::LocateDirectory); foreach(const QString & folder, customLumas) { QDir directory(folder); QStringList filesnames = directory.entryList(filters, QDir::Files); foreach(const QString & fname, filesnames) { imagenamelist.append(fname); imagefiles.append(directory.absoluteFilePath(fname)); } } // Check for MLT lumas QUrl folder(QString(mlt_environment("MLT_DATA")) + QDir::separator() + "lumas" + QDir::separator() + QString(mlt_environment("MLT_NORMALISATION"))); QDir lumafolder(folder.path()); QStringList filesnames = lumafolder.entryList(filters, QDir::Files); foreach(const QString & fname, filesnames) { imagenamelist.append(fname); imagefiles.append(lumafolder.absoluteFilePath(fname)); } //TODO adapt to Wipe transition QDomElement lumaTransition = MainWindow::transitions.getEffectByTag(QStringLiteral("luma"), QStringLiteral("luma")); QDomNodeList params = lumaTransition.elementsByTagName(QStringLiteral("parameter")); for (int i = 0; i < params.count(); ++i) { QDomElement e = params.item(i).toElement(); if (e.attribute(QStringLiteral("tag")) == QLatin1String("resource")) { e.setAttribute(QStringLiteral("paramlistdisplay"), imagenamelist.join(QStringLiteral(","))); e.setAttribute(QStringLiteral("paramlist"), imagefiles.join(QStringLiteral(";"))); break; } } QDomElement compositeTransition = MainWindow::transitions.getEffectByTag(QStringLiteral("composite"), QStringLiteral("composite")); params = compositeTransition.elementsByTagName(QStringLiteral("parameter")); for (int i = 0; i < params.count(); ++i) { QDomElement e = params.item(i).toElement(); if (e.attribute(QStringLiteral("tag")) == QLatin1String("luma")) { e.setAttribute(QStringLiteral("paramlistdisplay"), imagenamelist.join(QStringLiteral(","))); e.setAttribute(QStringLiteral("paramlist"), imagefiles.join(QStringLiteral(";"))); break; } } } // static QDomDocument initEffects::getUsedCustomEffects(const QMap & effectids) { QMapIterator i(effectids); QDomDocument doc; QDomElement list = doc.createElement(QStringLiteral("customeffects")); doc.appendChild(list); while (i.hasNext()) { i.next(); int ix = MainWindow::customEffects.hasEffect(i.value(), i.key()); if (ix > -1) { QDomElement e = MainWindow::customEffects.at(ix); list.appendChild(doc.importNode(e, true)); } } return doc; } //static bool initEffects::parseEffectFiles(Mlt::Repository* repository, const QString &locale) { bool movit = false; QStringList::Iterator more; QStringList::Iterator it; QStringList fileList; QString itemName; if (!repository) { //qDebug() << "Repository didn't finish initialisation" ; return movit; } // Warning: Mlt::Factory::init() resets the locale to the default system value, make sure we keep correct locale if (!locale.isEmpty()) { #ifndef Q_OS_MAC setlocale(LC_NUMERIC, locale.toUtf8().constData()); #else setlocale(LC_NUMERIC_MASK, locale.toUtf8().constData()); #endif } // Retrieve the list of MLT's available effects. Mlt::Properties *filters = repository->filters(); QStringList filtersList; int max = filters->count(); for (int i = 0; i < max; ++i) filtersList << filters->get_name(i); delete filters; // Retrieve the list of available producers. Mlt::Properties *producers = repository->producers(); QStringList producersList; max = producers->count(); for (int i = 0; i < max; ++i) producersList << producers->get_name(i); KdenliveSettings::setProducerslist(producersList); delete producers; if (filtersList.contains(QStringLiteral("glsl.manager"))) { Mlt::Properties *consumers = repository->consumers(); QStringList consumersList; max = consumers->count(); for (int i = 0; i < max; ++i) consumersList << consumers->get_name(i); delete consumers; if (consumersList.contains(QStringLiteral("rtaudio"))) { // enable movit GPU effects / display. Currently, Movit crashes with sdl_audio, // So enable only when rtaudio is available movit = true; } else KdenliveSettings::setGpu_accel(false); } else KdenliveSettings::setGpu_accel(false); // Retrieve the list of available transitions. Mlt::Properties *transitions = repository->transitions(); QStringList transitionsItemList; max = transitions->count(); for (int i = 0; i < max; ++i) { //qDebug()<<"TRANSITION "<get_name(i); transitionsItemList << transitions->get_name(i); } delete transitions; // Create structure holding all transitions descriptions so that if an XML file has no description, we take it from MLT QMap transDescriptions; foreach(const QString & transname, transitionsItemList) { QDomDocument doc = createDescriptionFromMlt(repository, QStringLiteral("transitions"), transname); if (!doc.isNull()) { if (doc.elementsByTagName(QStringLiteral("description")).count() > 0) { QString desc = doc.documentElement().firstChildElement(QStringLiteral("description")).text(); if (!desc.isEmpty()) { transDescriptions.insert(transname, desc); } } } } transitionsItemList.sort(); // Get list of installed luma files QStringList imagenamelist; QStringList imagefiles; QStringList fileFilters; QString defaultWipeLuma; fileFilters << QStringLiteral("*.png") << QStringLiteral("*.pgm"); QStringList customLumas = QStandardPaths::locateAll(QStandardPaths::DataLocation, QStringLiteral("lumas"), QStandardPaths::LocateDirectory); foreach(const QString &folder, customLumas) { QDir dir(folder); QStringList filesnames = dir.entryList(fileFilters, QDir::Files); foreach(const QString & fname, filesnames) { imagenamelist.append(fname); imagefiles.append(dir.absoluteFilePath(fname)); if (fname.startsWith(QLatin1String("linear_x"))) { defaultWipeLuma = dir.absoluteFilePath(fname); } } } // Check for MLT luma files. QUrl folder(QString(mlt_environment("MLT_DATA")) + QDir::separator() + "lumas" + QDir::separator() + QString(mlt_environment("MLT_NORMALISATION"))); QDir lumafolder(folder.path()); QStringList filesnames = lumafolder.entryList(fileFilters, QDir::Files); foreach(const QString & fname, filesnames) { imagenamelist.append(fname); imagefiles.append(lumafolder.absoluteFilePath(fname)); } imagenamelist.prepend(i18n("None (Dissolve)")); imagefiles.prepend(QString()); // Parse xml transition files QStringList direc = QStandardPaths::locateAll(QStandardPaths::DataLocation, QStringLiteral("transitions"), QStandardPaths::LocateDirectory); // Iterate through effects directories to parse all XML files. for (more = direc.begin(); more != direc.end(); ++more) { QDir directory(*more); QStringList filter; filter << QStringLiteral("*.xml"); fileList = directory.entryList(filter, QDir::Files); for (it = fileList.begin(); it != fileList.end(); ++it) { itemName = directory.absoluteFilePath(*it); parseTransitionFile(&MainWindow::transitions, itemName, repository, transDescriptions, imagefiles, imagenamelist, defaultWipeLuma); } } // Remove blacklisted transitions from the list. QFile file(QStandardPaths::locate(QStandardPaths::DataLocation, QStringLiteral("blacklisted_transitions.txt"))); if (file.open(QIODevice::ReadOnly)) { QTextStream in(&file); while (!in.atEnd()) { QString black = in.readLine().simplified(); if (!black.isEmpty() && !black.startsWith('#') && transitionsItemList.contains(black)) transitionsItemList.removeAll(black); } file.close(); } // Fill transitions list. fillTransitionsList(repository, &MainWindow::transitions, transitionsItemList, imagefiles, imagenamelist); // Remove blacklisted effects from the filters list. QStringList mltFiltersList = filtersList; QStringList mltBlackList; QFile file2(QStandardPaths::locate(QStandardPaths::DataLocation, QStringLiteral("blacklisted_effects.txt"))); if (file2.open(QIODevice::ReadOnly)) { QTextStream in(&file2); while (!in.atEnd()) { QString black = in.readLine().simplified(); if (!black.isEmpty() && !black.startsWith('#') && mltFiltersList.contains(black)) { mltFiltersList.removeAll(black); mltBlackList << black; } } file2.close(); } /* * Cleanup the global lists. We use QMap because of its automatic sorting * (by key) and key uniqueness (using insert() instead of insertMulti()). * This introduces some more cycles (while removing them from other parts of * the code and centralising them), but due to the way this methods, QMap * and EffectsList are implemented, there's no easy way to make it * differently without reinplementing something (which should really be * done). */ QDomElement effectInfo; QMap effectsMap; QMap videoEffectsMap; QMap audioEffectsMap; // Create transitions max = MainWindow::transitions.count(); for (int i = 0; i < max; ++i) { effectInfo = MainWindow::transitions.at(i); effectsMap.insert(effectInfo.firstChildElement(QStringLiteral("name")).text().toLower().toUtf8().data(), effectInfo); } MainWindow::transitions.clearList(); foreach(const QDomElement & effect, effectsMap) MainWindow::transitions.append(effect); effectsMap.clear(); // Create structure holding all effects descriptions so that if an XML effect has no description, we take it from MLT QMap effectDescriptions; foreach(const QString & filtername, mltBlackList) { QDomDocument doc = createDescriptionFromMlt(repository, QStringLiteral("filters"), filtername); if (!doc.isNull()) { if (doc.elementsByTagName(QStringLiteral("description")).count() > 0) { QString desc = doc.documentElement().firstChildElement(QStringLiteral("description")).text(); //WARNING: TEMPORARY FIX for unusable MLT SOX parameters description if (desc.startsWith(QLatin1String("Process audio using a SoX"))) { // Remove MLT's SOX generated effects since the parameters properties are unusable for us continue; } if (!desc.isEmpty()) { effectDescriptions.insert(filtername, desc); } } } } // Create effects from MLT foreach(const QString & filtername, mltFiltersList) { QDomDocument doc = createDescriptionFromMlt(repository, QStringLiteral("filters"), filtername); //WARNING: TEMPORARY FIX for empty MLT effects descriptions - disable effects without parameters - jbm 09-06-2011 if (!doc.isNull() && doc.elementsByTagName(QStringLiteral("parameter")).count() > 0) { if (doc.documentElement().attribute(QStringLiteral("type")) == QLatin1String("audio")) { if (doc.elementsByTagName(QStringLiteral("description")).count() > 0) { QString desc = doc.documentElement().firstChildElement(QStringLiteral("description")).text(); //WARNING: TEMPORARY FIX for unusable MLT SOX parameters description if (desc.startsWith(QLatin1String("Process audio using a SoX"))) { // Remove MLT's SOX generated effects since the parameters properties are unusable for us } else { audioEffectsMap.insert(doc.documentElement().firstChildElement(QStringLiteral("name")).text().toLower().toUtf8().data(), doc.documentElement()); } } } else videoEffectsMap.insert(doc.documentElement().firstChildElement(QStringLiteral("name")).text().toLower().toUtf8().data(), doc.documentElement()); } } // Set the directories to look into for effects. direc = QStandardPaths::locateAll(QStandardPaths::DataLocation, QStringLiteral("effects"), QStandardPaths::LocateDirectory); // Iterate through effects directories to parse all XML files. for (more = direc.begin(); more != direc.end(); ++more) { QDir directory(*more); QStringList filter; filter << QStringLiteral("*.xml"); fileList = directory.entryList(filter, QDir::Files); for (it = fileList.begin(); it != fileList.end(); ++it) { itemName = directory.absoluteFilePath(*it); parseEffectFile(&MainWindow::customEffects, &MainWindow::audioEffects, &MainWindow::videoEffects, itemName, filtersList, producersList, repository, effectDescriptions); } } // Create custom effects max = MainWindow::customEffects.count(); for (int i = 0; i < max; ++i) { effectInfo = MainWindow::customEffects.at(i); if (effectInfo.tagName() == QLatin1String("effectgroup")) { effectsMap.insert(effectInfo.attribute(QStringLiteral("name")).toUtf8().data(), effectInfo); } else effectsMap.insert(effectInfo.firstChildElement(QStringLiteral("name")).text().toUtf8().data(), effectInfo); } MainWindow::customEffects.clearList(); foreach(const QDomElement & effect, effectsMap) MainWindow::customEffects.append(effect); effectsMap.clear(); // Create audio effects max = MainWindow::audioEffects.count(); for (int i = 0; i < max; ++i) { effectInfo = MainWindow::audioEffects.at(i); audioEffectsMap.insert(effectInfo.firstChildElement(QStringLiteral("name")).text().toLower().toUtf8().data(), effectInfo); } MainWindow::audioEffects.clearList(); foreach(const QDomElement & effect, audioEffectsMap) MainWindow::audioEffects.append(effect); // Create video effects max = MainWindow::videoEffects.count(); for (int i = 0; i < max; ++i) { effectInfo = MainWindow::videoEffects.at(i); videoEffectsMap.insert(effectInfo.firstChildElement(QStringLiteral("name")).text().toLower().toUtf8().data(), effectInfo); } MainWindow::videoEffects.clearList(); foreach(const QDomElement & effect, videoEffectsMap) MainWindow::videoEffects.append(effect); return movit; } // static void initEffects::parseCustomEffectsFile() { MainWindow::customEffects.clearList(); /* * Why a QMap? See parseEffectFiles(). It's probably useless here, but we * cannot be sure about it. */ QMap effectsMap; QString path = QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/effects"; QDir directory = QDir(path); QStringList filter; filter << QStringLiteral("*.xml"); const QStringList fileList = directory.entryList(filter, QDir::Files); /* * We need to declare these variables outside the foreach, or the QMap will * refer to non existing variables (QMap::insert() takes references as * parameters). */ QDomDocument doc; QDomNodeList effects; QDomElement e; int unknownGroupCount = 0; foreach(const QString & filename, fileList) { QString itemName = directory.absoluteFilePath(filename); QFile file(itemName); doc.setContent(&file, false); file.close(); QDomElement base = doc.documentElement(); if (base.tagName() == QLatin1String("effectgroup")) { QString groupName = base.attribute(QStringLiteral("name")); if (groupName.isEmpty()) { groupName = i18n("Group %1", unknownGroupCount); base.setAttribute(QStringLiteral("name"), groupName); unknownGroupCount++; } effectsMap.insert(groupName.toLower().toUtf8().data(), base); } else if (base.tagName() == QLatin1String("effect")) { effectsMap.insert(base.firstChildElement(QStringLiteral("name")).text().toLower().toUtf8().data(), base); } else qDebug() << "Unsupported effect file: " << itemName; } foreach(const QDomElement & effect, effectsMap) MainWindow::customEffects.append(effect); } // static void initEffects::parseEffectFile(EffectsList *customEffectList, EffectsList *audioEffectList, EffectsList *videoEffectList, const QString &name, QStringList filtersList, QStringList producersList, Mlt::Repository *repository, QMap effectDescriptions) { QDomDocument doc; QFile file(name); doc.setContent(&file, false); file.close(); QDomElement documentElement; QDomNodeList effects; QDomElement base = doc.documentElement(); QStringList addedTags; effects = doc.elementsByTagName(QStringLiteral("effect")); int i = effects.count(); if (i == 0) { qDebug() << "+++++++++++++\nEffect broken: " << name<<"\n+++++++++++";; return; } bool needsLocaleConversion = false; i--; for (; i >= 0 ; i--) { QLocale locale; QDomNode n = effects.item(i); if (n.isNull()) continue; documentElement = n.toElement(); QString tag = documentElement.attribute(QStringLiteral("tag"), QString()); QString id = documentElement.hasAttribute(QStringLiteral("id")) ? documentElement.attribute(QStringLiteral("id")) : tag; if (addedTags.contains(id)) { // We already processed a version of that filter continue; } //If XML has no description, take it fom MLT's descriptions if (effectDescriptions.contains(tag)) { QDomNodeList desc = documentElement.elementsByTagName(QStringLiteral("description")); if (desc.isEmpty()) { QDomElement d = documentElement.ownerDocument().createElement(QStringLiteral("description")); QDomText value = documentElement.ownerDocument().createTextNode(effectDescriptions.value(tag)); d.appendChild(value); documentElement.appendChild(d); } } if (documentElement.hasAttribute(QStringLiteral("LC_NUMERIC"))) { // set a locale for that file locale = QLocale(documentElement.attribute(QStringLiteral("LC_NUMERIC"))); if (locale.decimalPoint() != QLocale().decimalPoint()) { needsLocaleConversion = true; } } locale.setNumberOptions(QLocale::OmitGroupSeparator); if (needsLocaleConversion) { // we need to convert all numbers to the system's locale (for example 0.5 -> 0,5) QChar separator = QLocale().decimalPoint(); QChar oldSeparator = locale.decimalPoint(); QDomNodeList params = documentElement.elementsByTagName(QStringLiteral("parameter")); for (int j = 0; j < params.count(); ++j) { QDomNamedNodeMap attrs = params.at(j).attributes(); for (int k = 0; k < attrs.count(); ++k) { QString nodeName = attrs.item(k).nodeName(); if (nodeName != QLatin1String("type") && nodeName != QLatin1String("name")) { QString val = attrs.item(k).nodeValue(); if (val.contains(oldSeparator)) { QString newVal = val.replace(oldSeparator, separator); attrs.item(k).setNodeValue(newVal); } } } } } double version = -1; Mlt::Properties *metadata = repository->metadata(filter_type, tag.toUtf8().data()); if (metadata && metadata->is_valid()) { version = metadata->get_double("version"); } if (metadata) delete metadata; if (documentElement.hasAttribute(QStringLiteral("version"))) { // a specific version of the filter is required if (locale.toDouble(documentElement.attribute(QStringLiteral("version"))) > version) { continue; } } if (version > -1) { // Add version info to XML QDomNode versionNode = doc.createElement(QStringLiteral("version")); versionNode.appendChild(doc.createTextNode(QLocale().toString(version))); documentElement.appendChild(versionNode); } // Parse effect information. if (base.tagName() != QLatin1String("effectgroup") && (filtersList.contains(tag) || producersList.contains(tag))) { QString type = documentElement.attribute(QStringLiteral("type"), QString()); if (type == QLatin1String("audio")) audioEffectList->append(documentElement); else if (type == QLatin1String("custom")) { customEffectList->append(documentElement); } else videoEffectList->append(documentElement); addedTags << id; } } if (base.tagName() == QLatin1String("effectgroup")) { QString type = base.attribute(QStringLiteral("type"), QString()); if (type == QLatin1String("audio")) audioEffectList->append(base); else if (type == QLatin1String("custom")) customEffectList->append(base); else videoEffectList->append(base); } } QDomDocument initEffects::createDescriptionFromMlt(Mlt::Repository* repository, const QString& /*type*/, const QString& filtername) { QDomDocument ret; Mlt::Properties *metadata = repository->metadata(filter_type, filtername.toLatin1().data()); ////qDebug() << filtername; if (metadata && metadata->is_valid()) { if (metadata->get("title") && metadata->get("identifier")) { QDomElement eff = ret.createElement(QStringLiteral("effect")); QString id = metadata->get("identifier"); eff.setAttribute(QStringLiteral("tag"), id); eff.setAttribute(QStringLiteral("id"), id); ////qDebug()<<"Effect: "<get("title"))); QDomElement desc = ret.createElement(QStringLiteral("description")); desc.appendChild(ret.createTextNode(metadata->get("description"))); QDomElement author = ret.createElement(QStringLiteral("author")); author.appendChild(ret.createTextNode(metadata->get("creator"))); QDomElement version = ret.createElement(QStringLiteral("version")); version.appendChild(ret.createTextNode(metadata->get("version"))); eff.appendChild(name); eff.appendChild(author); eff.appendChild(desc); eff.appendChild(version); Mlt::Properties tags((mlt_properties) metadata->get_data("tags")); if (QString(tags.get(0)) == QLatin1String("Audio")) eff.setAttribute(QStringLiteral("type"), QStringLiteral("audio")); /*for (int i = 0; i < tags.count(); ++i) //qDebug()<get_data("parameters")); for (int j = 0; param_props.is_valid() && j < param_props.count(); ++j) { QDomElement params = ret.createElement(QStringLiteral("parameter")); Mlt::Properties paramdesc((mlt_properties) param_props.get_data(param_props.get_name(j))); params.setAttribute(QStringLiteral("name"), paramdesc.get("identifier")); if (params.attribute(QStringLiteral("name")) == QLatin1String("argument")) { // This parameter has to be given as attribute when using command line, do not show it in Kdenlive continue; } if (paramdesc.get("maximum")) params.setAttribute(QStringLiteral("max"), paramdesc.get("maximum")); if (paramdesc.get("minimum")) params.setAttribute(QStringLiteral("min"), paramdesc.get("minimum")); QString paramType = paramdesc.get("type"); if (paramType == QLatin1String("integer")) { if (params.attribute(QStringLiteral("min")) == QLatin1String("0") && params.attribute(QStringLiteral("max")) == QLatin1String("1")) params.setAttribute(QStringLiteral("type"), QStringLiteral("bool")); else params.setAttribute(QStringLiteral("type"), QStringLiteral("constant")); } else if (paramType == QLatin1String("float")) { params.setAttribute(QStringLiteral("type"), QStringLiteral("constant")); // param type is float, set default decimals to 3 params.setAttribute(QStringLiteral("decimals"), QStringLiteral("3")); } else if (paramType == QLatin1String("boolean")) params.setAttribute(QStringLiteral("type"), QStringLiteral("bool")); else if (paramType == QLatin1String("geometry")) { params.setAttribute(QStringLiteral("type"), QStringLiteral("geometry")); } else { params.setAttribute(QStringLiteral("type"), paramType); if (!QString(paramdesc.get("format")).isEmpty()) params.setAttribute(QStringLiteral("format"), paramdesc.get("format")); } if (paramdesc.get("default")) params.setAttribute(QStringLiteral("default"), paramdesc.get("default")); if (paramdesc.get("value")) { params.setAttribute(QStringLiteral("value"), paramdesc.get("value")); } else { params.setAttribute(QStringLiteral("value"), paramdesc.get("default")); } - QDomElement pname = ret.createElement(QStringLiteral("name")); - pname.appendChild(ret.createTextNode(paramdesc.get("title"))); - params.appendChild(pname); + QString paramName = paramdesc.get("title"); + if (!paramName.isEmpty()) { + QDomElement pname = ret.createElement(QStringLiteral("name")); + pname.appendChild(ret.createTextNode(paramName)); + params.appendChild(pname); + } if (paramdesc.get("description")) { QDomElement desc = ret.createElement(QStringLiteral("comment")); desc.appendChild(ret.createTextNode(paramdesc.get("description"))); params.appendChild(desc); } eff.appendChild(params); } ret.appendChild(eff); } } delete metadata; metadata = 0; /*QString outstr; QTextStream str(&outstr); ret.save(str, 2); //qDebug() << outstr;*/ return ret; } void initEffects::fillTransitionsList(Mlt::Repository *repository, EffectsList *transitions, QStringList names, QStringList imagefiles, QStringList imagenamelist) { // Remove transitions that are not implemented. int pos = names.indexOf(QStringLiteral("mix")); if (pos != -1) names.takeAt(pos); // Remove unused luma transition, replaced with a custom composite Wipe transition pos = names.indexOf(QStringLiteral("luma")); if (pos != -1) names.takeAt(pos); //WARNING: this is a hack to get around temporary invalid metadata in MLT, 2nd of june 2011 JBM QStringList customTransitions; customTransitions << QStringLiteral("composite") << QStringLiteral("affine") << QStringLiteral("mix") << QStringLiteral("region"); foreach(const QString & name, names) { QDomDocument ret; QDomElement ktrans = ret.createElement(QStringLiteral("transition")); ret.appendChild(ktrans); ktrans.setAttribute(QStringLiteral("tag"), name); QDomElement tname = ret.createElement(QStringLiteral("name")); QDomElement desc = ret.createElement(QStringLiteral("description")); ktrans.appendChild(tname); ktrans.appendChild(desc); Mlt::Properties *metadata = NULL; if (!customTransitions.contains(name)) metadata = repository->metadata(transition_type, name.toUtf8().data()); if (metadata && metadata->is_valid()) { // If possible, set name and description. //qDebug()<<" / / FOUND TRANS: "<get("title"); if (metadata->get("title") && metadata->get("identifier")) tname.appendChild(ret.createTextNode(metadata->get("title"))); desc.appendChild(ret.createTextNode(metadata->get("description"))); Mlt::Properties param_props((mlt_properties) metadata->get_data("parameters")); for (int i = 0; param_props.is_valid() && i < param_props.count(); ++i) { QDomElement params = ret.createElement(QStringLiteral("parameter")); Mlt::Properties paramdesc((mlt_properties) param_props.get_data(param_props.get_name(i))); params.setAttribute(QStringLiteral("name"), paramdesc.get("identifier")); if (paramdesc.get("maximum")) params.setAttribute(QStringLiteral("max"), paramdesc.get_double("maximum")); if (paramdesc.get("minimum")) params.setAttribute(QStringLiteral("min"), paramdesc.get_double("minimum")); if (paramdesc.get("default")) params.setAttribute(QStringLiteral("default"), paramdesc.get("default")); if (QString(paramdesc.get("type")) == QLatin1String("integer")) { if (params.attribute(QStringLiteral("min")) == QLatin1String("0") && params.attribute(QStringLiteral("max")) == QLatin1String("1")) params.setAttribute(QStringLiteral("type"), QStringLiteral("bool")); else { params.setAttribute(QStringLiteral("type"), QStringLiteral("constant")); params.setAttribute(QStringLiteral("factor"), QStringLiteral("100")); } } else if (QString(paramdesc.get("type")) == QLatin1String("float")) { params.setAttribute(QStringLiteral("type"), QStringLiteral("double")); if (paramdesc.get_int("maximum") == 1) { params.setAttribute(QStringLiteral("factor"), 100); params.setAttribute(QStringLiteral("max"), 100); params.setAttribute(QStringLiteral("default"), paramdesc.get_double("default") * 100); } } else if (QString(paramdesc.get("type")) == QLatin1String("boolean")) params.setAttribute(QStringLiteral("type"), QStringLiteral("bool")); if (!QString(paramdesc.get("format")).isEmpty()) { params.setAttribute(QStringLiteral("type"), QStringLiteral("complex")); params.setAttribute(QStringLiteral("format"), paramdesc.get("format")); } if (paramdesc.get("value")) params.setAttribute(QStringLiteral("value"), paramdesc.get("value")); else params.setAttribute(QStringLiteral("value"), params.attribute(QStringLiteral("default"))); QDomElement pname = ret.createElement(QStringLiteral("name")); pname.appendChild(ret.createTextNode(paramdesc.get("title"))); params.appendChild(pname); ktrans.appendChild(params); } } else { /* * Check for Kdenlive installed luma files, add empty string at * start for no luma file. */ // Implement default transitions. //TODO: create xml files for transitions in data/transitions instead of hardcoding here QList paramList; if (name == QLatin1String("composite")) { ktrans.setAttribute(QStringLiteral("id"), name); tname.appendChild(ret.createTextNode(i18n("Composite"))); desc.appendChild(ret.createTextNode(i18n("A key-framable alpha-channel compositor for two frames."))); paramList.append(quickParameterFill(ret, i18n("Geometry"), QStringLiteral("geometry"), QStringLiteral("geometry"), QStringLiteral("0%/0%:100%x100%:100"), QStringLiteral("-500;-500;-500;-500;0"), QStringLiteral("500;500;500;500;100"))); paramList.append(quickParameterFill(ret, i18n("Alpha Channel Operation"), QStringLiteral("operator"), QStringLiteral("list"), QStringLiteral("over"), QLatin1String(""), QLatin1String(""), QStringLiteral("over,and,or,xor"), i18n("Over,And,Or,Xor"))); paramList.append(quickParameterFill(ret, i18n("Align"), QStringLiteral("aligned"), QStringLiteral("bool"), QStringLiteral("0"), QStringLiteral("0"), QStringLiteral("1"))); paramList.append(quickParameterFill(ret, i18n("Fill"), QStringLiteral("fill"), QStringLiteral("bool"), QStringLiteral("1"), QStringLiteral("0"), QStringLiteral("1"))); paramList.append(quickParameterFill(ret, i18n("Distort"), QStringLiteral("distort"), QStringLiteral("bool"), QStringLiteral("0"), QStringLiteral("0"), QStringLiteral("1"))); paramList.append(quickParameterFill(ret, i18n("Wipe Method"), QStringLiteral("luma"), QStringLiteral("list"), QLatin1String(""), QLatin1String(""), QLatin1String(""), imagefiles.join(QStringLiteral(";")), imagenamelist.join(QStringLiteral(",")))); paramList.append(quickParameterFill(ret, i18n("Wipe Softness"), QStringLiteral("softness"), QStringLiteral("double"), QStringLiteral("0"), QStringLiteral("0"), QStringLiteral("100"), QLatin1String(""), QLatin1String(""), QStringLiteral("100"))); paramList.append(quickParameterFill(ret, i18n("Wipe Invert"), QStringLiteral("luma_invert"), QStringLiteral("bool"), QStringLiteral("0"), QStringLiteral("0"), QStringLiteral("1"))); paramList.append(quickParameterFill(ret, i18n("Force Progressive Rendering"), QStringLiteral("progressive"), QStringLiteral("bool"), QStringLiteral("1"), QStringLiteral("0"), QStringLiteral("1"))); paramList.append(quickParameterFill(ret, i18n("Force Deinterlace Overlay"), QStringLiteral("deinterlace"), QStringLiteral("bool"), QStringLiteral("0"), QStringLiteral("0"), QStringLiteral("1"))); } else if (name == QLatin1String("affine")) { tname.appendChild(ret.createTextNode(i18n("Affine"))); ret.documentElement().setAttribute(QStringLiteral("showrotation"), QStringLiteral("1")); /*paramList.append(quickParameterFill(ret, i18n("Rotate Y"), "rotate_y", "double", "0", "0", "360")); paramList.append(quickParameterFill(ret, i18n("Rotate X"), "rotate_x", "double", "0", "0", "360")); paramList.append(quickParameterFill(ret, i18n("Rotate Z"), "rotate_z", "double", "0", "0", "360")); paramList.append(quickParameterFill(ret, i18n("Fix Rotate Y"), "fix_rotate_y", "double", "0", "0", "360")); paramList.append(quickParameterFill(ret, i18n("Fix Rotate X"), "fix_rotate_x", "double", "0", "0", "360")); paramList.append(quickParameterFill(ret, i18n("Fix Rotate Z"), "fix_rotate_z", "double", "0", "0", "360")); paramList.append(quickParameterFill(ret, i18n("Shear Y"), "shear_y", "double", "0", "0", "360")); paramList.append(quickParameterFill(ret, i18n("Shear X"), "shear_x", "double", "0", "0", "360")); paramList.append(quickParameterFill(ret, i18n("Shear Z"), "shear_z", "double", "0", "0", "360"));*/ /*paramList.append(quickParameterFill(ret, i18n("Fix Shear Y"), "fix_shear_y", "double", "0", "0", "360")); paramList.append(quickParameterFill(ret, i18n("Fix Shear X"), "fix_shear_x", "double", "0", "0", "360")); paramList.append(quickParameterFill(ret, i18n("Fix Shear Z"), "fix_shear_z", "double", "0", "0", "360"));*/ paramList.append(quickParameterFill(ret, QStringLiteral("keyed"), QStringLiteral("keyed"), QStringLiteral("fixed"), QStringLiteral("1"), QStringLiteral("1"), QStringLiteral("1"))); paramList.append(quickParameterFill(ret, i18n("Geometry"), QStringLiteral("geometry"), QStringLiteral("geometry"), QStringLiteral("0/0:100%x100%:100%"), QStringLiteral("0/0:100%x100%:100%"), QStringLiteral("0/0:100%x100%:100%"), QLatin1String(""), QLatin1String(""), QLatin1String(""), QLatin1String(""), QLatin1String(""), QStringLiteral("true"))); paramList.append(quickParameterFill(ret, i18n("Distort"), QStringLiteral("distort"), QStringLiteral("bool"), QStringLiteral("0"), QStringLiteral("0"), QStringLiteral("1"))); paramList.append(quickParameterFill(ret, i18n("Rotate X"), QStringLiteral("rotate_x"), QStringLiteral("addedgeometry"), QStringLiteral("0"), QStringLiteral("-1800"), QStringLiteral("1800"), QString(), QString(), QStringLiteral("10"))); paramList.append(quickParameterFill(ret, i18n("Rotate Y"), QStringLiteral("rotate_y"), QStringLiteral("addedgeometry"), QStringLiteral("0"), QStringLiteral("-1800"), QStringLiteral("1800"), QString(), QString(), QStringLiteral("10"))); paramList.append(quickParameterFill(ret, i18n("Rotate Z"), QStringLiteral("rotate_z"), QStringLiteral("addedgeometry"), QStringLiteral("0"), QStringLiteral("-1800"), QStringLiteral("1800"), QString(), QString(), QStringLiteral("10"))); /*paramList.append(quickParameterFill(ret, i18n("Rotate Y"), "rotate_y", "simplekeyframe", "0", "-1800", "1800", QString(), QString(), "10")); paramList.append(quickParameterFill(ret, i18n("Rotate Z"), "rotate_z", "simplekeyframe", "0", "-1800", "1800", QString(), QString(), "10"));*/ paramList.append(quickParameterFill(ret, i18n("Fix Shear Y"), QStringLiteral("shear_y"), QStringLiteral("double"), QStringLiteral("0"), QStringLiteral("0"), QStringLiteral("360"))); paramList.append(quickParameterFill(ret, i18n("Fix Shear X"), QStringLiteral("shear_x"), QStringLiteral("double"), QStringLiteral("0"), QStringLiteral("0"), QStringLiteral("360"))); paramList.append(quickParameterFill(ret, i18n("Fix Shear Z"), QStringLiteral("shear_z"), QStringLiteral("double"), QStringLiteral("0"), QStringLiteral("0"), QStringLiteral("360"))); } else if (name == QLatin1String("mix")) { tname.appendChild(ret.createTextNode(i18n("Mix"))); } else if (name == QLatin1String("region")) { ktrans.setAttribute(QStringLiteral("id"), name); tname.appendChild(ret.createTextNode(i18n("Region"))); desc.appendChild(ret.createTextNode(i18n("Use alpha channel of another clip to create a transition."))); paramList.append(quickParameterFill(ret, i18n("Transparency clip"), QStringLiteral("resource"), QStringLiteral("url"), QLatin1String(""), QLatin1String(""), QLatin1String(""), QLatin1String(""), QLatin1String(""), QLatin1String(""))); paramList.append(quickParameterFill(ret, i18n("Geometry"), QStringLiteral("composite.geometry"), QStringLiteral("geometry"), QStringLiteral("0%/0%:100%x100%:100"), QStringLiteral("-500;-500;-500;-500;0"), QStringLiteral("500;500;500;500;100"))); paramList.append(quickParameterFill(ret, i18n("Alpha Channel Operation"), QStringLiteral("composite.operator"), QStringLiteral("list"), QStringLiteral("over"), QLatin1String(""), QLatin1String(""), QStringLiteral("over,and,or,xor"), i18n("Over,And,Or,Xor"))); paramList.append(quickParameterFill(ret, i18n("Align"), QStringLiteral("composite.aligned"), QStringLiteral("bool"), QStringLiteral("1"), QStringLiteral("0"), QStringLiteral("1"))); paramList.append(quickParameterFill(ret, i18n("Fill"), QStringLiteral("composite.fill"), QStringLiteral("bool"), QStringLiteral("1"), QStringLiteral("0"), QStringLiteral("1"))); paramList.append(quickParameterFill(ret, i18n("Distort"), QStringLiteral("composite.distort"), QStringLiteral("bool"), QStringLiteral("0"), QStringLiteral("0"), QStringLiteral("1"))); paramList.append(quickParameterFill(ret, i18n("Wipe File"), QStringLiteral("composite.luma"), QStringLiteral("list"), QLatin1String(""), QLatin1String(""), QLatin1String(""), imagefiles.join(QStringLiteral(";")), imagenamelist.join(QStringLiteral(",")))); paramList.append(quickParameterFill(ret, i18n("Wipe Softness"), QStringLiteral("composite.softness"), QStringLiteral("double"), QStringLiteral("0"), QStringLiteral("0"), QStringLiteral("100"), QLatin1String(""), QLatin1String(""), QStringLiteral("100"))); paramList.append(quickParameterFill(ret, i18n("Wipe Invert"), QStringLiteral("composite.luma_invert"), QStringLiteral("bool"), QStringLiteral("0"), QStringLiteral("0"), QStringLiteral("1"))); paramList.append(quickParameterFill(ret, i18n("Force Progressive Rendering"), QStringLiteral("composite.progressive"), QStringLiteral("bool"), QStringLiteral("1"), QStringLiteral("0"), QStringLiteral("1"))); paramList.append(quickParameterFill(ret, i18n("Force Deinterlace Overlay"), QStringLiteral("composite.deinterlace"), QStringLiteral("bool"), QStringLiteral("0"), QStringLiteral("0"), QStringLiteral("1"))); } foreach(const QDomElement & e, paramList) ktrans.appendChild(e); } delete metadata; metadata = 0; // Add the transition to the global list. ////qDebug() << ret.toString(); transitions->append(ret.documentElement()); } // Add some virtual transitions. QString slidetrans = "" + i18n("Slide") + "" + i18n("Slide image from one side to another.") + "" + i18n("Direction") + " " + i18n("Align") + "" + i18n("Force Progressive Rendering") + "" + i18n("Force Deinterlace Overlay") + "" + i18nc("@property: means that the image is inverted", "Invert") + ""; QDomDocument ret; ret.setContent(slidetrans); transitions->append(ret.documentElement()); QString dissolve = "" + i18n("Dissolve") + "" + i18n("Fade out one video while fading in the other video.") + "" + i18n("Reverse") + ""; ret.setContent(dissolve); transitions->append(ret.documentElement()); /*QString alphatrans = "" + i18n("Alpha Transparency") + "" + i18n("Make alpha channel transparent.") + "" + i18n("Direction") + "" + i18n("Rescale") + "" + i18n("Align") + ""; ret.setContent(alphatrans); transitions->append(ret.documentElement());*/ } QDomElement initEffects::quickParameterFill(QDomDocument & doc, const QString &name, const QString &tag, const QString &type, const QString &def, const QString &min, const QString &max, const QString &list, const QString &listdisplaynames, const QString &factor, const QString &namedesc, const QString &format, const QString &opacity) { QDomElement parameter = doc.createElement(QStringLiteral("parameter")); parameter.setAttribute(QStringLiteral("tag"), tag); parameter.setAttribute(QStringLiteral("default"), def); parameter.setAttribute(QStringLiteral("type"), type); parameter.setAttribute(QStringLiteral("name"), tag); parameter.setAttribute(QStringLiteral("max"), max); parameter.setAttribute(QStringLiteral("min"), min); if (!list.isEmpty()) parameter.setAttribute(QStringLiteral("paramlist"), list); if (!listdisplaynames.isEmpty()) parameter.setAttribute(QStringLiteral("paramlistdisplay"), listdisplaynames); if (!factor.isEmpty()) parameter.setAttribute(QStringLiteral("factor"), factor); if (!namedesc.isEmpty()) parameter.setAttribute(QStringLiteral("namedesc"), namedesc); if (!format.isEmpty()) parameter.setAttribute(QStringLiteral("format"), format); if (!opacity.isEmpty()) parameter.setAttribute(QStringLiteral("opacity"), opacity); QDomElement pname = doc.createElement(QStringLiteral("name")); pname.appendChild(doc.createTextNode(name)); parameter.appendChild(pname); return parameter; } // static void initEffects::parseTransitionFile(EffectsList *transitionList, const QString &name, Mlt::Repository *repository, QMap effectDescriptions, QStringList imagefiles, QStringList imagenamelist, const QString &defaultWipeLuma) { QDomDocument doc; QFile file(name); if (!file.open(QIODevice::ReadOnly)) { return; } QTextStream out(&file); QString fileContent = out.readAll(); file.close(); // Check for special keywords fileContent.replace(QLatin1String("%lumaPaths"), imagefiles.join(QLatin1Char(';'))); fileContent.replace(QLatin1String("%lumaNames"), imagenamelist.join(QLatin1Char(','))); fileContent.replace(QLatin1String("%lumaDefault"), defaultWipeLuma); doc.setContent(fileContent, false); QDomElement documentElement; QDomNodeList effects; QDomElement base = doc.documentElement(); QStringList addedTags; effects = doc.elementsByTagName(QStringLiteral("transition")); int i = effects.count(); if (i == 0) { qDebug() << "+++++++++++++\nEffect broken: " << name<<"\n+++++++++++";; return; } bool needsLocaleConversion = false; i--; for (; i >= 0 ; i--) { QLocale locale; QDomNode n = effects.item(i); if (n.isNull()) continue; documentElement = n.toElement(); QString id = documentElement.attribute(QStringLiteral("id")); if (addedTags.contains(id)) { // We already processed a version of that filter continue; } //If XML has no description, take it fom MLT's descriptions if (effectDescriptions.contains(id)) { QDomNodeList desc = documentElement.elementsByTagName(QStringLiteral("description")); if (desc.isEmpty()) { QDomElement d = documentElement.ownerDocument().createElement(QStringLiteral("description")); QDomText value = documentElement.ownerDocument().createTextNode(effectDescriptions.value(id)); d.appendChild(value); documentElement.appendChild(d); } } if (documentElement.hasAttribute(QStringLiteral("LC_NUMERIC"))) { // set a locale for that file locale = QLocale(documentElement.attribute(QStringLiteral("LC_NUMERIC"))); if (locale.decimalPoint() != QLocale().decimalPoint()) { needsLocaleConversion = true; } } locale.setNumberOptions(QLocale::OmitGroupSeparator); if (needsLocaleConversion) { // we need to convert all numbers to the system's locale (for example 0.5 -> 0,5) QChar separator = QLocale().decimalPoint(); QChar oldSeparator = locale.decimalPoint(); QDomNodeList params = documentElement.elementsByTagName(QStringLiteral("parameter")); for (int j = 0; j < params.count(); ++j) { QDomNamedNodeMap attrs = params.at(j).attributes(); for (int k = 0; k < attrs.count(); ++k) { QString nodeName = attrs.item(k).nodeName(); if (nodeName != QLatin1String("type") && nodeName != QLatin1String("name")) { QString val = attrs.item(k).nodeValue(); if (val.contains(oldSeparator)) { QString newVal = val.replace(oldSeparator, separator); attrs.item(k).setNodeValue(newVal); } } } } } double version = -1; Mlt::Properties *metadata = repository->metadata(transition_type, id.toUtf8().data()); if (metadata && metadata->is_valid()) { version = metadata->get_double("version"); } if (metadata) delete metadata; if (documentElement.hasAttribute(QStringLiteral("version"))) { // a specific version of the filter is required if (locale.toDouble(documentElement.attribute(QStringLiteral("version"))) > version) { continue; } } if (version > -1) { // Add version info to XML QDomNode versionNode = doc.createElement(QStringLiteral("version")); versionNode.appendChild(doc.createTextNode(QLocale().toString(version))); documentElement.appendChild(versionNode); } addedTags << id; } transitionList->append(base); } diff --git a/src/effectstack/graphicsscenerectmove.cpp b/src/effectstack/graphicsscenerectmove.cpp index 53628dd8b..d2f4ac0ce 100644 --- a/src/effectstack/graphicsscenerectmove.cpp +++ b/src/effectstack/graphicsscenerectmove.cpp @@ -1,736 +1,889 @@ /*************************************************************************** * copyright (C) 2008 by Marco Gittler (g.marco@freenet.de) * * Copyright (C) 2008 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 "graphicsscenerectmove.h" #include "titler/titledocument.h" #include "titler/gradientwidget.h" #include #include #include #include #include #include #include #include #include #include #include #include MyTextItem::MyTextItem(const QString &txt, QGraphicsItem *parent) : QGraphicsTextItem(txt, parent) , m_alignment(Qt::AlignLeft) , m_useShadow(false) { setCacheMode(QGraphicsItem::ItemCoordinateCache); + setFlag(QGraphicsItem::ItemSendsGeometryChanges, true); document()->setDocumentMargin(0); updateGeometry(); connect(document(), SIGNAL(contentsChange(int, int, int)), this, SLOT(updateGeometry(int, int, int))); } Qt::Alignment MyTextItem::alignment() const { return m_alignment; } void MyTextItem::updateShadow(bool enabled, int blur, int xoffset, int yoffset, QColor color) { m_shadowOffset = QPoint(xoffset, yoffset); m_shadowBlur = blur; m_shadowColor = color; m_useShadow = enabled; updateShadow(); update(); } QStringList MyTextItem::shadowInfo() const { QStringList info; info << QString::number(m_useShadow) << m_shadowColor.name(QColor::HexArgb) << QString::number( m_shadowBlur) << QString::number(m_shadowOffset.x()) << QString::number(m_shadowOffset.y()); return info; } void MyTextItem::loadShadow(QStringList info) { if (info.count() < 5) return; updateShadow((info.at(0).toInt() == true), info.at(2).toInt(), info.at(3).toInt(),info.at(4).toInt(), QColor(info.at(1))); } void MyTextItem::setAlignment(Qt::Alignment alignment) { m_alignment = alignment; QTextBlockFormat format; format.setAlignment(alignment); QTextCursor cursor = textCursor(); // save cursor position int position = textCursor().position(); cursor.select(QTextCursor::Document); cursor.mergeBlockFormat(format); cursor.clearSelection(); cursor.setPosition(position); // restore cursor position setTextCursor(cursor); } void MyTextItem::updateGeometry(int, int, int) { updateGeometry(); // update gradient if necessary QString gradientData = data(TitleDocument::Gradient).toString(); if (!gradientData.isEmpty()) { QTextCursor cursor = textCursor(); QTextCharFormat cformat; QRectF rect = boundingRect(); int position = textCursor().position(); QLinearGradient gr = GradientWidget::gradientFromString(gradientData, rect.width(), rect.height()); cursor.select(QTextCursor::Document); cformat.setForeground(QBrush(gr)); cursor.mergeCharFormat(cformat); cursor.clearSelection(); cursor.setPosition(position); // restore cursor position setTextCursor(cursor); } if (m_useShadow) { updateShadow(); } } void MyTextItem::updateShadow() { QString text = toPlainText(); if (text.isEmpty()) { m_shadow = QImage(); return; } QFontMetrics metrics(font()); //ADJUST TO CURRENT SETTING int lineSpacing = data(TitleDocument::LineSpacing).toInt() + metrics.lineSpacing(); QPainterPath path; // Calculate line width QStringList lines = text.split('\n'); double linePos = metrics.ascent(); QRectF bounding = boundingRect(); foreach(const QString &line, lines) { QPainterPath linePath; linePath.addText(0, linePos, font(), line); linePos += lineSpacing; if ( m_alignment == Qt::AlignHCenter ) { double offset = (bounding.width() - metrics.width(line)) / 2; linePath.translate(offset, 0); } else if ( m_alignment == Qt::AlignRight ) { double offset = (bounding.width() - metrics.width(line)); linePath.translate(offset, 0); } path.addPath(linePath); } // Calculate position of text in parent item QRectF pathRect = QRectF(0, 0, bounding.width(), linePos - lineSpacing + metrics.descent() ); QPointF offset = bounding.center() - pathRect.center() + m_shadowOffset; path.translate(offset); QRectF fullSize = bounding.united(path.boundingRect()); m_shadow = QImage(fullSize.width(), fullSize.height(), QImage::Format_ARGB32_Premultiplied); m_shadow.fill(Qt::transparent); QPainter painter(&m_shadow); painter.fillPath(path, QBrush(m_shadowColor)); painter.end(); if (m_shadowBlur > 0) { blurShadow(m_shadow, m_shadowBlur); } } void MyTextItem::blurShadow(QImage &result, int radius) { int tab[] = { 14, 10, 8, 6, 5, 5, 4, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2 }; int alpha = (radius < 1) ? 16 : (radius > 17) ? 1 : tab[radius-1]; int r1 = 0; int r2 = result.height() - 1; int c1 = 0; int c2 = result.width() - 1; int bpl = result.bytesPerLine(); int rgba[4]; unsigned char* p; int i1 = 0; int i2 = 3; i1 = i2 = (QSysInfo::ByteOrder == QSysInfo::BigEndian ? 0 : 3); for (int col = c1; col <= c2; col++) { p = result.scanLine(r1) + col * 4; for (int i = i1; i <= i2; i++) rgba[i] = p[i] << 4; p += bpl; for (int j = r1; j < r2; j++, p += bpl) for (int i = i1; i <= i2; i++) p[i] = (rgba[i] += ((p[i] << 4) - rgba[i]) * alpha / 16) >> 4; } for (int row = r1; row <= r2; row++) { p = result.scanLine(row) + c1 * 4; for (int i = i1; i <= i2; i++) rgba[i] = p[i] << 4; p += 4; for (int j = c1; j < c2; j++, p += 4) for (int i = i1; i <= i2; i++) p[i] = (rgba[i] += ((p[i] << 4) - rgba[i]) * alpha / 16) >> 4; } for (int col = c1; col <= c2; col++) { p = result.scanLine(r2) + col * 4; for (int i = i1; i <= i2; i++) rgba[i] = p[i] << 4; p -= bpl; for (int j = r1; j < r2; j++, p -= bpl) for (int i = i1; i <= i2; i++) p[i] = (rgba[i] += ((p[i] << 4) - rgba[i]) * alpha / 16) >> 4; } for (int row = r1; row <= r2; row++) { p = result.scanLine(row) + c2 * 4; for (int i = i1; i <= i2; i++) rgba[i] = p[i] << 4; p -= 4; for (int j = c1; j < c2; j++, p -= 4) for (int i = i1; i <= i2; i++) p[i] = (rgba[i] += ((p[i] << 4) - rgba[i]) * alpha / 16) >> 4; } } void MyTextItem::paint( QPainter *painter, const QStyleOptionGraphicsItem * option, QWidget* w) { if (m_useShadow && !m_shadow.isNull()) { painter->drawImage(0, 0, m_shadow); } QGraphicsTextItem::paint(painter, option, w); } void MyTextItem::updateGeometry() { QPointF topRightPrev = boundingRect().topRight(); setTextWidth(-1); setTextWidth(boundingRect().width()); setAlignment(m_alignment); QPointF topRight = boundingRect().topRight(); if (m_alignment & Qt::AlignRight) { setPos(pos() + (topRightPrev - topRight)); } } QRectF MyTextItem::baseBoundingRect() const { QRectF base = QGraphicsTextItem::boundingRect(); QTextCursor cur(document()); cur.select(QTextCursor::Document); QTextBlockFormat format = cur.blockFormat(); int lines = document()->lineCount(); int lineHeight = format.lineHeight(); int lineHeight2 = QFontMetrics(font()).lineSpacing(); if (lines > 1) { base.setHeight(lines * lineHeight2 + lineHeight * (lines - 1)); } return base; } QRectF MyTextItem::boundingRect() const { QRectF base = baseBoundingRect(); base.setRight(base.right() + m_shadowOffset.x()); base.setBottom(base.bottom() + m_shadowOffset.y()); return base; } +QVariant MyTextItem::itemChange(GraphicsItemChange change, const QVariant &value) +{ + if (change == ItemPositionChange && scene()) { + QPoint newPos = value.toPoint(); + if (QApplication::mouseButtons() == Qt::LeftButton && qobject_cast (scene())) { + GraphicsSceneRectMove* customScene = qobject_cast (scene()); + int gridSize = customScene->gridSize(); + int xV = (newPos.x()/gridSize)*gridSize; + int yV = (newPos.y()/gridSize)*gridSize; + newPos = QPoint(xV, yV); + } + return newPos; + } else if (change == QGraphicsItem::ItemSelectedHasChanged) { + if (value.toBool() == false) { + // Make sure to deselect text when item loses focus + QTextCursor cur(document()); + cur.clearSelection(); + setTextCursor(cur); + } + } + return QGraphicsItem::itemChange(change, value); +} + MyRectItem::MyRectItem(QGraphicsItem *parent) : QGraphicsRectItem(parent) { setCacheMode(QGraphicsItem::ItemCoordinateCache); + setFlag(QGraphicsItem::ItemSendsGeometryChanges, true); } void MyRectItem::setRect(const QRectF & rectangle) { QGraphicsRectItem::setRect(rectangle); if (m_rect != rectangle && !data(TitleDocument::Gradient).isNull()) { m_rect = rectangle; QLinearGradient gr = GradientWidget::gradientFromString(data(TitleDocument::Gradient).toString(), m_rect.width(), m_rect.height()); setBrush(QBrush(gr)); } } +QVariant MyRectItem::itemChange(GraphicsItemChange change, const QVariant &value) +{ + if (change == ItemPositionChange && scene()) { + QPoint newPos = value.toPoint(); + if (QApplication::mouseButtons() == Qt::LeftButton && qobject_cast (scene())) { + GraphicsSceneRectMove* customScene = qobject_cast (scene()); + int gridSize = customScene->gridSize(); + int xV = (newPos.x()/gridSize)*gridSize; + int yV = (newPos.y()/gridSize)*gridSize; + newPos = QPoint(xV, yV); + } + return newPos; + } + return QGraphicsItem::itemChange(change, value); +} + +MyPixmapItem::MyPixmapItem(const QPixmap &pixmap, QGraphicsItem *parent) : + QGraphicsPixmapItem(pixmap, parent) +{ + setCacheMode(QGraphicsItem::ItemCoordinateCache); + setFlag(QGraphicsItem::ItemSendsGeometryChanges, true); +} + +QVariant MyPixmapItem::itemChange(GraphicsItemChange change, const QVariant &value) +{ + if (change == ItemPositionChange && scene()) { + QPoint newPos = value.toPoint(); + if (QApplication::mouseButtons() == Qt::LeftButton && qobject_cast (scene())) { + GraphicsSceneRectMove* customScene = qobject_cast (scene()); + int gridSize = customScene->gridSize(); + int xV = (newPos.x()/gridSize)*gridSize; + int yV = (newPos.y()/gridSize)*gridSize; + newPos = QPoint(xV, yV); + } + return newPos; + } + return QGraphicsItem::itemChange(change, value); +} + +MySvgItem::MySvgItem(const QString &fileName, QGraphicsItem *parent) : + QGraphicsSvgItem(fileName, parent) +{ + setCacheMode(QGraphicsItem::ItemCoordinateCache); + setFlag(QGraphicsItem::ItemSendsGeometryChanges, true); +} + +QVariant MySvgItem::itemChange(GraphicsItemChange change, const QVariant &value) +{ + if (change == ItemPositionChange && scene()) { + QPoint newPos = value.toPoint(); + if (QApplication::mouseButtons() == Qt::LeftButton && qobject_cast (scene())) { + GraphicsSceneRectMove* customScene = qobject_cast (scene()); + int gridSize = customScene->gridSize(); + int xV = (newPos.x()/gridSize)*gridSize; + int yV = (newPos.y()/gridSize)*gridSize; + newPos = QPoint(xV, yV); + } + return newPos; + } + return QGraphicsItem::itemChange(change, value); +} GraphicsSceneRectMove::GraphicsSceneRectMove(QObject *parent) : QGraphicsScene(parent), m_selectedItem(NULL), m_resizeMode(NoResize), m_possibleAction(NoResize), - m_tool(TITLE_RECTANGLE) + m_tool(TITLE_RECTANGLE), + m_gridSize(20), + m_createdText(false) { //grabMouse(); m_zoom = 1.0; setBackgroundBrush(QBrush(Qt::transparent)); m_fontSize = 0; } void GraphicsSceneRectMove::setSelectedItem(QGraphicsItem *item) { clearSelection(); m_selectedItem = item; item->setSelected(true); update(); } TITLETOOL GraphicsSceneRectMove::tool() const { return m_tool; } void GraphicsSceneRectMove::setTool(TITLETOOL tool) { m_tool = tool; switch (m_tool) { case TITLE_RECTANGLE: setCursor(Qt::CrossCursor); break; case TITLE_TEXT: setCursor(Qt::IBeamCursor); break; default: setCursor(Qt::ArrowCursor); } } void GraphicsSceneRectMove::keyPressEvent(QKeyEvent * keyEvent) { if (m_selectedItem == NULL || !(m_selectedItem->flags() & QGraphicsItem::ItemIsMovable)) { QGraphicsScene::keyPressEvent(keyEvent); return; } if (m_selectedItem->type() == QGraphicsTextItem::Type) { MyTextItem *t = static_cast(m_selectedItem); if (t->textInteractionFlags() & Qt::TextEditorInteraction) { QGraphicsScene::keyPressEvent(keyEvent); return; } } - int diff = 1; - if (keyEvent->modifiers() & Qt::ControlModifier) diff = 10; + int diff = m_gridSize; + if (keyEvent->modifiers() & Qt::ControlModifier) diff = m_gridSize * 5; switch (keyEvent->key()) { case Qt::Key_Left: foreach (QGraphicsItem *qgi, selectedItems()) { qgi->moveBy(-diff,0); } emit itemMoved(); break; case Qt::Key_Right: foreach (QGraphicsItem *qgi, selectedItems()) { qgi->moveBy( diff,0); } emit itemMoved(); break; case Qt::Key_Up: foreach (QGraphicsItem *qgi, selectedItems()) { qgi->moveBy(0,-diff); } emit itemMoved(); break; case Qt::Key_Down: foreach (QGraphicsItem *qgi, selectedItems()) { qgi->moveBy(0, diff); } emit itemMoved(); break; case Qt::Key_Delete: case Qt::Key_Backspace: foreach (QGraphicsItem *qgi, selectedItems()) { if (qgi->data(-1).toInt() == -1) continue; removeItem(qgi); delete qgi; } m_selectedItem = NULL; emit selectionChanged(); break; default: QGraphicsScene::keyPressEvent(keyEvent); } emit actionFinished(); } void GraphicsSceneRectMove::mouseDoubleClickEvent(QGraphicsSceneMouseEvent* e) { QPointF p = e->scenePos(); p += QPoint(-2, -2); m_resizeMode = NoResize; m_selectedItem = NULL; // http://www.kdenlive.org/mantis/view.php?id=1035 QList i = items(QRectF(p , QSizeF(4, 4)).toRect()); if (i.isEmpty()) return; + int ix = 1; QGraphicsItem* g = i.first(); - if (g->type() == QGraphicsTextItem::Type) { + while (!(g->flags() & QGraphicsItem::ItemIsSelectable) && ix < i.count()) { + g = i.at(ix); + ix++; + } + if (g && g->type() == QGraphicsTextItem::Type && g->flags() & QGraphicsItem::ItemIsSelectable) { m_selectedItem = g; MyTextItem *t = static_cast(g); t->setTextInteractionFlags(Qt::TextEditorInteraction); } else emit doubleClickEvent(); QGraphicsScene::mouseDoubleClickEvent(e); } void GraphicsSceneRectMove::mouseReleaseEvent(QGraphicsSceneMouseEvent *e) { if (m_tool == TITLE_RECTANGLE && m_selectedItem) setSelectedItem(m_selectedItem); QGraphicsScene::mouseReleaseEvent(e); + if (m_createdText) { + m_selectedItem->setSelected(true); + MyTextItem *newText = static_cast(m_selectedItem); + QTextCursor cur(newText->document()); + cur.select(QTextCursor::Document); + newText->setTextCursor(cur); + m_createdText = false; + } emit actionFinished(); } void GraphicsSceneRectMove::mousePressEvent(QGraphicsSceneMouseEvent* e) { - m_clickPoint = e->scenePos(); + int xPos = ((int) e->scenePos().x() / m_gridSize) * m_gridSize; + int yPos = ((int) e->scenePos().y() / m_gridSize) * m_gridSize; + m_clickPoint = QPointF(xPos, yPos); + //m_clickPoint = e->scenePos(); QPointF p = e->scenePos(); p += QPoint(-2, -2); m_resizeMode = m_possibleAction; const QList list = items(QRectF(p , QSizeF(4, 4)).toRect()); QGraphicsItem *item = NULL; if (e->modifiers() != Qt::ControlModifier) { clearSelection(); } if (m_tool == TITLE_SELECT) { foreach(QGraphicsItem *g, list) { //qDebug() << " - - CHECKING ITEM Z:" << g->zValue() << ", TYPE: " << g->type(); // check is there is a selected item in list - if (g->zValue() > -1000 && g->isSelected()) { + if (!(g->flags() & QGraphicsItem::ItemIsSelectable)) { + continue; + } + if (g->zValue() > -1000/* && g->isSelected()*/) { + g->setSelected(true); item = g; break; } } if (item == NULL || !(item->flags() & QGraphicsItem::ItemIsSelectable)) { if (m_selectedItem && m_selectedItem->type() == QGraphicsTextItem::Type) { // disable text editing MyTextItem *t = static_cast(m_selectedItem); t->textCursor().setPosition(0); QTextBlock cur = t->textCursor().block(); t->setTextCursor(QTextCursor(cur)); t->setTextInteractionFlags(Qt::NoTextInteraction); } m_selectedItem = NULL; foreach(QGraphicsItem* g, list) { if (g->zValue() > -1000) { item = g; break; } } } if (item != NULL && item->flags() & QGraphicsItem::ItemIsMovable) { m_sceneClickPoint = e->scenePos(); m_selectedItem = item; //qDebug() << "///////// ITEM TYPE: " << item->type(); if (item->type() == QGraphicsTextItem::Type) { MyTextItem *t = static_cast(item); if (t->textInteractionFlags() == Qt::TextEditorInteraction) { QGraphicsScene::mousePressEvent(e); return; } t->setTextInteractionFlags(Qt::NoTextInteraction); + t->setFlag(QGraphicsItem::ItemSendsGeometryChanges, true); setCursor(Qt::ClosedHandCursor); } else if (item->type() == QGraphicsRectItem::Type || item->type() == QGraphicsSvgItem::Type || item->type() == QGraphicsPixmapItem::Type) { QRectF r1; if (m_selectedItem->type() == QGraphicsRectItem::Type) r1 = ((QGraphicsRectItem*)m_selectedItem)->rect().normalized(); else r1 = m_selectedItem->boundingRect().normalized(); r1.translate(m_selectedItem->scenePos()); switch (m_resizeMode) { case BottomRight: case Right: case Down: m_clickPoint = r1.topLeft(); e->accept(); break; case TopLeft: case Left: case Up: m_clickPoint = r1.bottomRight(); e->accept(); break; case TopRight: m_clickPoint = r1.bottomLeft(); e->accept(); break; case BottomLeft: m_clickPoint = r1.topRight(); e->accept(); break; default: break; } } } } else if (m_tool == TITLE_RECTANGLE) { - m_sceneClickPoint = e->scenePos(); + m_sceneClickPoint = QPointF(xPos, yPos); m_selectedItem = NULL; } else if (m_tool == TITLE_TEXT) { - MyTextItem *textItem = new MyTextItem(QString(), NULL); + MyTextItem *textItem = new MyTextItem(QStringLiteral("Text"), NULL); + yPos = (((int) e->scenePos().y() - (int)(m_fontSize / 2)) / m_gridSize) * m_gridSize; + textItem->setPos(xPos, yPos); addItem(textItem); - emit newText(textItem); textItem->setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable); textItem->setTextInteractionFlags(Qt::TextEditorInteraction); - textItem->setPos(e->scenePos() - QPointF(0, (int)(m_fontSize / 2))); + emit newText(textItem); m_selectedItem = textItem; m_selectedItem->setSelected(true); + m_createdText = true; } QGraphicsScene::mousePressEvent(e); //qDebug() << "////// MOUSE CLICK, RESIZE MODE: " << m_resizeMode; } void GraphicsSceneRectMove::clearTextSelection() { if (m_selectedItem && m_selectedItem->type() == QGraphicsTextItem::Type) { // disable text editing MyTextItem *t = static_cast(m_selectedItem); t->textCursor().setPosition(0); QTextBlock cur = t->textCursor().block(); t->setTextCursor(QTextCursor(cur)); t->setTextInteractionFlags(Qt::NoTextInteraction); } m_selectedItem = NULL; clearSelection(); } void GraphicsSceneRectMove::mouseMoveEvent(QGraphicsSceneMouseEvent* e) { if (e->buttons() != Qt::NoButton && !m_selectedItem) { QList viewlist = views(); if (viewlist.isEmpty()) { // invalid e->accept(); return; } QGraphicsView *view = viewlist.at(0); if (view->mapFromScene((e->scenePos() - m_clickPoint)).manhattanLength() < QApplication::startDragDistance()) { e->accept(); return; } } if (m_selectedItem && (e->buttons() & Qt::LeftButton)) { if (m_selectedItem->type() == QGraphicsRectItem::Type || m_selectedItem->type() == QGraphicsSvgItem::Type || m_selectedItem->type() == QGraphicsPixmapItem::Type) { QRectF newrect; if (m_selectedItem->type() == QGraphicsRectItem::Type) newrect = ((QGraphicsRectItem*)m_selectedItem)->rect(); else newrect = m_selectedItem->boundingRect(); - QPointF newpoint = e->scenePos(); + int xPos = ((int) e->scenePos().x() / m_gridSize) * m_gridSize; + int yPos = ((int) e->scenePos().y() / m_gridSize) * m_gridSize; + QPointF newpoint(xPos, yPos); switch (m_resizeMode) { case BottomRight: case BottomLeft: case TopRight: case TopLeft: newrect = QRectF(m_clickPoint, newpoint).normalized(); break; case Up: newrect = QRectF(m_clickPoint, QPointF(m_clickPoint.x() - newrect.width(), newpoint.y())).normalized(); break; case Down: newrect = QRectF(m_clickPoint, QPointF(newrect.width() + m_clickPoint.x(), newpoint.y())).normalized(); break; case Right: newrect = QRectF(m_clickPoint, QPointF(newpoint.x(), m_clickPoint.y() + newrect.height())).normalized(); break; case Left: newrect = QRectF(m_clickPoint, QPointF(newpoint.x(), m_clickPoint.y() - newrect.height())).normalized(); break; default: break; } if (m_selectedItem->type() == QGraphicsRectItem::Type && m_resizeMode != NoResize) { MyRectItem *gi = (MyRectItem*)m_selectedItem; // Resize using aspect ratio if (!m_selectedItem->data(0).isNull()) { // we want to keep aspect ratio double hRatio = (double) newrect.width() / m_selectedItem->data(0).toInt(); double vRatio = (double) newrect.height() / m_selectedItem->data(1).toInt(); if (hRatio < vRatio) newrect.setHeight(m_selectedItem->data(1).toInt() * hRatio); else newrect.setWidth(m_selectedItem->data(0).toInt() * vRatio); } gi->setPos(newrect.topLeft()); gi->setRect(QRectF(QPointF(), newrect.bottomRight() - newrect.topLeft())); return; } QGraphicsScene::mouseMoveEvent(e); } else if (m_selectedItem->type() == QGraphicsTextItem::Type) { MyTextItem *t = static_cast(m_selectedItem); if (t->textInteractionFlags() & Qt::TextEditorInteraction) { QGraphicsScene::mouseMoveEvent(e); return; } - QPointF diff = e->scenePos() - m_sceneClickPoint; + QGraphicsScene::mouseMoveEvent(e); m_sceneClickPoint = e->scenePos(); - foreach (QGraphicsItem *qgi, selectedItems()) { qgi->moveBy(diff.x(), diff.y()); } } emit itemMoved(); } else if (m_tool == TITLE_SELECT) { QList viewlist = views(); QGraphicsView *view = NULL; if (viewlist.size() > 0) view = viewlist[0]; QPointF p = e->scenePos(); p += QPoint(-2, -2); m_resizeMode = NoResize; bool itemFound = false; QList list = items(QRectF(p , QSizeF(4, 4)).toRect()); foreach(const QGraphicsItem* g, list) { + if (!(g->flags() & QGraphicsItem::ItemIsSelectable)) { + continue; + } if ((g->type() == QGraphicsSvgItem::Type || g->type() == QGraphicsPixmapItem::Type) && g->zValue() > -1000) { // image or svg item setCursor(Qt::OpenHandCursor); + itemFound = true; break; } else if (g->type() == QGraphicsRectItem::Type && g->zValue() > -1000) { if (view == NULL) continue; QRectF r1 = ((const QGraphicsRectItem*)g)->rect().normalized(); itemFound = true; // Item mapped coordinates QPolygon r = g->deviceTransform(view->viewportTransform()).map(r1).toPolygon(); QPainterPath top(r.point(0)); top.lineTo(r.point(1)); QPainterPath bottom(r.point(2)); bottom.lineTo(r.point(3)); QPainterPath left(r.point(0)); left.lineTo(r.point(3)); QPainterPath right(r.point(1)); right.lineTo(r.point(2)); // The area interested by the mouse pointer QPoint viewPos = view->mapFromScene(e->scenePos()); QPainterPath mouseArea; QFontMetrics metrics(font()); int box = metrics.lineSpacing() / 2; mouseArea.addRect(viewPos.x() - box, viewPos.y() - box, 2 * box, 2 * box); // Check for collisions between the mouse and the borders if (mouseArea.contains(r.point(0))) { m_possibleAction = TopLeft; setCursor(Qt::SizeFDiagCursor); } else if (mouseArea.contains(r.point(2))) { m_possibleAction = BottomRight; setCursor(Qt::SizeFDiagCursor); } else if (mouseArea.contains(r.point(1))) { m_possibleAction = TopRight; setCursor(Qt::SizeBDiagCursor); } else if (mouseArea.contains(r.point(3))) { m_possibleAction = BottomLeft; setCursor(Qt::SizeBDiagCursor); } else if (top.intersects(mouseArea)) { m_possibleAction = Up; setCursor(Qt::SizeVerCursor); } else if (bottom.intersects(mouseArea)) { m_possibleAction = Down; setCursor(Qt::SizeVerCursor); } else if (right.intersects(mouseArea)) { m_possibleAction = Right; setCursor(Qt::SizeHorCursor); } else if (left.intersects(mouseArea)) { m_possibleAction = Left; setCursor(Qt::SizeHorCursor); } else { setCursor(Qt::OpenHandCursor); m_possibleAction = NoResize; } } - if (!itemFound) { - m_possibleAction = NoResize; - setCursor(Qt::ArrowCursor); - } break; } + if (!itemFound) { + m_possibleAction = NoResize; + setCursor(Qt::ArrowCursor); + } QGraphicsScene::mouseMoveEvent(e); } else if (m_tool == TITLE_RECTANGLE && e->buttons() & Qt::LeftButton) { if (m_selectedItem == NULL) { // create new rect item QRectF r(0, 0, e->scenePos().x() - m_sceneClickPoint.x(), e->scenePos().y() - m_sceneClickPoint.y()); r = r.normalized(); MyRectItem *rect = new MyRectItem(); rect->setRect(QRectF(0, 0, r.width(), r.height())); addItem(rect); m_selectedItem = rect; m_selectedItem->setPos(m_sceneClickPoint); m_selectedItem->setSelected(true); emit newRect(rect); - m_selectedItem->setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable); + m_selectedItem->setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable | QGraphicsItem::ItemSendsGeometryChanges); m_resizeMode = BottomRight; QGraphicsScene::mouseMoveEvent(e); } } } void GraphicsSceneRectMove::wheelEvent(QGraphicsSceneWheelEvent * wheelEvent) { if (wheelEvent->modifiers() == Qt::ControlModifier) { QList viewlist = views(); ////qDebug() << wheelEvent->delta() << ' ' << zoom; if (viewlist.size() > 0) { if (wheelEvent->delta() > 0) emit sceneZoom(true); else emit sceneZoom(false); } } else wheelEvent->setAccepted(false); } void GraphicsSceneRectMove::setScale(double s) { if (m_zoom < 1.0 / 7.0 && s < 1.0) return; else if (m_zoom > 10.0 / 7.9 && s > 1.0) return; QList viewlist = views(); if (viewlist.size() > 0) { viewlist[0]->scale(s, s); m_zoom = m_zoom * s; } ////qDebug()<<"////////// ZOOM: "< viewlist = views(); if (viewlist.size() > 0) { viewlist[0]->resetTransform(); viewlist[0]->scale(s, s); m_zoom = s; } ////qDebug()<<"////////// ZOOM: "< l = views(); foreach(QGraphicsView* v, l) { v->setCursor(c); } } void GraphicsSceneRectMove::slotUpdateFontSize(int s) { m_fontSize = s; } +void GraphicsSceneRectMove::drawForeground(QPainter *painter, const QRectF &rect) { + // draw the grid if needed + if (m_gridSize <= 1) + return; + + QPen pen(QColor(255, 0, 0, 100)); + painter->setPen(pen); + + qreal left = int(rect.left()) - (int(rect.left()) % m_gridSize); + qreal top = int(rect.top()) - (int(rect.top()) % m_gridSize); + QVector points; + for (qreal x = left; x < rect.right(); x += m_gridSize){ + for (qreal y = top; y < rect.bottom(); y += m_gridSize){ + points.append(QPointF(x,y)); + } + } + painter->drawPoints(points.data(), points.size()); +} + +int GraphicsSceneRectMove::gridSize() const +{ + return m_gridSize; +} + +void GraphicsSceneRectMove::slotUseGrid(bool enableGrid) +{ + m_gridSize = enableGrid ? 20 : 1; +} + +void GraphicsSceneRectMove::addNewItem(QGraphicsItem *item) +{ + clearSelection(); + addItem(item); + item->setSelected(true); + m_selectedItem = item; +} + diff --git a/src/effectstack/graphicsscenerectmove.h b/src/effectstack/graphicsscenerectmove.h index a16db48bd..b820d3ad8 100644 --- a/src/effectstack/graphicsscenerectmove.h +++ b/src/effectstack/graphicsscenerectmove.h @@ -1,115 +1,143 @@ /*************************************************************************** * copyright (C) 2008 by Marco Gittler (g.marco@freenet.de) * * Copyright (C) 2008 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 * ***************************************************************************/ #ifndef GRAPHICSSCENERECTMOVE_H #define GRAPHICSSCENERECTMOVE_H #include #include +#include enum resizeModes {NoResize = 0, TopLeft, BottomLeft, TopRight, BottomRight, Left, Right, Up, Down}; enum TITLETOOL { TITLE_SELECT = 0, TITLE_RECTANGLE = 1, TITLE_TEXT = 2, TITLE_IMAGE = 3 }; class MyTextItem: public QGraphicsTextItem { Q_OBJECT public: MyTextItem(const QString &, QGraphicsItem *parent = Q_NULLPTR); void setAlignment(Qt::Alignment alignment); /** @brief returns an extended bounding containing shadow */ QRectF boundingRect() const; /** @brief returns the normal bounding rect around text */ QRectF baseBoundingRect() const; Qt::Alignment alignment() const; void updateShadow(bool enabled, int blur, int xoffset, int yoffset, QColor color); QStringList shadowInfo() const; void loadShadow(QStringList info); void paint( QPainter *painter, const QStyleOptionGraphicsItem * option, QWidget* w); +protected: + virtual QVariant itemChange(GraphicsItemChange change, const QVariant &value); + private: Qt::Alignment m_alignment; QImage m_shadow; QPoint m_shadowOffset; int m_shadowBlur; QColor m_shadowColor; bool m_useShadow; void updateShadow(); void blurShadow(QImage &image, int radius); public slots: void updateGeometry(int, int, int); void updateGeometry(); }; class MyRectItem: public QGraphicsRectItem { public: MyRectItem(QGraphicsItem *parent = 0); void setRect(const QRectF & rectangle); +protected: + virtual QVariant itemChange(GraphicsItemChange change, const QVariant &value); private: QRectF m_rect; }; +class MyPixmapItem: public QGraphicsPixmapItem +{ +public: + MyPixmapItem(const QPixmap &pixmap, QGraphicsItem *parent = 0); +protected: + virtual QVariant itemChange(GraphicsItemChange change, const QVariant &value); +}; + +class MySvgItem: public QGraphicsSvgItem +{ +public: + MySvgItem(const QString &fileName, QGraphicsItem *parent = 0); +protected: + virtual QVariant itemChange(GraphicsItemChange change, const QVariant &value); +}; + class GraphicsSceneRectMove: public QGraphicsScene { Q_OBJECT public: explicit GraphicsSceneRectMove(QObject* parent = 0); void setSelectedItem(QGraphicsItem *item); void setScale(double s); void setZoom(double s); void setTool(TITLETOOL tool); TITLETOOL tool() const; void clearTextSelection(); + int gridSize() const; + void addNewItem(QGraphicsItem *item); public slots: void slotUpdateFontSize(int s); + void slotUseGrid(bool enableGrid); protected: virtual void keyPressEvent(QKeyEvent * keyEvent); virtual void mousePressEvent(QGraphicsSceneMouseEvent*); virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent*); virtual void mouseDoubleClickEvent(QGraphicsSceneMouseEvent* e); /** @brief Resizes and moves items */ virtual void mouseMoveEvent(QGraphicsSceneMouseEvent*); virtual void wheelEvent(QGraphicsSceneWheelEvent * wheelEvent); + void drawForeground(QPainter *painter, const QRectF &rect); private: void setCursor(QCursor); double m_zoom; QGraphicsItem* m_selectedItem; resizeModes m_resizeMode; resizeModes m_possibleAction; QPointF m_sceneClickPoint; TITLETOOL m_tool; QPointF m_clickPoint; int m_fontSize; + int m_gridSize; + bool m_createdText; signals: void itemMoved(); void sceneZoom(bool); void newRect(QGraphicsRectItem *); void newText(MyTextItem *); void actionFinished(); void doubleClickEvent(); }; #endif diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 2a8907ced..392f3ba6a 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1,3447 +1,3453 @@ /*************************************************************************** * 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 "mainwindowadaptor.h" #include "core.h" #include "bin/projectclip.h" #include "bin/generators/generators.h" #include "library/librarywidget.h" #include "monitor/scopes/audiographspectrum.h" #include "mltcontroller/clipcontroller.h" #include "kdenlivesettings.h" #include "dialogs/kdenlivesettingsdialog.h" #include "dialogs/clipcreationdialog.h" #include "effectslist/initeffects.h" #include "project/dialogs/projectsettings.h" #include "project/clipmanager.h" #include "monitor/monitor.h" #include "monitor/recmonitor.h" #include "monitor/monitormanager.h" #include "doc/kdenlivedoc.h" #include "timeline/timeline.h" #include "timeline/track.h" #include "timeline/customtrackview.h" #include "effectslist/effectslistview.h" #include "effectslist/effectbasket.h" #include "effectstack/effectstackview2.h" #include "project/transitionsettings.h" #include "mltcontroller/bincontroller.h" #include "mltcontroller/producerqueue.h" #include "dialogs/renderwidget.h" #include "renderer.h" #include "dialogs/wizard.h" #include "project/projectcommands.h" #include "titler/titlewidget.h" #include "timeline/markerdialog.h" #include "timeline/clipitem.h" #include "interfaces.h" #include "project/cliptranscode.h" #include "scopes/scopemanager.h" #include "project/dialogs/archivewidget.h" #include "utils/resourcewidget.h" #include "layoutmanagement.h" #include "hidetitlebars.h" #include "mltconnection.h" #include "project/projectmanager.h" #include "timeline/timelinesearch.h" #include #include "utils/thememanager.h" #include "utils/KoIconUtils.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 #include #include #include #include #include #include #include #include #include #include #include #include #include static const char version[] = KDENLIVE_VERSION; namespace Mlt { class Producer; }; EffectsList MainWindow::videoEffects; EffectsList MainWindow::audioEffects; EffectsList MainWindow::customEffects; EffectsList MainWindow::transitions; QMap MainWindow::m_lumacache; /*static bool sortByNames(const QPair &a, const QPair &b) { return a.first < b.first; }*/ // determine the the default KDE style as defined BY THE USER // (as opposed to whatever style KDE considers default) static QString defaultStyle(const char *fallback=Q_NULLPTR) { KSharedConfigPtr kdeGlobals = KSharedConfig::openConfig(QStringLiteral("kdeglobals"), KConfig::NoGlobals); KConfigGroup cg(kdeGlobals, "KDE"); return cg.readEntry("widgetStyle", fallback); } MainWindow::MainWindow(const QString &MltPath, const QUrl &Url, const QString & clipsToLoad, QWidget *parent) : KXmlGuiWindow(parent), m_timelineArea(NULL), m_stopmotion(NULL), m_effectStack(NULL), m_exitCode(EXIT_SUCCESS), m_effectList(NULL), m_transitionList(NULL), m_clipMonitor(NULL), m_projectMonitor(NULL), m_recMonitor(NULL), m_renderWidget(NULL), m_themeInitialized(false), m_isDarkTheme(false) { qRegisterMetaType ("audioShortVector"); qRegisterMetaType< QVector > ("QVector"); qRegisterMetaType ("MessageType"); qRegisterMetaType ("stringMap"); qRegisterMetaType ("audioByteArray"); qRegisterMetaType< QVector > (); qRegisterMetaType ("QDomElement"); qRegisterMetaType ("requestClipInfo"); qRegisterMetaType ("MltVideoProfile"); Core::build(this); // Widget themes for non KDE users KActionMenu *stylesAction= new KActionMenu(i18n("Style"), this); QActionGroup *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(); QString desktopStyle = QApplication::style()->objectName(); if (QString::compare(desktopStyle, QLatin1String("GTK+"), Qt::CaseInsensitive) == 0 && KdenliveSettings::widgetstyle().isEmpty()) { if (availableStyles.contains(QLatin1String("breeze"), Qt::CaseInsensitive)) { // Auto switch to Breeze theme KdenliveSettings::setWidgetstyle(QStringLiteral("Breeze")); } else if (availableStyles.contains(QLatin1String("fusion"), Qt::CaseInsensitive)) { KdenliveSettings::setWidgetstyle(QStringLiteral("Fusion")); } } // Add default style action QAction *defaultStyle = new QAction(i18n("Default"), stylesGroup); defaultStyle->setCheckable(true); stylesAction->addAction(defaultStyle); if (KdenliveSettings::widgetstyle().isEmpty()) { defaultStyle->setChecked(true); } foreach(const QString &style, availableStyles) { QAction *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); // Color schemes KActionMenu *themeAction= new KActionMenu(i18n("Theme"), this); ThemeManager::instance()->setThemeMenuAction(themeAction); ThemeManager::instance()->setCurrentTheme(KdenliveSettings::colortheme()); connect(ThemeManager::instance(), SIGNAL(signalThemeChanged(const QString &)), this, SLOT(slotThemeChanged(const QString &)), Qt::DirectConnection); if (!KdenliveSettings::widgetstyle().isEmpty() && QString::compare(desktopStyle, KdenliveSettings::widgetstyle(), Qt::CaseInsensitive) != 0) { // User wants a custom widget style, init doChangeStyle(); } else ThemeManager::instance()->slotChangePalette(); new RenderingAdaptor(this); pCore->initialize(); MltConnection::locateMeltAndProfilesPath(MltPath); KdenliveSettings::setCurrent_profile(KdenliveSettings::default_profile()); // If using a custom profile, make sure the file exists or fallback to default if (KdenliveSettings::current_profile().startsWith(QStringLiteral("/")) && !QFile::exists(KdenliveSettings::current_profile())) { KMessageBox::sorry(this, i18n("Cannot find your default profile, switching to ATSC 1080p 25")); KdenliveSettings::setCurrent_profile("atsc_1080p_25"); KdenliveSettings::setDefault_profile("atsc_1080p_25"); } m_commandStack = new QUndoGroup; m_timelineArea = new QTabWidget(this); //m_timelineArea->setTabReorderingEnabled(true); m_timelineArea->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); m_timelineArea->setMinimumHeight(200); // Hide tabbar QTabBar *bar = m_timelineArea->findChild(); bar->setHidden(true); m_gpuAllowed = initEffects::parseEffectFiles(pCore->binController()->mltRepository()); //initEffects::parseCustomEffectsFile(); m_shortcutRemoveFocus = new QShortcut(QKeySequence(QStringLiteral("Esc")), this); connect(m_shortcutRemoveFocus, SIGNAL(activated()), this, SLOT(slotRemoveFocus())); /// Add Widgets setDockNestingEnabled(true); setCentralWidget(m_timelineArea); m_projectBinDock = addDock(i18n("Project Bin"), QStringLiteral("project_bin"), pCore->bin()); 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(), SIGNAL(clipNeedsReload(QString,bool)),this, SLOT(slotUpdateClip(QString,bool))); connect(pCore->bin(), SIGNAL(findInTimeline(QString)), this, SLOT(slotClipInTimeline(QString))); //TODO deprecated, replace with Bin methods if necessary /*connect(m_projectList, SIGNAL(loadingIsOver()), this, SLOT(slotElapsedTime())); connect(m_projectList, SIGNAL(displayMessage(QString,int,MessageType)), this, SLOT(slotGotProgressInfo(QString,int,MessageType))); 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)));*/ connect(m_clipMonitor, SIGNAL(extractZone(QString)), pCore->bin(), SLOT(slotStartCutJob(QString))); connect(m_clipMonitor, SIGNAL(passKeyPress(QKeyEvent*)), this, SLOT(triggerKey(QKeyEvent*))); m_projectMonitor = new Monitor(Kdenlive::ProjectMonitor, pCore->monitorManager(), this); connect(m_projectMonitor, SIGNAL(passKeyPress(QKeyEvent*)), this, SLOT(triggerKey(QKeyEvent*))); connect(m_projectMonitor, &Monitor::addMarker, this, &MainWindow::slotAddMarkerGuideQuickly); connect(m_projectMonitor, &Monitor::deleteMarker, this, &MainWindow::slotDeleteClipMarker); connect(m_projectMonitor, &Monitor::seekToPreviousSnap, this, &MainWindow::slotSnapRewind); connect(m_projectMonitor, &Monitor::seekToNextSnap, this, &MainWindow::slotSnapForward); connect(m_projectMonitor, SIGNAL(updateGuide(int, QString)), this, SLOT(slotEditGuide(int, QString))); /* //TODO disabled until ported to qml m_recMonitor = new RecMonitor(Kdenlive::RecordMonitor, pCore->monitorManager(), this); connect(m_recMonitor, SIGNAL(addProjectClip(QUrl)), this, SLOT(slotAddProjectClip(QUrl))); connect(m_recMonitor, SIGNAL(addProjectClipList(QList)), this, SLOT(slotAddProjectClipList(QList))); */ pCore->monitorManager()->initMonitors(m_clipMonitor, m_projectMonitor, m_recMonitor); connect(m_clipMonitor, SIGNAL(addMasterEffect(QString,QDomElement)), pCore->bin(), SLOT(slotEffectDropped(QString,QDomElement))); // Audio spectrum scope m_audioSpectrum = new AudioGraphSpectrum(pCore->monitorManager()); QDockWidget * spectrumDock = addDock(i18n("Audio Spectrum"), QStringLiteral("audiospectrum"), m_audioSpectrum); connect(this, &MainWindow::reloadTheme, m_audioSpectrum, &AudioGraphSpectrum::refreshPixmap); // Close library and audiospectrum on first run libraryDock->close(); spectrumDock->close(); m_effectStack = new EffectStackView2(m_projectMonitor, this); connect(m_effectStack, SIGNAL(startFilterJob(const ItemInfo&,const QString&,QMap&,QMap&,QMap&)), pCore->bin(), SLOT(slotStartFilterJob(const ItemInfo &,const QString&,QMap&,QMap&,QMap&))); connect(pCore->bin(), SIGNAL(masterClipSelected(ClipController *, Monitor *)), m_effectStack, SLOT(slotMasterClipItemSelected(ClipController *, Monitor *))); connect(pCore->bin(), SIGNAL(masterClipUpdated(ClipController *, Monitor *)), m_effectStack, SLOT(slotRefreshMasterClipEffects(ClipController *, Monitor *))); connect(m_effectStack, SIGNAL(addMasterEffect(QString,QDomElement)), pCore->bin(), SLOT(slotEffectDropped(QString,QDomElement))); connect(m_effectStack, SIGNAL(removeMasterEffect(QString,QDomElement)), pCore->bin(), SLOT(slotDeleteEffect(QString,QDomElement))); connect(m_effectStack, SIGNAL(reloadEffects()), this, SLOT(slotReloadEffects())); connect(m_effectStack, SIGNAL(displayMessage(QString,int)), this, SLOT(slotGotProgressInfo(QString,int))); m_effectStackDock = addDock(i18n("Properties"), QStringLiteral("effect_stack"), m_effectStack); m_effectList = new EffectsListView(); m_effectListDock = addDock(i18n("Effects"), QStringLiteral("effect_list"), m_effectList); m_transitionList = new EffectsListView(EffectsListView::TransitionMode); m_transitionListDock = addDock(i18n("Transitions"), QStringLiteral("transition_list"), m_transitionList); // 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); if (m_recMonitor) { m_recMonitorDock = addDock(i18n("Record Monitor"), QStringLiteral("record_monitor"), m_recMonitor); } m_undoView = new QUndoView(); m_undoView->setCleanIcon(KoIconUtils::themedIcon(QStringLiteral("edit-clear"))); m_undoView->setEmptyLabel(i18n("Clean")); m_undoView->setGroup(m_commandStack); m_undoViewDock = addDock(i18n("Undo History"), QStringLiteral("undo_history"), m_undoView); setupActions(); // Color and icon theme stuff addAction(QStringLiteral("themes_menu"), themeAction); connect(m_commandStack, SIGNAL(cleanChanged(bool)), m_saveAction, SLOT(setDisabled(bool))); addAction(QStringLiteral("styles_menu"), stylesAction); // Close non-general docks for the initial layout // only show important ones m_undoViewDock->close(); /// Tabify Widgets /// tabifyDockWidget(m_transitionListDock, m_effectListDock); tabifyDockWidget(m_effectListDock, m_effectStackDock); tabifyDockWidget(m_clipMonitorDock, m_projectMonitorDock); if (m_recMonitor) { tabifyDockWidget(m_clipMonitorDock, m_recMonitorDock); } readOptions(); QAction *action; // Stop motion actions. Beware of the order, we MUST use the same order in stopmotion/stopmotion.cpp m_stopmotion_actions = new KActionCategory(i18n("Stop Motion"), actionCollection()); action = new QAction(KoIconUtils::themedIcon(QStringLiteral("media-record")), i18n("Capture frame"), this); //action->setShortcutContext(Qt::WidgetWithChildrenShortcut); m_stopmotion_actions->addAction(QStringLiteral("stopmotion_capture"), action); action = new QAction(i18n("Switch live / captured frame"), this); //action->setShortcutContext(Qt::WidgetWithChildrenShortcut); m_stopmotion_actions->addAction(QStringLiteral("stopmotion_switch"), action); action = new QAction(KoIconUtils::themedIcon(QStringLiteral("edit-paste")), i18n("Show last frame over video"), this); action->setCheckable(true); action->setChecked(false); m_stopmotion_actions->addAction(QStringLiteral("stopmotion_overlay"), action); // 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_effectList->reloadEffectList(m_effectsMenu, m_effectActions); m_transitionsMenu = new QMenu(i18n("Add Transition"), this); m_transitionActions = new KActionCategory(i18n("Transitions"), actionCollection()); m_transitionList->reloadEffectList(m_transitionsMenu, m_transitionActions); ScopeManager *scmanager = new ScopeManager(this); new LayoutManagement(this); new HideTitleBars(this); new TimelineSearch(this); m_extraFactory = new KXMLGUIClient(this); buildDynamicActions(); // Add shortcut to action tooltips QList< KActionCollection * > collections = KActionCollection::allCollections(); for (int i = 0; i < collections.count(); ++i) { KActionCollection *coll = collections.at(i); foreach( QAction* tempAction, coll->actions()) { // find the shortcut pattern and delete (note the preceding space in the RegEx) QString strippedTooltip = tempAction->toolTip().remove(QRegExp("\\s\\(.*\\)")); // append shortcut if it exists for action if (tempAction->shortcut() == QKeySequence(0)) tempAction->setToolTip( strippedTooltip); else tempAction->setToolTip( strippedTooltip + " (" + tempAction->shortcut().toString() + ")"); } } // Create Effect Basket (dropdown list of favorites) m_effectBasket = new EffectBasket(m_effectList); connect(m_effectBasket, SIGNAL(addEffect(QDomElement)), this, SLOT(slotAddEffect(QDomElement))); QWidgetAction *widgetlist = new QWidgetAction(this); widgetlist->setDefaultWidget(m_effectBasket); widgetlist->setText(i18n("Favorite Effects")); widgetlist->setToolTip(i18n("Favorite Effects")); widgetlist->setIcon(KoIconUtils::themedIcon("favorite")); QMenu *menu = new QMenu(this); menu->addAction(widgetlist); QToolButton *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(KoIconUtils::themedIcon(QStringLiteral("favorite"))); QWidgetAction* toolButtonAction = new QWidgetAction(this); toolButtonAction->setText(i18n("Favorite Effects")); toolButtonAction->setIcon(KoIconUtils::themedIcon("favorite")); toolButtonAction->setDefaultWidget(basketButton); addAction(QStringLiteral("favorite_effects"), toolButtonAction); connect(toolButtonAction, SIGNAL(triggered(bool)), basketButton, SLOT(showMenu())); setupGUI(); /*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, SIGNAL(triggered(QAction*)), this, SLOT(slotSwitchMonitorOverlay(QAction*))); m_projectMonitor->setupMenu(static_cast(factory()->container(QStringLiteral("monitor_go"), this)), monitorOverlay, m_playZone, m_loopZone, NULL, 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(KoIconUtils::themedIcon(QStringLiteral("go-jump"))); pCore->bin()->setupGeneratorMenu(); connect(pCore->monitorManager(), SIGNAL(updateOverlayInfos(int,int)), this, SLOT(slotUpdateMonitorOverlays(int,int))); // Setup and fill effects and transitions menus. QMenu *m = static_cast(factory()->container(QStringLiteral("video_effects_menu"), this)); connect(m, SIGNAL(triggered(QAction*)), this, SLOT(slotAddVideoEffect(QAction*))); connect(m_effectsMenu, SIGNAL(triggered(QAction*)), this, SLOT(slotAddVideoEffect(QAction*))); connect(m_transitionsMenu, SIGNAL(triggered(QAction*)), this, SLOT(slotAddTransition(QAction*))); m_timelineContextMenu = new QMenu(this); m_timelineContextClipMenu = new QMenu(this); m_timelineContextTransitionMenu = new QMenu(this); m_timelineContextMenu->addAction(actionCollection()->action(QStringLiteral("insert_space"))); m_timelineContextMenu->addAction(actionCollection()->action(QStringLiteral("delete_space"))); m_timelineContextMenu->addAction(actionCollection()->action(KStandardAction::name(KStandardAction::Paste))); m_timelineContextClipMenu->addAction(actionCollection()->action(QStringLiteral("clip_in_project_tree"))); //m_timelineContextClipMenu->addAction(actionCollection()->action("clip_to_project_tree")); m_timelineContextClipMenu->addAction(actionCollection()->action(QStringLiteral("delete_timeline_clip"))); m_timelineContextClipMenu->addSeparator(); m_timelineContextClipMenu->addAction(actionCollection()->action(QStringLiteral("group_clip"))); m_timelineContextClipMenu->addAction(actionCollection()->action(QStringLiteral("ungroup_clip"))); m_timelineContextClipMenu->addAction(actionCollection()->action(QStringLiteral("split_audio"))); m_timelineContextClipMenu->addAction(actionCollection()->action(QStringLiteral("set_audio_align_ref"))); m_timelineContextClipMenu->addAction(actionCollection()->action(QStringLiteral("align_audio"))); m_timelineContextClipMenu->addSeparator(); m_timelineContextClipMenu->addAction(actionCollection()->action(QStringLiteral("cut_timeline_clip"))); m_timelineContextClipMenu->addAction(actionCollection()->action(KStandardAction::name(KStandardAction::Copy))); m_timelineContextClipMenu->addAction(actionCollection()->action(QStringLiteral("paste_effects"))); m_timelineContextClipMenu->addSeparator(); QMenu *markersMenu = static_cast(factory()->container(QStringLiteral("marker_menu"), this)); m_timelineContextClipMenu->addMenu(markersMenu); m_timelineContextClipMenu->addSeparator(); m_timelineContextClipMenu->addMenu(m_transitionsMenu); m_timelineContextClipMenu->addMenu(m_effectsMenu); m_timelineContextTransitionMenu->addAction(actionCollection()->action(QStringLiteral("delete_timeline_clip"))); m_timelineContextTransitionMenu->addAction(actionCollection()->action(KStandardAction::name(KStandardAction::Copy))); m_timelineContextTransitionMenu->addAction(actionCollection()->action(QStringLiteral("auto_transition"))); connect(m_effectList, SIGNAL(addEffect(QDomElement)), this, SLOT(slotAddEffect(QDomElement))); connect(m_effectList, SIGNAL(reloadEffects()), this, SLOT(slotReloadEffects())); slotConnectMonitors(); // Populate encoding profiles KConfig conf(QStringLiteral("encodingprofiles.rc"), KConfig::CascadeConfig, QStandardPaths::DataLocation); if (KdenliveSettings::proxyparams().isEmpty() || KdenliveSettings::proxyextension().isEmpty()) { KConfigGroup group(&conf, "proxy"); QMap< QString, QString > values = group.entryMap(); QMapIterator i(values); if (i.hasNext()) { i.next(); QString data = i.value(); KdenliveSettings::setProxyparams(data.section(';', 0, 0)); KdenliveSettings::setProxyextension(data.section(';', 1, 1)); } } if (KdenliveSettings::v4l_parameters().isEmpty() || KdenliveSettings::v4l_extension().isEmpty()) { KConfigGroup group(&conf, "video4linux"); QMap< QString, QString > values = group.entryMap(); QMapIterator i(values); if (i.hasNext()) { i.next(); QString data = i.value(); KdenliveSettings::setV4l_parameters(data.section(';', 0, 0)); KdenliveSettings::setV4l_extension(data.section(';', 1, 1)); } } if (KdenliveSettings::grab_parameters().isEmpty() || KdenliveSettings::grab_extension().isEmpty()) { KConfigGroup group(&conf, "screengrab"); QMap< QString, QString > values = group.entryMap(); QMapIterator i(values); if (i.hasNext()) { i.next(); QString data = i.value(); KdenliveSettings::setGrab_parameters(data.section(';', 0, 0)); KdenliveSettings::setGrab_extension(data.section(';', 1, 1)); } } if (KdenliveSettings::decklink_parameters().isEmpty() || KdenliveSettings::decklink_extension().isEmpty()) { KConfigGroup group(&conf, "decklink"); QMap< QString, QString > values = group.entryMap(); QMapIterator i(values); if (i.hasNext()) { i.next(); QString data = i.value(); KdenliveSettings::setDecklink_parameters(data.section(';', 0, 0)); KdenliveSettings::setDecklink_extension(data.section(';', 1, 1)); } } emit GUISetupDone(); pCore->projectManager()->init(Url, clipsToLoad); QTimer::singleShot(0, pCore->projectManager(), SLOT(slotLoadOnOpen())); connect(this, SIGNAL(reloadTheme()), this, SLOT(slotReloadTheme()), Qt::UniqueConnection); #ifdef USE_JOGSHUTTLE new JogManager(this); #endif scmanager->slotCheckActiveScopes(); //KMessageBox::information(this, "Warning, development version for testing only. we are currently working on core functionnalities,\ndo not save any project or your project files might be corrupted."); } void MainWindow::slotThemeChanged(const QString &theme) { disconnect(this, SIGNAL(reloadTheme()), this, SLOT(slotReloadTheme())); KSharedConfigPtr config = KSharedConfig::openConfig(theme); setPalette(KColorScheme::createApplicationPalette(config)); qApp->setPalette(palette()); QPalette plt = palette(); KdenliveSettings::setColortheme(theme); if (m_effectStack) { m_effectStack->updatePalette(); m_effectStack->transitionConfig()->updatePalette(); } if (m_effectList) m_effectList->updatePalette(); if (m_transitionList) m_transitionList->updatePalette(); if (m_clipMonitor) m_clipMonitor->setPalette(plt); if (m_projectMonitor) m_projectMonitor->setPalette(plt); setStatusBarStyleSheet(plt); if (pCore->projectManager() && pCore->projectManager()->currentTimeline()) { pCore->projectManager()->currentTimeline()->updatePalette(); } if (m_timelineArea) { m_timelineArea->setPalette(plt); } if (statusBar()) { const QObjectList children = statusBar()->children(); foreach(QObject * child, children) { if (child->isWidgetType()) ((QWidget*)child)->setPalette(plt); const QObjectList subchildren = child->children(); foreach(QObject * subchild, subchildren) { if (subchild->isWidgetType()) ((QWidget*)subchild)->setPalette(plt); } } } 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(); if (m_effectStack && m_effectStack->transitionConfig()) m_effectStack->transitionConfig()->refreshIcons(); if (m_effectList) m_effectList->refreshIcons(); if (m_effectStack) m_effectStack->refreshIcons(); if (pCore->projectManager() && pCore->projectManager()->currentTimeline()) { pCore->projectManager()->currentTimeline()->refreshIcons(); } foreach(QAction* action, actionCollection()->actions()) { QIcon icon = action->icon(); if (icon.isNull()) continue; QString iconName = icon.name(); QIcon newIcon = KoIconUtils::themedIcon(iconName); if (newIcon.isNull()) continue; action->setIcon(newIcon); } } m_themeInitialized = true; m_isDarkTheme = useDarkIcons; connect(this, SIGNAL(reloadTheme()), this, SLOT(slotReloadTheme()), Qt::UniqueConnection); } bool MainWindow::event(QEvent *e) { switch (e->type()) { case QEvent::ApplicationPaletteChange: emit reloadTheme(); e->accept(); break; default: break; } return KXmlGuiWindow::event(e); } void MainWindow::slotReloadTheme() { ThemeManager::instance()->slotSettingsChanged(); } MainWindow::~MainWindow() { delete m_stopmotion; delete m_audioSpectrum; m_effectStack->slotClipItemSelected(NULL, m_projectMonitor); m_effectStack->slotTransitionItemSelected(NULL, 0, QPoint(), false); if (m_projectMonitor) m_projectMonitor->stop(); if (m_clipMonitor) m_clipMonitor->stop(); delete pCore; delete m_effectStack; delete m_projectMonitor; delete m_clipMonitor; delete m_shortcutRemoveFocus; qDeleteAll(m_transitions); Mlt::Factory::close(); } //virtual bool MainWindow::queryClose() { //TODO: use this function? 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() == false) 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, SIGNAL(triggered(QAction *)), this, SLOT(buildGenerator(QAction *))); } 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::addToMenu(QObject *plugin, const QStringList &texts, QMenu *menu, const char *member, QActionGroup *actionGroup) { //qDebug() << "// ADD to MENU" << texts; foreach(const QString & text, texts) { QAction *action = new QAction(text, plugin); action->setData(text); connect(action, SIGNAL(triggered()), this, member); menu->addAction(action); if (actionGroup) { action->setCheckable(true); actionGroup->addAction(action); } } } void MainWindow::generateClip() { QAction *action = qobject_cast(sender()); ClipGenerator *iGenerator = qobject_cast(action->parent()); KdenliveDoc *project = pCore->projectManager()->current(); QUrl clipUrl = iGenerator->generatedClip(KdenliveSettings::rendererpath(), action->data().toString(), project->projectFolder(), QStringList(), QStringList(), project->fps(), project->width(), project->height()); if (clipUrl.isValid()) { pCore->bin()->droppedUrls(QList () << clipUrl); } } void MainWindow::saveProperties(KConfigGroup &config) { // save properties here KXmlGuiWindow::saveProperties(config); //TODO: fix session management if (qApp->isSavingSession()) { if (pCore->projectManager()->current() && !pCore->projectManager()->current()->url().isEmpty()) { config.writeEntry("kdenlive_lastUrl", pCore->projectManager()->current()->url().path()); } } } 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(); } void MainWindow::slotReloadEffects() { initEffects::parseCustomEffectsFile(); m_effectList->reloadEffectList(m_effectsMenu, m_effectActions); } void MainWindow::configureNotifications() { KNotifyConfigWidget::configure(this); } void MainWindow::slotFullScreen() { KToggleFullScreenAction::setFullScreen(this, actionCollection()->action(QStringLiteral("fullscreen"))->isChecked()); } void MainWindow::slotAddEffect(const QDomElement &effect) { if (effect.isNull()) { //qDebug() << "--- ERROR, TRYING TO APPEND NULL EFFECT"; return; } QDomElement effectToAdd = effect.cloneNode().toElement(); EFFECTMODE status = m_effectStack->effectStatus(); if (status == TIMELINE_TRACK) { pCore->projectManager()->currentTimeline()->projectView()->slotAddTrackEffect(effectToAdd, pCore->projectManager()->currentTimeline()->tracksCount() - m_effectStack->trackIndex()); } else if (status == TIMELINE_CLIP) { pCore->projectManager()->currentTimeline()->projectView()->slotAddEffectToCurrentItem(effectToAdd); } else if (status == MASTER_CLIP) { pCore->bin()->slotEffectDropped(QString(), effectToAdd); } } void MainWindow::slotUpdateClip(const QString &id, bool reload) { ProjectClip *clip = pCore->bin()->getBinClip(id); if (!clip) { return; } pCore->projectManager()->currentTimeline()->projectView()->slotUpdateClip(id, reload); } void MainWindow::slotConnectMonitors() { //connect(m_projectList, SIGNAL(deleteProjectClips(QStringList,QMap)), this, SLOT(slotDeleteProjectClips(QStringList,QMap))); connect(m_projectMonitor->render, SIGNAL(gotFileProperties(requestClipInfo,ClipController *)), pCore->bin(), SLOT(slotProducerReady(requestClipInfo,ClipController *)), Qt::DirectConnection); connect(m_clipMonitor, SIGNAL(refreshClipThumbnail(QString)), pCore->bin(), SLOT(slotRefreshClipThumbnail(QString))); connect(m_projectMonitor, SIGNAL(requestFrameForAnalysis(bool)), this, SLOT(slotMonitorRequestRenderFrame(bool))); connect(m_projectMonitor, &Monitor::createSplitOverlay, this, &MainWindow::createSplitOverlay); connect(m_projectMonitor, &Monitor::removeSplitOverlay, this, &MainWindow::removeSplitOverlay); } void MainWindow::createSplitOverlay(Mlt::Filter *filter) { if (pCore->projectManager()->currentTimeline()) { if (pCore->projectManager()->currentTimeline()->projectView()->createSplitOverlay(filter)) { m_projectMonitor->activateSplit(); } } } void MainWindow::removeSplitOverlay() { if (pCore->projectManager()->currentTimeline()) { pCore->projectManager()->currentTimeline()->projectView()->removeSplitOverlay(); } } void MainWindow::addAction(const QString &name, QAction *action) { m_actionNames.append(name); actionCollection()->addAction(name, action); actionCollection()->setDefaultShortcut(action, action->shortcut()); //Fix warning about setDefaultShortcut } QAction *MainWindow::addAction(const QString &name, const QString &text, const QObject *receiver, const char *member, const QIcon &icon, const QKeySequence &shortcut) { QAction *action = new QAction(text, this); if (!icon.isNull()) { action->setIcon(icon); } if (!shortcut.isEmpty()) { action->setShortcut(shortcut); } addAction(name, action); connect(action, SIGNAL(triggered(bool)), receiver, member); return action; } void MainWindow::setupActions() { m_statusProgressBar = new QProgressBar(this); m_statusProgressBar->setMinimum(0); m_statusProgressBar->setMaximum(100); m_statusProgressBar->setMaximumWidth(150); m_statusProgressBar->setVisible(false); KToolBar *toolbar = new KToolBar(QStringLiteral("statusToolBar"), this, Qt::BottomToolBarArea); toolbar->setMovable(false); setStatusBarStyleSheet(palette()); QString styleBorderless = QStringLiteral("QToolButton { border-width: 0px;margin: 1px 3px 0px;padding: 0px;}"); //create edit mode buttons m_normalEditTool = new QAction(KoIconUtils::themedIcon(QStringLiteral("kdenlive-normal-edit")), i18n("Normal mode"), this); m_normalEditTool->setShortcut(i18nc("Normal editing", "n")); toolbar->addAction(m_normalEditTool); m_normalEditTool->setCheckable(true); m_normalEditTool->setChecked(true); m_overwriteEditTool = new QAction(KoIconUtils::themedIcon(QStringLiteral("kdenlive-overwrite-edit")), i18n("Overwrite mode"), this); //m_overwriteEditTool->setShortcut(i18nc("Overwrite mode shortcut", "o")); toolbar->addAction(m_overwriteEditTool); m_overwriteEditTool->setCheckable(true); m_overwriteEditTool->setChecked(false); m_insertEditTool = new QAction(KoIconUtils::themedIcon(QStringLiteral("kdenlive-insert-edit")), i18n("Insert mode"), this); //m_insertEditTool->setShortcut(i18nc("Insert mode shortcut", "i")); toolbar->addAction(m_insertEditTool); m_insertEditTool->setCheckable(true); m_insertEditTool->setChecked(false); // not implemented yet m_insertEditTool->setEnabled(false); QActionGroup *editGroup = new QActionGroup(this); editGroup->addAction(m_normalEditTool); editGroup->addAction(m_overwriteEditTool); editGroup->addAction(m_insertEditTool); editGroup->setExclusive(true); connect(editGroup, SIGNAL(triggered(QAction*)), this, SLOT(slotChangeEdit(QAction*))); //connect(m_overwriteEditTool, SIGNAL(toggled(bool)), this, SLOT(slotSetOverwriteMode(bool))); toolbar->addSeparator(); // create tools buttons m_buttonSelectTool = new QAction(KoIconUtils::themedIcon(QStringLiteral("cursor-arrow")), i18n("Selection tool"), this); m_buttonSelectTool->setShortcut(i18nc("Selection tool shortcut", "s")); toolbar->addAction(m_buttonSelectTool); m_buttonSelectTool->setCheckable(true); m_buttonSelectTool->setChecked(true); m_buttonRazorTool = new QAction(KoIconUtils::themedIcon(QStringLiteral("edit-cut")), i18n("Razor tool"), this); m_buttonRazorTool->setShortcut(i18nc("Razor tool shortcut", "x")); toolbar->addAction(m_buttonRazorTool); m_buttonRazorTool->setCheckable(true); m_buttonRazorTool->setChecked(false); m_buttonSpacerTool = new QAction(KoIconUtils::themedIcon(QStringLiteral("distribute-horizontal-x")), i18n("Spacer tool"), this); m_buttonSpacerTool->setShortcut(i18nc("Spacer tool shortcut", "m")); toolbar->addAction(m_buttonSpacerTool); m_buttonSpacerTool->setCheckable(true); m_buttonSpacerTool->setChecked(false); QActionGroup *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, SIGNAL(triggered(QAction*)), this, SLOT(slotChangeTool(QAction*))); toolbar->addSeparator(); QIcon ic = KoIconUtils::themedIcon(QStringLiteral("zoom-fit-best")); m_buttonFitZoom = new QAction(ic, i18n("Fit zoom to project"), this); toolbar->addAction(m_buttonFitZoom); m_buttonFitZoom->setCheckable(false); m_zoomOut = new QAction(KoIconUtils::themedIcon(QStringLiteral("zoom-out")), i18n("Zoom Out"), this); toolbar->addAction(m_zoomOut); m_zoomOut->setShortcut(Qt::CTRL + Qt::Key_Minus); m_zoomSlider = new QSlider(Qt::Horizontal, this); m_zoomSlider->setMaximum(13); m_zoomSlider->setPageStep(1); m_zoomSlider->setInvertedAppearance(true); m_zoomSlider->setInvertedControls(true); m_zoomSlider->setMaximumWidth(150); m_zoomSlider->setMinimumWidth(100); toolbar->addWidget(m_zoomSlider); m_zoomIn = new QAction(KoIconUtils::themedIcon(QStringLiteral("zoom-in")), i18n("Zoom In"), this); toolbar->addAction(m_zoomIn); m_zoomIn->setShortcut(Qt::CTRL + Qt::Key_Plus); actionWidget = toolbar->widgetForAction(m_buttonFitZoom); actionWidget->setMaximumWidth(max); actionWidget->setMaximumHeight(max - 4); actionWidget->setStyleSheet(styleBorderless); actionWidget = toolbar->widgetForAction(m_zoomIn); actionWidget->setMaximumWidth(max); actionWidget->setMaximumHeight(max - 4); actionWidget->setStyleSheet(styleBorderless); actionWidget = toolbar->widgetForAction(m_zoomOut); actionWidget->setMaximumWidth(max); actionWidget->setMaximumHeight(max - 4); actionWidget->setStyleSheet(styleBorderless); connect(m_zoomSlider, SIGNAL(valueChanged(int)), this, SLOT(slotSetZoom(int))); connect(m_zoomSlider, SIGNAL(sliderMoved(int)), this, SLOT(slotShowZoomSliderToolTip(int))); connect(m_buttonFitZoom, SIGNAL(triggered()), this, SLOT(slotFitZoom())); connect(m_zoomIn, SIGNAL(triggered(bool)), this, SLOT(slotZoomIn())); connect(m_zoomOut, SIGNAL(triggered(bool)), this, SLOT(slotZoomOut())); toolbar->addSeparator(); //create automatic audio split button m_buttonAutomaticSplitAudio = new QAction(KoIconUtils::themedIcon(QStringLiteral("kdenlive-split-audio")), i18n("Split audio and video automatically"), this); toolbar->addAction(m_buttonAutomaticSplitAudio); m_buttonAutomaticSplitAudio->setCheckable(true); m_buttonAutomaticSplitAudio->setChecked(KdenliveSettings::splitaudio()); connect(m_buttonAutomaticSplitAudio, SIGNAL(triggered()), this, SLOT(slotSwitchSplitAudio())); m_buttonVideoThumbs = new QAction(KoIconUtils::themedIcon(QStringLiteral("kdenlive-show-videothumb")), i18n("Show video thumbnails"), this); toolbar->addAction(m_buttonVideoThumbs); m_buttonVideoThumbs->setCheckable(true); m_buttonVideoThumbs->setChecked(KdenliveSettings::videothumbnails()); connect(m_buttonVideoThumbs, SIGNAL(triggered()), this, SLOT(slotSwitchVideoThumbs())); m_buttonAudioThumbs = new QAction(KoIconUtils::themedIcon(QStringLiteral("kdenlive-show-audiothumb")), i18n("Show audio thumbnails"), this); toolbar->addAction(m_buttonAudioThumbs); m_buttonAudioThumbs->setCheckable(true); m_buttonAudioThumbs->setChecked(KdenliveSettings::audiothumbnails()); connect(m_buttonAudioThumbs, SIGNAL(triggered()), this, SLOT(slotSwitchAudioThumbs())); m_buttonShowMarkers = new QAction(KoIconUtils::themedIcon(QStringLiteral("kdenlive-show-markers")), i18n("Show markers comments"), this); toolbar->addAction(m_buttonShowMarkers); m_buttonShowMarkers->setCheckable(true); m_buttonShowMarkers->setChecked(KdenliveSettings::showmarkers()); connect(m_buttonShowMarkers, SIGNAL(triggered()), this, SLOT(slotSwitchMarkersComments())); m_buttonSnap = new QAction(KoIconUtils::themedIcon(QStringLiteral("kdenlive-snap")), i18n("Snap"), this); toolbar->addAction(m_buttonSnap); m_buttonSnap->setCheckable(true); m_buttonSnap->setChecked(KdenliveSettings::snaptopoints()); connect(m_buttonSnap, SIGNAL(triggered()), this, SLOT(slotSwitchSnap())); actionWidget = toolbar->widgetForAction(m_buttonAutomaticSplitAudio); actionWidget->setMaximumWidth(max); actionWidget->setMaximumHeight(max - 4); actionWidget = toolbar->widgetForAction(m_buttonVideoThumbs); actionWidget->setMaximumWidth(max); actionWidget->setMaximumHeight(max - 4); actionWidget = toolbar->widgetForAction(m_buttonAudioThumbs); actionWidget->setMaximumWidth(max); actionWidget->setMaximumHeight(max - 4); actionWidget = toolbar->widgetForAction(m_buttonShowMarkers); actionWidget->setMaximumWidth(max); actionWidget->setMaximumHeight(max - 4); actionWidget = toolbar->widgetForAction(m_buttonSnap); actionWidget->setMaximumWidth(max); actionWidget->setMaximumHeight(max - 4); m_messageLabel = new StatusBarMessageLabel(this); m_messageLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::MinimumExpanding); statusBar()->addWidget(m_messageLabel, 10); statusBar()->addWidget(m_statusProgressBar, 0); statusBar()->addPermanentWidget(toolbar); m_timeFormatButton = new KSelectAction(QStringLiteral("00:00:00:00 / 00:00:00:00"), this); QFont fixedFont = QFontDatabase::systemFont(QFontDatabase::FixedFont); m_timeFormatButton->setFont(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, SIGNAL(triggered(int)), this, SLOT(slotUpdateTimecodeFormat(int))); m_timeFormatButton->setToolBarMode(KSelectAction::MenuMode); toolbar->addAction(m_timeFormatButton); const QFontMetrics metric(fixedFont); int requiredWidth = metric.boundingRect(QStringLiteral("00:00:00:00 / 00:00:00:00")).width() + 20; actionWidget = toolbar->widgetForAction(m_timeFormatButton); actionWidget->setObjectName(QStringLiteral("timecode")); actionWidget->setMinimumWidth(requiredWidth); addAction(QStringLiteral("normal_mode"), m_normalEditTool); addAction(QStringLiteral("overwrite_mode"), m_overwriteEditTool); addAction(QStringLiteral("insert_mode"), m_insertEditTool); addAction(QStringLiteral("select_tool"), m_buttonSelectTool); addAction(QStringLiteral("razor_tool"), m_buttonRazorTool); addAction(QStringLiteral("spacer_tool"), m_buttonSpacerTool); addAction(QStringLiteral("automatic_split_audio"), m_buttonAutomaticSplitAudio); 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("zoom_in"), m_zoomIn); addAction(QStringLiteral("zoom_out"), m_zoomOut); KNS3::standardAction(i18n("Download New Wipes..."), this, SLOT(slotGetNewLumaStuff()), actionCollection(), "get_new_lumas"); KNS3::standardAction(i18n("Download New Render Profiles..."), this, SLOT(slotGetNewRenderStuff()), actionCollection(), "get_new_profiles"); KNS3::standardAction(i18n("Download New Project Profiles..."), this, SLOT(slotGetNewMltProfileStuff()), actionCollection(), "get_new_mlt_profiles"); KNS3::standardAction(i18n("Download New Title Templates..."), this, SLOT(slotGetNewTitleStuff()), actionCollection(), "get_new_titles"); addAction(QStringLiteral("run_wizard"), i18n("Run Config Wizard"), this, SLOT(slotRunWizard()), KoIconUtils::themedIcon(QStringLiteral("tools-wizard"))); addAction(QStringLiteral("project_settings"), i18n("Project Settings"), this, SLOT(slotEditProjectSettings()), KoIconUtils::themedIcon(QStringLiteral("configure"))); addAction(QStringLiteral("project_render"), i18n("Render"), this, SLOT(slotRenderProject()), KoIconUtils::themedIcon(QStringLiteral("media-record")), Qt::CTRL + Qt::Key_Return); addAction(QStringLiteral("project_clean"), i18n("Clean Project"), this, SLOT(slotCleanProject()), KoIconUtils::themedIcon(QStringLiteral("edit-clear"))); //TODO //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()), KoIconUtils::themedIcon(QStringLiteral("media-playback-start")), Qt::CTRL + Qt::Key_Space); m_loopZone = addAction(QStringLiteral("monitor_loop_zone"), i18n("Loop Zone"), pCore->monitorManager(), SLOT(slotLoopZone()), KoIconUtils::themedIcon(QStringLiteral("media-playback-start")), Qt::ALT + Qt::Key_Space); m_loopClip = addAction(QStringLiteral("monitor_loop_clip"), i18n("Loop selected clip"), m_projectMonitor, SLOT(slotLoopClip()), KoIconUtils::themedIcon(QStringLiteral("media-playback-start"))); m_loopClip->setEnabled(false); addAction(QStringLiteral("dvd_wizard"), i18n("DVD Wizard"), this, SLOT(slotDvdWizard()), KoIconUtils::themedIcon(QStringLiteral("media-optical"))); addAction(QStringLiteral("transcode_clip"), i18n("Transcode Clips"), this, SLOT(slotTranscodeClip()), KoIconUtils::themedIcon(QStringLiteral("edit-copy"))); addAction(QStringLiteral("archive_project"), i18n("Archive Project"), this, SLOT(slotArchiveProject()), KoIconUtils::themedIcon(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()), KoIconUtils::themedIcon(QStringLiteral("document-open"))); QAction *overlayInfo = new QAction(KoIconUtils::themedIcon(QStringLiteral("help-hint")), i18n("Monitor Info Overlay"), this); addAction(QStringLiteral("monitor_overlay"), overlayInfo); overlayInfo->setCheckable(true); overlayInfo->setData(0x01); QAction *overlayTCInfo = new QAction(KoIconUtils::themedIcon(QStringLiteral("help-hint")), i18n("Monitor Overlay Timecode"), this); addAction(QStringLiteral("monitor_overlay_tc"), overlayTCInfo); overlayTCInfo->setCheckable(true); overlayTCInfo->setData(0x02); QAction *overlayMarkerInfo = new QAction(KoIconUtils::themedIcon(QStringLiteral("help-hint")), i18n("Monitor Overlay Markers"), this); addAction(QStringLiteral("monitor_overlay_markers"), overlayMarkerInfo); overlayMarkerInfo->setCheckable(true); overlayMarkerInfo->setData(0x04); QAction *overlaySafeInfo = new QAction(KoIconUtils::themedIcon(QStringLiteral("help-hint")), i18n("Monitor Overlay Safe Zones"), this); addAction(QStringLiteral("monitor_overlay_safezone"), overlaySafeInfo); overlaySafeInfo->setCheckable(true); overlaySafeInfo->setData(0x08); QAction *overlayAudioInfo = new QAction(KoIconUtils::themedIcon(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()); connect(dropFrames, SIGNAL(toggled(bool)), this, SLOT(slotSwitchDropFrames(bool))); 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, SIGNAL(triggered(int)), this, SLOT(slotSetMonitorGamma(int))); 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); QAction *resizeStart = new QAction(QIcon(), i18n("Resize Item Start"), this); addAction(QStringLiteral("resize_timeline_clip_start"), resizeStart); resizeStart->setShortcut(Qt::Key_1); connect(resizeStart, SIGNAL(triggered(bool)), this, SLOT(slotResizeItemStart())); QAction *resizeEnd = new QAction(QIcon(), i18n("Resize Item End"), this); addAction(QStringLiteral("resize_timeline_clip_end"), resizeEnd); resizeEnd->setShortcut(Qt::Key_2); connect(resizeEnd, SIGNAL(triggered(bool)), this, SLOT(slotResizeItemEnd())); addAction(QStringLiteral("monitor_seek_snap_backward"), i18n("Go to Previous Snap Point"), this, SLOT(slotSnapRewind()), KoIconUtils::themedIcon(QStringLiteral("media-seek-backward")), Qt::ALT + Qt::Key_Left); addAction(QStringLiteral("seek_clip_start"), i18n("Go to Clip Start"), this, SLOT(slotClipStart()), KoIconUtils::themedIcon(QStringLiteral("media-seek-backward")), Qt::Key_Home); addAction(QStringLiteral("seek_clip_end"), i18n("Go to Clip End"), this, SLOT(slotClipEnd()), KoIconUtils::themedIcon(QStringLiteral("media-seek-forward")), Qt::Key_End); addAction(QStringLiteral("monitor_seek_snap_forward"), i18n("Go to Next Snap Point"), this, SLOT(slotSnapForward()), KoIconUtils::themedIcon(QStringLiteral("media-seek-forward")), Qt::ALT + Qt::Key_Right); addAction(QStringLiteral("delete_timeline_clip"), i18n("Delete Selected Item"), this, SLOT(slotDeleteItem()), KoIconUtils::themedIcon(QStringLiteral("edit-delete")), Qt::Key_Delete); addAction(QStringLiteral("align_playhead"), i18n("Align Playhead to Mouse Position"), this, SLOT(slotAlignPlayheadToMousePos()), QIcon(), Qt::Key_P); 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, SIGNAL(triggered(bool)), this, SLOT(slotAutoTransition())); addAction(QStringLiteral("group_clip"), i18n("Group Clips"), this, SLOT(slotGroupClips()), KoIconUtils::themedIcon(QStringLiteral("object-group")), Qt::CTRL + Qt::Key_G); QAction * ungroupClip = addAction(QStringLiteral("ungroup_clip"), i18n("Ungroup Clips"), this, SLOT(slotUnGroupClips()), KoIconUtils::themedIcon(QStringLiteral("object-ungroup")), Qt::CTRL + Qt::SHIFT + Qt::Key_G); ungroupClip->setData("ungroup_clip"); addAction(QStringLiteral("edit_item_duration"), i18n("Edit Duration"), this, SLOT(slotEditItemDuration()), KoIconUtils::themedIcon(QStringLiteral("measure"))); addAction(QStringLiteral("save_timeline_clip"), i18n("Save clip"), this, SLOT(slotSaveTimelineClip()), KoIconUtils::themedIcon(QStringLiteral("document-save"))); addAction(QStringLiteral("clip_in_project_tree"), i18n("Clip in Project Bin"), this, SLOT(slotClipInProjectTree()), KoIconUtils::themedIcon(QStringLiteral("go-jump-definition"))); addAction(QStringLiteral("overwrite_to_in_point"), i18n("Insert Clip Zone in Timeline (Overwrite)"), this, SLOT(slotInsertClipOverwrite()), QIcon(), Qt::Key_V); addAction(QStringLiteral("select_timeline_clip"), i18n("Select Clip"), this, SLOT(slotSelectTimelineClip()), KoIconUtils::themedIcon(QStringLiteral("edit-select")), Qt::Key_Plus); addAction(QStringLiteral("deselect_timeline_clip"), i18n("Deselect Clip"), this, SLOT(slotDeselectTimelineClip()), KoIconUtils::themedIcon(QStringLiteral("edit-select")), Qt::Key_Minus); addAction(QStringLiteral("select_add_timeline_clip"), i18n("Add Clip To Selection"), this, SLOT(slotSelectAddTimelineClip()), KoIconUtils::themedIcon(QStringLiteral("edit-select")), Qt::ALT + Qt::Key_Plus); addAction(QStringLiteral("select_timeline_transition"), i18n("Select Transition"), this, SLOT(slotSelectTimelineTransition()), KoIconUtils::themedIcon(QStringLiteral("edit-select")), Qt::SHIFT + Qt::Key_Plus); addAction(QStringLiteral("deselect_timeline_transition"), i18n("Deselect Transition"), this, SLOT(slotDeselectTimelineTransition()), KoIconUtils::themedIcon(QStringLiteral("edit-select")), Qt::SHIFT + Qt::Key_Minus); addAction(QStringLiteral("select_add_timeline_transition"), i18n("Add Transition To Selection"), this, SLOT(slotSelectAddTimelineTransition()), KoIconUtils::themedIcon(QStringLiteral("edit-select")), Qt::ALT + Qt::SHIFT + Qt::Key_Plus); addAction(QStringLiteral("cut_timeline_clip"), i18n("Cut Clip"), this, SLOT(slotCutTimelineClip()), KoIconUtils::themedIcon(QStringLiteral("edit-cut")), Qt::SHIFT + Qt::Key_R); addAction(QStringLiteral("add_clip_marker"), i18n("Add Marker"), this, SLOT(slotAddClipMarker()), KoIconUtils::themedIcon(QStringLiteral("bookmark-new"))); addAction(QStringLiteral("delete_clip_marker"), i18n("Delete Marker"), this, SLOT(slotDeleteClipMarker()), KoIconUtils::themedIcon(QStringLiteral("edit-delete"))); addAction(QStringLiteral("delete_all_clip_markers"), i18n("Delete All Markers"), this, SLOT(slotDeleteAllClipMarkers()), KoIconUtils::themedIcon(QStringLiteral("edit-delete"))); QAction * editClipMarker = addAction(QStringLiteral("edit_clip_marker"), i18n("Edit Marker"), this, SLOT(slotEditClipMarker()), KoIconUtils::themedIcon(QStringLiteral("document-properties"))); editClipMarker->setData(QStringLiteral("edit_marker")); addAction(QStringLiteral("add_marker_guide_quickly"), i18n("Add Marker/Guide quickly"), this, SLOT(slotAddMarkerGuideQuickly()), KoIconUtils::themedIcon(QStringLiteral("bookmark-new")), Qt::Key_Asterisk); QAction * splitAudio = addAction(QStringLiteral("split_audio"), i18n("Split Audio"), this, SLOT(slotSplitAudio()), KoIconUtils::themedIcon(QStringLiteral("document-new"))); // "A+V" as data means this action should only be available for clips with audio AND video splitAudio->setData("A+V"); QAction * setAudioAlignReference = addAction(QStringLiteral("set_audio_align_ref"), i18n("Set Audio Reference"), this, SLOT(slotSetAudioAlignReference())); // "A" as data means this action should only be available for clips with audio setAudioAlignReference->setData("A"); QAction * alignAudio = addAction(QStringLiteral("align_audio"), i18n("Align Audio to Reference"), this, SLOT(slotAlignAudio()), QIcon()); // "A" as data means this action should only be available for clips with audio alignAudio->setData("A"); QAction * audioOnly = new QAction(KoIconUtils::themedIcon(QStringLiteral("document-new")), i18n("Audio Only"), this); addAction(QStringLiteral("clip_audio_only"), audioOnly); audioOnly->setData("clip_audio_only"); audioOnly->setCheckable(true); QAction * videoOnly = new QAction(KoIconUtils::themedIcon(QStringLiteral("document-new")), i18n("Video Only"), this); addAction(QStringLiteral("clip_video_only"), videoOnly); videoOnly->setData("clip_video_only"); videoOnly->setCheckable(true); QAction * audioAndVideo = new QAction(KoIconUtils::themedIcon(QStringLiteral("document-new")), i18n("Audio and Video"), this); addAction(QStringLiteral("clip_audio_and_video"), audioAndVideo); audioAndVideo->setData("clip_audio_and_video"); audioAndVideo->setCheckable(true); m_clipTypeGroup = new QActionGroup(this); m_clipTypeGroup->addAction(audioOnly); m_clipTypeGroup->addAction(videoOnly); m_clipTypeGroup->addAction(audioAndVideo); connect(m_clipTypeGroup, SIGNAL(triggered(QAction*)), this, SLOT(slotUpdateClipType(QAction*))); m_clipTypeGroup->setEnabled(false); addAction(QStringLiteral("insert_space"), i18n("Insert Space"), this, SLOT(slotInsertSpace())); addAction(QStringLiteral("delete_space"), i18n("Remove Space"), this, SLOT(slotRemoveSpace())); 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 *configTracks = new QAction(KoIconUtils::themedIcon(QStringLiteral("configure")), i18n("Configure Tracks"), this); connect(configTracks, &QAction::triggered, this, &MainWindow::slotConfigTrack); timelineActions->addAction(QStringLiteral("config_tracks"), configTracks); 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(KoIconUtils::themedIcon(QStringLiteral("kdenlive-select-all"))); selectAll->setShortcutContext(Qt::WidgetWithChildrenShortcut); timelineActions->addAction(QStringLiteral("select_all_tracks"), selectAll); kdenliveCategoryMap.insert(QStringLiteral("timeline"), timelineActions); addAction(QStringLiteral("add_guide"), i18n("Add Guide"), this, SLOT(slotAddGuide()), KoIconUtils::themedIcon(QStringLiteral("document-new"))); addAction(QStringLiteral("delete_guide"), i18n("Delete Guide"), this, SLOT(slotDeleteGuide()), KoIconUtils::themedIcon(QStringLiteral("edit-delete"))); addAction(QStringLiteral("edit_guide"), i18n("Edit Guide"), this, SLOT(slotEditGuide()), KoIconUtils::themedIcon(QStringLiteral("document-properties"))); addAction(QStringLiteral("delete_all_guides"), i18n("Delete All Guides"), this, SLOT(slotDeleteAllGuides()), KoIconUtils::themedIcon(QStringLiteral("edit-delete"))); QAction *pasteEffects = addAction(QStringLiteral("paste_effects"), i18n("Paste Effects"), this, SLOT(slotPasteEffects()), KoIconUtils::themedIcon(QStringLiteral("edit-paste"))); pasteEffects->setData("paste_effects"); m_saveAction = KStandardAction::save(pCore->projectManager(), SLOT(saveFile()), actionCollection()); m_saveAction->setIcon(KoIconUtils::themedIcon(QStringLiteral("document-save"))); addAction(QStringLiteral("save_selection"), i18n("Save Selection"), pCore->projectManager(), SLOT(slotSaveSelection()), KoIconUtils::themedIcon(QStringLiteral("document-save"))); QAction *sentToLibrary = addAction(QStringLiteral("send_library"), i18n("Add Selection to Library"), pCore->library(), SLOT(slotAddToLibrary()), KoIconUtils::themedIcon(QStringLiteral("bookmark-new"))); pCore->library()->setupActions(QList () << sentToLibrary); QAction *a = KStandardAction::quit(this, SLOT(close()), actionCollection()); a->setIcon(KoIconUtils::themedIcon(QStringLiteral("application-exit"))); // TODO: make the following connection to slotEditKeys work //KStandardAction::keyBindings(this, SLOT(slotEditKeys()), actionCollection()); a = KStandardAction::preferences(this, SLOT(slotPreferences()), actionCollection()); a->setIcon(KoIconUtils::themedIcon(QStringLiteral("configure"))); a = KStandardAction::configureNotifications(this, SLOT(configureNotifications()), actionCollection()); a->setIcon(KoIconUtils::themedIcon(QStringLiteral("configure"))); a = KStandardAction::copy(this, SLOT(slotCopy()), actionCollection()); a->setIcon(KoIconUtils::themedIcon(QStringLiteral("edit-copy"))); a = KStandardAction::paste(this, SLOT(slotPaste()), actionCollection()); a->setIcon(KoIconUtils::themedIcon(QStringLiteral("edit-paste"))); a = KStandardAction::fullScreen(this, SLOT(slotFullScreen()), this, actionCollection()); a->setIcon(KoIconUtils::themedIcon(QStringLiteral("view-fullscreen"))); QAction *undo = KStandardAction::undo(m_commandStack, SLOT(undo()), actionCollection()); undo->setIcon(KoIconUtils::themedIcon(QStringLiteral("edit-undo"))); undo->setEnabled(false); connect(m_commandStack, SIGNAL(canUndoChanged(bool)), undo, SLOT(setEnabled(bool))); QAction *redo = KStandardAction::redo(m_commandStack, SLOT(redo()), actionCollection()); redo->setIcon(KoIconUtils::themedIcon(QStringLiteral("edit-redo"))); redo->setEnabled(false); connect(m_commandStack, SIGNAL(canRedoChanged(bool)), redo, SLOT(setEnabled(bool))); QMenu *addClips = new QMenu(); QAction *addClip = addAction(QStringLiteral("add_clip"), i18n("Add Clip"), pCore->bin(), SLOT(slotAddClip()), KoIconUtils::themedIcon(QStringLiteral("kdenlive-add-clip"))); addClips->addAction(addClip); QAction *action = addAction(QStringLiteral("add_color_clip"), i18n("Add Color Clip"), pCore->bin(), SLOT(slotCreateProjectClip()), KoIconUtils::themedIcon(QStringLiteral("kdenlive-add-color-clip"))); action->setData((int) Color); addClips->addAction(action); action = addAction(QStringLiteral("add_slide_clip"), i18n("Add Slideshow Clip"), pCore->bin(), SLOT(slotCreateProjectClip()), KoIconUtils::themedIcon(QStringLiteral("kdenlive-add-slide-clip"))); action->setData((int) SlideShow); addClips->addAction(action); action = addAction(QStringLiteral("add_text_clip"), i18n("Add Title Clip"), pCore->bin(), SLOT(slotCreateProjectClip()), KoIconUtils::themedIcon(QStringLiteral("kdenlive-add-text-clip"))); action->setData((int) Text); addClips->addAction(action); action = addAction(QStringLiteral("add_text_template_clip"), i18n("Add Template Title"), pCore->bin(), SLOT(slotCreateProjectClip()), KoIconUtils::themedIcon(QStringLiteral("kdenlive-add-text-clip"))); action->setData((int) TextTemplate); addClips->addAction(action); action = addAction(QStringLiteral("add_qtext_clip"), i18n("Add Simple Text Clip"), pCore->bin(), SLOT(slotCreateProjectClip()), KoIconUtils::themedIcon(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()), KoIconUtils::themedIcon(QStringLiteral("folder-new"))); addClips->addAction(addAction(QStringLiteral("download_resource"), i18n("Online Resources"), this, SLOT(slotDownloadResources()), KoIconUtils::themedIcon(QStringLiteral("edit-download")))); QAction *clipProperties = addAction(QStringLiteral("clip_properties"), i18n("Clip Properties"), pCore->bin(), SLOT(slotSwitchClipProperties()), KoIconUtils::themedIcon(QStringLiteral("document-edit"))); clipProperties->setCheckable(true); clipProperties->setData("clip_properties"); QAction *openClip = addAction(QStringLiteral("edit_clip"), i18n("Edit Clip"), pCore->bin(), SLOT(slotOpenClip()), KoIconUtils::themedIcon(QStringLiteral("document-open"))); openClip->setData("edit_clip"); openClip->setEnabled(false); QAction *deleteClip = addAction(QStringLiteral("delete_clip"), i18n("Delete Clip"), pCore->bin(), SLOT(slotDeleteClip()), KoIconUtils::themedIcon(QStringLiteral("edit-delete"))); deleteClip->setData("delete_clip"); deleteClip->setEnabled(false); QAction *reloadClip = addAction(QStringLiteral("reload_clip"), i18n("Reload Clip"), pCore->bin(), SLOT(slotReloadClip()), KoIconUtils::themedIcon(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)), KoIconUtils::themedIcon(QStringLiteral("favorite"))); disableEffects->setData("disable_timeline_effects"); disableEffects->setCheckable(true); disableEffects->setChecked(false); QAction *duplicateClip = addAction(QStringLiteral("duplicate_clip"), i18n("Duplicate Clip"), pCore->bin(), SLOT(slotDuplicateClip()), KoIconUtils::themedIcon(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); //TODO: port stopmotion to new Monitor code //addAction("stopmotion", i18n("Stop Motion Capture"), this, SLOT(slotOpenStopmotion()), KoIconUtils::themedIcon("image-x-generic")); addAction(QStringLiteral("ripple_delete"), i18n("Ripple Delete"), this, SLOT(slotRippleDelete()), QIcon(), Qt::CTRL + Qt::Key_X); QHash actions; 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()]; for (int i = 0; i < transitions.count(); ++i) { QStringList effectInfo = transitions.effectIdInfo(i); if (effectInfo.isEmpty()) continue; QAction *a = new QAction(effectInfo.at(0), this); a->setData(effectInfo); a->setIconVisibleInMenu(false); m_transitions << a; QString id = effectInfo.at(2); if (id.isEmpty()) id = effectInfo.at(1); transitionActions->addAction("transition_" + id, a); } } void MainWindow::setStatusBarStyleSheet(const QPalette &p) { KColorScheme scheme(p.currentColorGroup(), KColorScheme::Window, KSharedConfig::openConfig(KdenliveSettings::colortheme())); QColor buttonBg = scheme.background(KColorScheme::LinkBackground).color(); QColor buttonBord = scheme.foreground(KColorScheme::LinkText).color(); QColor buttonBord2 = scheme.shade(KColorScheme::LightShade); statusBar()->setStyleSheet(QStringLiteral("QStatusBar QLabel {font-size:%1pt;} QStatusBar::item { border: 0px; font-size:%1pt;padding:0px; }").arg(statusBar()->font().pointSize())); QString style1 = QStringLiteral("QToolBar { border: 0px } QToolButton { border-style: inset; border:1px solid transparent;border-radius: 3px;margin: 0px 3px;padding: 0px;} QToolButton#timecode {padding-right:10px;} QToolButton:hover { background: %3;border-style: inset; border:1px solid %3;border-radius: 3px;} QToolButton:checked { background-color: %1; border-style: inset; border:1px solid %2;border-radius: 3px;}").arg(buttonBg.name(), buttonBord.name(), buttonBord2.name()); statusBar()->setStyleSheet(style1); } void MainWindow::saveOptions() { KdenliveSettings::self()->save(); KSharedConfigPtr config = KSharedConfig::openConfig(); pCore->projectManager()->recentFilesAction()->saveEntries(KConfigGroup(config, "Recent Files")); config->sync(); } void MainWindow::readOptions() { KSharedConfigPtr config = KSharedConfig::openConfig(); pCore->projectManager()->recentFilesAction()->loadEntries(KConfigGroup(config, "Recent Files")); if (KdenliveSettings::defaultprojectfolder().isEmpty()) { QDir dir(QDir::homePath()); if (!dir.mkdir(QStringLiteral("kdenlive"))) { qDebug() << "/// ERROR CREATING PROJECT FOLDER: "; } else { dir.cd("kdenlive"); KdenliveSettings::setDefaultprojectfolder(dir.absolutePath()); } } KConfigGroup initialGroup(config, "version"); if (!initialGroup.exists() || KdenliveSettings::ffmpegpath().isEmpty() || KdenliveSettings::ffplaypath().isEmpty()) { // this is our first run, show Wizard QPointer w = new Wizard(false, this); if (w->exec() == QDialog::Accepted && w->isOk()) { w->adjustSettings(); delete w; } else { delete w; ::exit(1); } } initialGroup.writeEntry("version", version); } void MainWindow::slotRunWizard() { QPointer w = new Wizard(false, this); if (w->exec() == QDialog::Accepted && w->isOk()) { w->adjustSettings(); } delete w; } void MainWindow::slotRefreshProfiles() { KdenliveSettingsDialog* d = static_cast (KConfigDialog::exists("settings")); if (d) { d->checkProfile(); } } void MainWindow::slotEditProjectSettings() { KdenliveDoc *project = pCore->projectManager()->current(); QPoint p = pCore->projectManager()->currentTimeline()->getTracksCount(); QPointer w = new ProjectSettings(project, project->metadata(), pCore->projectManager()->currentTimeline()->projectView()->extractTransitionsLumas(), p.x(), p.y(), project->projectFolder().path(), true, !project->isModified(), this); connect(w, SIGNAL(disableProxies()), this, SLOT(slotDisableProxies())); connect(w, SIGNAL(refreshProfiles()), this, SLOT(slotRefreshProfiles())); if (w->exec() == QDialog::Accepted) { QString profile = w->selectedProfile(); project->setProjectFolder(w->selectedFolder()); bool modified = false; if (m_recMonitor) { m_recMonitor->slotUpdateCaptureFolder(project->projectFolder().path() + QDir::separator()); } if (m_renderWidget) { m_renderWidget->setDocumentPath(project->projectFolder().path() + QDir::separator()); } if (KdenliveSettings::videothumbnails() != w->enableVideoThumbs()) { slotSwitchVideoThumbs(); } if (KdenliveSettings::audiothumbnails() != w->enableAudioThumbs()) { slotSwitchAudioThumbs(); } if (project->profilePath() != profile) { KdenliveSettings::setCurrent_profile(profile); pCore->projectManager()->slotResetProfiles(); slotUpdateDocumentState(true); } if (project->getDocumentProperty(QStringLiteral("proxyparams")) != w->proxyParams()) { modified = true; project->setDocumentProperty(QStringLiteral("proxyparams"), w->proxyParams()); if (pCore->binController()->clipCount() > 0 && KMessageBox::questionYesNo(this, i18n("You have changed the proxy parameters. Do you want to recreate all proxy clips for this project?")) == KMessageBox::Yes) { //TODO: rebuild all proxies //m_projectList->rebuildProxies(); } } if (project->getDocumentProperty(QStringLiteral("proxyextension")) != w->proxyExtension()) { modified = true; project->setDocumentProperty(QStringLiteral("proxyextension"), w->proxyExtension()); } 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 (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()); } if (modified) project->setModified(); } delete w; } void MainWindow::slotDisableProxies() { pCore->projectManager()->current()->setDocumentProperty(QStringLiteral("enableproxy"), QString::number((int) false)); pCore->projectManager()->current()->setModified(); slotUpdateProxySettings(); } void MainWindow::slotRenderProject() { KdenliveDoc *project = pCore->projectManager()->current(); if (!m_renderWidget) { QString projectfolder = project ? project->projectFolder().path() + QDir::separator() : KdenliveSettings::defaultprojectfolder(); MltVideoProfile profile; if (project) { profile = project->mltProfile(); m_renderWidget = new RenderWidget(projectfolder, project->useProxy(), profile, this); connect(m_renderWidget, SIGNAL(shutdown()), this, SLOT(slotShutdown())); connect(m_renderWidget, SIGNAL(selectedRenderProfile(QMap)), this, SLOT(slotSetDocumentRenderProfile(QMap))); connect(m_renderWidget, SIGNAL(prepareRenderingData(bool,bool,QString)), this, SLOT(slotPrepareRendering(bool,bool,QString))); connect(m_renderWidget, SIGNAL(abortProcess(QString)), this, SIGNAL(abortRenderJob(QString))); connect(m_renderWidget, SIGNAL(openDvdWizard(QString)), this, SLOT(slotDvdWizard(QString))); m_renderWidget->setProfile(project->mltProfile()); m_renderWidget->setGuides(pCore->projectManager()->currentTimeline()->projectView()->guidesData(), project->projectDuration()); m_renderWidget->setDocumentPath(project->projectFolder().path() + QDir::separator()); m_renderWidget->setRenderProfile(project->getRenderProperties()); } } slotCheckRenderStatus(); m_renderWidget->show(); //m_renderWidget->showNormal(); // What are the following lines supposed to do? //pCore->projectManager()->currentTimeline()->tracksNumber(); //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) { if (m_renderWidget) m_renderWidget->setRenderJob(url, progress); } void MainWindow::setRenderingFinished(const QString &url, int status, const QString &error) { if (m_renderWidget) m_renderWidget->setRenderStatus(url, status, error); } 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->projectManager()->current()) { switch (m_timeFormatButton->currentItem()) { case 0: m_timeFormatButton->setText(pCore->projectManager()->current()->timecode().getTimecodeFromFrames(pos) + " / " + pCore->projectManager()->current()->timecode().getTimecodeFromFrames(pCore->projectManager()->currentTimeline()->duration())); break; default: m_timeFormatButton->setText(QString::number(pos) + " / " + QString::number(pCore->projectManager()->currentTimeline()->duration())); } } } void MainWindow::slotUpdateProjectDuration(int pos) { if (pCore->projectManager()->current()) { pCore->projectManager()->currentTimeline()->setDuration(pos); slotUpdateMousePosition(pCore->projectManager()->currentTimeline()->projectView()->getMousePos()); } } void MainWindow::slotUpdateDocumentState(bool modified) { setWindowTitle(pCore->projectManager()->current()->description()); setWindowModified(modified); m_saveAction->setEnabled(modified); } void MainWindow::connectDocument() { KdenliveDoc *project = pCore->projectManager()->current(); Timeline *trackView = pCore->projectManager()->currentTimeline(); connect(project, SIGNAL(startAutoSave()), pCore->projectManager(), SLOT(slotStartAutoSave())); connect(project, SIGNAL(reloadEffects()), this, SLOT(slotReloadEffects())); KdenliveSettings::setProject_fps(project->fps()); m_clipMonitorDock->raise(); m_effectStack->transitionConfig()->updateProjectFormat(); //connect(m_projectList, SIGNAL(refreshClip(QString,bool)), trackView->projectView(), SLOT(slotRefreshThumbs(QString,bool))); //connect(m_projectList, SIGNAL(projectModified()), project, SLOT(setModified())); //connect(m_projectList, SIGNAL(clipNameChanged(QString,QString)), trackView->projectView(), SLOT(clipNameChanged(QString,QString))); connect(trackView, SIGNAL(configTrack()), this, SLOT(slotConfigTrack())); connect(trackView, SIGNAL(updateTracksInfo()), this, SLOT(slotUpdateTrackInfo())); connect(trackView, SIGNAL(mousePosition(int)), this, SLOT(slotUpdateMousePosition(int))); connect(pCore->producerQueue(), SIGNAL(infoProcessingFinished()), trackView->projectView(), SLOT(slotInfoProcessingFinished()), Qt::DirectConnection); connect(trackView->projectView(), SIGNAL(importKeyframes(GraphicsRectItem,QString,QString)), this, SLOT(slotProcessImportKeyframes(GraphicsRectItem,QString,QString))); connect(m_projectMonitor, &Monitor::multitrackView, trackView, &Timeline::slotMultitrackView); connect(m_projectMonitor, SIGNAL(renderPosition(int)), trackView, SLOT(moveCursorPos(int))); connect(m_projectMonitor, SIGNAL(zoneUpdated(QPoint)), trackView, SLOT(slotSetZone(QPoint))); connect(m_projectMonitor, SIGNAL(zoneUpdated(QPoint)), project, SLOT(setModified())); connect(m_clipMonitor, SIGNAL(zoneUpdated(QPoint)), project, SLOT(setModified())); connect(project, SIGNAL(docModified(bool)), this, SLOT(slotUpdateDocumentState(bool))); connect(trackView->projectView(), SIGNAL(guidesUpdated()), this, SLOT(slotGuidesUpdated())); connect(project, SIGNAL(saveTimelinePreview(QString)), trackView, SLOT(slotSaveTimelinePreview(QString))); connect(trackView, SIGNAL(showTrackEffects(int,TrackInfo)), this, SLOT(slotTrackSelected(int,TrackInfo))); connect(trackView->projectView(), SIGNAL(clipItemSelected(ClipItem*,bool,bool)), this, SLOT(slotTimelineClipSelected(ClipItem*,bool,bool))); 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))); connect(trackView->projectView(), SIGNAL(transitionItemSelected(Transition*,int,QPoint,bool)), this, SLOT(slotActivateTransitionView(Transition*))); connect(trackView->projectView(), SIGNAL(zoomIn()), this, SLOT(slotZoomIn())); connect(trackView->projectView(), SIGNAL(zoomOut()), this, SLOT(slotZoomOut())); connect(trackView, SIGNAL(setZoom(int)), this, SLOT(slotSetZoom(int))); connect(trackView->projectView(), SIGNAL(displayMessage(QString,MessageType)), m_messageLabel, SLOT(setMessage(QString,MessageType))); connect(pCore->bin(), SIGNAL(clipNameChanged(QString)), trackView->projectView(), SLOT(clipNameChanged(QString))); connect(pCore->bin(), SIGNAL(displayMessage(QString,MessageType)), m_messageLabel, SLOT(setMessage(QString,MessageType))); connect(trackView->projectView(), SIGNAL(showClipFrame(const QString&,int)), pCore->bin(), SLOT(selectClipById(const QString&,int))); connect(trackView->projectView(), SIGNAL(playMonitor()), m_projectMonitor, SLOT(slotPlay())); 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, SIGNAL(updateEffect(ClipItem*,int,QDomElement,QDomElement,int,bool)), trackView->projectView(), SLOT(slotUpdateClipEffect(ClipItem*,int,QDomElement,QDomElement,int,bool))); connect(m_effectStack, SIGNAL(updateClipRegion(ClipItem*,int,QString)), trackView->projectView(), SLOT(slotUpdateClipRegion(ClipItem*,int,QString))); 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, SIGNAL(refreshEffectStack(ClipItem*)), trackView->projectView(), SLOT(slotRefreshEffects(ClipItem*))); connect(m_effectStack, SIGNAL(seekTimeline(int)), trackView->projectView(), SLOT(seekCursorPos(int))); 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(), SIGNAL(seekTimeline(int)), trackView->projectView() , SLOT(seekCursorPos(int))); connect(trackView->projectView(), SIGNAL(activateDocumentMonitor()), m_projectMonitor, SLOT(slotActivateMonitor())); connect(project, &KdenliveDoc::updateFps, trackView, &Timeline::updateProfile, Qt::DirectConnection); connect(trackView, SIGNAL(zoneMoved(int,int)), this, SLOT(slotZoneMoved(int,int))); trackView->projectView()->setContextMenu(m_timelineContextMenu, m_timelineContextClipMenu, m_timelineContextTransitionMenu, m_clipTypeGroup, static_cast(factory()->container(QStringLiteral("marker_menu"), this))); if (m_renderWidget) { slotCheckRenderStatus(); m_renderWidget->setProfile(project->mltProfile()); m_renderWidget->setGuides(pCore->projectManager()->currentTimeline()->projectView()->guidesData(), project->projectDuration()); m_renderWidget->setDocumentPath(project->projectFolder().path() + QDir::separator()); m_renderWidget->setRenderProfile(project->getRenderProperties()); } m_zoomSlider->setValue(project->zoom().x()); m_commandStack->setActiveStack(project->commandStack()); KdenliveSettings::setProject_display_ratio(project->dar()); setWindowTitle(project->description()); setWindowModified(project->isModified()); m_saveAction->setEnabled(project->isModified()); m_normalEditTool->setChecked(true); connect(m_projectMonitor, SIGNAL(durationChanged(int)), this, SLOT(slotUpdateProjectDuration(int))); pCore->monitorManager()->setDocument(project); trackView->updateProfile(false); if (m_recMonitor) { m_recMonitor->slotUpdateCaptureFolder(project->projectFolder().path() + QDir::separator()); } //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(); // Make sure monitor is visible so that it is painted black on startup //show(); //pCore->monitorManager()->activateMonitor(Kdenlive::ClipMonitor, true); // set tool to select tool m_buttonSelectTool->setChecked(true); connect(m_projectMonitorDock, SIGNAL(visibilityChanged(bool)), m_projectMonitor, SLOT(slotRefreshMonitor(bool)), Qt::UniqueConnection); connect(m_clipMonitorDock, SIGNAL(visibilityChanged(bool)), m_clipMonitor, SLOT(slotRefreshMonitor(bool)), Qt::UniqueConnection); } void MainWindow::slotZoneMoved(int start, int end) { pCore->projectManager()->current()->setZone(start, end); m_projectMonitor->slotZoneMoved(start, end); } void MainWindow::slotGuidesUpdated() { QMap guidesData = pCore->projectManager()->currentTimeline()->projectView()->guidesData(); if (m_renderWidget) m_renderWidget->setGuides(guidesData, pCore->projectManager()->current()->projectDuration()); m_projectMonitor->setGuides(guidesData); } void MainWindow::slotEditKeys() { KShortcutsDialog dialog(KShortcutsEditor::AllActions, KShortcutsEditor::LetterShortcutsAllowed, this); 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 (m_stopmotion) { m_stopmotion->slotLive(false); } 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}"); foreach (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, SIGNAL(settingsChanged(QString)), this, SLOT(updateConfiguration())); connect(dialog, SIGNAL(settingsChanged(QString)), SIGNAL(configurationChanged())); connect(dialog, SIGNAL(doResetProfile()), pCore->projectManager(), SLOT(slotResetProfiles())); connect(dialog, SIGNAL(restartKdenlive()), this, SLOT(slotRestart())); connect(dialog, SIGNAL(updateLibraryFolder()), pCore, SIGNAL(updateLibraryPath())); if (m_recMonitor) { connect(dialog, SIGNAL(updateCaptureFolder()), this, SLOT(slotUpdateCaptureFolder())); connect(dialog, SIGNAL(updateFullScreenGrab()), m_recMonitor, SLOT(slotUpdateFullScreenGrab())); } dialog->show(); if (page != -1) { dialog->showPage(page, option); } } 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::slotUpdateCaptureFolder() { if (m_recMonitor) { if (pCore->projectManager()->current()) m_recMonitor->slotUpdateCaptureFolder(pCore->projectManager()->current()->projectFolder().path() + QDir::separator()); else m_recMonitor->slotUpdateCaptureFolder(KdenliveSettings::defaultprojectfolder()); } } void MainWindow::updateConfiguration() { //TODO: we should apply settings to all projects, not only the current one if (pCore->projectManager()->currentTimeline()) { pCore->projectManager()->currentTimeline()->refresh(); pCore->projectManager()->currentTimeline()->projectView()->checkAutoScroll(); pCore->projectManager()->currentTimeline()->checkTrackHeight(); } m_buttonAudioThumbs->setChecked(KdenliveSettings::audiothumbnails()); m_buttonVideoThumbs->setChecked(KdenliveSettings::videothumbnails()); m_buttonShowMarkers->setChecked(KdenliveSettings::showmarkers()); m_buttonAutomaticSplitAudio->setChecked(KdenliveSettings::splitaudio()); // Update list of transcoding profiles buildDynamicActions(); loadClipActions(); } void MainWindow::slotSwitchSplitAudio() { KdenliveSettings::setSplitaudio(!KdenliveSettings::splitaudio()); m_buttonAutomaticSplitAudio->setChecked(KdenliveSettings::splitaudio()); } void MainWindow::slotSwitchVideoThumbs() { KdenliveSettings::setVideothumbnails(!KdenliveSettings::videothumbnails()); if (pCore->projectManager()->currentTimeline()) { pCore->projectManager()->currentTimeline()->projectView()->slotUpdateAllThumbs(); } m_buttonVideoThumbs->setChecked(KdenliveSettings::videothumbnails()); } void MainWindow::slotSwitchAudioThumbs() { KdenliveSettings::setAudiothumbnails(!KdenliveSettings::audiothumbnails()); pCore->binController()->checkAudioThumbs(); if (pCore->projectManager()->currentTimeline()) { pCore->projectManager()->currentTimeline()->refresh(); pCore->projectManager()->currentTimeline()->projectView()->checkAutoScroll(); } m_buttonAudioThumbs->setChecked(KdenliveSettings::audiothumbnails()); } void MainWindow::slotSwitchMarkersComments() { KdenliveSettings::setShowmarkers(!KdenliveSettings::showmarkers()); if (pCore->projectManager()->currentTimeline()) { pCore->projectManager()->currentTimeline()->refresh(); } m_buttonShowMarkers->setChecked(KdenliveSettings::showmarkers()); } void MainWindow::slotSwitchSnap() { KdenliveSettings::setSnaptopoints(!KdenliveSettings::snaptopoints()); m_buttonSnap->setChecked(KdenliveSettings::snaptopoints()); } void MainWindow::slotDeleteItem() { if (QApplication::focusWidget() && QApplication::focusWidget()->parentWidget() && QApplication::focusWidget()->parentWidget()->parentWidget() && QApplication::focusWidget()->parentWidget()->parentWidget() == pCore->bin()) { pCore->bin()->slotDeleteClip(); } else { QWidget *widget = QApplication::focusWidget(); while (widget && widget != this) { if (widget == m_effectStackDock) { m_effectStack->deleteCurrentEffect(); return; } widget = widget->parentWidget(); } // effect stack has no focus if (pCore->projectManager()->currentTimeline()) { pCore->projectManager()->currentTimeline()->projectView()->deleteSelectedClips(); } } } void MainWindow::slotAddClipMarker() { KdenliveDoc *project = pCore->projectManager()->current(); ClipController *clip = NULL; GenTime pos; if (m_projectMonitor->isActive()) { if (pCore->projectManager()->currentTimeline()) { ClipItem *item = pCore->projectManager()->currentTimeline()->projectView()->getActiveClipUnderCursor(); if (item) { pos = GenTime((int)((m_projectMonitor->position() - item->startPos() + item->cropStart()).frames(project->fps()) * item->speed() + 0.5), project->fps()); clip = pCore->binController()->getController(item->getBinId()); } } } else { clip = m_clipMonitor->currentController(); pos = m_clipMonitor->position(); } if (!clip) { m_messageLabel->setMessage(i18n("Cannot find clip to add marker"), ErrorMessage); return; } QString id = clip->clipId(); CommentedTime marker(pos, i18n("Marker"), KdenliveSettings::default_marker_type()); QPointer d = new MarkerDialog(clip, marker, project->timecode(), i18n("Add Marker"), this); if (d->exec() == QDialog::Accepted) { pCore->bin()->slotAddClipMarker(id, QList () << d->newMarker()); QString hash = clip->getClipHash(); if (!hash.isEmpty()) project->cacheImage(hash + '#' + QString::number(d->newMarker().time().frames(project->fps())), d->markerImage()); } delete d; } void MainWindow::slotDeleteClipMarker(bool allowGuideDeletion) { ClipController *clip = NULL; GenTime pos; if (m_projectMonitor->isActive()) { if (pCore->projectManager()->currentTimeline()) { ClipItem *item = pCore->projectManager()->currentTimeline()->projectView()->getActiveClipUnderCursor(); if (item) { pos = (m_projectMonitor->position() - item->startPos() + item->cropStart()) / item->speed(); clip = pCore->binController()->getController(item->getBinId()); } } } else { clip = m_clipMonitor->currentController(); pos = m_clipMonitor->position(); } if (!clip) { m_messageLabel->setMessage(i18n("Cannot find clip to remove marker"), ErrorMessage); return; } QString id = clip->clipId(); QString comment = clip->markerComment(pos); if (comment.isEmpty()) { if (allowGuideDeletion && m_projectMonitor->isActive()) { slotDeleteGuide(); } else m_messageLabel->setMessage(i18n("No marker found at cursor time"), ErrorMessage); return; } pCore->bin()->deleteClipMarker(comment, id, pos); } void MainWindow::slotDeleteAllClipMarkers() { ClipController *clip = NULL; if (m_projectMonitor->isActive()) { if (pCore->projectManager()->currentTimeline()) { ClipItem *item = pCore->projectManager()->currentTimeline()->projectView()->getActiveClipUnderCursor(); if (item) { clip = pCore->binController()->getController(item->getBinId()); } } } else { clip = m_clipMonitor->currentController(); } if (!clip) { m_messageLabel->setMessage(i18n("Cannot find clip to remove marker"), ErrorMessage); return; } pCore->bin()->deleteAllClipMarkers(clip->clipId()); } void MainWindow::slotEditClipMarker() { ClipController *clip = NULL; GenTime pos; if (m_projectMonitor->isActive()) { if (pCore->projectManager()->currentTimeline()) { ClipItem *item = pCore->projectManager()->currentTimeline()->projectView()->getActiveClipUnderCursor(); if (item) { pos = (m_projectMonitor->position() - item->startPos() + item->cropStart()) / item->speed(); clip = pCore->binController()->getController(item->getBinId()); } } } else { clip = m_clipMonitor->currentController(); pos = m_clipMonitor->position(); } if (!clip) { m_messageLabel->setMessage(i18n("Cannot find clip to remove marker"), ErrorMessage); return; } QString id = clip->clipId(); CommentedTime oldMarker = clip->markerAt(pos); if (oldMarker == CommentedTime()) { m_messageLabel->setMessage(i18n("No marker found at cursor time"), ErrorMessage); return; } QPointer d = new MarkerDialog(clip, oldMarker, pCore->projectManager()->current()->timecode(), i18n("Edit Marker"), this); if (d->exec() == QDialog::Accepted) { pCore->bin()->slotAddClipMarker(id, QList () <newMarker()); QString hash = clip->getClipHash(); if (!hash.isEmpty()) pCore->projectManager()->current()->cacheImage(hash + '#' + QString::number(d->newMarker().time().frames(pCore->projectManager()->current()->fps())), d->markerImage()); if (d->newMarker().time() != pos) { // remove old marker oldMarker.setMarkerType(-1); pCore->bin()->slotAddClipMarker(id, QList () <projectManager()->currentTimeline() || !pCore->projectManager()->current()) return; if (m_clipMonitor->isActive()) { ClipController *clip = m_clipMonitor->currentController(); GenTime pos = m_clipMonitor->position(); if (!clip) { m_messageLabel->setMessage(i18n("Cannot find clip to add marker"), ErrorMessage); return; } //TODO: allow user to set default marker category CommentedTime marker(pos, pCore->projectManager()->current()->timecode().getDisplayTimecode(pos, false), KdenliveSettings::default_marker_type()); pCore->bin()->slotAddClipMarker(clip->clipId(), QList () <projectManager()->currentTimeline()->projectView()->slotAddGuide(false); } } void MainWindow::slotAddGuide() { if (pCore->projectManager()->currentTimeline()) pCore->projectManager()->currentTimeline()->projectView()->slotAddGuide(); } void MainWindow::slotInsertSpace() { if (pCore->projectManager()->currentTimeline()) pCore->projectManager()->currentTimeline()->projectView()->slotInsertSpace(); } void MainWindow::slotRemoveSpace() { if (pCore->projectManager()->currentTimeline()) pCore->projectManager()->currentTimeline()->projectView()->slotRemoveSpace(); } void MainWindow::slotRippleDelete() { if (!m_projectMonitor->isActive() || !pCore->projectManager()->currentTimeline()) return; int zoneStart = m_projectMonitor->getZoneStart(); int zoneEnd = m_projectMonitor->getZoneEnd(); if (!zoneStart && zoneEnd == (pCore->projectManager()->currentTimeline()->duration() - 1)) return; int zoneFrameCount = zoneEnd - zoneStart; m_projectMonitor->slotZoneStart(); pCore->projectManager()->currentTimeline()->projectView()->setCursorPos(zoneStart); pCore->projectManager()->currentTimeline()->projectView()->slotSelectAllClips(); pCore->projectManager()->currentTimeline()->projectView()->cutSelectedClips(); pCore->projectManager()->currentTimeline()->projectView()->resetSelectionGroup(false); m_projectMonitor->slotZoneEnd(); pCore->projectManager()->currentTimeline()->projectView()->setCursorPos(zoneEnd); zoneEnd++; pCore->projectManager()->currentTimeline()->projectView()->selectItemsRightOfFrame(zoneEnd); pCore->projectManager()->currentTimeline()->projectView()->setInPoint(); pCore->projectManager()->currentTimeline()->projectView()->resetSelectionGroup(false); zoneEnd++; pCore->projectManager()->currentTimeline()->projectView()->selectItemsRightOfFrame(zoneEnd); pCore->projectManager()->currentTimeline()->projectView()->spaceToolMoveToSnapPos((double) zoneEnd); pCore->projectManager()->currentTimeline()->projectView()->spaceToolMoveToSnapPos((double) zoneStart); GenTime timeOffset = GenTime(zoneFrameCount * -1, pCore->projectManager()->current()->fps()); pCore->projectManager()->currentTimeline()->projectView()->completeSpaceOperation(-1, timeOffset); m_projectMonitor->slotZoneStart(); pCore->projectManager()->currentTimeline()->projectView()->setCursorPos(zoneStart); pCore->projectManager()->currentTimeline()->projectView()->resetSelectionGroup(false); return; } void MainWindow::slotInsertTrack() { pCore->monitorManager()->activateMonitor(Kdenlive::ProjectMonitor); if (pCore->projectManager()->currentTimeline()) { int ix = pCore->projectManager()->currentTimeline()->projectView()->selectedTrack(); pCore->projectManager()->currentTimeline()->projectView()->slotInsertTrack(ix ); } if (pCore->projectManager()->current()) { m_effectStack->transitionConfig()->updateProjectFormat(); } } void MainWindow::slotDeleteTrack() { pCore->monitorManager()->activateMonitor(Kdenlive::ProjectMonitor); if (pCore->projectManager()->currentTimeline()) { int ix = pCore->projectManager()->currentTimeline()->projectView()->selectedTrack(); pCore->projectManager()->currentTimeline()->projectView()->slotDeleteTrack(ix); } if (pCore->projectManager()->current()) { m_effectStack->transitionConfig()->updateProjectFormat(); } } void MainWindow::slotConfigTrack() { pCore->monitorManager()->activateMonitor(Kdenlive::ProjectMonitor); if (pCore->projectManager()->currentTimeline()) { int ix = pCore->projectManager()->currentTimeline()->projectView()->selectedTrack(); pCore->projectManager()->currentTimeline()->projectView()->slotConfigTracks(ix); } if (pCore->projectManager()->current()) m_effectStack->transitionConfig()->updateProjectFormat(); } void MainWindow::slotSelectTrack() { pCore->monitorManager()->activateMonitor(Kdenlive::ProjectMonitor); if (pCore->projectManager()->currentTimeline()) { pCore->projectManager()->currentTimeline()->projectView()->slotSelectClipsInTrack(); } } void MainWindow::slotSelectAllTracks() { pCore->monitorManager()->activateMonitor(Kdenlive::ProjectMonitor); if (pCore->projectManager()->currentTimeline()) pCore->projectManager()->currentTimeline()->projectView()->slotSelectAllClips(); } void MainWindow::slotEditGuide(int pos, QString text) { if (pCore->projectManager()->currentTimeline()) pCore->projectManager()->currentTimeline()->projectView()->slotEditGuide(pos, text); } void MainWindow::slotDeleteGuide() { if (pCore->projectManager()->currentTimeline()) pCore->projectManager()->currentTimeline()->projectView()->slotDeleteGuide(); } void MainWindow::slotDeleteAllGuides() { if (pCore->projectManager()->currentTimeline()) pCore->projectManager()->currentTimeline()->projectView()->slotDeleteAllGuides(); } void MainWindow::slotCutTimelineClip() { if (pCore->projectManager()->currentTimeline()) pCore->projectManager()->currentTimeline()->projectView()->cutSelectedClips(); } void MainWindow::slotInsertClipOverwrite() { if (pCore->projectManager()->currentTimeline()) { QStringList data = m_clipMonitor->getZoneInfo(); pCore->projectManager()->currentTimeline()->projectView()->insertZoneOverwrite(data, pCore->projectManager()->currentTimeline()->projectView()->seekPosition()); } } void MainWindow::slotSelectTimelineClip() { if (pCore->projectManager()->currentTimeline()) pCore->projectManager()->currentTimeline()->projectView()->selectClip(true); } void MainWindow::slotSelectTimelineTransition() { if (pCore->projectManager()->currentTimeline()) pCore->projectManager()->currentTimeline()->projectView()->selectTransition(true); } void MainWindow::slotDeselectTimelineClip() { if (pCore->projectManager()->currentTimeline()) pCore->projectManager()->currentTimeline()->projectView()->selectClip(false, true); } void MainWindow::slotDeselectTimelineTransition() { if (pCore->projectManager()->currentTimeline()) pCore->projectManager()->currentTimeline()->projectView()->selectTransition(false, true); } void MainWindow::slotSelectAddTimelineClip() { if (pCore->projectManager()->currentTimeline()) pCore->projectManager()->currentTimeline()->projectView()->selectClip(true, true); } void MainWindow::slotSelectAddTimelineTransition() { if (pCore->projectManager()->currentTimeline()) pCore->projectManager()->currentTimeline()->projectView()->selectTransition(true, true); } void MainWindow::slotGroupClips() { if (pCore->projectManager()->currentTimeline()) pCore->projectManager()->currentTimeline()->projectView()->groupClips(); } void MainWindow::slotUnGroupClips() { if (pCore->projectManager()->currentTimeline()) pCore->projectManager()->currentTimeline()->projectView()->groupClips(false); } void MainWindow::slotEditItemDuration() { if (pCore->projectManager()->currentTimeline()) pCore->projectManager()->currentTimeline()->projectView()->editItemDuration(); } void MainWindow::slotAddProjectClip(const QUrl &url) { pCore->bin()->droppedUrls(QList() << url); } void MainWindow::slotAddProjectClipList(const QList &urls) { pCore->bin()->droppedUrls(urls); } void MainWindow::slotAddTransition(QAction *result) { if (!result) return; 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::slotAddVideoEffect(QAction *result) { if (!result) { return; } const int VideoEffect = 1; const int AudioEffect = 2; QStringList info = result->data().toStringList(); if (info.isEmpty() || info.size() < 3) { return; } QDomElement effect ; if (info.last() == QString::number((int) VideoEffect)) { effect = videoEffects.getEffectByTag(info.at(0), info.at(1)); } else if (info.last() == QString::number((int) AudioEffect)) { effect = audioEffects.getEffectByTag(info.at(0), info.at(1)); } else { effect = customEffects.getEffectByTag(info.at(0), info.at(1)); } if (!effect.isNull()) { slotAddEffect(effect); } else { m_messageLabel->setMessage(i18n("Cannot find effect %1 / %2", info.at(0), info.at(1)), ErrorMessage); } } void MainWindow::slotZoomIn() { m_zoomSlider->setValue(m_zoomSlider->value() - 1); slotShowZoomSliderToolTip(); } void MainWindow::slotZoomOut() { m_zoomSlider->setValue(m_zoomSlider->value() + 1); 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) { value = qMax(m_zoomSlider->minimum(), value); value = qMin(m_zoomSlider->maximum(), value); if (pCore->projectManager()->currentTimeline()) { pCore->projectManager()->currentTimeline()->slotChangeZoom(value); } m_zoomOut->setEnabled(value < m_zoomSlider->maximum()); m_zoomIn->setEnabled(value > m_zoomSlider->minimum()); slotUpdateZoomSliderToolTip(value); m_zoomSlider->blockSignals(true); m_zoomSlider->setValue(value); m_zoomSlider->blockSignals(false); } 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) { m_zoomSlider->setToolTip(i18n("Zoom Level: %1/13", (13 - zoomlevel))); } void MainWindow::slotGotProgressInfo(const QString &message, int progress, MessageType type) { if (type == DefaultMessage) { m_statusProgressBar->setValue(progress); } m_messageLabel->setMessage(message, type); if (progress >= 0) { if (type == DefaultMessage) { m_statusProgressBar->setVisible(true); } } else { m_statusProgressBar->setVisible(false); } } void MainWindow::customEvent(QEvent* e) { if (e->type() == QEvent::User) m_messageLabel->setMessage(static_cast (e)->message(), MltError); } void MainWindow::slotTimelineClipSelected(ClipItem* item, bool reloadStack, bool raise) { m_effectStack->slotClipItemSelected(item, m_projectMonitor, reloadStack); m_projectMonitor->slotSetSelectedClip(item); if (raise) { m_effectStack->raiseWindow(m_effectStackDock); } } void MainWindow::slotTrackSelected(int index, const TrackInfo &info, bool raise) { m_effectStack->slotTrackItemSelected(index, info, m_projectMonitor); if (raise) { m_effectStack->raiseWindow(m_effectStackDock); } } void MainWindow::slotActivateTransitionView(Transition *transition) { if (transition) m_effectStack->raiseWindow(m_effectStackDock); } void MainWindow::slotSnapRewind() { if (m_projectMonitor->isActive()) { if (pCore->projectManager()->currentTimeline()) { pCore->projectManager()->currentTimeline()->projectView()->slotSeekToPreviousSnap(); } } else { m_clipMonitor->slotSeekToPreviousSnap(); } } void MainWindow::slotSnapForward() { if (m_projectMonitor->isActive()) { if (pCore->projectManager()->currentTimeline()) { pCore->projectManager()->currentTimeline()->projectView()->slotSeekToNextSnap(); } } else { m_clipMonitor->slotSeekToNextSnap(); } } void MainWindow::slotClipStart() { if (m_projectMonitor->isActive()) { if (pCore->projectManager()->currentTimeline()) { pCore->projectManager()->currentTimeline()->projectView()->clipStart(); } } } void MainWindow::slotClipEnd() { if (m_projectMonitor->isActive()) { if (pCore->projectManager()->currentTimeline()) { pCore->projectManager()->currentTimeline()->projectView()->clipEnd(); } } } 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) { if (!pCore->projectManager()->currentTimeline()) return; if (action == m_overwriteEditTool) pCore->projectManager()->currentTimeline()->projectView()->setEditMode(OverwriteEdit); else if (action == m_insertEditTool) pCore->projectManager()->currentTimeline()->projectView()->setEditMode(InsertEdit); else pCore->projectManager()->currentTimeline()->projectView()->setEditMode(NormalEdit); } void MainWindow::slotSetTool(ProjectTool tool) { if (pCore->projectManager()->current() && pCore->projectManager()->currentTimeline()) { //pCore->projectManager()->current()->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); pCore->projectManager()->currentTimeline()->projectView()->setTool(tool); } } void MainWindow::slotCopy() { if (pCore->projectManager()->currentTimeline()) pCore->projectManager()->currentTimeline()->projectView()->copyClip(); } void MainWindow::slotPaste() { if (pCore->projectManager()->currentTimeline()) pCore->projectManager()->currentTimeline()->projectView()->pasteClip(); } void MainWindow::slotPasteEffects() { if (pCore->projectManager()->currentTimeline()) pCore->projectManager()->currentTimeline()->projectView()->pasteClipEffects(); } void MainWindow::slotClipInTimeline(const QString &clipId) { if (pCore->projectManager()->currentTimeline()) { QList matching = pCore->projectManager()->currentTimeline()->projectView()->findId(clipId); QMenu *inTimelineMenu = static_cast(factory()->container(QStringLiteral("clip_in_timeline"), this)); QList actionList; for (int i = 0; i < matching.count(); ++i) { QString track = pCore->projectManager()->currentTimeline()->getTrackInfo(matching.at(i).track).trackName; QString start = pCore->projectManager()->current()->timecode().getTimecode(matching.at(i).startPos); int j = 0; QAction *a = new QAction(track + ": " + start, inTimelineMenu); a->setData(QStringList() << track << start); connect(a, SIGNAL(triggered()), this, SLOT(slotSelectClipInTimeline())); while (j < actionList.count()) { if (actionList.at(j)->text() > a->text()) break; j++; } actionList.insert(j, a); } QList list = inTimelineMenu->actions(); unplugActionList("timeline_occurences"); qDeleteAll(list); plugActionList("timeline_occurences", actionList); if (actionList.isEmpty()) { inTimelineMenu->setEnabled(false); } else { inTimelineMenu->setEnabled(true); } } } void MainWindow::slotClipInProjectTree() { if (pCore->projectManager()->currentTimeline()) { int pos = -1; QPoint zone; const QString selectedId = pCore->projectManager()->currentTimeline()->projectView()->getClipUnderCursor(&pos, &zone); if (selectedId.isEmpty()) { return; } m_projectBinDock->raise(); pCore->bin()->selectClipById(selectedId, pos, zone); if (m_projectMonitor->isActive()) { slotSwitchMonitors(); } } } void MainWindow::slotSelectClipInTimeline() { if (pCore->projectManager()->currentTimeline()) { QAction *action = qobject_cast(sender()); QStringList data = action->data().toStringList(); pCore->projectManager()->currentTimeline()->projectView()->selectFound(data.at(0), data.at(1)); } } /** 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->projectManager()->current()->projectFolder().path() + QDir::separator(); if (baseClip == NULL) { tmppath.append("untitled.mlt"); } else { tmppath.append((baseClip->name().isEmpty() ? baseClip->fileURL().fileName() : baseClip->name()) + '-' + QString::number(zone.x()).rightJustified(4, '0') + ".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() { if (pCore->projectManager()->currentTimeline()) pCore->projectManager()->currentTimeline()->projectView()->setInPoint(); } void MainWindow::slotResizeItemEnd() { if (pCore->projectManager()->currentTimeline()) pCore->projectManager()->currentTimeline()->projectView()->setOutPoint(); } int MainWindow::getNewStuff(const QString &configFile) { KNS3::Entry::List entries; QPointer dialog = new KNS3::DownloadDialog(configFile); if (dialog->exec()) entries = dialog->changedEntries(); foreach(const KNS3::Entry & entry, entries) { if (entry.status() == KNS3::Entry::Installed) qDebug() << "// Installed files: " << entry.installedFiles(); } delete dialog; return entries.size(); } void MainWindow::slotGetNewTitleStuff() { if (getNewStuff(QStringLiteral("kdenlive_titles.knsrc")) > 0) { // get project title path QString titlePath = pCore->projectManager()->current()->projectFolder().path(); titlePath.append(QStringLiteral("/titles/")); TitleWidget::refreshTitleTemplates(titlePath); } } void MainWindow::slotGetNewLumaStuff() { if (getNewStuff(QStringLiteral("kdenlive_wipes.knsrc")) > 0) { initEffects::refreshLumas(); pCore->projectManager()->currentTimeline()->projectView()->reloadTransitionLumas(); } } void MainWindow::slotGetNewRenderStuff() { if (getNewStuff(QStringLiteral("kdenlive_renderprofiles.knsrc")) > 0) if (m_renderWidget) m_renderWidget->reloadProfiles(); } void MainWindow::slotGetNewMltProfileStuff() { if (getNewStuff(QStringLiteral("kdenlive_projectprofiles.knsrc")) > 0) { // update the list of profiles in settings dialog KdenliveSettingsDialog* d = static_cast (KConfigDialog::exists(QStringLiteral("settings"))); if (d) d->checkProfile(); } } void MainWindow::slotAutoTransition() { if (pCore->projectManager()->currentTimeline()) pCore->projectManager()->currentTimeline()->projectView()->autoTransition(); } void MainWindow::slotSplitAudio() { if (pCore->projectManager()->currentTimeline()) pCore->projectManager()->currentTimeline()->projectView()->splitAudio(); } void MainWindow::slotSetAudioAlignReference() { if (pCore->projectManager()->currentTimeline()) { pCore->projectManager()->currentTimeline()->projectView()->setAudioAlignReference(); } } void MainWindow::slotAlignAudio() { if (pCore->projectManager()->currentTimeline()) { pCore->projectManager()->currentTimeline()->projectView()->alignAudio(); } } void MainWindow::slotUpdateClipType(QAction *action) { if (pCore->projectManager()->currentTimeline()) { PlaylistState::ClipState state = PlaylistState::Original; if (action->data().toString() == QLatin1String("clip_audio_only")) state = PlaylistState::AudioOnly; else if (action->data().toString() == QLatin1String("clip_video_only")) state = PlaylistState::VideoOnly; pCore->projectManager()->currentTimeline()->projectView()->setClipType(state); } } 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 == false) { 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("clipjobs"); unplugActionList("clip_jobs"); plugActionList("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; foreach(QAction *a, list) { sorted.insert(a->text(), a); sortedList << a->text(); } QList orderedList; sortedList.sort(Qt::CaseInsensitive); foreach(const QString &text, sortedList) { orderedList << sorted.value(text); } unplugActionList( "dock_actions" ); plugActionList( "dock_actions", orderedList); } void MainWindow::buildDynamicActions() { KActionCategory *ts = NULL; if (kdenliveCategoryMap.contains(QStringLiteral("clipjobs"))) { ts = kdenliveCategoryMap.take(QStringLiteral("clipjobs")); delete ts; } ts = new KActionCategory(i18n("Clip Jobs"), m_extraFactory->actionCollection()); Mlt::Profile profile; Mlt::Filter *filter; foreach(const QString &stab, QStringList() << "vidstab" << "videostab2" << "videostab") { filter = Mlt::Factory::filter(profile, (char*)stab.toUtf8().constData()); if (filter && filter->is_valid()) { QAction *action = new QAction(i18n("Stabilize") + " (" + stab + ")", m_extraFactory->actionCollection()); action->setData(QStringList() << QString::number((int) AbstractClipJob::FILTERCLIPJOB) << stab); ts->addAction(action->text(), action); connect(action, SIGNAL(triggered(bool)), pCore->bin(), SLOT(slotStartClipJob(bool))); delete filter; break; } delete filter; } filter = Mlt::Factory::filter(profile,(char*)"motion_est"); if (filter) { if (filter->is_valid()) { QAction *action = new QAction(i18n("Automatic scene split"), m_extraFactory->actionCollection()); QStringList stabJob; stabJob << QString::number((int) AbstractClipJob::FILTERCLIPJOB) << QStringLiteral("motion_est"); action->setData(stabJob); ts->addAction(action->text(), action); connect(action, SIGNAL(triggered(bool)), pCore->bin(), SLOT(slotStartClipJob(bool))); } delete filter; } if (KdenliveSettings::producerslist().contains(QStringLiteral("timewarp"))) { QAction *action = new QAction(i18n("Reverse clip"), m_extraFactory->actionCollection()); QStringList stabJob; stabJob << QString::number((int) AbstractClipJob::FILTERCLIPJOB) << QStringLiteral("timewarp"); action->setData(stabJob); ts->addAction(action->text(), action); connect(action, SIGNAL(triggered(bool)), pCore->bin(), SLOT(slotStartClipJob(bool))); } 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, SIGNAL(triggered(bool)), pCore->bin(), SLOT(slotStartClipJob(bool))); 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; } 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::DataLocation, QStringLiteral("kdenlivetranscodingrc")), KConfig::CascadeConfig); KConfigGroup transConfig(config, "Transcoding"); // read the entries QMap< QString, QString > profiles = transConfig.entryMap(); QMapIterator i(profiles); while (i.hasNext()) { i.next(); QStringList data; data << QString::number((int) AbstractClipJob::TRANSCODEJOB); data << i.value().split(';'); QAction *a = new QAction(i.key(), m_extraFactory->actionCollection()); a->setData(data); if (data.count() > 1) a->setToolTip(data.at(1)); // slottranscode connect(a, SIGNAL(triggered(bool)), pCore->bin(), SLOT(slotStartClipJob(bool))); if (data.count() > 3 && data.at(3) == "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 = NULL; 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, SIGNAL(triggered(bool)), this, SLOT(slotShowTimeline(bool))); guiActions->addAction(showTimeline->text(), showTimeline); actionCollection()->addAction(showTimeline->text(), showTimeline); QList docks = findChildren(); for (int i = 0; i < docks.count(); ++i) { QDockWidget* dock = docks.at(i); 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) { QString params; QString desc; QString condition; if (urls.isEmpty()) { QAction *action = qobject_cast(sender()); QStringList data = action->data().toStringList(); pCore->bin()->startClipJob(data); return; } if (urls.isEmpty()) { m_messageLabel->setMessage(i18n("No clip to transcode"), ErrorMessage); return; } ClipTranscode *d = new ClipTranscode(urls, params, QStringList(), desc); connect(d, SIGNAL(addClip(QUrl)), this, SLOT(slotAddProjectClip(QUrl))); d->show(); } void MainWindow::slotTranscodeClip() { QString allExtensions = ClipCreationDialog::getExtensions().join(QStringLiteral(" ")); const QString dialogFilter = i18n("All Supported Files") + '(' + allExtensions + ");;" + i18n("All Files") + "(*)"; 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->projectManager()->current(); 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::slotPrepareRendering(bool scriptExport, bool zoneOnly, const QString &chapterFile) { KdenliveDoc *project = pCore->projectManager()->current(); if (m_renderWidget == NULL) return; QString scriptPath; QString playlistPath; QString mltSuffix(QStringLiteral(".mlt")); QList playlistPaths; QList trackNames; int tracksCount = 1; bool stemExport = m_renderWidget->isStemAudioExportEnabled(); if (scriptExport) { //QString scriptsFolder = project->projectFolder().path(QUrl::AddTrailingSlash) + "scripts/"; QString path = m_renderWidget->getFreeScriptName(project->url()); QPointer getUrl = new KUrlRequesterDialog(QUrl::fromLocalFile(path), i18n("Create Render Script"), this); getUrl->urlRequester()->setMode(KFile::File); if (getUrl->exec() == QDialog::Rejected) { delete getUrl; return; } scriptPath = getUrl->selectedUrl().path(); delete getUrl; QFile f(scriptPath); if (f.exists()) { if (KMessageBox::warningYesNo(this, i18n("Script file already exists. Do you want to overwrite it?")) != KMessageBox::Yes) return; } playlistPath = scriptPath; } else { QTemporaryFile temp(QDir::tempPath() + QStringLiteral("/kdenlive_rendering_XXXXXX.mlt")); temp.setAutoRemove(false); temp.open(); playlistPath = temp.fileName(); } QString playlistContent = pCore->projectManager()->projectSceneList(); if (!chapterFile.isEmpty()) { int in = 0; int out; if (!zoneOnly) out = (int) GenTime(project->projectDuration()).frames(project->fps()); else { in = pCore->projectManager()->currentTimeline()->inPoint(); out = pCore->projectManager()->currentTimeline()->outPoint(); } QDomDocument doc; QDomElement chapters = doc.createElement(QStringLiteral("chapters")); chapters.setAttribute(QStringLiteral("fps"), project->fps()); doc.appendChild(chapters); QMap guidesData = pCore->projectManager()->currentTimeline()->projectView()->guidesData(); QMapIterator g(guidesData); QLocale locale; while (g.hasNext()) { g.next(); int time = (int) GenTime(g.key()).frames(project->fps()); if (time >= in && time < out) { if (zoneOnly) time = time - in; QDomElement chapter = doc.createElement(QStringLiteral("chapter")); chapters.appendChild(chapter); chapter.setAttribute(QStringLiteral("title"), g.value()); chapter.setAttribute(QStringLiteral("time"), time); } } if (chapters.childNodes().count() > 0) { if (pCore->projectManager()->currentTimeline()->projectView()->hasGuide(out, 0) == -1) { // Always insert a guide in pos 0 QDomElement chapter = doc.createElement(QStringLiteral("chapter")); chapters.insertBefore(chapter, QDomNode()); chapter.setAttribute(QStringLiteral("title"), i18nc("the first in a list of chapters", "Start")); chapter.setAttribute(QStringLiteral("time"), QStringLiteral("0")); } // save chapters file QFile file(chapterFile); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { qWarning() << "////// ERROR writing DVD CHAPTER file: " << chapterFile; } else { file.write(doc.toString().toUtf8()); if (file.error() != QFile::NoError) { qWarning() << "////// ERROR writing DVD CHAPTER file: " << chapterFile; } file.close(); } } } // check if audio export is selected bool exportAudio; if (m_renderWidget->automaticAudioExport()) { exportAudio = pCore->projectManager()->currentTimeline()->checkProjectAudio(); } else { exportAudio = m_renderWidget->selectedAudioExport(); } // Set playlist audio volume to 100% QDomDocument doc; doc.setContent(playlistContent); QDomElement tractor = doc.documentElement().firstChildElement(QStringLiteral("tractor")); if (!tractor.isNull()) { QDomNodeList props = tractor.elementsByTagName(QStringLiteral("property")); for (int i = 0; i < props.count(); ++i) { if (props.at(i).toElement().attribute(QStringLiteral("name")) == QLatin1String("meta.volume")) { props.at(i).firstChild().setNodeValue(QStringLiteral("1")); break; } } } + // Add autoclose to playlists. + QDomNodeList playlists = doc.elementsByTagName("playlist"); + for (int i = 0; i < playlists.length();++i) { + playlists.item(i).toElement().setAttribute("autoclose", 1); + } + // Do we want proxy rendering if (project->useProxy() && !m_renderWidget->proxyRendering()) { QString root = doc.documentElement().attribute(QStringLiteral("root")); // replace proxy clips with originals //TODO QMap proxies = pCore->binController()->getProxies(); QDomNodeList producers = doc.elementsByTagName(QStringLiteral("producer")); QString producerResource; QString suffix; for (int n = 0; n < producers.length(); ++n) { QDomElement e = producers.item(n).toElement(); producerResource = EffectsList::property(e, QStringLiteral("resource")); if (producerResource.isEmpty()) { continue; } if (!producerResource.startsWith('/')) { producerResource.prepend(root + '/'); } if (producerResource.contains('?')) { // slowmotion producer suffix = '?' + producerResource.section('?', 1); producerResource = producerResource.section('?', 0, 0); } else { suffix.clear(); } if (!producerResource.isEmpty()) { if (proxies.contains(producerResource)) { EffectsList::setProperty(e, QStringLiteral("resource"), proxies.value(producerResource) + suffix); // We need to delete the "aspect_ratio" property because proxy clips // sometimes have different ratio than original clips EffectsList::removeProperty(e, QStringLiteral("aspect_ratio")); EffectsList::removeMetaProperties(e); } } } } QList docList; // check which audio tracks have to be exported if (stemExport) { Timeline* ct = pCore->projectManager()->currentTimeline(); int allTracksCount = ct->tracksCount(); // reset tracks count (tracks to be rendered) tracksCount = 0; // begin with track 1 (track zero is a hidden black track) for (int i = 1; i < allTracksCount; i++) { Track* track = ct->track(i); // add only tracks to render list that are not muted and have audio if (track && !track->info().isMute && track->hasAudio()) { QDomDocument docCopy = doc.cloneNode(true).toDocument(); QString trackName = track->info().trackName; // save track name trackNames << trackName; qDebug() << "Track-Name: " << trackName; // create stem export doc content QDomNodeList tracks = docCopy.elementsByTagName(QStringLiteral("track")); for (int j = 0; j < allTracksCount; j++) { if (j != i) { // mute other tracks tracks.at(j).toElement().setAttribute(QStringLiteral("hide"), QStringLiteral("both")); } } docList << docCopy; tracksCount++; } } } else { docList << doc; } // create full playlistPaths for (int i = 0; i < tracksCount; i++) { QString plPath(playlistPath); // add track number to path name if (stemExport) { plPath = plPath + "_" + QString(trackNames.at(i)).replace(QLatin1String(" "), QLatin1String("_")); } // add mlt suffix plPath += mltSuffix; playlistPaths << plPath; qDebug() << "playlistPath: " << plPath << endl; // Do save scenelist QFile file(plPath); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { m_messageLabel->setMessage(i18n("Cannot write to file %1", plPath), ErrorMessage); return; } file.write(docList.at(i).toString().toUtf8()); if (file.error() != QFile::NoError) { m_messageLabel->setMessage(i18n("Cannot write to file %1", plPath), ErrorMessage); file.close(); return; } file.close(); } m_renderWidget->slotExport(scriptExport, pCore->projectManager()->currentTimeline()->inPoint(), pCore->projectManager()->currentTimeline()->outPoint(), project->metadata(), playlistPaths, trackNames, scriptPath, exportAudio); } void MainWindow::slotUpdateTimecodeFormat(int ix) { KdenliveSettings::setFrametimecode(ix == 1); m_clipMonitor->updateTimecodeFormat(); m_projectMonitor->updateTimecodeFormat(); m_effectStack->transitionConfig()->updateTimecodeFormat(); m_effectStack->updateTimecodeFormat(); pCore->bin()->updateTimecodeFormat(); //pCore->projectManager()->currentTimeline()->projectView()->clearSelection(); pCore->projectManager()->currentTimeline()->updateRuler(); slotUpdateMousePosition(pCore->projectManager()->currentTimeline()->projectView()->getMousePos()); } void MainWindow::slotRemoveFocus() { statusBar()->setFocus(); statusBar()->clearFocus(); } void MainWindow::slotShutdown() { pCore->projectManager()->current()->setModified(false); // Call shutdown QDBusConnectionInterface* interface = QDBusConnection::sessionBus().interface(); if (interface && 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 && 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::slotUpdateTrackInfo() { m_effectStack->transitionConfig()->updateProjectFormat(); } void MainWindow::slotSwitchMonitors() { pCore->monitorManager()->slotSwitchMonitors(!m_clipMonitor->isActive()); if (m_projectMonitor->isActive()) pCore->projectManager()->currentTimeline()->projectView()->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() == NULL) return; QStringList info = m_clipMonitor->getZoneInfo(); pCore->bin()->slotAddClipCut(info.at(0), info.at(1).toInt(), info.at(2).toInt()); } void MainWindow::slotInsertZoneToTimeline() { if (pCore->projectManager()->currentTimeline() == NULL || m_clipMonitor->currentController() == NULL) return; QStringList info = m_clipMonitor->getZoneInfo(); pCore->projectManager()->currentTimeline()->projectView()->insertClipCut(m_clipMonitor->activeClipId(), info.at(1).toInt(), info.at(2).toInt()); } void MainWindow::slotMonitorRequestRenderFrame(bool request) { if (request) { m_projectMonitor->render->sendFrameForAnalysis = true; return; } else { 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 qDebug() << "Any scope accepting new frames? " << request; #endif if (!request) { m_projectMonitor->render->sendFrameForAnalysis = false; } } void MainWindow::slotOpenStopmotion() { if (m_stopmotion == NULL) { m_stopmotion = new StopmotionWidget(pCore->monitorManager(), pCore->projectManager()->current()->projectFolder(), m_stopmotion_actions->actions(), this); //TODO //connect(m_stopmotion, SIGNAL(addOrUpdateSequence(QString)), m_projectList, SLOT(slotAddOrUpdateSequence(QString))); //for (int i = 0; i < m_gfxScopesList.count(); ++i) { // Check if we need the renderer to send a new frame for update /*if (!m_scopesList.at(i)->widget()->visibleRegion().isEmpty() && !(static_cast(m_scopesList.at(i)->widget())->autoRefreshEnabled())) request = true;*/ //connect(m_stopmotion, SIGNAL(gotFrame(QImage)), static_cast(m_gfxScopesList.at(i)->widget()), SLOT(slotRenderZoneUpdated(QImage))); //static_cast(m_scopesList.at(i)->widget())->slotMonitorCapture(); //} } m_stopmotion->show(); } void MainWindow::slotUpdateProxySettings() { KdenliveDoc *project = pCore->projectManager()->current(); if (m_renderWidget) m_renderWidget->updateProxyConfig(project->useProxy()); if (KdenliveSettings::enableproxy()) { QDir dir(pCore->projectManager()->current()->projectFolder().path()); dir.mkdir(QStringLiteral("proxy")); } pCore->bin()->refreshProxySettings(); } void MainWindow::slotArchiveProject() { QList list = pCore->binController()->getControllerList(); pCore->binController()->saveDocumentProperties(pCore->projectManager()->current()->documentProperties(), pCore->projectManager()->currentTimeline()->projectView()->guidesData()); QDomDocument doc = pCore->projectManager()->current()->xmlSceneList(m_projectMonitor->sceneList()); QPointer d = new ArchiveWidget(pCore->projectManager()->current()->url().fileName(), doc, list, pCore->projectManager()->currentTimeline()->projectView()->extractTransitionsLumas(), this); if (d->exec()) { m_messageLabel->setMessage(i18n("Archiving project"), OperationCompletedMessage); } delete d; } void MainWindow::slotElapsedTime() { //qDebug()<<"-----------------------------------------\n"<<"Time elapsed: "<projectManager()->current()) currentFolder = pCore->projectManager()->current()->projectFolder().path(); else currentFolder = KdenliveSettings::defaultprojectfolder(); ResourceWidget *d = new ResourceWidget(currentFolder); connect(d, SIGNAL(addClip(QUrl)), this, SLOT(slotAddProjectClip(QUrl))); d->show(); } void MainWindow::slotSaveTimelineClip() { if (pCore->projectManager()->currentTimeline() && m_projectMonitor->render) { ClipItem *clip = pCore->projectManager()->currentTimeline()->projectView()->getActiveClipUnderCursor(true); if (!clip) { m_messageLabel->setMessage(i18n("Select a clip to save"), InformationMessage); return; } QUrl url = QFileDialog::getSaveFileUrl(this, i18n("Save clip"), pCore->projectManager()->current()->projectFolder(), i18n("MLT playlist (*.mlt)")); if (url.isValid()) { m_projectMonitor->render->saveClip(clip->track(), clip->startPos(), url); } } } void MainWindow::slotProcessImportKeyframes(GraphicsRectItem type, const QString &tag, const QString& data) { if (type == AVWidget) { // This data should be sent to the effect stack m_effectStack->setKeyframes(tag, data); } else if (type == TransitionWidget) { // This data should be sent to the transition stack m_effectStack->transitionConfig()->setKeyframes(tag, data); } else { // Error } } void MainWindow::slotAlignPlayheadToMousePos() { pCore->monitorManager()->activateMonitor(Kdenlive::ProjectMonitor); pCore->projectManager()->currentTimeline()->projectView()->slotAlignPlayheadToMousePos(); } 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() + ev->modifiers()); } else { seq = QKeySequence(ev->key()); } QList< KActionCollection * > collections = KActionCollection::allCollections(); for (int i = 0; i < collections.count(); ++i) { KActionCollection *coll = collections.at(i); foreach( 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); 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(); foreach(QAction *ac, actions) { int data = ac->data().toInt(); if (data == 0x010) { ac->setEnabled(id == Kdenlive::ClipMonitor); } ac->setChecked(code & data); } } 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)); // Changing widget style resets color theme, so update color theme again ThemeManager::instance()->slotChangePalette(); } #ifdef DEBUG_MAINW #undef DEBUG_MAINW #endif diff --git a/src/timeline/customtrackview.cpp b/src/timeline/customtrackview.cpp index 54515adf0..9588e37f1 100644 --- a/src/timeline/customtrackview.cpp +++ b/src/timeline/customtrackview.cpp @@ -1,8182 +1,8182 @@ /*************************************************************************** * 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 "customtrackview.h" #include "timeline.h" #include "track.h" #include "clipitem.h" #include "timelinecommands.h" #include "transition.h" #include "markerdialog.h" #include "clipdurationdialog.h" #include "abstractgroupitem.h" #include "spacerdialog.h" #include "trackdialog.h" #include "tracksconfigdialog.h" #include "mltcontroller/clipcontroller.h" #include "mltcontroller/effectscontroller.h" #include "definitions.h" #include "kdenlivesettings.h" #include "renderer.h" #include "bin/projectclip.h" #include "mainwindow.h" #include "transitionhandler.h" #include "project/clipmanager.h" #include "utils/KoIconUtils.h" #include "effectslist/initeffects.h" #include "effectstack/widgets/keyframeimport.h" #include "dialogs/profilesdialog.h" #include "managers/guidemanager.h" #include "managers/razormanager.h" #include "managers/selectmanager.h" #include "ui_keyframedialog_ui.h" #include "ui_addtrack_ui.h" #include "lib/audio/audioEnvelope.h" #include "lib/audio/audioCorrelation.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #define SEEK_INACTIVE (-1) //#define DEBUG bool sortGuidesList(const Guide *g1 , const Guide *g2) { return (*g1).position() < (*g2).position(); } CustomTrackView::CustomTrackView(KdenliveDoc *doc, Timeline *timeline, CustomTrackScene* projectscene, QWidget *parent) : QGraphicsView(projectscene, parent) , m_tracksHeight(KdenliveSettings::trackheight()) , m_projectDuration(0) , m_cursorPos(0) , m_cursorOffset(0) , m_document(doc) , m_timeline(timeline) , m_scene(projectscene) , m_cursorLine(NULL) , m_cutLine(NULL) , m_operationMode(None) , m_moveOpMode(None) , m_dragItem(NULL) , m_dragGuide(NULL) , m_visualTip(NULL) , m_keyProperties(NULL) , m_autoScroll(KdenliveSettings::autoscroll()) , m_timelineContextMenu(NULL) , m_timelineContextClipMenu(NULL) , m_timelineContextTransitionMenu(NULL) , m_timelineContextKeyframeMenu(NULL) , m_selectKeyframeType(NULL) , m_markerMenu(NULL) , m_autoTransition(NULL) , m_pasteEffectsAction(NULL) , m_ungroupAction(NULL) , m_editGuide(NULL) , m_deleteGuide(NULL) , m_clipTypeGroup(NULL) , m_scrollOffset(0) , m_clipDrag(false) , m_findIndex(0) , m_tool(SelectTool) , m_copiedItems() , m_menuPosition() , m_selectionGroup(NULL) , m_selectedTrack(1) , m_spacerOffset(0) , m_audioCorrelator(NULL) , m_audioAlignmentReference(NULL) , m_controlModifier(false) { if (doc) { m_commandStack = doc->commandStack(); } else { m_commandStack = NULL; } m_ct = 0; setMouseTracking(true); setAcceptDrops(true); setFrameShape(QFrame::NoFrame); setLineWidth(0); //setCacheMode(QGraphicsView::CacheBackground); setAutoFillBackground(false); setViewportUpdateMode(QGraphicsView::MinimalViewportUpdate); setContentsMargins(0, 0, 0, 0); KColorScheme scheme(palette().currentColorGroup(), KColorScheme::Window, KSharedConfig::openConfig(KdenliveSettings::colortheme())); m_selectedTrackColor = scheme.background(KColorScheme::ActiveBackground ).color(); m_selectedTrackColor.setAlpha(150); m_keyPropertiesTimer = new QTimeLine(800); m_keyPropertiesTimer->setFrameRange(0, 5); m_keyPropertiesTimer->setUpdateInterval(100); m_keyPropertiesTimer->setLoopCount(0); m_tipColor = QColor(0, 192, 0, 200); m_tipPen.setColor(QColor(255, 255, 255, 100)); m_tipPen.setWidth(3); setSceneRect(0, 0, sceneRect().width(), m_tracksHeight); verticalScrollBar()->setMaximum(m_tracksHeight); verticalScrollBar()->setTracking(true); // repaint guides when using vertical scroll connect(verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(slotRefreshGuides())); connect(horizontalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(slotRefreshCutLine())); m_cursorLine = projectscene->addLine(0, 0, 0, m_tracksHeight); m_cursorLine->setZValue(1000); QPen pen1 = QPen(); pen1.setWidth(1); QColor line(palette().text().color()); line.setAlpha(100); pen1.setColor(line); m_cursorLine->setPen(pen1); connect(&m_scrollTimer, SIGNAL(timeout()), this, SLOT(slotCheckMouseScrolling())); m_scrollTimer.setInterval(100); m_scrollTimer.setSingleShot(true); connect(&m_thumbsTimer, SIGNAL(timeout()), this, SLOT(slotFetchNextThumbs())); m_thumbsTimer.setInterval(500); m_thumbsTimer.setSingleShot(true); QIcon razorIcon = KoIconUtils::themedIcon(QStringLiteral("edit-cut")); m_razorCursor = QCursor(razorIcon.pixmap(32, 32)); m_spacerCursor = QCursor(Qt::SplitHCursor); connect(m_document->renderer(), SIGNAL(prepareTimelineReplacement(QString)), this, SLOT(slotPrepareTimelineReplacement(QString)), Qt::DirectConnection); connect(m_document->renderer(), SIGNAL(replaceTimelineProducer(QString)), this, SLOT(slotReplaceTimelineProducer(QString)), Qt::DirectConnection); connect(m_document->renderer(), SIGNAL(updateTimelineProducer(QString)), this, SLOT(slotUpdateTimelineProducer(QString))); connect(m_document->renderer(), SIGNAL(rendererPosition(int)), this, SLOT(setCursorPos(int))); scale(1, 1); setAlignment(Qt::AlignLeft | Qt::AlignTop); } CustomTrackView::~CustomTrackView() { qDeleteAll(m_guides); m_guides.clear(); m_waitingThumbs.clear(); delete m_keyPropertiesTimer; } //virtual void CustomTrackView::keyPressEvent(QKeyEvent * event) { if (event->key() == Qt::Key_Up) { slotTrackUp(); event->accept(); } else if (event->key() == Qt::Key_Down) { slotTrackDown(); event->accept(); } else QWidget::keyPressEvent(event); } void CustomTrackView::setDocumentModified() { m_document->setModified(true); } void CustomTrackView::setContextMenu(QMenu *timeline, QMenu *clip, QMenu *transition, QActionGroup *clipTypeGroup, QMenu *markermenu) { m_clipTypeGroup = clipTypeGroup; m_timelineContextMenu = timeline; m_timelineContextClipMenu = clip; m_timelineContextTransitionMenu = transition; connect(m_timelineContextTransitionMenu, SIGNAL(aboutToHide()), this, SLOT(slotResetMenuPosition())); connect(m_timelineContextMenu, SIGNAL(aboutToHide()), this, SLOT(slotResetMenuPosition())); connect(m_timelineContextClipMenu, SIGNAL(aboutToHide()), this, SLOT(slotResetMenuPosition())); connect(m_timelineContextTransitionMenu, SIGNAL(triggered(QAction*)), this, SLOT(slotContextMenuActivated())); connect(m_timelineContextMenu, SIGNAL(triggered(QAction*)), this, SLOT(slotContextMenuActivated())); connect(m_timelineContextClipMenu, SIGNAL(triggered(QAction*)), this, SLOT(slotContextMenuActivated())); m_markerMenu = new QMenu(i18n("Go to marker..."), this); m_markerMenu->setEnabled(false); markermenu->addMenu(m_markerMenu); connect(m_markerMenu, SIGNAL(triggered(QAction*)), this, SLOT(slotGoToMarker(QAction*))); QList list = m_timelineContextClipMenu->actions(); for (int i = 0; i < list.count(); ++i) { if (list.at(i)->data().toString() == QLatin1String("paste_effects")) m_pasteEffectsAction = list.at(i); else if (list.at(i)->data().toString() == QLatin1String("ungroup_clip")) m_ungroupAction = list.at(i); else if (list.at(i)->data().toString() == QLatin1String("A")) m_audioActions.append(list.at(i)); else if (list.at(i)->data().toString() == QLatin1String("A+V")) m_avActions.append(list.at(i)); } list = m_timelineContextTransitionMenu->actions(); for (int i = 0; i < list.count(); ++i) { if (list.at(i)->data().toString() == QLatin1String("auto")) { m_autoTransition = list.at(i); break; } } m_timelineContextMenu->addSeparator(); m_deleteGuide = new QAction(QIcon::fromTheme(QStringLiteral("edit-delete")), i18n("Delete Guide"), this); connect(m_deleteGuide, SIGNAL(triggered()), this, SLOT(slotDeleteTimeLineGuide())); m_timelineContextMenu->addAction(m_deleteGuide); m_editGuide = new QAction(QIcon::fromTheme(QStringLiteral("document-properties")), i18n("Edit Guide"), this); connect(m_editGuide, SIGNAL(triggered()), this, SLOT(slotEditTimeLineGuide())); m_timelineContextMenu->addAction(m_editGuide); } void CustomTrackView::slotDoResetMenuPosition() { m_menuPosition = QPoint(); } void CustomTrackView::slotResetMenuPosition() { // after a short time (so that the action is triggered / or menu is closed, we reset the menu pos QTimer::singleShot(300, this, SLOT(slotDoResetMenuPosition())); } void CustomTrackView::slotContextMenuActivated() { // Menu disappeared, restore default operation mode m_operationMode = None; } void CustomTrackView::checkAutoScroll() { m_autoScroll = KdenliveSettings::autoscroll(); } int CustomTrackView::getFrameWidth() const { return (int) (m_tracksHeight * m_document->dar() + 0.5); } void CustomTrackView::updateSceneFrameWidth(bool fpsChanged) { int frameWidth = getFrameWidth(); if (fpsChanged && m_projectDuration > 0) { reloadTimeline(); } else { QList itemList = items(); ClipItem *item; for (int i = 0; i < itemList.count(); ++i) { if (itemList.at(i)->type() == AVWidget) { item = static_cast(itemList.at(i)); item->resetFrameWidth(frameWidth); } } } } bool CustomTrackView::checkTrackHeight(bool force) { if (!force && m_tracksHeight == KdenliveSettings::trackheight() && sceneRect().height() == m_tracksHeight * m_timeline->visibleTracksCount()) return false; int frameWidth = getFrameWidth(); if (m_tracksHeight != KdenliveSettings::trackheight()) { QList itemList = items(); ClipItem *item; Transition *transitionitem; m_tracksHeight = KdenliveSettings::trackheight(); // Remove all items, and re-add them one by one to avoid collisions for (int i = 0; i < itemList.count(); ++i) { if (itemList.at(i)->type() == AVWidget || itemList.at(i)->type() == TransitionWidget) { m_scene->removeItem(itemList.at(i)); } } bool snap = KdenliveSettings::snaptopoints(); KdenliveSettings::setSnaptopoints(false); for (int i = 0; i < itemList.count(); ++i) { if (itemList.at(i)->type() == AVWidget) { item = static_cast(itemList.at(i)); item->setRect(0, 0, item->rect().width(), m_tracksHeight - 1); item->setPos((qreal) item->startPos().frames(m_document->fps()), getPositionFromTrack(item->track()) + 1); m_scene->addItem(item); item->resetFrameWidth(frameWidth); } else if (itemList.at(i)->type() == TransitionWidget) { transitionitem = static_cast(itemList.at(i)); transitionitem->setRect(0, 0, transitionitem->rect().width(), m_tracksHeight / 3 * 2 - 1); transitionitem->setPos((qreal) transitionitem->startPos().frames(m_document->fps()), getPositionFromTrack(transitionitem->track()) + transitionitem->itemOffset()); m_scene->addItem(transitionitem); } } KdenliveSettings::setSnaptopoints(snap); } double newHeight = m_tracksHeight * m_timeline->visibleTracksCount() * matrix().m22(); m_cursorLine->setLine(0, 0, 0, newHeight - 1); if (m_cutLine) { m_cutLine->setLine(0, 0, 0, m_tracksHeight * m_scene->scale().y()); } for (int i = 0; i < m_guides.count(); ++i) { m_guides.at(i)->setLine(0, 0, 0, newHeight - 1); } setSceneRect(0, 0, sceneRect().width(), m_tracksHeight * m_timeline->visibleTracksCount()); viewport()->update(); return true; } /** Zoom or move viewport on mousewheel * * If mousewheel+Ctrl, zooms in/out on the timeline. * * With Ctrl, moves viewport towards end of timeline if down/back, * opposite on up/forward. * * See also http://www.kdenlive.org/mantis/view.php?id=265 */ void CustomTrackView::wheelEvent(QWheelEvent * e) { if (e->modifiers() == Qt::ControlModifier) { if (m_moveOpMode == None || m_moveOpMode == WaitingForConfirm || m_moveOpMode == ZoomTimeline) { if (e->delta() > 0) emit zoomIn(); else emit zoomOut(); } } else if (e->modifiers() == Qt::AltModifier) { if (m_moveOpMode == None || m_moveOpMode == WaitingForConfirm || m_moveOpMode == ZoomTimeline) { if (e->delta() > 0) slotSeekToNextSnap(); else slotSeekToPreviousSnap(); } } else { if (m_moveOpMode == ResizeStart || m_moveOpMode == ResizeEnd) { // Don't allow scrolling + resizing return; } if (m_operationMode == None || m_operationMode == ZoomTimeline) { // Prevent unwanted object move m_scene->isZooming = true; } if (e->delta() <= 0) horizontalScrollBar()->setValue(horizontalScrollBar()->value() + horizontalScrollBar()->singleStep()); else horizontalScrollBar()->setValue(horizontalScrollBar()->value() - horizontalScrollBar()->singleStep()); if (m_operationMode == None || m_operationMode == ZoomTimeline) { m_scene->isZooming = false; } } } int CustomTrackView::getPreviousVideoTrack(int track) { int i = track - 1; for (; i > 0; i--) { if (m_timeline->getTrackInfo(i).type == VideoTrack) break; } return i; } int CustomTrackView::getNextVideoTrack(int track) { for (; track < m_timeline->visibleTracksCount(); track++) { if (m_timeline->getTrackInfo(track).type == VideoTrack) break; } return track; } void CustomTrackView::slotFetchNextThumbs() { if (!m_waitingThumbs.isEmpty()) { ClipItem *item = m_waitingThumbs.takeFirst(); while (item == NULL && !m_waitingThumbs.isEmpty()) { item = m_waitingThumbs.takeFirst(); } if (item) item->slotFetchThumbs(); if (!m_waitingThumbs.isEmpty()) m_thumbsTimer.start(); } } void CustomTrackView::slotCheckMouseScrolling() { if (m_scrollOffset == 0) { m_scrollTimer.stop(); return; } horizontalScrollBar()->setValue(horizontalScrollBar()->value() + m_scrollOffset); m_scrollTimer.start(); } void CustomTrackView::slotCheckPositionScrolling() { // If mouse is at a border of the view, scroll if (m_moveOpMode != Seek) return; if (mapFromScene(m_cursorPos, 0).x() < 3) { if (horizontalScrollBar()->value() == 0) return; horizontalScrollBar()->setValue(horizontalScrollBar()->value() - 2); QTimer::singleShot(200, this, SLOT(slotCheckPositionScrolling())); seekCursorPos(mapToScene(QPoint(-2, 0)).x()); } else if (viewport()->width() - 3 < mapFromScene(m_cursorPos + 1, 0).x()) { horizontalScrollBar()->setValue(horizontalScrollBar()->value() + 2); seekCursorPos(mapToScene(QPoint(viewport()->width(), 0)).x() + 1); QTimer::singleShot(200, this, SLOT(slotCheckPositionScrolling())); } } void CustomTrackView::slotAlignPlayheadToMousePos() { /* get curser point ref in screen coord */ QPoint ps = QCursor::pos(); /* get xPos in scene coord */ int mappedXPos = qMax((int)(mapToScene(mapFromGlobal(ps)).x() + 0.5), 0); /* move playhead to new xPos*/ seekCursorPos(mappedXPos); } int CustomTrackView::getMousePos() const { return qMax((int)(mapToScene(mapFromGlobal(QCursor::pos())).x() + 0.5), 0); } void CustomTrackView::spaceToolMoveToSnapPos(double snappedPos) { // Make sure there is no collision QList children = m_selectionGroup->childItems(); QPainterPath shape = m_selectionGroup->clipGroupSpacerShape(QPointF(snappedPos - m_selectionGroup->sceneBoundingRect().left(), 0)); QList collidingItems = scene()->items(shape, Qt::IntersectsItemShape); collidingItems.removeAll(m_selectionGroup); for (int i = 0; i < children.count(); ++i) { if (children.at(i)->type() == GroupWidget) { QList subchildren = children.at(i)->childItems(); for (int j = 0; j < subchildren.count(); ++j) collidingItems.removeAll(subchildren.at(j)); } collidingItems.removeAll(children.at(i)); } bool collision = false; int offset = 0; for (int i = 0; i < collidingItems.count(); ++i) { if (!collidingItems.at(i)->isEnabled()) continue; if (collidingItems.at(i)->type() == AVWidget && snappedPos < m_selectionGroup->sceneBoundingRect().left()) { AbstractClipItem *item = static_cast (collidingItems.at(i)); // Moving backward, determine best pos QPainterPath clipPath; clipPath.addRect(item->sceneBoundingRect()); QPainterPath res = shape.intersected(clipPath); offset = qMax(offset, (int)(res.boundingRect().width() + 0.5)); } } snappedPos += offset; // make sure we have no collision shape = m_selectionGroup->clipGroupSpacerShape(QPointF(snappedPos - m_selectionGroup->sceneBoundingRect().left(), 0)); collidingItems = scene()->items(shape, Qt::IntersectsItemShape); collidingItems.removeAll(m_selectionGroup); for (int i = 0; i < children.count(); ++i) { if (children.at(i)->type() == GroupWidget) { QList subchildren = children.at(i)->childItems(); for (int j = 0; j < subchildren.count(); ++j) collidingItems.removeAll(subchildren.at(j)); } collidingItems.removeAll(children.at(i)); } for (int i = 0; i < collidingItems.count(); ++i) { if (!collidingItems.at(i)->isEnabled()) continue; if (collidingItems.at(i)->type() == AVWidget) { collision = true; break; } } if (!collision) { // Check transitions shape = m_selectionGroup->transitionGroupShape(QPointF(snappedPos - m_selectionGroup->sceneBoundingRect().left(), 0)); collidingItems = scene()->items(shape, Qt::IntersectsItemShape); collidingItems.removeAll(m_selectionGroup); for (int i = 0; i < children.count(); ++i) { if (children.at(i)->type() == GroupWidget) { QList subchildren = children.at(i)->childItems(); for (int j = 0; j < subchildren.count(); ++j) collidingItems.removeAll(subchildren.at(j)); } collidingItems.removeAll(children.at(i)); } offset = 0; for (int i = 0; i < collidingItems.count(); ++i) { if (collidingItems.at(i)->type() == TransitionWidget && snappedPos < m_selectionGroup->sceneBoundingRect().left()) { AbstractClipItem *item = static_cast (collidingItems.at(i)); // Moving backward, determine best pos QPainterPath clipPath; clipPath.addRect(item->sceneBoundingRect()); QPainterPath res = shape.intersected(clipPath); offset = qMax(offset, (int)(res.boundingRect().width() + 0.5)); } } snappedPos += offset; // make sure we have no collision shape = m_selectionGroup->transitionGroupShape(QPointF(snappedPos - m_selectionGroup->sceneBoundingRect().left(), 0)); collidingItems = scene()->items(shape, Qt::IntersectsItemShape); collidingItems.removeAll(m_selectionGroup); for (int i = 0; i < children.count(); ++i) { if (children.at(i)->type() == GroupWidget) { QList subchildren = children.at(i)->childItems(); for (int j = 0; j < subchildren.count(); ++j) collidingItems.removeAll(subchildren.at(j)); } collidingItems.removeAll(children.at(i)); } for (int i = 0; i < collidingItems.count(); ++i) { if (collidingItems.at(i)->type() == TransitionWidget) { collision = true; break; } } } if (!collision) m_selectionGroup->setTransform(QTransform::fromTranslate(snappedPos - m_selectionGroup->sceneBoundingRect().left(), 0), true); } // virtual void CustomTrackView::mouseMoveEvent(QMouseEvent * event) { int pos = event->x(); int mappedXPos = qMax((int)(mapToScene(event->pos()).x()), 0); double snappedPos = getSnapPointForPos(mappedXPos); emit mousePosition(mappedXPos); if (m_cutLine) { m_cutLine->setPos(mappedXPos, getPositionFromTrack(getTrackFromPos(mapToScene(event->pos()).y()))); } if (m_moveOpMode == Seek && event->buttons() != Qt::NoButton) { QGraphicsView::mouseMoveEvent(event); if (mappedXPos != m_document->renderer()->getCurrentSeekPosition() && mappedXPos != cursorPos()) { seekCursorPos(mappedXPos); slotCheckPositionScrolling(); } return; } if (m_moveOpMode == ScrollTimeline) { QGraphicsView::mouseMoveEvent(event); return; } if (event->buttons() & Qt::MidButton) return; if (m_moveOpMode == RubberSelection) { QGraphicsView::mouseMoveEvent(event); return; } if (m_moveOpMode == WaitingForConfirm && event->buttons() != Qt::NoButton) { bool move = (event->pos() - m_clickEvent).manhattanLength() >= QApplication::startDragDistance(); if (move) { m_moveOpMode = m_operationMode; } } if (m_moveOpMode != None && m_moveOpMode != WaitingForConfirm && event->buttons() != Qt::NoButton) { if (m_dragItem && m_operationMode != ZoomTimeline) m_clipDrag = true; if (m_dragItem && m_tool == SelectTool) { if (m_moveOpMode == MoveOperation && m_clipDrag) { QGraphicsView::mouseMoveEvent(event); // If mouse is at a border of the view, scroll if (pos < 5) { m_scrollOffset = -30; m_scrollTimer.start(); } else if (viewport()->width() - pos < 10) { m_scrollOffset = 30; m_scrollTimer.start(); } else if (m_scrollTimer.isActive()) { m_scrollTimer.stop(); } } else if (m_moveOpMode == ResizeStart) { m_document->renderer()->pause(); if (!m_controlModifier && m_dragItem->type() == AVWidget && m_dragItem->parentItem() && m_dragItem->parentItem() != m_selectionGroup) { AbstractGroupItem *parent = static_cast (m_dragItem->parentItem()); if (parent) parent->resizeStart((int)(snappedPos - m_dragItemInfo.startPos.frames(m_document->fps()))); } else { m_dragItem->resizeStart((int)(snappedPos), true, false); } QString crop = m_document->timecode().getDisplayTimecode(m_dragItem->cropStart(), KdenliveSettings::frametimecode()); QString duration = m_document->timecode().getDisplayTimecode(m_dragItem->cropDuration(), KdenliveSettings::frametimecode()); QString offset = m_document->timecode().getDisplayTimecode(m_dragItem->cropStart() - m_dragItemInfo.cropStart, KdenliveSettings::frametimecode()); emit displayMessage(i18n("Crop from start:") + ' ' + crop + ' ' + i18n("Duration:") + ' ' + duration + ' ' + i18n("Offset:") + ' ' + offset, InformationMessage); } else if (m_moveOpMode == ResizeEnd) { m_document->renderer()->pause(); if (!m_controlModifier && m_dragItem->type() == AVWidget && m_dragItem->parentItem() && m_dragItem->parentItem() != m_selectionGroup) { AbstractGroupItem *parent = static_cast (m_dragItem->parentItem()); if (parent) { parent->resizeEnd((int)(snappedPos - m_dragItemInfo.endPos.frames(m_document->fps()))); } } else { m_dragItem->resizeEnd((int)(snappedPos), false); } QString duration = m_document->timecode().getDisplayTimecode(m_dragItem->cropDuration(), KdenliveSettings::frametimecode()); QString offset = m_document->timecode().getDisplayTimecode(m_dragItem->cropDuration() - m_dragItemInfo.cropDuration, KdenliveSettings::frametimecode()); emit displayMessage(i18n("Duration:") + ' ' + duration + ' ' + i18n("Offset:") + ' ' + offset, InformationMessage); } else if (m_moveOpMode == FadeIn) { static_cast(m_dragItem)->setFadeIn(static_cast(mappedXPos - m_dragItem->startPos().frames(m_document->fps()))); } else if (m_moveOpMode == FadeOut) { static_cast(m_dragItem)->setFadeOut(static_cast(m_dragItem->endPos().frames(m_document->fps()) - mappedXPos)); } else if (m_moveOpMode == KeyFrame) { GenTime keyFramePos = GenTime(mappedXPos, m_document->fps()) - m_dragItem->startPos(); double value = m_dragItem->mapFromScene(mapToScene(event->pos()).toPoint()).y(); m_dragItem->updateKeyFramePos(keyFramePos.frames(fps()), value); QString position = m_document->timecode().getDisplayTimecodeFromFrames(m_dragItem->selectedKeyFramePos(), KdenliveSettings::frametimecode()); emit displayMessage(position + " : " + QString::number(m_dragItem->editedKeyFrameValue()), InformationMessage); } removeTipAnimation(); event->accept(); return; } else if (m_moveOpMode == MoveGuide) { removeTipAnimation(); QGraphicsView::mouseMoveEvent(event); return; } else if (m_moveOpMode == Spacer && m_selectionGroup) { // spacer tool snappedPos = getSnapPointForPos(mappedXPos + m_spacerOffset); if (snappedPos < 0) snappedPos = 0; spaceToolMoveToSnapPos(snappedPos); } } if (m_tool == SpacerTool) { setCursor(m_spacerCursor); event->accept(); return; } QList itemList = items(event->pos()); QGraphicsRectItem *item = NULL; bool abort = false; GuideManager::checkOperation(itemList, this, event, m_operationMode, abort); if (abort) { return; } if (!abort) { for (int i = 0; i < itemList.count(); ++i) { if (itemList.at(i)->type() == AVWidget || itemList.at(i)->type() == TransitionWidget) { item = (QGraphicsRectItem*) itemList.at(i); break; } } } switch (m_tool) { case RazorTool: setCursor(m_razorCursor); RazorManager::checkOperation(item, this, event, mappedXPos, m_operationMode, abort); break; case SelectTool: default: SelectManager::checkOperation(item, this, event, m_selectionGroup, m_operationMode, m_moveOpMode); break; } } QString CustomTrackView::getDisplayTimecode(const GenTime &time) const { return m_document->timecode().getDisplayTimecode(time, KdenliveSettings::frametimecode()); } QString CustomTrackView::getDisplayTimecodeFromFrames(int frames) const { return m_document->timecode().getDisplayTimecodeFromFrames(frames, KdenliveSettings::frametimecode()); } void CustomTrackView::graphicsViewMouseEvent(QMouseEvent * event) { QGraphicsView::mouseMoveEvent(event); } void CustomTrackView::createRectangleSelection(QMouseEvent * event) { setDragMode(QGraphicsView::RubberBandDrag); setViewportUpdateMode(QGraphicsView::FullViewportUpdate); if (!(event->modifiers() & Qt::ControlModifier)) { resetSelectionGroup(); if (m_dragItem) { emit clipItemSelected(NULL); m_dragItem->setMainSelectedClip(false); m_dragItem = NULL; } scene()->clearSelection(); } m_moveOpMode = RubberSelection; QGraphicsView::mousePressEvent(event); } QList CustomTrackView::selectAllItemsToTheRight(int x) { return items(x, 1, mapFromScene(sceneRect().width(), 0).x() - x, sceneRect().height()); } int CustomTrackView::spaceToolSelectTrackOnly(int track, QList &selection) { if (m_timeline->getTrackInfo(track).isLocked) { // Cannot use spacer on locked track emit displayMessage(i18n("Cannot use spacer in a locked track"), ErrorMessage); return -1; } QRectF rect(mapToScene(m_clickEvent).x(), getPositionFromTrack(track) + m_tracksHeight / 2, sceneRect().width() - mapToScene(m_clickEvent).x(), m_tracksHeight / 2 - 2); bool isOk; selection = checkForGroups(rect, &isOk); if (!isOk) { // groups found on track, do not allow the move emit displayMessage(i18n("Cannot use spacer in a track with a group"), ErrorMessage); return -1; } //qDebug() << "SPACER TOOL + CTRL, SELECTING ALL CLIPS ON TRACK " << track << " WITH SELECTION RECT " << m_clickEvent.x() << '/' << track * m_tracksHeight + 1 << "; " << mapFromScene(sceneRect().width(), 0).x() - m_clickEvent.x() << '/' << m_tracksHeight - 2; return 0; } void CustomTrackView::createGroupForSelectedItems(QList &selection) { QList offsetList; // create group to hold selected items m_selectionMutex.lock(); m_selectionGroup = new AbstractGroupItem(m_document->fps()); scene()->addItem(m_selectionGroup); for (int i = 0; i < selection.count(); ++i) { if (selection.at(i)->parentItem() == 0 && (selection.at(i)->type() == AVWidget || selection.at(i)->type() == TransitionWidget)) { AbstractClipItem *item = static_cast(selection.at(i)); if (item->isItemLocked()) continue; offsetList.append(item->startPos()); offsetList.append(item->endPos()); m_selectionGroup->addItem(selection.at(i)); } else if (selection.at(i)->type() == GroupWidget) { if (static_cast(selection.at(i))->isItemLocked()) continue; QList children = selection.at(i)->childItems(); for (int j = 0; j < children.count(); ++j) { AbstractClipItem *item = static_cast(children.at(j)); offsetList.append(item->startPos()); offsetList.append(item->endPos()); } m_selectionGroup->addItem(selection.at(i)); } else if (selection.at(i)->parentItem() && !selection.contains(selection.at(i)->parentItem())) { if (static_cast(selection.at(i)->parentItem())->isItemLocked()) continue; m_selectionGroup->addItem(selection.at(i)->parentItem()); } } m_spacerOffset = m_selectionGroup->sceneBoundingRect().left() - (int)(mapToScene(m_clickEvent).x()); m_selectionMutex.unlock(); if (!offsetList.isEmpty()) { qSort(offsetList); QList cleandOffsetList; GenTime startOffset = offsetList.takeFirst(); for (int k = 0; k < offsetList.size(); ++k) { GenTime newoffset = offsetList.at(k) - startOffset; if (newoffset != GenTime() && !cleandOffsetList.contains(newoffset)) { cleandOffsetList.append(newoffset); } } updateSnapPoints(NULL, cleandOffsetList, true); } } void CustomTrackView::spaceToolSelect(QMouseEvent * event) { QList selection; if (event->modifiers() == Qt::ControlModifier) { // Ctrl + click, select all items on track after click position int track = getTrackFromPos(mapToScene(m_clickEvent).y()); if (spaceToolSelectTrackOnly(track, selection)) return; } else { // Select all items on all tracks after click position selection = selectAllItemsToTheRight(event->pos().x()); //qDebug() << "SELELCTING ELEMENTS WITHIN =" << event->pos().x() << '/' << 1 << ", " << mapFromScene(sceneRect().width(), 0).x() - event->pos().x() << '/' << sceneRect().height(); } createGroupForSelectedItems(selection); m_operationMode = Spacer; } void CustomTrackView::selectItemsRightOfFrame(int frame) { QList selection = selectAllItemsToTheRight(mapFromScene(frame, 1).x()); createGroupForSelectedItems(selection); } void CustomTrackView::updateTimelineSelection() { if (m_dragItem) { m_dragItem->setZValue(99); if (m_dragItem->parentItem()) m_dragItem->parentItem()->setZValue(99); // clip selected, update effect stack if (m_dragItem->type() == AVWidget && !m_dragItem->isItemLocked()) { ClipItem *selected = static_cast (m_dragItem); emit clipItemSelected(selected, false); } else { emit clipItemSelected(NULL); } if (m_dragItem->type() == TransitionWidget && m_dragItem->isEnabled()) { // update transition menu action m_autoTransition->setChecked(static_cast(m_dragItem)->isAutomatic()); m_autoTransition->setEnabled(true); // A transition is selected QPoint p; ClipItem *transitionClip = getClipItemAtStart(m_dragItemInfo.startPos, m_dragItemInfo.track); if (transitionClip && transitionClip->binClip()) { int frameWidth = transitionClip->binClip()->getProducerIntProperty(QStringLiteral("meta.media.width")); int frameHeight = transitionClip->binClip()->getProducerIntProperty(QStringLiteral("meta.media.height")); double factor = transitionClip->binClip()->getProducerProperty(QStringLiteral("aspect_ratio")).toDouble(); if (factor == 0) factor = 1.0; p.setX((int)(frameWidth * factor + 0.5)); p.setY(frameHeight); } emit transitionItemSelected(static_cast (m_dragItem), getPreviousVideoTrack(m_dragItem->track()), p); } else { emit transitionItemSelected(NULL); m_autoTransition->setEnabled(false); } } else { emit clipItemSelected(NULL); emit transitionItemSelected(NULL); m_autoTransition->setEnabled(false); } } // virtual void CustomTrackView::mousePressEvent(QMouseEvent * event) { setFocus(Qt::MouseFocusReason); m_menuPosition = QPoint(); if (m_moveOpMode == MoveOperation) { // click while dragging, ignore event->ignore(); return; } m_moveOpMode = WaitingForConfirm; m_clipDrag = false; // special cases (middle click button or ctrl / shift click) if (event->button() == Qt::MidButton) { if (m_operationMode == KeyFrame) { if (m_dragItem->type() == AVWidget) { ClipItem *item = static_cast(m_dragItem); m_dragItem->insertKeyframe(item->getEffectAtIndex(item->selectedEffectIndex()), m_dragItem->selectedKeyFramePos(), -1, true); m_dragItem->update(); } } else { emit playMonitor(); m_operationMode = None; } return; } if (event->button() == Qt::LeftButton) { if (event->modifiers() & Qt::ShiftModifier && m_tool == SelectTool) { createRectangleSelection(event); return; } if (m_tool != RazorTool) activateMonitor(); else if (m_document->renderer()->isPlaying()) { m_document->renderer()->pause(); return; } } m_dragGuide = NULL; m_clickEvent = event->pos(); // check item under mouse QList collisionList = items(m_clickEvent); if (event->button() == Qt::LeftButton && event->modifiers() == Qt::ControlModifier && m_tool != SpacerTool && collisionList.count() == 0) { // Pressing Ctrl + left mouse button in an empty area scrolls the timeline setDragMode(QGraphicsView::ScrollHandDrag); m_moveOpMode = ScrollTimeline; QGraphicsView::mousePressEvent(event); return; } // if a guide and a clip were pressed, just select the guide for (int i = 0; i < collisionList.count(); ++i) { if (collisionList.at(i)->type() == GUIDEITEM) { // a guide item was pressed m_dragGuide = static_cast(collisionList.at(i)); if (event->button() == Qt::LeftButton) { // move it m_dragGuide->setFlag(QGraphicsItem::ItemIsMovable, true); m_operationMode = MoveGuide; // deselect all clips so that only the guide will move m_scene->clearSelection(); resetSelectionGroup(false); updateSnapPoints(NULL); QGraphicsView::mousePressEvent(event); return; } else // show context menu break; } } // Find first clip, transition or group under mouse (when no guides selected) int ct = 0; AbstractGroupItem *dragGroup = NULL; AbstractClipItem *collisionClip = NULL; bool found = false; QStringList lockedTracks; double yOffset = 0; m_selectionMutex.lock(); while (!m_dragGuide && ct < collisionList.count()) { if (collisionList.at(ct)->type() == AVWidget || collisionList.at(ct)->type() == TransitionWidget) { collisionClip = static_cast (collisionList.at(ct)); if (collisionClip->isItemLocked() || !collisionClip->isEnabled()) { ct++; continue; } if (collisionClip == m_dragItem) { collisionClip = NULL; } else { if (m_dragItem) { m_dragItem->setMainSelectedClip(false); } m_dragItem = collisionClip; m_dragItem->setMainSelectedClip(true); } found = true; for (int i = 1; i < m_timeline->tracksCount(); ++i) { if (m_timeline->getTrackInfo(i).isLocked) lockedTracks << QString::number(i); } yOffset = mapToScene(m_clickEvent).y() - m_dragItem->scenePos().y(); m_dragItem->setProperty("y_absolute", yOffset); m_dragItem->setProperty("locked_tracks", lockedTracks); m_dragItemInfo = m_dragItem->info(); if (m_selectionGroup) { m_selectionGroup->setProperty("y_absolute", yOffset); m_selectionGroup->setProperty("locked_tracks", lockedTracks); } if (m_dragItem->parentItem() && m_dragItem->parentItem()->type() == GroupWidget && m_dragItem->parentItem() != m_selectionGroup) { QGraphicsItem *topGroup = m_dragItem->parentItem(); while (topGroup->parentItem() && topGroup->parentItem()->type() == GroupWidget && topGroup->parentItem() != m_selectionGroup) { topGroup = topGroup->parentItem(); } dragGroup = static_cast (topGroup); dragGroup->setProperty("y_absolute", yOffset); dragGroup->setProperty("locked_tracks", lockedTracks); } break; } ct++; } m_selectionMutex.unlock(); if (!found) { if (m_dragItem) m_dragItem->setMainSelectedClip(false); m_dragItem = NULL; } // Add shadow to dragged item, currently disabled because of painting artifacts /*if (m_dragItem) { QGraphicsDropShadowEffect *eff = new QGraphicsDropShadowEffect(); eff->setBlurRadius(5); eff->setOffset(3, 3); m_dragItem->setGraphicsEffect(eff); }*/ // No item under click if (m_dragItem == NULL && m_tool != SpacerTool) { resetSelectionGroup(false); m_scene->clearSelection(); updateClipTypeActions(NULL); updateTimelineSelection(); if (event->button() == Qt::LeftButton) { m_moveOpMode = Seek; setCursor(Qt::ArrowCursor); seekCursorPos((int)(mapToScene(event->x(), 0).x())); event->setAccepted(true); QGraphicsView::mousePressEvent(event); return; } } // context menu requested if (event->button() == Qt::RightButton) { // Check if we want keyframes context menu if (!m_dragItem && !m_dragGuide) { // check if there is a guide close to mouse click QList guidesCollisionList = items(event->pos().x() - 5, event->pos().y(), 10, 2); // a rect of height < 2 does not always collide with the guide for (int i = 0; i < guidesCollisionList.count(); ++i) { if (guidesCollisionList.at(i)->type() == GUIDEITEM) { m_dragGuide = static_cast (guidesCollisionList.at(i)); break; } } // keep this to support multiple guides context menu in the future (?) /*if (guidesCollisionList.at(0)->type() != GUIDEITEM) guidesCollisionList.removeAt(0); } if (!guidesCollisionList.isEmpty()) m_dragGuide = static_cast (guidesCollisionList.at(0));*/ } m_menuPosition = m_clickEvent; /* if (dragGroup == NULL) { if (m_dragItem && m_dragItem->parentItem() && m_dragItem->parentItem() != m_selectionGroup) dragGroup = static_cast (m_dragItem->parentItem()); } */ if (m_dragItem) { if (!m_dragItem->isSelected()) { resetSelectionGroup(); m_scene->clearSelection(); m_dragItem->setSelected(true); } m_dragItem->setZValue(99); if (m_dragItem->parentItem()) m_dragItem->parentItem()->setZValue(99); } event->accept(); updateTimelineSelection(); return; } if (event->button() == Qt::LeftButton) { if (m_tool == SpacerTool) { resetSelectionGroup(false); m_scene->clearSelection(); updateClipTypeActions(NULL); spaceToolSelect(event); QGraphicsView::mousePressEvent(event); return; } // Razor tool if (m_tool == RazorTool) { if (!m_dragItem) { // clicked in empty area, ignore event->accept(); return; } GenTime cutPos = GenTime((int)(mapToScene(event->pos()).x()), m_document->fps()); if (m_dragItem->type() == TransitionWidget) { emit displayMessage(i18n("Cannot cut a transition"), ErrorMessage); } else { m_document->renderer()->pause(); if (m_dragItem->parentItem() && m_dragItem->parentItem() != m_selectionGroup) { razorGroup(static_cast(m_dragItem->parentItem()), cutPos); } else { ClipItem *clip = static_cast (m_dragItem); if (cutPos > clip->startPos() && cutPos < clip->endPos()) { RazorClipCommand* command = new RazorClipCommand(this, clip->info(), clip->effectList(), cutPos); m_commandStack->push(command); } } } m_dragItem->setMainSelectedClip(false); m_dragItem = NULL; event->accept(); return; } } bool itemSelected = false; if (m_dragItem->isSelected()) { itemSelected = true; } else if (m_dragItem->parentItem() && m_dragItem->parentItem()->isSelected()) { itemSelected = true; } else if (dragGroup && dragGroup->isSelected()) { itemSelected = true; } bool selected = !m_dragItem->isSelected(); QGraphicsView::mousePressEvent(event); if (event->modifiers() & Qt::ControlModifier) { // Handle ctrl click events resetSelectionGroup(); m_dragItem->setSelected(selected); groupSelectedItems(QList (), false, true); if (selected) { m_selectionMutex.lock(); if (m_selectionGroup) { m_selectionGroup->setProperty("y_absolute", yOffset); m_selectionGroup->setProperty("locked_tracks", lockedTracks); } m_selectionMutex.unlock(); } else { m_dragItem->setMainSelectedClip(false); m_dragItem = NULL; } updateTimelineSelection(); return; } if (itemSelected == false) { // User clicked a non selected item, select it resetSelectionGroup(false); m_scene->clearSelection(); m_dragItem->setSelected(true); m_dragItem->setZValue(99); if (m_dragItem->parentItem()) m_dragItem->parentItem()->setZValue(99); // A refresh seems necessary otherwise in zoomed mode, some clips disappear //viewport()->update(); /*if (event->modifiers() == Qt::ControlModifier) { event->ignore(); } else if (!itemSelected) { event->accept(); }*/ //if (event->button() == Qt::LeftButton) QGraphicsView::mousePressEvent(event); /*if (event->modifiers() == Qt::ControlModifier) { if (!selected) { // item was deselected, update m_dragItem groupSelectedItems(QList (), false, true); } }*/ /*if (!itemSelected) { // Item was not selected, trigger click if (event->button() == Qt::LeftButton) QGraphicsView::mousePressEvent(event); } else { // Item was selected and we ctrl+clicked on it. Deselect it but don't allow moving it event->ignore(); }*/ /* if (dragGroup) { dragGroup->setSelected(selected); QList children = dragGroup->childItems(); for (int i = 0; i < children.count(); ++i) { children.at(i)->setSelected(selected); } if (dragGroup->parentItem()) { dragGroup->parentItem()->setSelected(selected); } } else m_dragItem->setSelected(selected);*/ //qDebug()<<" / / CURRNETLY SELECTED ITEM: "<startPos().frames(25); if (m_dragItem && m_dragItem->type() == AVWidget) { ClipItem *clip = static_cast(m_dragItem); updateClipTypeActions(dragGroup == NULL ? clip : NULL); m_pasteEffectsAction->setEnabled(m_copiedItems.count() == 1); } else updateClipTypeActions(NULL); } else { m_selectionMutex.lock(); if (m_selectionGroup) { QList children = m_selectionGroup->childItems(); for (int i = 0; i < children.count(); ++i) { children.at(i)->setSelected(itemSelected); } m_selectionGroup->setSelected(itemSelected); } if (dragGroup) { dragGroup->setSelected(itemSelected); } m_dragItem->setSelected(itemSelected); m_selectionMutex.unlock(); } m_selectionMutex.lock(); if (m_selectionGroup) { m_selectionGroup->setProperty("y_absolute", yOffset); m_selectionGroup->setProperty("locked_tracks", lockedTracks); } m_selectionMutex.unlock(); //if (collisionClip != NULL || m_dragItem == NULL) { updateTimelineSelection(); //} // If clicked item is selected, allow move //if (!(event->modifiers() | Qt::ControlModifier) && m_operationMode == NONE) //QGraphicsView::mousePressEvent(event); if (event->button() == Qt::LeftButton) { if (m_dragItem) { if (m_selectionGroup && m_dragItem->parentItem() == m_selectionGroup) { // all other modes break the selection, so the user probably wants to move it m_operationMode = MoveOperation; } else { if (m_dragItem->rect().width() * transform().m11() < 15) { // If the item is very small, only allow move m_operationMode = MoveOperation; } else m_operationMode = m_dragItem->operationMode(m_dragItem->mapFromScene(mapToScene(event->pos()))); if (m_operationMode == ResizeEnd) { // FIXME: find a better way to avoid move in ClipItem::itemChange? m_dragItem->setProperty("resizingEnd", true); } } } else m_operationMode = None; } m_controlModifier = (event->modifiers() == Qt::ControlModifier); // Update snap points if (m_selectionGroup == NULL) { if (m_operationMode == ResizeEnd || m_operationMode == ResizeStart) updateSnapPoints(NULL); else updateSnapPoints(m_dragItem); } else { m_selectionMutex.lock(); QList offsetList; QList children = m_selectionGroup->childItems(); for (int i = 0; i < children.count(); ++i) { if (children.at(i)->type() == AVWidget || children.at(i)->type() == TransitionWidget) { AbstractClipItem *item = static_cast (children.at(i)); offsetList.append(item->startPos()); offsetList.append(item->endPos()); } } if (!offsetList.isEmpty()) { qSort(offsetList); GenTime startOffset = offsetList.takeFirst(); QList cleandOffsetList; for (int k = 0; k < offsetList.size(); ++k) { GenTime newoffset = offsetList.at(k) - startOffset; if (newoffset != GenTime() && !cleandOffsetList.contains(newoffset)) { cleandOffsetList.append(newoffset); } } updateSnapPoints(NULL, cleandOffsetList, true); } m_selectionMutex.unlock(); } if (m_operationMode == KeyFrame) { m_dragItem->prepareKeyframeMove(); return; } else if (m_operationMode == MoveOperation) { setCursor(Qt::ClosedHandCursor); } else if (m_operationMode == TransitionStart && event->modifiers() != Qt::ControlModifier) { ItemInfo info; info.startPos = m_dragItem->startPos(); info.track = m_dragItem->track(); int transitiontrack = getPreviousVideoTrack(info.track); ClipItem *transitionClip = NULL; if (transitiontrack != 0) transitionClip = getClipItemAtMiddlePoint(info.startPos.frames(m_document->fps()), transitiontrack); if (transitionClip && transitionClip->endPos() < m_dragItem->endPos()) { info.endPos = transitionClip->endPos(); } else { GenTime transitionDuration(65, m_document->fps()); if (m_dragItem->cropDuration() < transitionDuration) info.endPos = m_dragItem->endPos(); else info.endPos = info.startPos + transitionDuration; } if (info.endPos == info.startPos) info.endPos = info.startPos + GenTime(65, m_document->fps()); // Check there is no other transition at that place double startY = getPositionFromTrack(info.track) + 1 + m_tracksHeight / 2; QRectF r(info.startPos.frames(m_document->fps()), startY, (info.endPos - info.startPos).frames(m_document->fps()), m_tracksHeight / 2); QList selection = m_scene->items(r); bool transitionAccepted = true; for (int i = 0; i < selection.count(); ++i) { if (selection.at(i)->type() == TransitionWidget) { Transition *tr = static_cast (selection.at(i)); if (tr->startPos() - info.startPos > GenTime(5, m_document->fps())) { if (tr->startPos() < info.endPos) info.endPos = tr->startPos(); } else transitionAccepted = false; } } if (transitionAccepted) slotAddTransition(static_cast(m_dragItem), info, transitiontrack); else emit displayMessage(i18n("Cannot add transition"), ErrorMessage); } else if (m_operationMode == TransitionEnd && event->modifiers() != Qt::ControlModifier) { ItemInfo info; info.endPos = GenTime(m_dragItem->endPos().frames(m_document->fps()), m_document->fps()); info.track = m_dragItem->track(); int transitiontrack = getPreviousVideoTrack(info.track); ClipItem *transitionClip = NULL; if (transitiontrack != 0) transitionClip = getClipItemAtMiddlePoint(info.endPos.frames(m_document->fps()), transitiontrack); if (transitionClip && transitionClip->startPos() > m_dragItem->startPos()) { info.startPos = transitionClip->startPos(); } else { GenTime transitionDuration(65, m_document->fps()); if (m_dragItem->cropDuration() < transitionDuration) info.startPos = m_dragItem->startPos(); else info.startPos = info.endPos - transitionDuration; } if (info.endPos == info.startPos) info.startPos = info.endPos - GenTime(65, m_document->fps()); QDomElement transition = MainWindow::transitions.getEffectByTag(QStringLiteral("luma"), QStringLiteral("dissolve")).cloneNode().toElement(); EffectsList::setParameter(transition, QStringLiteral("reverse"), QStringLiteral("1")); // Check there is no other transition at that place double startY = getPositionFromTrack(info.track) + 1 + m_tracksHeight / 2; QRectF r(info.startPos.frames(m_document->fps()), startY, (info.endPos - info.startPos).frames(m_document->fps()), m_tracksHeight / 2); QList selection = m_scene->items(r); bool transitionAccepted = true; for (int i = 0; i < selection.count(); ++i) { if (selection.at(i)->type() == TransitionWidget) { Transition *tr = static_cast (selection.at(i)); if (info.endPos - tr->endPos() > GenTime(5, m_document->fps())) { if (tr->endPos() > info.startPos) info.startPos = tr->endPos(); } else transitionAccepted = false; } } if (transitionAccepted) slotAddTransition(static_cast(m_dragItem), info, transitiontrack, transition); else emit displayMessage(i18n("Cannot add transition"), ErrorMessage); } else if ((m_operationMode == ResizeStart || m_operationMode == ResizeEnd) && m_selectionGroup) { resetSelectionGroup(false); m_dragItem->setSelected(true); } } void CustomTrackView::rebuildGroup(int childTrack, const GenTime &childPos) { const QPointF p((int)childPos.frames(m_document->fps()), getPositionFromTrack(childTrack) + m_tracksHeight / 2); QList list = scene()->items(p); AbstractGroupItem *group = NULL; for (int i = 0; i < list.size(); ++i) { if (!list.at(i)->isEnabled()) continue; if (list.at(i)->type() == GroupWidget) { group = static_cast (list.at(i)); break; } } rebuildGroup(group); } void CustomTrackView::rebuildGroup(AbstractGroupItem *group) { if (group) { m_selectionMutex.lock(); if (group == m_selectionGroup) m_selectionGroup = NULL; QList children = group->childItems(); m_document->clipManager()->removeGroup(group); for (int i = 0; i < children.count(); ++i) { group->removeFromGroup(children.at(i)); } scene()->destroyItemGroup(group); m_selectionMutex.unlock(); groupSelectedItems(children, group != m_selectionGroup, true); } } void CustomTrackView::resetSelectionGroup(bool selectItems) { QMutexLocker lock(&m_selectionMutex); if (m_selectionGroup) { // delete selection group bool snap = KdenliveSettings::snaptopoints(); KdenliveSettings::setSnaptopoints(false); QList children = m_selectionGroup->childItems(); scene()->destroyItemGroup(m_selectionGroup); m_selectionGroup = NULL; for (int i = 0; i < children.count(); ++i) { if (children.at(i)->parentItem() == 0) { if ((children.at(i)->type() == AVWidget || children.at(i)->type() == TransitionWidget)) { if (!static_cast (children.at(i))->isItemLocked()) { children.at(i)->setFlag(QGraphicsItem::ItemIsMovable, true); children.at(i)->setSelected(selectItems); } } else if (children.at(i)->type() == GroupWidget) { children.at(i)->setFlag(QGraphicsItem::ItemIsMovable, true); children.at(i)->setSelected(selectItems); } } } KdenliveSettings::setSnaptopoints(snap); } } void CustomTrackView::groupSelectedItems(QList selection, bool createNewGroup, bool selectNewGroup) { QMutexLocker lock(&m_selectionMutex); if (m_selectionGroup) { qDebug() << "///// ERROR, TRYING TO OVERRIDE EXISTING GROUP"; return; } if (selection.isEmpty()) selection = m_scene->selectedItems(); // Split groups and items QSet groupsList; QSet itemsList; for (int i = 0; i < selection.count(); ++i) { if (selectNewGroup) selection.at(i)->setSelected(true); if (selection.at(i)->type() == GroupWidget) { AbstractGroupItem *it = static_cast (selection.at(i)); while (it->parentItem() && it->parentItem()->type() == GroupWidget) { it = static_cast (it->parentItem()); } if (!it || it->isItemLocked()) continue; groupsList.insert(it); } } for (int i = 0; i < selection.count(); ++i) { if (selection.at(i)->type() == AVWidget || selection.at(i)->type() == TransitionWidget) { if (selection.at(i)->parentItem() && selection.at(i)->parentItem()->type() == GroupWidget) { AbstractGroupItem *it = static_cast (selection.at(i)->parentItem()); while (it->parentItem() && it->parentItem()->type() == GroupWidget) { it = static_cast (it->parentItem()); } if (!it || it->isItemLocked()) continue; groupsList.insert(it); } else { AbstractClipItem *it = static_cast (selection.at(i)); if (!it || it->isItemLocked()) continue; itemsList.insert(selection.at(i)); } } } if (itemsList.isEmpty() && groupsList.isEmpty()) return; if (itemsList.count() == 1 && groupsList.isEmpty()) { // only one item selected: QSetIterator it(itemsList); m_dragItem = static_cast(it.next()); m_dragItem->setMainSelectedClip(true); m_dragItem->setSelected(true); return; } QRectF rectUnion; // Find top left position of selection foreach (const QGraphicsItemGroup *value, groupsList) { rectUnion = rectUnion.united(value->sceneBoundingRect()); } foreach (const QGraphicsItem *value, itemsList) { rectUnion = rectUnion.united(value->sceneBoundingRect()); } bool snap = KdenliveSettings::snaptopoints(); KdenliveSettings::setSnaptopoints(false); if (createNewGroup) { AbstractGroupItem *newGroup = m_document->clipManager()->createGroup(); newGroup->setPos(rectUnion.left(), rectUnion.top() - 1); QPointF diff = newGroup->pos(); newGroup->setTransform(QTransform::fromTranslate(-diff.x(), -diff.y()), true); //newGroup->translate((int) -rectUnion.left(), (int) -rectUnion.top() + 1); // Check if we are trying to include a group in a group foreach (QGraphicsItemGroup *value, groupsList) { newGroup->addItem(value); } foreach (QGraphicsItemGroup *value, groupsList) { QList children = value->childItems(); for (int i = 0; i < children.count(); ++i) { if (children.at(i)->type() == AVWidget || children.at(i)->type() == TransitionWidget) itemsList.insert(children.at(i)); } AbstractGroupItem *grp = static_cast(value); m_document->clipManager()->removeGroup(grp); if (grp == m_selectionGroup) m_selectionGroup = NULL; scene()->destroyItemGroup(grp); } foreach (QGraphicsItem *value, itemsList) { newGroup->addItem(value); } scene()->addItem(newGroup); KdenliveSettings::setSnaptopoints(snap); if (selectNewGroup) newGroup->setSelected(true); } else { m_selectionGroup = new AbstractGroupItem(m_document->fps()); m_selectionGroup->setPos(rectUnion.left(), rectUnion.top() - 1); QPointF diff = m_selectionGroup->pos(); //m_selectionGroup->translate((int) - rectUnion.left(), (int) -rectUnion.top() + 1); m_selectionGroup->setTransform(QTransform::fromTranslate(- diff.x(), -diff.y()), true); scene()->addItem(m_selectionGroup); foreach (QGraphicsItemGroup *value, groupsList) { m_selectionGroup->addItem(value); } foreach (QGraphicsItem *value, itemsList) { m_selectionGroup->addItem(value); } KdenliveSettings::setSnaptopoints(snap); if (m_selectionGroup) { m_selectionGroupInfo.startPos = GenTime(m_selectionGroup->scenePos().x(), m_document->fps()); m_selectionGroupInfo.track = m_selectionGroup->track(); if (selectNewGroup) m_selectionGroup->setSelected(true); } } } void CustomTrackView::mouseDoubleClickEvent(QMouseEvent *event) { if (m_dragItem && m_dragItem->keyframesCount() > 0) { // add keyframe GenTime keyFramePos = GenTime((int)(mapToScene(event->pos()).x()), m_document->fps()) - m_dragItem->startPos();// + m_dragItem->cropStart(); int single = m_dragItem->keyframesCount(); double val = m_dragItem->getKeyFrameClipHeight(mapToScene(event->pos()).y() - m_dragItem->scenePos().y()); ClipItem * item = static_cast (m_dragItem); QDomElement oldEffect = item->selectedEffect().cloneNode().toElement(); if (single == 1) { item->insertKeyframe(item->getEffectAtIndex(item->selectedEffectIndex()), (item->cropDuration()).frames(m_document->fps()) - 1, -1, true); } //QString previous = item->keyframes(item->selectedEffectIndex()); item->insertKeyframe(item->getEffectAtIndex(item->selectedEffectIndex()), keyFramePos.frames(m_document->fps()), val); //QString next = item->keyframes(item->selectedEffectIndex()); QDomElement newEffect = item->selectedEffect().cloneNode().toElement(); EditEffectCommand *command = new EditEffectCommand(this, item->track(), item->startPos(), oldEffect, newEffect, item->selectedEffectIndex(), false, false); m_commandStack->push(command); updateEffect(item->track(), item->startPos(), item->selectedEffect()); emit clipItemSelected(item, item->selectedEffectIndex()); } else if (m_dragItem && !m_dragItem->isItemLocked()) { editItemDuration(); } else { QList collisionList = items(event->pos()); if (collisionList.count() == 1 && collisionList.at(0)->type() == GUIDEITEM) { Guide *editGuide = static_cast(collisionList.at(0)); if (editGuide) slotEditGuide(editGuide->info()); } } } void CustomTrackView::editItemDuration() { AbstractClipItem *item; if (m_dragItem) { item = m_dragItem; } else { if (m_scene->selectedItems().count() == 1) { item = static_cast (m_scene->selectedItems().at(0)); } else { if (m_scene->selectedItems().empty()) emit displayMessage(i18n("Cannot find clip to edit"), ErrorMessage); else emit displayMessage(i18n("Cannot edit the duration of multiple items"), ErrorMessage); return; } } if (!item) { emit displayMessage(i18n("Cannot find clip to edit"), ErrorMessage); return; } if (item->type() == GroupWidget || (item->parentItem() && item->parentItem()->type() == GroupWidget)) { emit displayMessage(i18n("Cannot edit an item in a group"), ErrorMessage); return; } if (!item->isItemLocked()) { GenTime minimum; GenTime maximum; if (item->type() == TransitionWidget) getTransitionAvailableSpace(item, minimum, maximum); else getClipAvailableSpace(item, minimum, maximum); QPointer d = new ClipDurationDialog(item, m_document->timecode(), minimum, maximum, this); if (d->exec() == QDialog::Accepted) { ItemInfo clipInfo = item->info(); ItemInfo startInfo = clipInfo; if (item->type() == TransitionWidget) { // move & resize transition clipInfo.startPos = d->startPos(); clipInfo.endPos = clipInfo.startPos + d->duration(); clipInfo.track = item->track(); MoveTransitionCommand *command = new MoveTransitionCommand(this, startInfo, clipInfo, true); updateTrackDuration(clipInfo.track, command); m_commandStack->push(command); } else { // move and resize clip ClipItem *clip = static_cast(item); QUndoCommand *moveCommand = new QUndoCommand(); moveCommand->setText(i18n("Edit clip")); if (d->duration() < item->cropDuration() || d->cropStart() != clipInfo.cropStart) { // duration was reduced, so process it first clipInfo.endPos = clipInfo.startPos + d->duration(); clipInfo.cropStart = d->cropStart(); resizeClip(startInfo, clipInfo); // TODO: find a way to apply adjusteffect after the resize command was done / undone new ResizeClipCommand(this, startInfo, clipInfo, false, true, moveCommand); adjustEffects(clip, startInfo, moveCommand); } if (d->startPos() != clipInfo.startPos) { startInfo = clipInfo; clipInfo.startPos = d->startPos(); clipInfo.endPos = item->endPos() + (clipInfo.startPos - startInfo.startPos); new MoveClipCommand(this, startInfo, clipInfo, true, moveCommand); } if (d->duration() > item->cropDuration()) { // duration was increased, so process it after move startInfo = clipInfo; clipInfo.endPos = clipInfo.startPos + d->duration(); clipInfo.cropStart = d->cropStart(); resizeClip(startInfo, clipInfo); // TODO: find a way to apply adjusteffect after the resize command was done / undone new ResizeClipCommand(this, startInfo, clipInfo, false, true, moveCommand); adjustEffects(clip, startInfo, moveCommand); } updateTrackDuration(clipInfo.track, moveCommand); m_commandStack->push(moveCommand); } } delete d; } else { emit displayMessage(i18n("Item is locked"), ErrorMessage); } } void CustomTrackView::contextMenuEvent(QContextMenuEvent * event) { if (m_operationMode == KeyFrame) { displayKeyframesMenu(event->globalPos(), m_dragItem); } else { displayContextMenu(event->globalPos(), m_dragItem); } event->accept(); } void CustomTrackView::displayKeyframesMenu(QPoint pos, AbstractClipItem *clip) { if (!m_timelineContextKeyframeMenu) { m_timelineContextKeyframeMenu = new QMenu(this); // Keyframe type widget m_selectKeyframeType = new KSelectAction(KoIconUtils::themedIcon(QStringLiteral("keyframes")), i18n("Interpolation"), this); QAction *discrete = new QAction(KoIconUtils::themedIcon(QStringLiteral("discrete")), i18n("Discrete"), this); discrete->setData((int) mlt_keyframe_discrete); discrete->setCheckable(true); m_selectKeyframeType->addAction(discrete); QAction *linear = new QAction(KoIconUtils::themedIcon(QStringLiteral("linear")), i18n("Linear"), this); linear->setData((int) mlt_keyframe_linear); linear->setCheckable(true); m_selectKeyframeType->addAction(linear); QAction *curve = new QAction(KoIconUtils::themedIcon(QStringLiteral("smooth")), i18n("Smooth"), this); curve->setData((int) mlt_keyframe_smooth); curve->setCheckable(true); m_selectKeyframeType->addAction(curve); m_timelineContextKeyframeMenu->addAction(m_selectKeyframeType); m_attachKeyframeToEnd = new QAction(i18n("Attach keyframe to end"), this); m_attachKeyframeToEnd->setCheckable(true); m_timelineContextKeyframeMenu->addAction(m_attachKeyframeToEnd); connect(m_selectKeyframeType, SIGNAL(triggered(QAction*)), this, SLOT(slotEditKeyframeType(QAction*))); connect(m_attachKeyframeToEnd, SIGNAL(triggered(bool)), this, SLOT(slotAttachKeyframeToEnd(bool))); } m_attachKeyframeToEnd->setChecked(clip->isAttachedToEnd()); m_selectKeyframeType->setCurrentAction(clip->parseKeyframeActions(m_selectKeyframeType->actions())); m_timelineContextKeyframeMenu->exec(pos); } void CustomTrackView::slotAttachKeyframeToEnd(bool attach) { ClipItem * item = static_cast (m_dragItem); QDomElement oldEffect = item->selectedEffect().cloneNode().toElement(); item->attachKeyframeToEnd(item->getEffectAtIndex(item->selectedEffectIndex()), attach); QDomElement newEffect = item->selectedEffect().cloneNode().toElement(); EditEffectCommand *command = new EditEffectCommand(this, item->track(), item->startPos(), oldEffect, newEffect, item->selectedEffectIndex(), false, false); m_commandStack->push(command); updateEffect(item->track(), item->startPos(), item->selectedEffect()); emit clipItemSelected(item, item->selectedEffectIndex()); } void CustomTrackView::slotEditKeyframeType(QAction *action) { int type = action->data().toInt(); ClipItem * item = static_cast (m_dragItem); QDomElement oldEffect = item->selectedEffect().cloneNode().toElement(); item->editKeyframeType(item->getEffectAtIndex(item->selectedEffectIndex()), type); QDomElement newEffect = item->selectedEffect().cloneNode().toElement(); EditEffectCommand *command = new EditEffectCommand(this, item->track(), item->startPos(), oldEffect, newEffect, item->selectedEffectIndex(), false, false); m_commandStack->push(command); updateEffect(item->track(), item->startPos(), item->selectedEffect()); emit clipItemSelected(item, item->selectedEffectIndex()); } void CustomTrackView::displayContextMenu(QPoint pos, AbstractClipItem *clip) { bool isGroup = clip != NULL && clip->parentItem() && clip->parentItem()->type() == GroupWidget && clip->parentItem() != m_selectionGroup; m_deleteGuide->setEnabled(m_dragGuide != NULL); m_editGuide->setEnabled(m_dragGuide != NULL); m_markerMenu->clear(); m_markerMenu->setEnabled(false); if (clip == NULL) { m_timelineContextMenu->popup(pos); } else if (isGroup) { m_pasteEffectsAction->setEnabled(m_copiedItems.count() == 1); m_ungroupAction->setEnabled(true); updateClipTypeActions(NULL); m_timelineContextClipMenu->popup(pos); } else { m_ungroupAction->setEnabled(false); if (clip->type() == AVWidget) { ClipItem *item = static_cast (clip); //build go to marker menu ClipController *controller = m_document->getClipController(item->getBinId()); if (controller) { QList markers = controller->commentedSnapMarkers(); int offset = (item->startPos()- item->cropStart()).frames(m_document->fps()); if (!markers.isEmpty()) { for (int i = 0; i < markers.count(); ++i) { int pos = (int) markers.at(i).time().frames(m_document->timecode().fps()); QString position = m_document->timecode().getTimecode(markers.at(i).time()) + ' ' + markers.at(i).comment(); QAction *go = m_markerMenu->addAction(position); go->setData(pos + offset); } } m_markerMenu->setEnabled(!m_markerMenu->isEmpty()); } updateClipTypeActions(item); m_pasteEffectsAction->setEnabled(m_copiedItems.count() == 1); m_timelineContextClipMenu->popup(pos); } else if (clip->type() == TransitionWidget) { m_timelineContextTransitionMenu->exec(pos); } } } void CustomTrackView::activateMonitor() { emit activateDocumentMonitor(); } void CustomTrackView::insertClipCut(const QString &id, int in, int out) { resetSelectionGroup(); ItemInfo info; info.startPos = GenTime(); info.cropStart = GenTime(in, m_document->fps()); info.endPos = GenTime(out - in, m_document->fps()); info.cropDuration = info.endPos - info.startPos; info.track = 0; // Check if clip can be inserted at that position ItemInfo pasteInfo = info; pasteInfo.startPos = GenTime(m_cursorPos, m_document->fps()); pasteInfo.endPos = pasteInfo.startPos + info.endPos; pasteInfo.track = selectedTrack(); bool ok = canBePastedTo(pasteInfo, AVWidget); if (!ok) { // Cannot be inserted at cursor pos, insert at end of track int duration = GenTime(m_timeline->track(pasteInfo.track)->length()).frames(m_document->fps()); pasteInfo.startPos = GenTime(duration, m_document->fps()); pasteInfo.endPos = pasteInfo.startPos + info.endPos; ok = canBePastedTo(pasteInfo, AVWidget); } if (!ok) { emit displayMessage(i18n("Cannot insert clip in timeline"), ErrorMessage); return; } // Add refresh command for undo QUndoCommand *addCommand = new QUndoCommand(); addCommand->setText(i18n("Add timeline clip")); new RefreshMonitorCommand(this, false, true, addCommand); new AddTimelineClipCommand(this, id, pasteInfo, EffectsList(), PlaylistState::Original, true, false, addCommand); new RefreshMonitorCommand(this, true, false, addCommand); updateTrackDuration(pasteInfo.track, addCommand); m_commandStack->push(addCommand); selectClip(true, false); // Automatic audio split if (KdenliveSettings::splitaudio()) splitAudio(false); } bool CustomTrackView::insertDropClips(const QMimeData *data, const QPoint &pos) { QPointF framePos = mapToScene(pos); int track = getTrackFromPos(framePos.y()); m_clipDrag = data->hasFormat(QStringLiteral("kdenlive/clip")) || data->hasFormat(QStringLiteral("kdenlive/producerslist")); // This is not a clip drag, maybe effect or other... if (!m_clipDrag) return false; m_scene->clearSelection(); if (m_dragItem) m_dragItem->setMainSelectedClip(false); m_dragItem = NULL; resetSelectionGroup(false); QMutexLocker lock(&m_selectionMutex); if (track <= 0 || track > m_timeline->tracksCount() - 1 || m_timeline->getTrackInfo(track).isLocked) return true; if (data->hasFormat(QStringLiteral("kdenlive/clip"))) { QStringList list = QString(data->data(QStringLiteral("kdenlive/clip"))).split(';'); ProjectClip *clip = m_document->getBinClip(list.at(0)); if (clip == NULL) { //qDebug() << " WARNING))))))))) CLIP NOT FOUND : " << list.at(0); return false; } if (!clip->isReady()) { emit displayMessage(i18n("Clip not ready"), ErrorMessage); return false; } ItemInfo info; info.startPos = GenTime(); info.cropStart = GenTime(list.at(1).toInt(), m_document->fps()); info.endPos = GenTime(list.at(2).toInt() - list.at(1).toInt(), m_document->fps()); info.cropDuration = info.endPos;// - info.startPos; info.track = 0; // Check if clip can be inserted at that position ItemInfo pasteInfo = info; pasteInfo.startPos = GenTime((int)(framePos.x() + 0.5), m_document->fps()); pasteInfo.endPos = pasteInfo.startPos + info.endPos; pasteInfo.track = track; framePos.setX((int)(framePos.x() + 0.5)); framePos.setY(getPositionFromTrack(track)); if (!canBePastedTo(pasteInfo, AVWidget)) { return true; } m_selectionGroup = new AbstractGroupItem(m_document->fps()); ClipItem *item = new ClipItem(clip, info, m_document->fps(), 1.0, 1, getFrameWidth()); m_selectionGroup->addItem(item); QList offsetList; offsetList.append(info.endPos); updateSnapPoints(NULL, offsetList); QStringList lockedTracks; for (int i = 1; i < m_timeline->tracksCount(); ++i) { if (m_timeline->getTrackInfo(i).isLocked) lockedTracks << QString::number(i); } m_selectionGroup->setProperty("locked_tracks", lockedTracks); m_selectionGroup->setPos(framePos); scene()->addItem(m_selectionGroup); m_selectionGroup->setSelected(true); } else if (data->hasFormat(QStringLiteral("kdenlive/producerslist"))) { QStringList ids = QString(data->data(QStringLiteral("kdenlive/producerslist"))).split(';'); QList offsetList; QList infoList; GenTime start = GenTime((int)(framePos.x() + 0.5), m_document->fps()); framePos.setX((int)(framePos.x() + 0.5)); framePos.setY(getPositionFromTrack(track)); // Check if user dragged a folder for (int i = 0; i < ids.size(); ++i) { if (ids.at(i).startsWith(QLatin1String("#"))) { QString folderId = ids.at(i); folderId.remove(0, 1); QStringList clipsInFolder = m_document->getBinFolderClipIds(folderId); ids.removeAt(i); for (int j = 0; j < clipsInFolder.count(); j++) { ids.insert(i + j, clipsInFolder.at(j)); } } } // Check if clips can be inserted at that position for (int i = 0; i < ids.size(); ++i) { QString clipData = ids.at(i); QString clipId = clipData.section('/', 0, 0); ProjectClip *clip = m_document->getBinClip(clipId); if (!clip || !clip->isReady()) { emit displayMessage(i18n("Clip not ready"), ErrorMessage); return false; } ItemInfo info; info.startPos = start; if (clipData.contains('/')) { // this is a clip zone, set in / out int in = clipData.section('/', 1, 1).toInt(); int out = clipData.section('/', 2, 2).toInt(); info.cropStart = GenTime(in, m_document->fps()); info.cropDuration = GenTime(out - in, m_document->fps()); } else { info.cropDuration = clip->duration(); } info.endPos = info.startPos + info.cropDuration; info.track = track; infoList.append(info); start += info.cropDuration; } if (!canBePastedTo(infoList, AVWidget)) { return true; } if (ids.size() > 1) { m_selectionGroup = new AbstractGroupItem(m_document->fps()); } start = GenTime(); for (int i = 0; i < ids.size(); ++i) { QString clipData = ids.at(i); QString clipId = clipData.section('/', 0, 0); ProjectClip *clip = m_document->getBinClip(clipId); ItemInfo info; info.startPos = start; if (clipData.contains('/')) { // this is a clip zone, set in / out int in = clipData.section('/', 1, 1).toInt(); int out = clipData.section('/', 2, 2).toInt(); info.cropStart = GenTime(in, m_document->fps()); info.cropDuration = GenTime(out - in, m_document->fps()); } else { info.cropDuration = clip->duration(); } info.endPos = info.startPos + info.cropDuration; info.track = 0; start += info.cropDuration; offsetList.append(start); ClipItem *item = new ClipItem(clip, info, m_document->fps(), 1.0, 1, getFrameWidth(), true); item->setPos(info.startPos.frames(m_document->fps()), item->itemOffset()); if (ids.size() > 1) m_selectionGroup->addItem(item); else { m_dragItem = item; m_dragItem->setMainSelectedClip(true); } item->setSelected(true); //TODO: //if (!clip->isPlaceHolder()) m_waitingThumbs.append(item); } updateSnapPoints(NULL, offsetList); QStringList lockedTracks; for (int i = 1; i < m_timeline->tracksCount(); ++i) { if (m_timeline->getTrackInfo(i).isLocked) lockedTracks << QString::number(i); } if (m_selectionGroup) { m_selectionGroup->setProperty("locked_tracks", lockedTracks); scene()->addItem(m_selectionGroup); m_selectionGroup->setPos(framePos); } else if (m_dragItem) { m_dragItem->setProperty("locked_tracks", lockedTracks); scene()->addItem(m_dragItem); m_dragItem->setPos(framePos); } //m_selectionGroup->setZValue(10); m_thumbsTimer.start(); } return true; } void CustomTrackView::dragEnterEvent(QDragEnterEvent * event) { if (insertDropClips(event->mimeData(), event->pos())) { if (event->source() == this) { event->setDropAction(Qt::MoveAction); event->accept(); } else { event->setDropAction(Qt::MoveAction); event->acceptProposedAction(); } } else { QGraphicsView::dragEnterEvent(event); } } bool CustomTrackView::itemCollision(AbstractClipItem *item, const ItemInfo &newPos) { QRectF shape = QRectF(newPos.startPos.frames(m_document->fps()), getPositionFromTrack(newPos.track) + 1, (newPos.endPos - newPos.startPos).frames(m_document->fps()) - 0.02, m_tracksHeight - 1); QList collindingItems = scene()->items(shape, Qt::IntersectsItemShape); collindingItems.removeAll(item); if (collindingItems.isEmpty()) { return false; } else { for (int i = 0; i < collindingItems.count(); ++i) { QGraphicsItem *collision = collindingItems.at(i); if (collision->type() == item->type()) { // Collision //qDebug() << "// COLLISIION DETECTED"; return true; } } return false; } } void CustomTrackView::slotRefreshEffects(ClipItem *clip) { int track = clip->track(); GenTime pos = clip->startPos(); if (!m_document->renderer()->mltRemoveEffect(track, pos, -1, false, false)) { emit displayMessage(i18n("Problem deleting effect"), ErrorMessage); return; } bool success = true; for (int i = 0; i < clip->effectsCount(); ++i) { if (!m_document->renderer()->mltAddEffect(track, pos, EffectsController::getEffectArgs(m_document->getProfileInfo(), clip->effect(i)), false)) success = false; } if (!success) emit displayMessage(i18n("Problem adding effect to clip"), ErrorMessage); m_document->renderer()->doRefresh(); } void CustomTrackView::addEffect(int track, GenTime pos, QDomElement effect) { if (pos < GenTime()) { // Add track effect if (effect.attribute(QStringLiteral("id")) == QLatin1String("speed")) { emit displayMessage(i18n("Cannot add speed effect to track"), ErrorMessage); return; } clearSelection(); m_timeline->addTrackEffect(track, effect); m_document->renderer()->mltAddTrackEffect(track, EffectsController::getEffectArgs(m_document->getProfileInfo(), effect)); emit updateTrackEffectState(track); emit showTrackEffects(track, m_timeline->getTrackInfo(track)); return; } ClipItem *clip = getClipItemAtStart(pos, track); if (clip) { // Special case: speed effect if (effect.attribute(QStringLiteral("id")) == QLatin1String("speed")) { if (clip->clipType() != Video && clip->clipType() != AV && clip->clipType() != Playlist) { emit displayMessage(i18n("Problem adding effect to clip"), ErrorMessage); return; } QLocale locale; locale.setNumberOptions(QLocale::OmitGroupSeparator); double speed = locale.toDouble(EffectsList::parameter(effect, QStringLiteral("speed"))) / 100.0; int strobe = EffectsList::parameter(effect, QStringLiteral("strobe")).toInt(); if (strobe == 0) strobe = 1; doChangeClipSpeed(clip->info(), clip->speedIndependantInfo(), clip->clipState(), speed, strobe, clip->getBinId()); EffectsParameterList params = clip->addEffect(m_document->getProfileInfo(), effect); if (clip->isSelected()) emit clipItemSelected(clip); return; } EffectsParameterList params = clip->addEffect(m_document->getProfileInfo(), effect); if (!m_document->renderer()->mltAddEffect(track, pos, params)) { emit displayMessage(i18n("Problem adding effect to clip"), ErrorMessage); clip->deleteEffect(params.paramValue(QStringLiteral("kdenlive_ix")).toInt()); } else clip->setSelectedEffect(params.paramValue(QStringLiteral("kdenlive_ix")).toInt()); if (clip->isMainSelectedClip()) emit clipItemSelected(clip); } else emit displayMessage(i18n("Cannot find clip to add effect"), ErrorMessage); } void CustomTrackView::deleteEffect(int track, const GenTime &pos, const QDomElement &effect) { int index = effect.attribute(QStringLiteral("kdenlive_ix")).toInt(); if (pos < GenTime()) { // Delete track effect if (m_document->renderer()->mltRemoveTrackEffect(track, index, true)) { m_timeline->removeTrackEffect(track, effect); } else emit displayMessage(i18n("Problem deleting effect"), ErrorMessage); emit updateTrackEffectState(track); emit showTrackEffects(track, m_timeline->getTrackInfo(track)); return; } // Special case: speed effect if (effect.attribute(QStringLiteral("id")) == QLatin1String("speed")) { ClipItem *clip = getClipItemAtStart(pos, track); if (clip) { doChangeClipSpeed(clip->info(), clip->speedIndependantInfo(), clip->clipState(), 1.0, 1, clip->getBinId(), true); clip->deleteEffect(index); emit clipItemSelected(clip); return; } } if (!m_document->renderer()->mltRemoveEffect(track, pos, index, true)) { //qDebug() << "// ERROR REMOV EFFECT: " << index << ", DISABLE: " << effect.attribute("disable"); emit displayMessage(i18n("Problem deleting effect"), ErrorMessage); return; } ClipItem *clip = getClipItemAtStart(pos, track); if (clip) { clip->deleteEffect(index); if (clip->isMainSelectedClip()) emit clipItemSelected(clip); } } void CustomTrackView::slotAddGroupEffect(QDomElement effect, AbstractGroupItem *group, AbstractClipItem *dropTarget) { QList itemList = group->childItems(); QUndoCommand *effectCommand = new QUndoCommand(); QString effectName; int offset = effect.attribute(QStringLiteral("clipstart")).toInt(); QDomElement namenode = effect.firstChildElement(QStringLiteral("name")); if (!namenode.isNull()) effectName = i18n(namenode.text().toUtf8().data()); else effectName = i18n("effect"); effectCommand->setText(i18n("Add %1", effectName)); for (int i = 0; i < itemList.count(); ++i) { if (itemList.at(i)->type() == GroupWidget) { itemList << itemList.at(i)->childItems(); } if (itemList.at(i)->type() == AVWidget) { ClipItem *item = static_cast (itemList.at(i)); if (effect.tagName() == QLatin1String("effectgroup")) { QDomNodeList effectlist = effect.elementsByTagName(QStringLiteral("effect")); for (int j = 0; j < effectlist.count(); ++j) { QDomElement subeffect = effectlist.at(j).toElement(); if (subeffect.hasAttribute(QStringLiteral("kdenlive_info"))) { // effect is in a group EffectInfo effectInfo; effectInfo.fromString(subeffect.attribute(QStringLiteral("kdenlive_info"))); if (effectInfo.groupIndex < 0) { // group needs to be appended effectInfo.groupIndex = item->nextFreeEffectGroupIndex(); subeffect.setAttribute(QStringLiteral("kdenlive_info"), effectInfo.toString()); } } processEffect(item, subeffect.cloneNode().toElement(), offset, effectCommand); } } else { processEffect(item, effect.cloneNode().toElement(), offset, effectCommand); } } } if (effectCommand->childCount() > 0) { m_commandStack->push(effectCommand); } else delete effectCommand; if (dropTarget) { clearSelection(false); m_dragItem = dropTarget; m_dragItem->setSelected(true); m_dragItem->setMainSelectedClip(true); emit clipItemSelected(static_cast(dropTarget)); } } void CustomTrackView::slotAddEffect(ClipItem *clip, const QDomElement &effect, int track) { if (clip == NULL) { // delete track effect AddEffectCommand *command = new AddEffectCommand(this, track, GenTime(-1), effect, true); m_commandStack->push(command); return; } else slotAddEffect(effect, clip->startPos(), clip->track()); } void CustomTrackView::slotDropEffect(ClipItem *clip, QDomElement effect, GenTime pos, int track) { if (clip == NULL) return; slotAddEffect(effect, pos, track); if (clip->parentItem()) { // Clip is in a group, should not happen //qDebug()<<"/// DROPPED ON ITEM IN GRP"; } else if (clip != m_dragItem) { clearSelection(false); m_dragItem = clip; m_dragItem->setMainSelectedClip(true); clip->setSelected(true); emit clipItemSelected(clip); } } void CustomTrackView::slotDropTransition(ClipItem *clip, QDomElement transition, QPointF scenePos) { if (clip == NULL) return; m_menuPosition = mapFromScene(scenePos); slotAddTransitionToSelectedClips(transition, QList() << clip); m_menuPosition = QPoint(); } void CustomTrackView::removeSplitOverlay() { m_timeline->removeSplitOverlay(); } bool CustomTrackView::createSplitOverlay(Mlt::Filter *filter) { if (!m_dragItem || m_dragItem->type() != AVWidget) { //TODO manage split clip return false; } return m_timeline->createOverlay(filter, m_dragItem->track(), m_dragItem->startPos().frames(m_document->fps())); } void CustomTrackView::slotAddEffectToCurrentItem(QDomElement effect) { slotAddEffect(effect, GenTime(), -1); } void CustomTrackView::slotAddEffect(QDomElement effect, const GenTime &pos, int track) { QList itemList; QUndoCommand *effectCommand = new QUndoCommand(); QString effectName; int offset = effect.attribute(QStringLiteral("clipstart")).toInt(); if (effect.tagName() == QLatin1String("effectgroup")) { effectName = effect.attribute(QStringLiteral("name")); } else { QDomElement namenode = effect.firstChildElement(QStringLiteral("name")); if (!namenode.isNull()) effectName = i18n(namenode.text().toUtf8().data()); else effectName = i18n("effect"); } effectCommand->setText(i18n("Add %1", effectName)); if (track == -1) { itemList = scene()->selectedItems(); } else if (itemList.isEmpty()) { ClipItem *clip = getClipItemAtStart(pos, track); if (clip) itemList.append(clip); } if (itemList.isEmpty()) emit displayMessage(i18n("Select a clip if you want to apply an effect"), ErrorMessage); //expand groups for (int i = 0; i < itemList.count(); ++i) { if (itemList.at(i)->type() == GroupWidget) { QList subitems = itemList.at(i)->childItems(); for (int j = 0; j < subitems.count(); ++j) { if (!itemList.contains(subitems.at(j))) itemList.append(subitems.at(j)); } } } for (int i = 0; i < itemList.count(); ++i) { if (itemList.at(i)->type() == AVWidget) { ClipItem *item = static_cast (itemList.at(i)); if (effect.tagName() == QLatin1String("effectgroup")) { QDomNodeList effectlist = effect.elementsByTagName(QStringLiteral("effect")); for (int j = 0; j < effectlist.count(); ++j) { QDomElement subeffect = effectlist.at(j).toElement(); if (subeffect.hasAttribute(QStringLiteral("kdenlive_info"))) { // effect is in a group EffectInfo effectInfo; effectInfo.fromString(subeffect.attribute(QStringLiteral("kdenlive_info"))); if (effectInfo.groupIndex < 0) { // group needs to be appended effectInfo.groupIndex = item->nextFreeEffectGroupIndex(); subeffect.setAttribute(QStringLiteral("kdenlive_info"), effectInfo.toString()); } } processEffect(item, subeffect, offset, effectCommand); } } else processEffect(item, effect, offset, effectCommand); } } if (effectCommand->childCount() > 0) { m_commandStack->push(effectCommand); } else delete effectCommand; } void CustomTrackView::processEffect(ClipItem *item, QDomElement effect, int offset, QUndoCommand *effectCommand) { if (effect.attribute(QStringLiteral("type")) == QLatin1String("audio")) { // Don't add audio effects on video clips if (item->clipState() == PlaylistState::VideoOnly || (item->clipType() != Audio && item->clipType() != AV && item->clipType() != Playlist)) { /* do not show error message when item is part of a group as the user probably knows what he does then * and the message is annoying when working with the split audio feature */ if (!item->parentItem() || item->parentItem() == m_selectionGroup) emit displayMessage(i18n("Cannot add an audio effect to this clip"), ErrorMessage); return; } } else if (effect.attribute(QStringLiteral("type")) == QLatin1String("video") || !effect.hasAttribute(QStringLiteral("type"))) { // Don't add video effect on audio clips if (item->clipState() == PlaylistState::AudioOnly || item->clipType() == Audio) { /* do not show error message when item is part of a group as the user probably knows what he does then * and the message is annoying when working with the split audio feature */ if (!item->parentItem() || item->parentItem() == m_selectionGroup) emit displayMessage(i18n("Cannot add a video effect to this clip"), ErrorMessage); return; } } if (effect.attribute(QStringLiteral("unique"), QStringLiteral("0")) != QLatin1String("0") && item->hasEffect(effect.attribute(QStringLiteral("tag")), effect.attribute(QStringLiteral("id"))) != -1) { emit displayMessage(i18n("Effect already present in clip"), ErrorMessage); return; } if (item->isItemLocked()) { return; } if (effect.attribute(QStringLiteral("id")) == QLatin1String("freeze") && m_cursorPos > item->startPos().frames(m_document->fps()) && m_cursorPos < item->endPos().frames(m_document->fps())) { item->initEffect(m_document->getProfileInfo() , effect, m_cursorPos - item->startPos().frames(m_document->fps()), offset); } else { item->initEffect(m_document->getProfileInfo() , effect, 0, offset); } new AddEffectCommand(this, item->track(), item->startPos(), effect, true, effectCommand); } void CustomTrackView::slotDeleteEffectGroup(ClipItem *clip, int track, QDomDocument doc, bool affectGroup) { QUndoCommand *delCommand = new QUndoCommand(); QString effectName = doc.documentElement().attribute(QStringLiteral("name")); delCommand->setText(i18n("Delete %1", effectName)); QDomNodeList effects = doc.elementsByTagName(QStringLiteral("effect")); for (int i = 0; i < effects.count(); ++i) { slotDeleteEffect(clip, track, effects.at(i).toElement(), affectGroup, delCommand); } m_commandStack->push(delCommand); } void CustomTrackView::slotDeleteEffect(ClipItem *clip, int track, QDomElement effect, bool affectGroup, QUndoCommand *parentCommand) { if (clip == NULL) { // delete track effect AddEffectCommand *command = new AddEffectCommand(this, track, GenTime(-1), effect, false, parentCommand); if (parentCommand == NULL) m_commandStack->push(command); return; } if (affectGroup && clip->parentItem() && clip->parentItem() == m_selectionGroup) { //clip is in a group, also remove the effect in other clips of the group QList items = m_selectionGroup->childItems(); QUndoCommand *delCommand = parentCommand == NULL ? new QUndoCommand() : parentCommand; QString effectName; QDomElement namenode = effect.firstChildElement(QStringLiteral("name")); if (!namenode.isNull()) effectName = i18n(namenode.text().toUtf8().data()); else effectName = i18n("effect"); delCommand->setText(i18n("Delete %1", effectName)); //expand groups for (int i = 0; i < items.count(); ++i) { if (items.at(i)->type() == GroupWidget) { QList subitems = items.at(i)->childItems(); for (int j = 0; j < subitems.count(); ++j) { if (!items.contains(subitems.at(j))) items.append(subitems.at(j)); } } } for (int i = 0; i < items.count(); ++i) { if (items.at(i)->type() == AVWidget) { ClipItem *item = static_cast (items.at(i)); int ix = item->hasEffect(effect.attribute(QStringLiteral("tag")), effect.attribute(QStringLiteral("id"))); if (ix != -1) { QDomElement eff = item->effectAtIndex(ix); new AddEffectCommand(this, item->track(), item->startPos(), eff, false, delCommand); } } } if (parentCommand == NULL) { if (delCommand->childCount() > 0) m_commandStack->push(delCommand); else delete delCommand; } return; } AddEffectCommand *command = new AddEffectCommand(this, clip->track(), clip->startPos(), effect, false, parentCommand); if (parentCommand == NULL) m_commandStack->push(command); } void CustomTrackView::updateEffect(int track, GenTime pos, QDomElement insertedEffect, bool updateEffectStack, bool replaceEffect) { if (insertedEffect.isNull()) { //qDebug()<<"// Trying to add null effect"; emit displayMessage(i18n("Problem editing effect"), ErrorMessage); return; } int ix = insertedEffect.attribute(QStringLiteral("kdenlive_ix")).toInt(); QDomElement effect = insertedEffect.cloneNode().toElement(); //qDebug() << "// update effect ix: " << effect.attribute("kdenlive_ix")<<", TAG: "<< insertedEffect.attribute("tag"); if (pos < GenTime()) { // editing a track effect EffectsParameterList effectParams = EffectsController::getEffectArgs(m_document->getProfileInfo(), effect); // check if we are trying to reset a keyframe effect /*if (effectParams.hasParam("keyframes") && effectParams.paramValue("keyframes").isEmpty()) { clip->initEffect(m_document->getProfileInfo() , effect); effectParams = EffectsController::getEffectArgs(effect); }*/ if (!m_document->renderer()->mltEditEffect(track, pos, effectParams, replaceEffect)) { emit displayMessage(i18n("Problem editing effect"), ErrorMessage); } m_timeline->setTrackEffect(track, ix, effect); emit updateTrackEffectState(track); return; } ClipItem *clip = getClipItemAtStart(pos, track); if (clip) { // Special case: speed effect if (effect.attribute(QStringLiteral("id")) == QLatin1String("speed")) { if (effect.attribute(QStringLiteral("disable")) == QLatin1String("1")) { doChangeClipSpeed(clip->info(), clip->speedIndependantInfo(), clip->clipState(), 1.0, 1, clip->getBinId()); } else { QLocale locale; locale.setNumberOptions(QLocale::OmitGroupSeparator); double speed = locale.toDouble(EffectsList::parameter(effect, QStringLiteral("speed"))) / 100.0; int strobe = EffectsList::parameter(effect, QStringLiteral("strobe")).toInt(); if (strobe == 0) strobe = 1; doChangeClipSpeed(clip->info(), clip->speedIndependantInfo(), clip->clipState(), speed, strobe, clip->getBinId()); } clip->updateEffect(effect); if (updateEffectStack && clip->isSelected()) emit clipItemSelected(clip); if (ix == clip->selectedEffectIndex()) { // make sure to update display of clip keyframes clip->setSelectedEffect(ix); } return; } EffectsParameterList effectParams = EffectsController::getEffectArgs(m_document->getProfileInfo(), effect); // check if we are trying to reset a keyframe effect if (effectParams.hasParam(QStringLiteral("keyframes")) && effectParams.paramValue(QStringLiteral("keyframes")).isEmpty()) { clip->initEffect(m_document->getProfileInfo() , effect); effectParams = EffectsController::getEffectArgs(m_document->getProfileInfo(), effect); } // Check if a fade effect was changed QString effectId = effect.attribute(QStringLiteral("id")); if (effectId == QLatin1String("fadein") || effectId == QLatin1String("fade_from_black") || effectId == QLatin1String("fadeout") || effectId == QLatin1String("fade_to_black")) { clip->setSelectedEffect(clip->selectedEffectIndex()); } bool success = m_document->renderer()->mltEditEffect(clip->track(), clip->startPos(), effectParams, replaceEffect); if (success) { clip->updateEffect(effect); if (updateEffectStack && clip->isSelected()) { emit clipItemSelected(clip); } if (ix == clip->selectedEffectIndex()) { // make sure to update display of clip keyframes clip->setSelectedEffect(ix); } } else emit displayMessage(i18n("Problem editing effect"), ErrorMessage); } else emit displayMessage(i18n("Cannot find clip to update effect"), ErrorMessage); } void CustomTrackView::updateEffectState(int track, GenTime pos, QList effectIndexes, bool disable, bool updateEffectStack) { if (pos < GenTime()) { // editing a track effect if (!m_document->renderer()->mltEnableEffects(track, pos, effectIndexes, disable)) { emit displayMessage(i18n("Problem editing effect"), ErrorMessage); return; } m_timeline->enableTrackEffects(track, effectIndexes, disable); emit updateTrackEffectState(track); emit showTrackEffects(track, m_timeline->getTrackInfo(track)); return; } // editing a clip effect ClipItem *clip = getClipItemAtStart(pos, track); if (clip) { bool success = m_document->renderer()->mltEnableEffects(clip->track(), clip->startPos(), effectIndexes, disable); if (success) { clip->enableEffects(effectIndexes, disable); if (updateEffectStack && clip->isSelected()) { emit clipItemSelected(clip); } if (effectIndexes.contains(clip->selectedEffectIndex())) { // make sure to update display of clip keyframes clip->setSelectedEffect(clip->selectedEffectIndex()); } } else emit displayMessage(i18n("Problem editing effect"), ErrorMessage); } else emit displayMessage(i18n("Cannot find clip to update effect"), ErrorMessage); } void CustomTrackView::moveEffect(int track, const GenTime &pos, const QList &oldPos, const QList &newPos) { if (pos < GenTime()) { // Moving track effect int documentTrack = track - 1; int max = m_timeline->getTrackEffects(documentTrack).count(); int new_position = newPos.at(0); if (new_position > max) { new_position = max; } int old_position = oldPos.at(0); for (int i = 0; i < newPos.count(); ++i) { QDomElement act = m_timeline->getTrackEffect(documentTrack, new_position); if (old_position > new_position) { // Moving up, we need to adjust index old_position = oldPos.at(i); new_position = newPos.at(i); } QDomElement before = m_timeline->getTrackEffect(documentTrack, old_position); if (!act.isNull() && !before.isNull()) { m_timeline->setTrackEffect(documentTrack, new_position, before); m_document->renderer()->mltMoveEffect(track, pos, old_position, new_position); } else emit displayMessage(i18n("Cannot move effect"), ErrorMessage); } emit showTrackEffects(track, m_timeline->getTrackInfo(documentTrack)); return; } ClipItem *clip = getClipItemAtStart(pos, track); if (clip) { int new_position = newPos.at(0); if (new_position > clip->effectsCount()) { new_position = clip->effectsCount(); } int old_position = oldPos.at(0); for (int i = 0; i < newPos.count(); ++i) { QDomElement act = clip->effectAtIndex(new_position); if (old_position > new_position) { // Moving up, we need to adjust index old_position = oldPos.at(i); new_position = newPos.at(i); } QDomElement before = clip->effectAtIndex(old_position); if (act.isNull() || before.isNull()) { emit displayMessage(i18n("Cannot move effect"), ErrorMessage); return; } clip->moveEffect(before, new_position); // special case: speed effect, which is a pseudo-effect, not appearing in MLT's effects if (act.attribute(QStringLiteral("id")) == QLatin1String("speed")) { m_document->renderer()->mltUpdateEffectPosition(track, pos, old_position, new_position); } else if (before.attribute(QStringLiteral("id")) == QLatin1String("speed")) { m_document->renderer()->mltUpdateEffectPosition(track, pos, new_position, old_position); } else m_document->renderer()->mltMoveEffect(track, pos, old_position, new_position); } clip->setSelectedEffect(newPos.at(0)); emit clipItemSelected(clip); } else emit displayMessage(i18n("Cannot move effect"), ErrorMessage); } void CustomTrackView::slotChangeEffectState(ClipItem *clip, int track, QList effectIndexes, bool disable) { ChangeEffectStateCommand *command; if (clip == NULL) { // editing track effect command = new ChangeEffectStateCommand(this, track, GenTime(-1), effectIndexes, disable, false, true); } else { // Check if we have a speed effect, disabling / enabling it needs a special procedure since it is a pseudoo effect QList speedEffectIndexes; for (int i = 0; i < effectIndexes.count(); ++i) { QDomElement effect = clip->effectAtIndex(effectIndexes.at(i)); if (effect.attribute(QStringLiteral("id")) == QLatin1String("speed")) { // speed effect speedEffectIndexes << effectIndexes.at(i); QDomElement newEffect = effect.cloneNode().toElement(); newEffect.setAttribute(QStringLiteral("disable"), (int) disable); EditEffectCommand *editcommand = new EditEffectCommand(this, clip->track(), clip->startPos(), effect, newEffect, effectIndexes.at(i), false, true); m_commandStack->push(editcommand); } } for (int j = 0; j < speedEffectIndexes.count(); ++j) { effectIndexes.removeAll(speedEffectIndexes.at(j)); } command = new ChangeEffectStateCommand(this, clip->track(), clip->startPos(), effectIndexes, disable, false, true); } m_commandStack->push(command); } void CustomTrackView::slotChangeEffectPosition(ClipItem *clip, int track, QList currentPos, int newPos) { MoveEffectCommand *command; if (clip == NULL) { // editing track effect command = new MoveEffectCommand(this, track, GenTime(-1), currentPos, newPos); } else command = new MoveEffectCommand(this, clip->track(), clip->startPos(), currentPos, newPos); m_commandStack->push(command); } void CustomTrackView::slotUpdateClipEffect(ClipItem *clip, int track, QDomElement oldeffect, QDomElement effect, int ix, bool refreshEffectStack) { EditEffectCommand *command; if (clip) command = new EditEffectCommand(this, clip->track(), clip->startPos(), oldeffect, effect, ix, refreshEffectStack, true); else command = new EditEffectCommand(this, track, GenTime(-1), oldeffect, effect, ix, refreshEffectStack, true); m_commandStack->push(command); } void CustomTrackView::slotUpdateClipRegion(ClipItem *clip, int ix, QString region) { QDomElement effect = clip->getEffectAtIndex(ix); QDomElement oldeffect = effect.cloneNode().toElement(); effect.setAttribute(QStringLiteral("region"), region); EditEffectCommand *command = new EditEffectCommand(this, clip->track(), clip->startPos(), oldeffect, effect, ix, true, true); m_commandStack->push(command); } ClipItem *CustomTrackView::cutClip(const ItemInfo &info, const GenTime &cutTime, bool cut, const EffectsList &oldStack, bool execute) { if (cut) { // cut clip ClipItem *item = getClipItemAtStart(info.startPos, info.track); if (!item || cutTime >= item->endPos() || cutTime <= item->startPos()) { emit displayMessage(i18n("Cannot find clip to cut"), ErrorMessage); if (item) qDebug() << "///////// ERROR CUTTING CLIP : (" << item->startPos().frames(25) << '-' << item->endPos().frames(25) << "), INFO: (" << info.startPos.frames(25) << '-' << info.endPos.frames(25) << ')' << ", CUT: " << cutTime.frames(25); else qDebug() << "/// ERROR NO CLIP at: " << info.startPos.frames(m_document->fps()) << ", track: " << info.track; return NULL; } if (execute) { if (!m_timeline->track(info.track)->cut(cutTime.seconds())) { // Error cuting clip in playlist return NULL; } } int cutPos = (int) cutTime.frames(m_document->fps()); ItemInfo newPos; newPos.startPos = cutTime; newPos.endPos = info.endPos; newPos.cropStart = item->info().cropStart + (cutTime - info.startPos); newPos.track = info.track; newPos.cropDuration = newPos.endPos - newPos.startPos; bool snap = KdenliveSettings::snaptopoints(); KdenliveSettings::setSnaptopoints(false); ClipItem *dup = item->clone(newPos); dup->binClip()->addRef(); dup->setPos(newPos.startPos.frames(m_document->fps()), getPositionFromTrack(newPos.track) + 1 + dup->itemOffset()); // remove unwanted effects // fade in from 2nd part of the clip int ix = dup->hasEffect(QString(), QStringLiteral("fadein")); if (ix != -1) { QDomElement oldeffect = dup->effectAtIndex(ix); dup->deleteEffect(oldeffect.attribute(QStringLiteral("kdenlive_ix")).toInt()); } ix = dup->hasEffect(QString(), QStringLiteral("fade_from_black")); if (ix != -1) { QDomElement oldeffect = dup->effectAtIndex(ix); dup->deleteEffect(oldeffect.attribute(QStringLiteral("kdenlive_ix")).toInt()); } // fade out from 1st part of the clip ix = item->hasEffect(QString(), QStringLiteral("fadeout")); if (ix != -1) { QDomElement oldeffect = item->effectAtIndex(ix); item->deleteEffect(oldeffect.attribute(QStringLiteral("kdenlive_ix")).toInt()); } ix = item->hasEffect(QString(), QStringLiteral("fade_to_black")); if (ix != -1) { QDomElement oldeffect = item->effectAtIndex(ix); item->deleteEffect(oldeffect.attribute(QStringLiteral("kdenlive_ix")).toInt()); } item->resizeEnd(cutPos); scene()->addItem(dup); if (item->checkKeyFrames(m_document->width(), m_document->height(), (info.cropDuration + info.cropStart).frames(m_document->fps()))) slotRefreshEffects(item); if (dup->checkKeyFrames(m_document->width(), m_document->height(), (info.cropDuration + info.cropStart).frames(m_document->fps()), (cutTime - item->startPos()).frames(m_document->fps()))) slotRefreshEffects(dup); KdenliveSettings::setSnaptopoints(snap); return dup; } else { // uncut clip ClipItem *item = getClipItemAtStart(info.startPos, info.track); ClipItem *dup = getClipItemAtStart(cutTime, info.track); if (!item || !dup || item == dup) { emit displayMessage(i18n("Cannot find clip to uncut"), ErrorMessage); return NULL; } if (!m_timeline->track(info.track)->del(cutTime.seconds())) { emit displayMessage(i18n("Error removing clip at %1 on track %2", m_document->timecode().getTimecodeFromFrames(cutTime.frames(m_document->fps())), m_timeline->getTrackInfo(info.track).trackName), ErrorMessage); return NULL; } dup->binClip()->removeRef(); bool snap = KdenliveSettings::snaptopoints(); KdenliveSettings::setSnaptopoints(false); m_waitingThumbs.removeAll(dup); if (dup->isSelected() && dup == m_dragItem) { item->setSelected(true); item->setMainSelectedClip(true); m_dragItem = item; emit clipItemSelected(item); } scene()->removeItem(dup); delete dup; dup = NULL; ItemInfo clipinfo = item->info(); bool success = m_timeline->track(clipinfo.track)->resize(clipinfo.startPos.seconds(), (info.endPos - cutTime).seconds(), true); if (success) { item->resizeEnd((int) info.endPos.frames(m_document->fps())); item->setEffectList(oldStack); } else { emit displayMessage(i18n("Error when resizing clip"), ErrorMessage); } KdenliveSettings::setSnaptopoints(snap); return item; } } void CustomTrackView::slotAddTransitionToSelectedClips(QDomElement transition, QList itemList) { if (itemList.isEmpty()) itemList = scene()->selectedItems(); if (itemList.count() == 1) { if (itemList.at(0)->type() == AVWidget) { ClipItem *item = static_cast(itemList.at(0)); ItemInfo info; info.track = item->track(); ClipItem *transitionClip = NULL; const int transitiontrack = getPreviousVideoTrack(info.track); GenTime pos = GenTime((int)(mapToScene(m_menuPosition).x()), m_document->fps()); if (pos < item->startPos() + item->cropDuration() / 2) { // add transition to clip start info.startPos = item->startPos(); if (transitiontrack != 0) transitionClip = getClipItemAtMiddlePoint(info.startPos.frames(m_document->fps()), transitiontrack); if (transitionClip && transitionClip->endPos() < item->endPos()) { info.endPos = transitionClip->endPos(); } else info.endPos = info.startPos + GenTime(65, m_document->fps()); // Check there is no other transition at that place double startY = getPositionFromTrack(info.track) + 1 + m_tracksHeight / 2; QRectF r(info.startPos.frames(m_document->fps()), startY, (info.endPos - info.startPos).frames(m_document->fps()), m_tracksHeight / 2); QList selection = m_scene->items(r); bool transitionAccepted = true; for (int i = 0; i < selection.count(); ++i) { if (selection.at(i)->type() == TransitionWidget) { Transition *tr = static_cast (selection.at(i)); if (tr->startPos() - info.startPos > GenTime(5, m_document->fps())) { if (tr->startPos() < info.endPos) info.endPos = tr->startPos(); } else transitionAccepted = false; } } if (transitionAccepted) slotAddTransition(item, info, transitiontrack, transition); else emit displayMessage(i18n("Cannot add transition"), ErrorMessage); } else { // add transition to clip end info.endPos = item->endPos(); if (transitiontrack != 0) transitionClip = getClipItemAtMiddlePoint(info.endPos.frames(m_document->fps()), transitiontrack); if (transitionClip && transitionClip->startPos() > item->startPos()) { info.startPos = transitionClip->startPos(); } else info.startPos = info.endPos - GenTime(65, m_document->fps()); if (transition.attribute(QStringLiteral("tag")) == QLatin1String("luma")) EffectsList::setParameter(transition, QStringLiteral("reverse"), QStringLiteral("1")); else if (transition.attribute(QStringLiteral("id")) == QLatin1String("slide")) EffectsList::setParameter(transition, QStringLiteral("invert"), QStringLiteral("1")); // Check there is no other transition at that place double startY = getPositionFromTrack(info.track) + 1 + m_tracksHeight / 2; QRectF r(info.startPos.frames(m_document->fps()), startY, (info.endPos - info.startPos).frames(m_document->fps()), m_tracksHeight / 2); QList selection = m_scene->items(r); bool transitionAccepted = true; for (int i = 0; i < selection.count(); ++i) { if (selection.at(i)->type() == TransitionWidget) { Transition *tr = static_cast (selection.at(i)); if (info.endPos - tr->endPos() > GenTime(5, m_document->fps())) { if (tr->endPos() > info.startPos) info.startPos = tr->endPos(); } else transitionAccepted = false; } } if (transitionAccepted) slotAddTransition(item, info, transitiontrack, transition); else emit displayMessage(i18n("Cannot add transition"), ErrorMessage); } } } else for (int i = 0; i < itemList.count(); ++i) { if (itemList.at(i)->type() == AVWidget) { ClipItem *item = static_cast(itemList.at(i)); ItemInfo info; info.startPos = item->startPos(); info.endPos = info.startPos + GenTime(65, m_document->fps()); info.track = item->track(); // Check there is no other transition at that place double startY = getPositionFromTrack(info.track) + 1 + m_tracksHeight / 2; QRectF r(info.startPos.frames(m_document->fps()), startY, (info.endPos - info.startPos).frames(m_document->fps()), m_tracksHeight / 2); QList selection = m_scene->items(r); bool transitionAccepted = true; for (int i = 0; i < selection.count(); ++i) { if (selection.at(i)->type() == TransitionWidget) { Transition *tr = static_cast (selection.at(i)); if (tr->startPos() - info.startPos > GenTime(5, m_document->fps())) { if (tr->startPos() < info.endPos) info.endPos = tr->startPos(); } else transitionAccepted = false; } } int transitiontrack = getPreviousVideoTrack(info.track); if (transitionAccepted) slotAddTransition(item, info, transitiontrack, transition); else emit displayMessage(i18n("Cannot add transition"), ErrorMessage); } } } void CustomTrackView::slotAddTransition(ClipItem* /*clip*/, ItemInfo transitionInfo, int endTrack, QDomElement transition) { if (transitionInfo.startPos >= transitionInfo.endPos) { emit displayMessage(i18n("Invalid transition"), ErrorMessage); return; } AddTransitionCommand* command = new AddTransitionCommand(this, transitionInfo, endTrack, transition, false, true); m_commandStack->push(command); } void CustomTrackView::addTransition(const ItemInfo &transitionInfo, int endTrack, const QDomElement ¶ms, bool refresh) { Transition *tr = new Transition(transitionInfo, endTrack, m_document->fps(), params, true); tr->setPos(transitionInfo.startPos.frames(m_document->fps()), getPositionFromTrack(transitionInfo.track) + tr->itemOffset() + 1); ////qDebug() << "---- ADDING transition " << params.attribute("value"); if (m_timeline->transitionHandler->addTransition(tr->transitionTag(), endTrack, transitionInfo.track, transitionInfo.startPos, transitionInfo.endPos, tr->toXML(), refresh)) { scene()->addItem(tr); } else { emit displayMessage(i18n("Cannot add transition"), ErrorMessage); delete tr; } } void CustomTrackView::deleteTransition(const ItemInfo &transitionInfo, int endTrack, QDomElement /*params*/, bool refresh) { Transition *item = getTransitionItemAt(transitionInfo.startPos, transitionInfo.track); if (!item) { emit displayMessage(i18n("Select clip to delete"), ErrorMessage); return; } m_timeline->transitionHandler->deleteTransition(item->transitionTag(), endTrack, transitionInfo.track, transitionInfo.startPos, transitionInfo.endPos, item->toXML(), refresh); if (m_dragItem == item) { m_dragItem->setMainSelectedClip(false); m_dragItem = NULL; } // animate item deletion item->closeAnimation(); emit transitionItemSelected(NULL); } void CustomTrackView::slotTransitionUpdated(Transition *tr, QDomElement old) { ////qDebug() << "TRANS UPDATE, TRACKS: " << old.attribute("transition_btrack") << ", NEW: " << tr->toXML().attribute("transition_btrack"); QDomElement xml = tr->toXML(); if (old.isNull() || xml.isNull()) { emit displayMessage(i18n("Cannot update transition"), ErrorMessage); return; } EditTransitionCommand *command = new EditTransitionCommand(this, tr->track(), tr->startPos(), old, xml, false); updateTrackDuration(tr->track(), command); m_commandStack->push(command); } void CustomTrackView::updateTransition(int track, const GenTime &pos, const QDomElement &oldTransition, const QDomElement &transition, bool updateTransitionWidget) { Transition *item = getTransitionItemAt(pos, track); if (!item) { qWarning() << "Unable to find transition at pos :" << pos.frames(m_document->fps()) << ", ON track: " << track; return; } bool force = false; if (oldTransition.attribute(QStringLiteral("transition_atrack")) != transition.attribute(QStringLiteral("transition_atrack")) || oldTransition.attribute(QStringLiteral("transition_btrack")) != transition.attribute(QStringLiteral("transition_btrack"))) force = true; m_timeline->transitionHandler->updateTransition(oldTransition.attribute(QStringLiteral("tag")), transition.attribute(QStringLiteral("tag")), transition.attribute(QStringLiteral("transition_btrack")).toInt(), transition.attribute(QStringLiteral("transition_atrack")).toInt(), item->startPos(), item->endPos(), transition, force); ////qDebug() << "ORIGINAL TRACK: "<< oldTransition.attribute("transition_btrack") << ", NEW TRACK: "<setTransitionParameters(transition); if (updateTransitionWidget) { ItemInfo info = item->info(); QPoint p; ClipItem *transitionClip = getClipItemAtStart(info.startPos, info.track); if (transitionClip && transitionClip->binClip()) { int frameWidth = transitionClip->binClip()->getProducerIntProperty(QStringLiteral("meta.media.width")); int frameHeight = transitionClip->binClip()->getProducerIntProperty(QStringLiteral("meta.media.height")); double factor = transitionClip->binClip()->getProducerProperty(QStringLiteral("aspect_ratio")).toDouble(); if (factor == 0) factor = 1.0; p.setX((int)(frameWidth * factor + 0.5)); p.setY(frameHeight); } emit transitionItemSelected(item, getPreviousVideoTrack(info.track), p, true); } } void CustomTrackView::dragMoveEvent(QDragMoveEvent * event) { if (m_clipDrag) { const QPointF pos = mapToScene(event->pos()); if (m_selectionGroup) { m_selectionGroup->setPos(pos); emit mousePosition((int)(m_selectionGroup->scenePos().x() + 0.5)); event->acceptProposedAction(); } else if (m_dragItem) { m_dragItem->setPos(pos); emit mousePosition((int)(m_dragItem->scenePos().x() + 0.5)); event->acceptProposedAction(); } else { // Drag enter was not possible, try again at mouse position insertDropClips(event->mimeData(), event->pos()); event->accept(); } } else { QGraphicsView::dragMoveEvent(event); } } void CustomTrackView::dragLeaveEvent(QDragLeaveEvent * event) { if ((m_selectionGroup || m_dragItem) && m_clipDrag) { m_thumbsTimer.stop(); m_waitingThumbs.clear(); QList items; QMutexLocker lock(&m_selectionMutex); if (m_selectionGroup) items = m_selectionGroup->childItems(); else if (m_dragItem) { m_dragItem->setMainSelectedClip(false); items.append(m_dragItem); } qDeleteAll(items); if (m_selectionGroup) scene()->destroyItemGroup(m_selectionGroup); m_selectionGroup = NULL; m_dragItem = NULL; event->accept(); } else { QGraphicsView::dragLeaveEvent(event); } } void CustomTrackView::enterEvent(QEvent * event) { if (m_tool == RazorTool && !m_cutLine) { m_cutLine = m_scene->addLine(0, 0, 0, m_tracksHeight * m_scene->scale().y()); m_cutLine->setZValue(1000); QPen pen1 = QPen(); pen1.setWidth(1); QColor line(Qt::red); pen1.setColor(line); m_cutLine->setPen(pen1); m_cutLine->setFlag(QGraphicsItem::ItemIgnoresTransformations, true); } QGraphicsView::enterEvent(event); } void CustomTrackView::leaveEvent(QEvent * event) { removeTipAnimation(); if (m_cutLine) { delete m_cutLine; m_cutLine = NULL; } QGraphicsView::leaveEvent(event); } void CustomTrackView::dropEvent(QDropEvent * event) { if ((m_selectionGroup || m_dragItem) && m_clipDrag) { QList items; if (m_selectionGroup) items = m_selectionGroup->childItems(); else if (m_dragItem) { m_dragItem->setMainSelectedClip(false); items.append(m_dragItem); } resetSelectionGroup(); m_dragItem = NULL; m_scene->clearSelection(); bool hasVideoClip = false; QUndoCommand *addCommand = new QUndoCommand(); addCommand->setText(i18n("Add timeline clip")); QList brokenClips; // Add refresh command for undo new RefreshMonitorCommand(this, false, true, addCommand); for (int i = 0; i < items.count(); ++i) { ClipItem *item = static_cast (items.at(i)); if (!hasVideoClip && (item->clipType() == AV || item->clipType() == Video)) hasVideoClip = true; if (items.count() == 1) { updateClipTypeActions(item); } else { updateClipTypeActions(NULL); } //TODO: take care of edit mode for undo //item->baseClip()->addReference(); //m_document->updateClip(item->baseClip()->getId()); ItemInfo info = item->info(); bool isLocked = m_timeline->getTrackInfo(info.track).isLocked; if (isLocked) item->setItemLocked(true); QString clipBinId = item->getBinId(); Mlt::Producer *prod; if (item->clipState() == PlaylistState::VideoOnly) { prod = m_document->renderer()->getBinVideoProducer(clipBinId); } else { prod = m_document->renderer()->getBinProducer(clipBinId); } if (!m_timeline->track(info.track)->add(info.startPos.seconds(), prod, info.cropStart.seconds(), (info.cropStart + info.cropDuration).seconds(), item->clipState(), item->needsDuplicate(), m_scene->editMode())) { emit displayMessage(i18n("Cannot insert clip in timeline"), ErrorMessage); brokenClips.append(item); continue; } item->binClip()->addRef(); adjustTimelineClips(m_scene->editMode(), item, ItemInfo(), addCommand, false); new AddTimelineClipCommand(this, clipBinId, item->info(), item->effectList(), item->clipState(), false, false, addCommand); updateTrackDuration(info.track, addCommand); if (item->binClip()->isTransparent() && getTransitionItemAtStart(info.startPos, info.track) == NULL) { // add transparency transition if space is available if (canBePastedTo(info, TransitionWidget)) { QDomElement trans = MainWindow::transitions.getEffectByTag(QStringLiteral("affine"), QString()).cloneNode().toElement(); new AddTransitionCommand(this, info, getPreviousVideoTrack(info.track), trans, false, true, addCommand); } } item->setSelected(true); } // Add refresh command for redo new RefreshMonitorCommand(this, false, false, addCommand); for (int i = 0; i < brokenClips.count(); i++) { items.removeAll(brokenClips.at(i)); } qDeleteAll(brokenClips); brokenClips.clear(); if (addCommand->childCount() > 0) m_commandStack->push(addCommand); else delete addCommand; // Automatic audio split if (KdenliveSettings::splitaudio()) splitAudio(false); /* // debug info QRectF rect(0, 1 * m_tracksHeight + m_tracksHeight / 2, sceneRect().width(), 2); QList selection = m_scene->items(rect); QStringList timelineList; //qDebug()<<"// ITEMS on TRACK: "<type() == AVWidget) { ClipItem *clip = static_cast (selection.at(i)); int start = clip->startPos().frames(m_document->fps()); int end = clip->endPos().frames(m_document->fps()); timelineList.append(QString::number(start) + '-' + QString::number(end)); } } //qDebug() << "// COMPARE:\n" << timelineList << "\n-------------------"; */ m_pasteEffectsAction->setEnabled(m_copiedItems.count() == 1); if (items.count() > 1) { groupSelectedItems(items); } else if (items.count() == 1) { m_dragItem = static_cast (items.at(0)); m_dragItem->setMainSelectedClip(true); emit clipItemSelected(static_cast(m_dragItem), false); } m_document->renderer()->doRefresh(); event->setDropAction(Qt::MoveAction); event->accept(); /// \todo enable when really working // alignAudio(); } else { QGraphicsView::dropEvent(event); } setFocus(); } void CustomTrackView::adjustTimelineClips(EditMode mode, ClipItem *item, ItemInfo posinfo, QUndoCommand *command, bool doIt) { bool snap = KdenliveSettings::snaptopoints(); KdenliveSettings::setSnaptopoints(false); if (mode == OverwriteEdit) { // if we are in overwrite mode, move clips accordingly ItemInfo info; if (item == NULL) info = posinfo; else info = item->info(); QRectF rect(info.startPos.frames(m_document->fps()), getPositionFromTrack(info.track) + m_tracksHeight / 2, (info.endPos - info.startPos).frames(m_document->fps()) - 1, 5); QList selection = m_scene->items(rect); if (item) selection.removeAll(item); for (int i = 0; i < selection.count(); ++i) { if (!selection.at(i)->isEnabled()) continue; if (selection.at(i)->type() == AVWidget) { ClipItem *clip = static_cast(selection.at(i)); if (clip->startPos() < info.startPos) { if (clip->endPos() > info.endPos) { ItemInfo clipInfo = clip->info(); ItemInfo dupInfo = clipInfo; GenTime diff = info.startPos - clipInfo.startPos; dupInfo.startPos = info.startPos; dupInfo.cropStart += diff; dupInfo.cropDuration = clipInfo.endPos - info.startPos; ItemInfo newdupInfo = dupInfo; GenTime diff2 = info.endPos - info.startPos; newdupInfo.startPos = info.endPos; newdupInfo.cropStart += diff2; newdupInfo.cropDuration = clipInfo.endPos - info.endPos; new RazorClipCommand(this, clipInfo, clip->effectList(), info.startPos, doIt, command); new ResizeClipCommand(this, dupInfo, newdupInfo, doIt, false, command); if (!doIt) { // GUI only ClipItem *dup = cutClip(clipInfo, info.startPos, true, EffectsList(false), false); if (dup) { dup->resizeStart(info.endPos.frames(m_document->fps())); } } } else { ItemInfo newclipInfo = clip->info(); newclipInfo.endPos = info.startPos; new ResizeClipCommand(this, clip->info(), newclipInfo, doIt, false, command); if (!doIt) { clip->resizeEnd(info.startPos.frames(m_document->fps())); } } } else if (clip->endPos() <= info.endPos) { new AddTimelineClipCommand(this, clip->getBinId(), clip->info(), clip->effectList(), clip->clipState(), doIt, true, command); if (!doIt) { // GUI only m_waitingThumbs.removeAll(clip); scene()->removeItem(clip); delete clip; clip = NULL; } } else { ItemInfo newclipInfo = clip->info(); newclipInfo.startPos = info.endPos; new ResizeClipCommand(this, clip->info(), newclipInfo, doIt, false, command); if (!doIt) { clip->resizeStart(info.endPos.frames(m_document->fps())); } } } } } else if (mode == InsertEdit) { // if we are in push mode, move clips accordingly ItemInfo info; if (item == NULL) info = posinfo; else info = item->info(); QRectF rect(info.startPos.frames(m_document->fps()), getPositionFromTrack(info.track) + m_tracksHeight / 2, (info.endPos - info.startPos).frames(m_document->fps()) - 1, 5); QList selection = m_scene->items(rect); if (item) selection.removeAll(item); for (int i = 0; i < selection.count(); ++i) { if (selection.at(i)->type() == AVWidget) { ClipItem *clip = static_cast(selection.at(i)); if (clip->startPos() < info.startPos) { if (clip->endPos() > info.startPos) { ItemInfo clipInfo = clip->info(); ItemInfo dupInfo = clipInfo; GenTime diff = info.startPos - clipInfo.startPos; dupInfo.startPos = info.startPos; dupInfo.cropStart += diff; dupInfo.cropDuration = clipInfo.endPos - info.startPos; new RazorClipCommand(this, clipInfo, clip->effectList(), info.startPos, true, command); } } // TODO: add insertspacecommand } } } KdenliveSettings::setSnaptopoints(snap); } void CustomTrackView::adjustTimelineTransitions(EditMode mode, Transition *item, QUndoCommand *command) { if (mode == OverwriteEdit) { // if we are in overwrite or push mode, move clips accordingly bool snap = KdenliveSettings::snaptopoints(); KdenliveSettings::setSnaptopoints(false); ItemInfo info = item->info(); QRectF rect(info.startPos.frames(m_document->fps()), getPositionFromTrack(info.track) + m_tracksHeight, (info.endPos - info.startPos).frames(m_document->fps()) - 1, 5); QList selection = m_scene->items(rect); selection.removeAll(item); for (int i = 0; i < selection.count(); ++i) { if (!selection.at(i)->isEnabled()) continue; if (selection.at(i)->type() == TransitionWidget) { Transition *tr = static_cast(selection.at(i)); if (tr->startPos() < info.startPos) { ItemInfo firstPos = tr->info(); ItemInfo newPos = firstPos; firstPos.endPos = item->startPos(); newPos.startPos = item->endPos(); new MoveTransitionCommand(this, tr->info(), firstPos, true, command); if (tr->endPos() > info.endPos) { // clone transition new AddTransitionCommand(this, newPos, tr->transitionEndTrack(), tr->toXML(), false, true, command); } } else if (tr->endPos() > info.endPos) { // just resize ItemInfo firstPos = tr->info(); firstPos.startPos = item->endPos(); new MoveTransitionCommand(this, tr->info(), firstPos, true, command); } else { // remove transition new AddTransitionCommand(this, tr->info(), tr->transitionEndTrack(), tr->toXML(), true, true, command); } } } KdenliveSettings::setSnaptopoints(snap); } } QStringList CustomTrackView::mimeTypes() const { QStringList qstrList; // list of accepted mime types for drop qstrList.append(QStringLiteral("text/plain")); qstrList.append(QStringLiteral("kdenlive/producerslist")); qstrList.append(QStringLiteral("kdenlive/clip")); return qstrList; } Qt::DropActions CustomTrackView::supportedDropActions() const { // returns what actions are supported when dropping return Qt::MoveAction; } void CustomTrackView::setDuration(int duration) { if (m_projectDuration == duration) return; int diff = qAbs(duration - sceneRect().width()); if (diff * matrix().m11() > -50) { if (matrix().m11() < 0.4) setSceneRect(0, 0, (duration + 100 / matrix().m11()), sceneRect().height()); else setSceneRect(0, 0, (duration + 300), sceneRect().height()); } m_projectDuration = duration; } int CustomTrackView::duration() const { return m_projectDuration; } void CustomTrackView::addTrack(const TrackInfo &type, int ix) { clearSelection(); emit transitionItemSelected(NULL); QList transitionInfos; if (ix == -1 || ix > m_timeline->tracksCount()) { ix = m_timeline->tracksCount() + 1; } // Prepare groups for reload QDomDocument doc; doc.setContent(m_document->groupsXml()); QDomNodeList groups; if (!doc.isNull()) { groups = doc.documentElement().elementsByTagName("group"); for (int nodeindex = 0; nodeindex < groups.count(); ++nodeindex) { QDomNode grp = groups.item(nodeindex); QDomNodeList nodes = grp.childNodes(); for (int itemindex = 0; itemindex < nodes.count(); ++itemindex) { QDomElement elem = nodes.item(itemindex).toElement(); if (!elem.hasAttribute("track")) continue; int track = elem.attribute("track").toInt(); if (track <= ix) { // No change continue; } else { elem.setAttribute("track", track + 1); } } } } // insert track in MLT's playlist transitionInfos = m_document->renderer()->mltInsertTrack(ix, type.trackName, type.type == VideoTrack); Mlt::Tractor *tractor = m_document->renderer()->lockService(); // When adding a track, MLT sometimes incorrectly updates transition's tracks if (ix < m_timeline->tracksCount()) { Mlt::Transition *tr = m_timeline->transitionHandler->getTransition(KdenliveSettings::gpu_accel() ? "movit.overlay" : "frei0r.cairoblend", ix+1, ix - 1, true); if (tr) { tr->set_tracks(ix, ix + 1); delete tr; } } // Check we have composite transitions where necessary m_timeline->updateComposites(); m_document->renderer()->unlockService(tractor); // Reload timeline and m_tracks structure from MLT's playlist reloadTimeline(); loadGroups(groups); } void CustomTrackView::reloadTimeline() { removeTipAnimation(); emit clipItemSelected(NULL); emit transitionItemSelected(NULL); QList selection = m_scene->items(); selection.removeAll(m_cursorLine); for (int i = 0; i < m_guides.count(); ++i) { selection.removeAll(m_guides.at(i)); } qDeleteAll<>(selection); m_timeline->getTracks(); m_timeline->getTransitions(); int maxHeight = m_tracksHeight * m_timeline->visibleTracksCount() * matrix().m22(); for (int i = 0; i < m_guides.count(); ++i) { m_guides.at(i)->setLine(0, 0, 0, maxHeight - 1); } m_cursorLine->setLine(0, 0, 0, maxHeight - 1); viewport()->update(); } void CustomTrackView::removeTrack(int ix) { // Clear effect stack clearSelection(); emit transitionItemSelected(NULL); //Delete composite transition Mlt::Tractor *tractor = m_document->renderer()->lockService(); QScopedPointer field(tractor->field()); if (m_timeline->getTrackInfo(ix).type == VideoTrack) { QScopedPointer tr(m_timeline->transitionHandler->getTransition(KdenliveSettings::gpu_accel() ? "movit.overlay" : "frei0r.cairoblend", ix, -1, true)); if (tr) { field->disconnect_service(*tr.data()); } QScopedPointer mixTr(m_timeline->transitionHandler->getTransition(QStringLiteral("mix"), ix, -1, true)); if (mixTr) { field->disconnect_service(*mixTr.data()); } } // Prepare groups for reload QDomDocument doc; doc.setContent(m_document->groupsXml()); QDomNodeList groups; if (!doc.isNull()) { groups = doc.documentElement().elementsByTagName("group"); for (int nodeindex = 0; nodeindex < groups.count(); ++nodeindex) { QDomNode grp = groups.item(nodeindex); QDomNodeList nodes = grp.childNodes(); for (int itemindex = 0; itemindex < nodes.count(); ++itemindex) { QDomElement elem = nodes.item(itemindex).toElement(); if (!elem.hasAttribute("track")) continue; int track = elem.attribute("track").toInt(); if (track < ix) { // No change continue; } else if (track > ix) { elem.setAttribute("track", track - 1); } else { // track == ix // A grouped item was on deleted track, remove it from group elem.setAttribute("track", -1); } } } } //Manually remove all transitions issued from track ix, otherwise MLT will relocate it to another track m_timeline->transitionHandler->deleteTrackTransitions(ix); // Delete track in MLT playlist tractor->remove_track(ix); m_timeline->updateComposites(); m_document->renderer()->unlockService(tractor); reloadTimeline(); loadGroups(groups); } void CustomTrackView::configTracks(const QList < TrackInfo > &trackInfos) { //TODO: fix me, use UNDO/REDO for (int i = 0; i < trackInfos.count(); ++i) { m_timeline->setTrackInfo(m_timeline->visibleTracksCount() - i, trackInfos.at(i)); } viewport()->update(); } void CustomTrackView::slotSwitchTrackAudio(int ix, bool enable) { m_timeline->switchTrackAudio(ix, enable); m_document->renderer()->doRefresh(); } void CustomTrackView::slotSwitchTrackLock(int ix, bool enable) { LockTrackCommand *command = new LockTrackCommand(this, ix, enable); m_commandStack->push(command); } void CustomTrackView::lockTrack(int ix, bool lock, bool requestUpdate) { m_timeline->lockTrack(ix, lock); if (requestUpdate) emit doTrackLock(ix, lock); AbstractClipItem *clip = NULL; QList selection = m_scene->items(QRectF(0, getPositionFromTrack(ix) + m_tracksHeight / 2, sceneRect().width(), m_tracksHeight / 2 - 2)); for (int i = 0; i < selection.count(); ++i) { if (selection.at(i)->type() == GroupWidget && static_cast(selection.at(i)) != m_selectionGroup) { if (selection.at(i)->parentItem() && m_selectionGroup) { selection.removeAll((QGraphicsItem*)m_selectionGroup); resetSelectionGroup(); } bool changeGroupLock = true; bool hasClipOnTrack = false; QList children = selection.at(i)->childItems(); for (int j = 0; j < children.count(); ++j) { if (children.at(j)->isSelected()) { if (children.at(j)->type() == AVWidget) emit clipItemSelected(NULL); else if (children.at(j)->type() == TransitionWidget) emit transitionItemSelected(NULL); else continue; } AbstractClipItem * child = static_cast (children.at(j)); if (child) { if (child == m_dragItem) { m_dragItem->setMainSelectedClip(false); m_dragItem = NULL; } // only unlock group, if it is not locked by another track too if (!lock && child->track() != ix && m_timeline->getTrackInfo(child->track()).isLocked) changeGroupLock = false; // only (un-)lock if at least one clip is on the track if (child->track() == ix) hasClipOnTrack = true; } } if (changeGroupLock && hasClipOnTrack) static_cast(selection.at(i))->setItemLocked(lock); } else if((selection.at(i)->type() == AVWidget || selection.at(i)->type() == TransitionWidget)) { if (selection.at(i)->parentItem()) { if (selection.at(i)->parentItem() == m_selectionGroup) { selection.removeAll((QGraphicsItem*)m_selectionGroup); resetSelectionGroup(); } else { // groups are handled separately continue; } } if (selection.at(i)->isSelected()) { if (selection.at(i)->type() == AVWidget) emit clipItemSelected(NULL); else emit transitionItemSelected(NULL); } clip = static_cast (selection.at(i)); clip->setItemLocked(lock); if (clip == m_dragItem) { m_dragItem->setMainSelectedClip(false); m_dragItem = NULL; } } } //qDebug() << "NEXT TRK STATE: " << m_timeline->getTrackInfo(tracknumber).isLocked; viewport()->update(); } void CustomTrackView::slotSwitchTrackVideo(int ix, bool enable) { m_timeline->switchTrackVideo(ix, enable); m_document->renderer()->doRefresh(); //TODO: create undo/redo command for this setDocumentModified(); } QList CustomTrackView::checkForGroups(const QRectF &rect, bool *ok) { // Check there is no group going over several tracks there, or that would result in timeline corruption QList selection = scene()->items(rect); *ok = true; int maxHeight = m_tracksHeight * 1.5; for (int i = 0; i < selection.count(); ++i) { // Check that we don't try to move a group with clips on other tracks if (selection.at(i)->type() == GroupWidget && (selection.at(i)->boundingRect().height() >= maxHeight)) { *ok = false; break; } else if (selection.at(i)->parentItem() && (selection.at(i)->parentItem()->boundingRect().height() >= maxHeight)) { *ok = false; break; } } return selection; } void CustomTrackView::slotRemoveSpace() { GenTime pos; int track = 0; if (m_menuPosition.isNull()) { pos = GenTime(cursorPos(), m_document->fps()); QPointer d = new TrackDialog(m_timeline, parentWidget()); d->comboTracks->setCurrentIndex(m_selectedTrack); d->label->setText(i18n("Track")); d->track_name->setHidden(true); d->name_label->setHidden(true); d->before_select->setHidden(true); d->setWindowTitle(i18n("Remove Space")); d->video_track->setHidden(true); d->audio_track->setHidden(true); if (d->exec() != QDialog::Accepted) { delete d; return; } // TODO check that this is the correct index track = m_timeline->visibleTracksCount() - d->comboTracks->currentIndex(); delete d; } else { pos = GenTime((int)(mapToScene(m_menuPosition).x()), m_document->fps()); track = getTrackFromPos(mapToScene(m_menuPosition).y()); } if (m_timeline->isTrackLocked(track)) { emit displayMessage(i18n("Cannot remove space in a locked track"), ErrorMessage); return; } ClipItem *item = getClipItemAtMiddlePoint(pos.frames(m_document->fps()), track); if (item) { emit displayMessage(i18n("You must be in an empty space to remove space (time: %1, track: %2)", m_document->timecode().getTimecodeFromFrames(mapToScene(m_menuPosition).x()), track), ErrorMessage); return; } int length = m_timeline->getSpaceLength(pos, track, true); if (length <= 0) { emit displayMessage(i18n("You must be in an empty space to remove space (time: %1, track: %2)", m_document->timecode().getTimecodeFromFrames(mapToScene(m_menuPosition).x()), track), ErrorMessage); return; } // Make sure there is no group in the way QRectF rect(pos.frames(m_document->fps()), getPositionFromTrack(track) + m_tracksHeight / 2, sceneRect().width() - pos.frames(m_document->fps()), m_tracksHeight / 2 - 2); bool isOk; QList items = checkForGroups(rect, &isOk); if (!isOk) { // groups found on track, do not allow the move emit displayMessage(i18n("Cannot remove space in a track with a group"), ErrorMessage); return; } QList clipsToMove; QList transitionsToMove; for (int i = 0; i < items.count(); ++i) { if (items.at(i)->type() == AVWidget || items.at(i)->type() == TransitionWidget) { AbstractClipItem *item = static_cast (items.at(i)); ItemInfo info = item->info(); if (item->type() == AVWidget) { clipsToMove.append(info); } else if (item->type() == TransitionWidget) { transitionsToMove.append(info); } } } if (!transitionsToMove.isEmpty()) { // Make sure that by moving the items, we don't get a transition collision // Find first transition ItemInfo info = transitionsToMove.at(0); for (int i = 1; i < transitionsToMove.count(); ++i) if (transitionsToMove.at(i).startPos < info.startPos) info = transitionsToMove.at(i); // make sure there are no transitions on the way QRectF rect(info.startPos.frames(m_document->fps()) - length, getPositionFromTrack(track) + m_tracksHeight / 2, length - 1, m_tracksHeight / 2 - 2); items = scene()->items(rect); int transitionCorrection = -1; for (int i = 0; i < items.count(); ++i) { if (items.at(i)->type() == TransitionWidget) { // There is a transition on the way AbstractClipItem *item = static_cast (items.at(i)); int transitionEnd = item->endPos().frames(m_document->fps()); if (transitionEnd > transitionCorrection) transitionCorrection = transitionEnd; } } if (transitionCorrection > 0) { // We need to fix the move length length = info.startPos.frames(m_document->fps()) - transitionCorrection; } // Make sure we don't send transition before 0 if (info.startPos.frames(m_document->fps()) < length) { // reduce length to maximum possible length = info.startPos.frames(m_document->fps()); } } InsertSpaceCommand *command = new InsertSpaceCommand(this, clipsToMove, transitionsToMove, track, GenTime(-length, m_document->fps()), true); updateTrackDuration(track, command); m_commandStack->push(command); } void CustomTrackView::slotInsertSpace() { int pos; int track = 0; if (m_menuPosition.isNull()) { pos = cursorPos(); } else { pos = (int) mapToScene(m_menuPosition).x(); track = getTrackFromPos(mapToScene(m_menuPosition).y()); } QPointer d = new SpacerDialog(GenTime(65, m_document->fps()), m_document->timecode(), track, m_timeline->getTracksInfo(), this); if (d->exec() != QDialog::Accepted) { delete d; return; } GenTime spaceDuration = d->selectedDuration(); track = d->selectedTrack(); delete d; QList items; if (track >= 0) { if (m_timeline->isTrackLocked(track)) { emit displayMessage(i18n("Cannot insert space in a locked track"), ErrorMessage); return; } ClipItem *item = getClipItemAtMiddlePoint(pos, track); if (item) pos = item->startPos().frames(m_document->fps()); // Make sure there is no group in the way QRectF rect(pos, getPositionFromTrack(track) + m_tracksHeight / 2, sceneRect().width() - pos, m_tracksHeight / 2 - 2); bool isOk; items = checkForGroups(rect, &isOk); if (!isOk) { // groups found on track, do not allow the move emit displayMessage(i18n("Cannot insert space in a track with a group"), ErrorMessage); return; } } else { QRectF rect(pos, 0, sceneRect().width() - pos, m_timeline->visibleTracksCount() * m_tracksHeight); items = scene()->items(rect); } QList clipsToMove; QList transitionsToMove; for (int i = 0; i < items.count(); ++i) { if (items.at(i)->type() == AVWidget || items.at(i)->type() == TransitionWidget) { AbstractClipItem *item = static_cast (items.at(i)); ItemInfo info = item->info(); if (item->type() == AVWidget) clipsToMove.append(info); else if (item->type() == TransitionWidget) transitionsToMove.append(info); } } if (!clipsToMove.isEmpty() || !transitionsToMove.isEmpty()) { InsertSpaceCommand *command = new InsertSpaceCommand(this, clipsToMove, transitionsToMove, track, spaceDuration, true); updateTrackDuration(track, command); m_commandStack->push(command); } } void CustomTrackView::insertSpace(QList clipsToMove, QList transToMove, int track, const GenTime &duration, const GenTime &offset) { int diff = duration.frames(m_document->fps()); resetSelectionGroup(); m_selectionMutex.lock(); m_selectionGroup = new AbstractGroupItem(m_document->fps()); scene()->addItem(m_selectionGroup); // Create lists with start pos for each track QMap trackClipStartList; QMap trackTransitionStartList; for (int i = 1; i < m_timeline->tracksCount() + 1; ++i) { trackClipStartList[i] = -1; trackTransitionStartList[i] = -1; } if (!clipsToMove.isEmpty()) for (int i = 0; i < clipsToMove.count(); ++i) { ClipItem *clip = getClipItemAtStart(clipsToMove.at(i).startPos + offset, clipsToMove.at(i).track); if (clip) { if (clip->parentItem()) { m_selectionGroup->addItem(clip->parentItem()); } else { m_selectionGroup->addItem(clip); } if (trackClipStartList.value(clipsToMove.at(i).track) == -1 || clipsToMove.at(i).startPos.frames(m_document->fps()) < trackClipStartList.value(clipsToMove.at(i).track)) trackClipStartList[clipsToMove.at(i).track] = clipsToMove.at(i).startPos.frames(m_document->fps()); } else { emit displayMessage(i18n("Cannot move clip at position %1, track %2", m_document->timecode().getTimecodeFromFrames((clipsToMove.at(i).startPos + offset).frames(m_document->fps())), clipsToMove.at(i).track), ErrorMessage); } } if (!transToMove.isEmpty()) for (int i = 0; i < transToMove.count(); ++i) { Transition *transition = getTransitionItemAtStart(transToMove.at(i).startPos + offset, transToMove.at(i).track); if (transition) { if (transition->parentItem()) { m_selectionGroup->addItem(transition->parentItem()); } else { m_selectionGroup->addItem(transition); } if (trackTransitionStartList.value(transToMove.at(i).track) == -1 || transToMove.at(i).startPos.frames(m_document->fps()) < trackTransitionStartList.value(transToMove.at(i).track)) trackTransitionStartList[transToMove.at(i).track] = transToMove.at(i).startPos.frames(m_document->fps()); } else emit displayMessage(i18n("Cannot move transition at position %1, track %2", m_document->timecode().getTimecodeFromFrames(transToMove.at(i).startPos.frames(m_document->fps())), transToMove.at(i).track), ErrorMessage); } m_selectionGroup->setTransform(QTransform::fromTranslate(diff, 0), true); // update items coordinates QList itemList = m_selectionGroup->childItems(); for (int i = 0; i < itemList.count(); ++i) { if (itemList.at(i)->type() == AVWidget || itemList.at(i)->type() == TransitionWidget) { int realTrack = getTrackFromPos(itemList.at(i)->scenePos().y()); static_cast < AbstractClipItem *>(itemList.at(i))->updateItem(realTrack); } else if (itemList.at(i)->type() == GroupWidget) { QList children = itemList.at(i)->childItems(); for (int j = 0; j < children.count(); ++j) { AbstractClipItem * clp = static_cast < AbstractClipItem *>(children.at(j)); int realTrack = getTrackFromPos(clp->scenePos().y()); clp->updateItem(realTrack); } } } m_selectionMutex.unlock(); resetSelectionGroup(false); m_document->renderer()->mltInsertSpace(trackClipStartList, trackTransitionStartList, track, duration, offset); } void CustomTrackView::deleteClip(const QString &clipId, QUndoCommand *deleteCommand) { resetSelectionGroup(); QList itemList = items(); new RefreshMonitorCommand(this, false, true, deleteCommand); int count = 0; for (int i = 0; i < itemList.count(); ++i) { if (itemList.at(i)->type() == AVWidget) { ClipItem *item = static_cast(itemList.at(i)); if (item->getBinId() == clipId) { count++; if (item->parentItem()) { // Clip is in a group, destroy the group new GroupClipsCommand(this, QList() << item->info(), QList(), false, deleteCommand); } new AddTimelineClipCommand(this, item->getBinId(), item->info(), item->effectList(), item->clipState(), true, true, deleteCommand); } } } if (count > 0) { new RefreshMonitorCommand(this, true, false, deleteCommand); updateTrackDuration(-1, deleteCommand); } } void CustomTrackView::seekCursorPos(int pos) { emit updateRuler(pos); m_document->renderer()->seek(pos); } int CustomTrackView::seekPosition() const { int seek = m_document->renderer()->requestedSeekPosition; if (seek == SEEK_INACTIVE) return m_cursorPos; return seek; } void CustomTrackView::setCursorPos(int pos) { if (pos != m_cursorPos) { emit cursorMoved(m_cursorPos, pos); m_cursorPos = pos; m_cursorLine->setPos(m_cursorPos + m_cursorOffset, 0); if (m_autoScroll) checkScrolling(); } //else emit updateRuler(); } int CustomTrackView::cursorPos() const { return m_cursorPos; } void CustomTrackView::moveCursorPos(int delta) { int currentPos = m_document->renderer()->requestedSeekPosition; if (currentPos == SEEK_INACTIVE) { currentPos = m_document->renderer()->seekFramePosition() + delta; } else { currentPos += delta; } emit updateRuler(currentPos); m_document->renderer()->seek(qMax(0, currentPos)); } void CustomTrackView::initCursorPos(int pos) { emit cursorMoved(m_cursorPos, pos); m_cursorPos = pos; m_cursorLine->setPos(m_cursorPos + 0.5, 0); checkScrolling(); } void CustomTrackView::checkScrolling() { QGraphicsView::ViewportUpdateMode mode = viewportUpdateMode(); setViewportUpdateMode(QGraphicsView::FullViewportUpdate); ensureVisible(seekPosition(), verticalScrollBar()->value() + 10, 2, 2, 50, 0); setViewportUpdateMode(mode); } void CustomTrackView::scrollToStart() { horizontalScrollBar()->setValue(0); } void CustomTrackView::completeSpaceOperation(int track, GenTime &timeOffset) { QList groups; if (timeOffset != GenTime()) { QList items = m_selectionGroup->childItems(); QList clipsToMove; QList transitionsToMove; // Create lists with start pos for each track QMap trackClipStartList; QMap trackTransitionStartList; for (int i = 1; i < m_timeline->tracksCount() + 1; ++i) { trackClipStartList[i] = -1; trackTransitionStartList[i] = -1; } for (int i = 0; i < items.count(); ++i) { if (items.at(i)->type() == GroupWidget) { AbstractGroupItem* group = static_cast(items.at(i)); if (!groups.contains(group)) groups.append(group); items += items.at(i)->childItems(); } } for (int i = 0; i < items.count(); ++i) { if (items.at(i)->type() == AVWidget) { AbstractClipItem *item = static_cast (items.at(i)); ItemInfo info = item->info(); clipsToMove.append(info); int realTrack = getTrackFromPos(item->scenePos().y()); item->updateItem(realTrack); if (trackClipStartList.value(info.track) == -1 || info.startPos.frames(m_document->fps()) < trackClipStartList.value(info.track)) trackClipStartList[info.track] = info.startPos.frames(m_document->fps()); } else if (items.at(i)->type() == TransitionWidget) { AbstractClipItem *item = static_cast (items.at(i)); ItemInfo info = item->info(); transitionsToMove.append(info); int realTrack = getTrackFromPos(item->scenePos().y()); item->updateItem(realTrack); if (trackTransitionStartList.value(info.track) == -1 || info.startPos.frames(m_document->fps()) < trackTransitionStartList.value(info.track)) trackTransitionStartList[info.track] = info.startPos.frames(m_document->fps()); } } if (!clipsToMove.isEmpty() || !transitionsToMove.isEmpty()) { InsertSpaceCommand *command = new InsertSpaceCommand(this, clipsToMove, transitionsToMove, track, timeOffset, false); updateTrackDuration(track, command); m_commandStack->push(command); m_document->renderer()->mltInsertSpace(trackClipStartList, trackTransitionStartList, track, timeOffset, GenTime()); } } resetSelectionGroup(); for (int i = 0; i < groups.count(); ++i) { rebuildGroup(groups.at(i)); } clearSelection(); m_operationMode = None; return; } void CustomTrackView::mouseReleaseEvent(QMouseEvent * event) { if (event->button() != Qt::LeftButton) { //event->ignore(); //m_moveOpMode = None; QGraphicsView::mouseReleaseEvent(event); return; } if (event->modifiers() & Qt::ControlModifier) { event->ignore(); } QGraphicsView::mouseReleaseEvent(event); setDragMode(QGraphicsView::NoDrag); if (m_moveOpMode == Seek) m_moveOpMode = None; if (m_moveOpMode == ScrollTimeline || m_moveOpMode == ZoomTimeline) { m_moveOpMode = None; //QGraphicsView::mouseReleaseEvent(event); //setDragMode(QGraphicsView::NoDrag); return; } m_clipDrag = false; //setViewportUpdateMode(QGraphicsView::MinimalViewportUpdate); /*if (m_dragItem) { m_dragItem->setGraphicsEffect(NULL); }*/ if (m_scrollTimer.isActive()) m_scrollTimer.stop(); if (m_moveOpMode == MoveGuide && m_dragGuide) { setCursor(Qt::ArrowCursor); m_dragGuide->setFlag(QGraphicsItem::ItemIsMovable, false); GenTime newPos = GenTime(m_dragGuide->pos().x(), m_document->fps()); if (newPos != m_dragGuide->position()) { EditGuideCommand *command = new EditGuideCommand(this, m_dragGuide->position(), m_dragGuide->label(), newPos, m_dragGuide->label(), false); m_commandStack->push(command); m_dragGuide->updateGuide(GenTime(m_dragGuide->pos().x(), m_document->fps())); qSort(m_guides.begin(), m_guides.end(), sortGuidesList); emit guidesUpdated(); } m_dragGuide = NULL; if (m_dragItem) { m_dragItem->setMainSelectedClip(false); } m_dragItem = NULL; m_moveOpMode = None; return; } else if (m_moveOpMode == Spacer && m_selectionGroup) { int track; if (event->modifiers() != Qt::ControlModifier) { // We are moving all tracks track = -1; } else track = getTrackFromPos(mapToScene(m_clickEvent).y()); GenTime timeOffset = GenTime((int)(m_selectionGroup->scenePos().x()), m_document->fps()) - m_selectionGroupInfo.startPos; completeSpaceOperation(track, timeOffset); } else if (m_moveOpMode == RubberSelection) { //setViewportUpdateMode(QGraphicsView::MinimalViewportUpdate); if (event->modifiers() != Qt::ControlModifier) { if (m_dragItem) m_dragItem->setMainSelectedClip(false); m_dragItem = NULL; } //event->accept(); resetSelectionGroup(); groupSelectedItems(); if (m_selectionGroup == NULL && m_dragItem) { // Only 1 item selected if (m_dragItem->type() == AVWidget) { m_dragItem->setMainSelectedClip(true); emit clipItemSelected(static_cast(m_dragItem), false); } } } if (m_dragItem == NULL && m_selectionGroup == NULL) { emit transitionItemSelected(NULL); m_moveOpMode = None; return; } ItemInfo info; if (m_dragItem) info = m_dragItem->info(); if (m_moveOpMode == MoveOperation) { setCursor(Qt::OpenHandCursor); if (m_dragItem->parentItem() == 0) { // we are moving one clip, easy if (m_dragItem->type() == AVWidget && (m_dragItemInfo.startPos != info.startPos || m_dragItemInfo.track != info.track)) { ClipItem *item = static_cast (m_dragItem); bool success = m_timeline->moveClip(m_dragItemInfo.track, m_dragItemInfo.startPos.seconds(), info.track, info.startPos.seconds(), item->clipState(), m_scene->editMode(), item->needsDuplicate()); if (success) { QUndoCommand *moveCommand = new QUndoCommand(); moveCommand->setText(i18n("Move clip")); adjustTimelineClips(m_scene->editMode(), item, ItemInfo(), moveCommand, false); bool isLocked = m_timeline->getTrackInfo(item->track()).isLocked; if (isLocked) item->setItemLocked(true); new MoveClipCommand(this, m_dragItemInfo, info, false, moveCommand); // Also move automatic transitions (on lower track) Transition *startTransition = getTransitionItemAtStart(m_dragItemInfo.startPos, m_dragItemInfo.track); ItemInfo startTrInfo; ItemInfo newStartTrInfo; bool moveStartTrans = false; bool moveEndTrans = false; if (startTransition && startTransition->isAutomatic()) { startTrInfo = startTransition->info(); newStartTrInfo = startTrInfo; newStartTrInfo.track = info.track; newStartTrInfo.startPos = info.startPos; newStartTrInfo.cropDuration = newStartTrInfo.endPos - info.startPos; //TODO if (m_dragItemInfo.track == info.track /*&& !item->baseClip()->isTransparent()*/ && getClipItemAtEnd(newStartTrInfo.endPos, startTransition->transitionEndTrack())) { // transition end should stay the same } else { // transition end should be adjusted to clip newStartTrInfo.endPos = newStartTrInfo.endPos + (newStartTrInfo.startPos - startTrInfo.startPos); } if (newStartTrInfo.startPos < newStartTrInfo.endPos) moveStartTrans = true; } if (startTransition == NULL || startTransition->endPos() < m_dragItemInfo.endPos) { // Check if there is a transition at clip end Transition *tr = getTransitionItemAtEnd(m_dragItemInfo.endPos, m_dragItemInfo.track); if (tr && tr->isAutomatic()) { ItemInfo trInfo = tr->info(); ItemInfo newTrInfo = trInfo; newTrInfo.track = info.track; newTrInfo.endPos = m_dragItem->endPos(); newTrInfo.cropDuration = newTrInfo.endPos - newTrInfo.startPos; //TODO if (m_dragItemInfo.track == info.track /*&& !item->baseClip()->isTransparent()*/ && getClipItemAtStart(trInfo.startPos, tr->transitionEndTrack())) { // transition start should stay the same } else { // transition start should be moved newTrInfo.startPos = newTrInfo.startPos + (newTrInfo.endPos - trInfo.endPos); } if (newTrInfo.startPos < newTrInfo.endPos) { moveEndTrans = true; if (moveStartTrans) { // we have to move both transitions, remove the start one so that there is no collision new AddTransitionCommand(this, startTrInfo, startTransition->transitionEndTrack(), startTransition->toXML(), true, true, moveCommand); } adjustTimelineTransitions(m_scene->editMode(), tr, moveCommand); if (tr->updateKeyframes(trInfo, newTrInfo)) { QDomElement old = tr->toXML(); QDomElement xml = tr->toXML(); m_timeline->transitionHandler->updateTransition(xml.attribute("tag"), xml.attribute("tag"), xml.attribute("transition_btrack").toInt(), xml.attribute("transition_atrack").toInt(), newTrInfo.startPos, newTrInfo.endPos, xml); new EditTransitionCommand(this, tr->track(), tr->startPos(), old, xml, false, moveCommand); } new MoveTransitionCommand(this, trInfo, newTrInfo, true, moveCommand); if (moveStartTrans) { // re-add transition in correct place int transTrack = startTransition->transitionEndTrack(); if (m_dragItemInfo.track != info.track && !startTransition->forcedTrack()) { transTrack = getPreviousVideoTrack(info.track); } adjustTimelineTransitions(m_scene->editMode(), startTransition, moveCommand); new AddTransitionCommand(this, newStartTrInfo, transTrack, startTransition->toXML(), false, true, moveCommand); } } } } if (moveStartTrans && !moveEndTrans) { adjustTimelineTransitions(m_scene->editMode(), startTransition, moveCommand); if (startTransition->updateKeyframes(startTrInfo, newStartTrInfo)) { QDomElement old = startTransition->toXML(); QDomElement xml = startTransition->toXML(); m_timeline->transitionHandler->updateTransition(xml.attribute("tag"), xml.attribute("tag"), xml.attribute("transition_btrack").toInt(), xml.attribute("transition_atrack").toInt(), newStartTrInfo.startPos, newStartTrInfo.endPos, xml); new EditTransitionCommand(this, startTransition->track(), startTransition->startPos(), old, xml, false, moveCommand); } new MoveTransitionCommand(this, startTrInfo, newStartTrInfo, true, moveCommand); } // Also move automatic transitions (on upper track) Transition *tr = getTransitionItemAtStart(m_dragItemInfo.startPos, m_dragItemInfo.track + 1); if (m_dragItemInfo.track == info.track && tr && tr->isAutomatic() && tr->transitionEndTrack() == m_dragItemInfo.track) { ItemInfo trInfo = tr->info(); ItemInfo newTrInfo = trInfo; newTrInfo.startPos = m_dragItem->startPos(); newTrInfo.cropDuration = newTrInfo.endPos - m_dragItem->startPos(); ClipItem * upperClip = getClipItemAtStart(m_dragItemInfo.startPos, m_dragItemInfo.track - 1); //TODO if (!upperClip /*|| !upperClip->baseClip()->isTransparent()*/) { if (!getClipItemAtEnd(newTrInfo.endPos, tr->track())) { // transition end should be adjusted to clip on upper track newTrInfo.endPos = newTrInfo.endPos + (newTrInfo.startPos - trInfo.startPos); } if (newTrInfo.startPos < newTrInfo.endPos) { adjustTimelineTransitions(m_scene->editMode(), tr, moveCommand); if (tr->updateKeyframes(trInfo, newTrInfo)) { QDomElement old = tr->toXML(); QDomElement xml = tr->toXML(); m_timeline->transitionHandler->updateTransition(xml.attribute("tag"), xml.attribute("tag"), xml.attribute("transition_btrack").toInt(), xml.attribute("transition_atrack").toInt(), newTrInfo.startPos, newTrInfo.endPos, xml); new EditTransitionCommand(this, tr->track(), tr->startPos(), old, xml, false, moveCommand); } new MoveTransitionCommand(this, trInfo, newTrInfo, true, moveCommand); } } } if (m_dragItemInfo.track == info.track && (tr == NULL || tr->endPos() < m_dragItemInfo.endPos)) { // Check if there is a transition at clip end tr = getTransitionItemAtEnd(m_dragItemInfo.endPos, m_dragItemInfo.track - 1); if (tr && tr->isAutomatic() && tr->transitionEndTrack() == m_dragItemInfo.track) { ItemInfo trInfo = tr->info(); ItemInfo newTrInfo = trInfo; newTrInfo.endPos = m_dragItem->endPos(); newTrInfo.cropDuration = m_dragItem->endPos() - newTrInfo.startPos; //qDebug() << "CLIP ENDS AT: " << newTrInfo.endPos.frames(25); //qDebug() << "CLIP STARTS AT: " << newTrInfo.startPos.frames(25); ClipItem * upperClip = getClipItemAtStart(m_dragItemInfo.startPos, m_dragItemInfo.track + 1); //TODO if (!upperClip /*|| !upperClip->baseClip()->isTransparent()*/) { if (!getClipItemAtStart(trInfo.startPos, tr->track())) { // transition start should be moved newTrInfo.startPos = newTrInfo.startPos + (newTrInfo.endPos - trInfo.endPos); } if (newTrInfo.startPos < newTrInfo.endPos) { adjustTimelineTransitions(m_scene->editMode(), tr, moveCommand); if (tr->updateKeyframes(trInfo, newTrInfo)) { QDomElement old = tr->toXML(); QDomElement xml = tr->toXML(); m_timeline->transitionHandler->updateTransition(xml.attribute("tag"), xml.attribute("tag"), xml.attribute("transition_btrack").toInt(), xml.attribute("transition_atrack").toInt(), newTrInfo.startPos, newTrInfo.endPos, xml); new EditTransitionCommand(this, tr->track(), tr->startPos(), old, xml, false, moveCommand); } new MoveTransitionCommand(this, trInfo, newTrInfo, true, moveCommand); } } } } updateTrackDuration(info.track, moveCommand); if (m_dragItemInfo.track != info.track) updateTrackDuration(m_dragItemInfo.track, moveCommand); m_commandStack->push(moveCommand); //checkTrackSequence(m_dragItem->track()); } else { // undo last move and emit error message bool snap = KdenliveSettings::snaptopoints(); KdenliveSettings::setSnaptopoints(false); item->setPos((int) m_dragItemInfo.startPos.frames(m_document->fps()), getPositionFromTrack(m_dragItemInfo.track) + 1); KdenliveSettings::setSnaptopoints(snap); emit displayMessage(i18n("Cannot move clip to position %1", m_document->timecode().getTimecodeFromFrames(info.startPos.frames(m_document->fps()))), ErrorMessage); } } else if (m_dragItem->type() == TransitionWidget && (m_dragItemInfo.startPos != info.startPos || m_dragItemInfo.track != info.track)) { Transition *transition = static_cast (m_dragItem); transition->updateTransitionEndTrack(getPreviousVideoTrack(m_dragItem->track())); if (!m_timeline->transitionHandler->moveTransition(transition->transitionTag(), m_dragItemInfo.track, m_dragItem->track(), transition->transitionEndTrack(), m_dragItemInfo.startPos, m_dragItemInfo.endPos, info.startPos, info.endPos)) { // Moving transition failed, revert to previous position emit displayMessage(i18n("Cannot move transition"), ErrorMessage); transition->setPos((int) m_dragItemInfo.startPos.frames(m_document->fps()), getPositionFromTrack(m_dragItemInfo.track) + 1); } else { QUndoCommand *moveCommand = new QUndoCommand(); moveCommand->setText(i18n("Move transition")); adjustTimelineTransitions(m_scene->editMode(), transition, moveCommand); new MoveTransitionCommand(this, m_dragItemInfo, info, false, moveCommand); updateTrackDuration(info.track, moveCommand); if (m_dragItemInfo.track != info.track) updateTrackDuration(m_dragItemInfo.track, moveCommand); m_commandStack->push(moveCommand); } } } else { // Moving several clips. We need to delete them and readd them to new position, // or they might overlap each other during the move QGraphicsItemGroup *group; if (m_selectionGroup) { group = static_cast (m_selectionGroup); } else { group = static_cast (m_dragItem->parentItem()); } QList items = group->childItems(); QList clipsToMove; QList transitionsToMove; GenTime timeOffset = GenTime(m_dragItem->scenePos().x(), m_document->fps()) - m_dragItemInfo.startPos; const int trackOffset = getTrackFromPos(m_dragItem->scenePos().y()) - m_dragItemInfo.track; QUndoCommand *moveGroup = new QUndoCommand(); moveGroup->setText(i18n("Move group")); if (timeOffset != GenTime() || trackOffset != 0) { // remove items in MLT playlist // Expand groups int max = items.count(); for (int i = 0; i < max; ++i) { if (items.at(i)->type() == GroupWidget) { items += items.at(i)->childItems(); } } m_timeline->blockTrackSignals(true); for (int i = 0; i < items.count(); ++i) { if (items.at(i)->type() != AVWidget && items.at(i)->type() != TransitionWidget) continue; AbstractClipItem *item = static_cast (items.at(i)); ItemInfo info = item->info(); if (item->type() == AVWidget) { if (!m_timeline->track(info.track)->del(info.startPos.seconds())) { emit displayMessage(i18n("Error removing clip at %1 on track %2", m_document->timecode().getTimecodeFromFrames(info.startPos.frames(m_document->fps())), m_timeline->getTrackInfo(info.track).trackName), ErrorMessage); } else { clipsToMove.append(info); } } else { transitionsToMove.append(info); Transition *tr = static_cast (item); m_timeline->transitionHandler->deleteTransition(tr->transitionTag(), tr->transitionEndTrack(), info.track, info.startPos, info.endPos, tr->toXML()); } } m_timeline->blockTrackSignals(false); for (int i = 0; i < items.count(); ++i) { // re-add items in correct place if (items.at(i)->type() != AVWidget && items.at(i)->type() != TransitionWidget) continue; AbstractClipItem *item = static_cast (items.at(i)); int realTrack = getTrackFromPos(item->scenePos().y()); item->updateItem(realTrack); ItemInfo info = item->info(); bool isLocked = m_timeline->getTrackInfo(info.track).isLocked; if (isLocked) { group->removeFromGroup(item); item->setItemLocked(true); } if (item->type() == AVWidget) { ClipItem *clip = static_cast (item); adjustTimelineClips(m_scene->editMode(), clip, ItemInfo(), moveGroup); /*Mlt::Producer *prod = m_document->renderer()->getTrackProducer(clip->getBinId(), info.track, clip->isAudioOnly(), clip->isVideoOnly()); //prod = prod->cut(info.cropStart.frames(fps()), (info.cropStart + info.cropDuration).frames(fps()) - 1); prod->set_in_and_out(info.cropStart.frames(fps()), (info.cropStart + info.cropDuration).frames(fps()) - 1); m_timeline->track(info.track)info.startPos.seconds(), prod, m_scene->editMode());*/ Mlt::Producer *prod; if (clip->clipState() == PlaylistState::VideoOnly) { prod = m_document->renderer()->getBinVideoProducer(clip->getBinId()); } else { prod = m_document->renderer()->getBinProducer(clip->getBinId()); } m_timeline->track(info.track)->add(info.startPos.seconds(), prod, info.cropStart.seconds(), (info.cropStart + info.cropDuration).seconds(), clip->clipState(), true, m_scene->editMode()); for (int i = 0; i < clip->effectsCount(); ++i) { m_document->renderer()->mltAddEffect(info.track, info.startPos, EffectsController::getEffectArgs(m_document->getProfileInfo(), clip->effect(i)), false); } } else { Transition *tr = static_cast (item); int newTrack = tr->transitionEndTrack(); if (!tr->forcedTrack()) { newTrack = getPreviousVideoTrack(info.track); } tr->updateTransitionEndTrack(newTrack); adjustTimelineTransitions(m_scene->editMode(), tr, moveGroup); m_timeline->transitionHandler->addTransition(tr->transitionTag(), newTrack, info.track, info.startPos, info.endPos, tr->toXML()); } } new MoveGroupCommand(this, clipsToMove, transitionsToMove, timeOffset, trackOffset, false, moveGroup); updateTrackDuration(-1, moveGroup); m_commandStack->push(moveGroup); //QPointF top = group->sceneBoundingRect().topLeft(); //QPointF oldpos = m_selectionGroup->scenePos(); ////qDebug()<<"SELECTION GRP POS: "<scenePos()<<", TOP: "<setPos(top); //TODO: get rid of the 3 lines below if (m_selectionGroup) { m_selectionGroupInfo.startPos = GenTime(m_selectionGroup->scenePos().x(), m_document->fps()); m_selectionGroupInfo.track = m_selectionGroup->track(); items = m_selectionGroup->childItems(); resetSelectionGroup(false); QSet groupList; QSet itemList; while (!items.isEmpty()) { QGraphicsItem *first = items.takeFirst(); if (first->type() == GroupWidget) { if (first != m_selectionGroup) { groupList.insert(first); } } else if (first->type() == AVWidget || first->type() == TransitionWidget) { if (first->parentItem() && first->parentItem()->type() == GroupWidget) { if (first->parentItem() != m_selectionGroup) { groupList.insert(first->parentItem()); } else itemList.insert(first); } else itemList.insert(first); } } foreach(QGraphicsItem *item, groupList) { itemList.unite(item->childItems().toSet()); rebuildGroup(static_cast (item)); } foreach(QGraphicsItem *item, itemList) { item->setSelected(true); if (item->parentItem()) item->parentItem()->setSelected(true); } resetSelectionGroup(); groupSelectedItems(itemList.toList()); } else { AbstractGroupItem *grp = static_cast (group); rebuildGroup(grp); } } } m_moveOpMode = None; m_document->renderer()->doRefresh(); } else if (m_moveOpMode == ResizeStart && m_dragItem && m_dragItem->startPos() != m_dragItemInfo.startPos) { // resize start if (!m_controlModifier && m_dragItem->type() == AVWidget && m_dragItem->parentItem() && m_dragItem->parentItem() != m_selectionGroup) { AbstractGroupItem *parent = static_cast (m_dragItem->parentItem()); if (parent) { QUndoCommand *resizeCommand = new QUndoCommand(); resizeCommand->setText(i18n("Resize group")); QList items = parent->childItems(); QList infos = parent->resizeInfos(); parent->clearResizeInfos(); int itemcount = 0; for (int i = 0; i < items.count(); ++i) { AbstractClipItem *item = static_cast(items.at(i)); if (item && item->type() == AVWidget) { ItemInfo info = infos.at(itemcount); prepareResizeClipStart(item, info, item->startPos().frames(m_document->fps()), false, resizeCommand); ++itemcount; } } m_commandStack->push(resizeCommand); } } else { prepareResizeClipStart(m_dragItem, m_dragItemInfo, m_dragItem->startPos().frames(m_document->fps())); if (m_dragItem->type() == AVWidget) static_cast (m_dragItem)->slotUpdateRange(); } } else if (m_moveOpMode == ResizeEnd && m_dragItem) { // resize end m_dragItem->setProperty("resizingEnd",QVariant()); if (m_dragItem->endPos() != m_dragItemInfo.endPos) { if (!m_controlModifier && m_dragItem->type() == AVWidget && m_dragItem->parentItem() && m_dragItem->parentItem() != m_selectionGroup) { AbstractGroupItem *parent = static_cast (m_dragItem->parentItem()); if (parent) { QUndoCommand *resizeCommand = new QUndoCommand(); resizeCommand->setText(i18n("Resize group")); QList items = parent->childItems(); QList infos = parent->resizeInfos(); parent->clearResizeInfos(); int itemcount = 0; for (int i = 0; i < items.count(); ++i) { AbstractClipItem *item = static_cast(items.at(i)); if (item && item->type() == AVWidget) { ItemInfo info = infos.at(itemcount); prepareResizeClipEnd(item, info, item->endPos().frames(m_document->fps()), false, resizeCommand); ++itemcount; } } updateTrackDuration(-1, resizeCommand); m_commandStack->push(resizeCommand); } } else { prepareResizeClipEnd(m_dragItem, m_dragItemInfo, m_dragItem->endPos().frames(m_document->fps())); if (m_dragItem->type() == AVWidget) static_cast (m_dragItem)->slotUpdateRange(); } } } else if (m_moveOpMode == FadeIn && m_dragItem) { ClipItem * item = static_cast (m_dragItem); // find existing video fade, if none then audio fade int fadeIndex = item->hasEffect(QLatin1String(""), QStringLiteral("fade_from_black")); int fadeIndex2 = item->hasEffect(QStringLiteral("volume"), QStringLiteral("fadein")); if (fadeIndex >= 0 && fadeIndex2 >= 0) { // We have 2 fadin effects, use currently selected or first one int current = item->selectedEffectIndex(); if (fadeIndex != current) { if (fadeIndex2 == current) { fadeIndex = current; } else fadeIndex = qMin(fadeIndex, fadeIndex2); } } else fadeIndex = qMax(fadeIndex, fadeIndex2); // resize fade in effect if (fadeIndex >= 0) { QDomElement oldeffect = item->effectAtIndex(fadeIndex); int end = item->fadeIn(); if (end == 0) { slotDeleteEffect(item, -1, oldeffect, false); } else { int start = item->cropStart().frames(m_document->fps()); end += start; QDomElement effect = oldeffect.cloneNode().toElement(); EffectsList::setParameter(oldeffect, QStringLiteral("in"), QString::number(start)); EffectsList::setParameter(oldeffect, QStringLiteral("out"), QString::number(end)); slotUpdateClipEffect(item, -1, effect, oldeffect, fadeIndex); emit clipItemSelected(item); } // new fade in } else if (item->fadeIn() != 0) { QDomElement effect; if (item->clipState() == PlaylistState::VideoOnly || (item->clipType() != Audio && item->clipState() != PlaylistState::AudioOnly && item->clipType() != Playlist)) { effect = MainWindow::videoEffects.getEffectByTag(QLatin1String(""), QStringLiteral("fade_from_black")).cloneNode().toElement(); } else { effect = MainWindow::audioEffects.getEffectByTag(QStringLiteral("volume"), QStringLiteral("fadein")).cloneNode().toElement(); } EffectsList::setParameter(effect, QStringLiteral("out"), QString::number(item->fadeIn())); slotAddEffect(effect, m_dragItem->startPos(), m_dragItem->track()); } } else if (m_moveOpMode == FadeOut && m_dragItem) { ClipItem * item = static_cast (m_dragItem); // find existing video fade, if none then audio fade int fadeIndex = item->hasEffect(QLatin1String(""), QStringLiteral("fade_to_black")); int fadeIndex2 = item->hasEffect(QStringLiteral("volume"), QStringLiteral("fadeout")); if (fadeIndex >= 0 && fadeIndex2 >= 0) { // We have 2 fadin effects, use currently selected or first one int current = item->selectedEffectIndex(); if (fadeIndex != current) { if (fadeIndex2 == current) { fadeIndex = current; } else fadeIndex = qMin(fadeIndex, fadeIndex2); } } else fadeIndex = qMax(fadeIndex, fadeIndex2); // resize fade out effect if (fadeIndex >= 0) { QDomElement oldeffect = item->effectAtIndex(fadeIndex); int start = item->fadeOut(); if (start == 0) { slotDeleteEffect(item, -1, oldeffect, false); } else { int end = (item->cropDuration() + item->cropStart()).frames(m_document->fps()); start = end - start; QDomElement effect = oldeffect.cloneNode().toElement(); EffectsList::setParameter(oldeffect, QStringLiteral("in"), QString::number(start)); EffectsList::setParameter(oldeffect, QStringLiteral("out"), QString::number(end)); slotUpdateClipEffect(item, -1, effect, oldeffect, fadeIndex); emit clipItemSelected(item); } // new fade out } else if (item->fadeOut() != 0) { QDomElement effect; if (item->clipState() == PlaylistState::VideoOnly || (item->clipType() != Audio && item->clipState() != PlaylistState::AudioOnly && item->clipType() != Playlist)) { effect = MainWindow::videoEffects.getEffectByTag(QLatin1String(""), QStringLiteral("fade_to_black")).cloneNode().toElement(); } else { effect = MainWindow::audioEffects.getEffectByTag(QStringLiteral("volume"), QStringLiteral("fadeout")).cloneNode().toElement(); } int end = (item->cropDuration() + item->cropStart()).frames(m_document->fps()); int start = end-item->fadeOut(); EffectsList::setParameter(effect, QStringLiteral("in"), QString::number(start)); EffectsList::setParameter(effect, QStringLiteral("out"), QString::number(end)); slotAddEffect(effect, m_dragItem->startPos(), m_dragItem->track()); } } else if (m_moveOpMode == KeyFrame && m_dragItem) { // update the MLT effect ClipItem * item = static_cast (m_dragItem); QDomElement oldEffect = item->selectedEffect().cloneNode().toElement(); // check if we want to remove keyframe double val = mapToScene(event->pos()).toPoint().y(); QRectF br = item->sceneBoundingRect(); double maxh = 100.0 / br.height(); val = (br.bottom() - val) * maxh; int start = item->cropStart().frames(m_document->fps()); int end = (item->cropStart() + item->cropDuration()).frames(m_document->fps()) - 1; if ((val < -50 || val > 150) && item->selectedKeyFramePos() != start && item->selectedKeyFramePos() != end && item->keyframesCount() > 1) { //delete keyframe item->removeKeyframe(item->getEffectAtIndex(item->selectedEffectIndex()), item->selectedKeyFramePos()); } else { item->movedKeyframe(item->getEffectAtIndex(item->selectedEffectIndex()), item->selectedKeyFramePos(), item->originalKeyFramePos()); } QDomElement newEffect = item->selectedEffect().cloneNode().toElement(); EditEffectCommand *command = new EditEffectCommand(this, item->track(), item->startPos(), oldEffect, newEffect, item->selectedEffectIndex(), false, false); m_commandStack->push(command); updateEffect(item->track(), item->startPos(), item->selectedEffect()); emit clipItemSelected(item); } else if (m_moveOpMode == WaitingForConfirm && m_operationMode == KeyFrame && m_dragItem) { emit setActiveKeyframe(m_dragItem->selectedKeyFramePos()); } m_moveOpMode = None; } void CustomTrackView::deleteClip(ItemInfo info, bool refresh) { ClipItem *item = getClipItemAtStart(info.startPos, info.track); m_ct++; if (!item) qDebug()<<"// PROBLEM FINDING CLIP ITEM TO REMOVVE!!!!!!!!!"; //m_document->renderer()->saveSceneList(QString("/tmp/error%1.mlt").arg(m_ct), QDomElement()); if (!item || !m_timeline->track(info.track)->del(info.startPos.seconds())) { emit displayMessage(i18n("Error removing clip at %1 on track %2", m_document->timecode().getTimecodeFromFrames(info.startPos.frames(m_document->fps())), m_timeline->getTrackInfo(info.track).trackName), ErrorMessage); return; } m_waitingThumbs.removeAll(item); item->stopThumbs(); item->binClip()->removeRef(); if (item->isSelected()) emit clipItemSelected(NULL); //TODO: notify bin of clip deletion? //item->baseClip()->removeReference(); //m_document->updateClip(item->baseClip()->getId()); /*if (item->baseClip()->isTransparent()) { // also remove automatic transition Transition *tr = getTransitionItemAt(info.startPos, info.track); if (tr && tr->isAutomatic()) { m_document->renderer()->mltDeleteTransition(tr->transitionTag(), tr->transitionEndTrack(), m_timeline->tracksCount() - info.track, info.startPos, info.endPos, tr->toXML()); scene()->removeItem(tr); delete tr; } }*/ if (m_dragItem == item) { m_dragItem->setMainSelectedClip(false); m_dragItem = NULL; } delete item; item = NULL; // animate item deletion //item->closeAnimation(); /*if (refresh) item->closeAnimation(); else { // no refresh, means we have several operations chained, we need to delete clip immediately // so that it does not get in the way of the other delete item; item = NULL; }*/ if (refresh) m_document->renderer()->doRefresh(); } void CustomTrackView::deleteSelectedClips() { resetSelectionGroup(); QList itemList = scene()->selectedItems(); if (itemList.count() == 0) { emit displayMessage(i18n("Select clip to delete"), ErrorMessage); return; } scene()->clearSelection(); QUndoCommand *deleteSelected = new QUndoCommand(); new RefreshMonitorCommand(this, false, true, deleteSelected); int groupCount = 0; int clipCount = 0; int transitionCount = 0; // expand & destroy groups for (int i = 0; i < itemList.count(); ++i) { if (itemList.at(i)->type() == GroupWidget) { groupCount++; QList children = itemList.at(i)->childItems(); QList clipInfos; QList transitionInfos; for (int j = 0; j < children.count(); ++j) { if (children.at(j)->type() == AVWidget) { AbstractClipItem *clip = static_cast (children.at(j)); if (!clip->isItemLocked()) clipInfos.append(clip->info()); } else if (children.at(j)->type() == TransitionWidget) { AbstractClipItem *clip = static_cast (children.at(j)); if (!clip->isItemLocked()) transitionInfos.append(clip->info()); } if (itemList.contains(children.at(j))) { children.removeAt(j); j--; } } itemList += children; if (clipInfos.count() > 0) new GroupClipsCommand(this, clipInfos, transitionInfos, false, deleteSelected); } else if (itemList.at(i)->parentItem() && itemList.at(i)->parentItem()->type() == GroupWidget) itemList.insert(i + 1, itemList.at(i)->parentItem()); } emit clipItemSelected(NULL); emit transitionItemSelected(NULL); for (int i = 0; i < itemList.count(); ++i) { if (itemList.at(i)->type() == AVWidget) { clipCount++; ClipItem *item = static_cast (itemList.at(i)); ////qDebug()<<"// DELETE CLP AT: "<info().startPos.frames(25); new AddTimelineClipCommand(this, item->getBinId(), item->info(), item->effectList(), item->clipState(), true, true, deleteSelected); } else if (itemList.at(i)->type() == TransitionWidget) { transitionCount++; Transition *item = static_cast (itemList.at(i)); ////qDebug()<<"// DELETE TRANS AT: "<info().startPos.frames(25); new AddTransitionCommand(this, item->info(), item->transitionEndTrack(), item->toXML(), true, true, deleteSelected); } } if (groupCount > 0 && clipCount == 0 && transitionCount == 0) deleteSelected->setText(i18np("Delete selected group", "Delete selected groups", groupCount)); else if (clipCount > 0 && groupCount == 0 && transitionCount == 0) deleteSelected->setText(i18np("Delete selected clip", "Delete selected clips", clipCount)); else if (transitionCount > 0 && groupCount == 0 && clipCount == 0) deleteSelected->setText(i18np("Delete selected transition", "Delete selected transitions", transitionCount)); else deleteSelected->setText(i18n("Delete selected items")); updateTrackDuration(-1, deleteSelected); new RefreshMonitorCommand(this, true, false, deleteSelected); m_commandStack->push(deleteSelected); } void CustomTrackView::doChangeClipSpeed(ItemInfo info, const ItemInfo &speedIndependantInfo, PlaylistState::ClipState state, const double speed, int strobe, const QString &id, bool removeEffect) { ClipItem *item = getClipItemAtStart(info.startPos, info.track); if (!item) { //qDebug() << "ERROR: Cannot find clip for speed change"; emit displayMessage(i18n("Cannot find clip for speed change"), ErrorMessage); return; } int endPos = m_timeline->changeClipSpeed(info, speedIndependantInfo, state, speed, strobe, m_document->renderer()->getBinProducer(id), removeEffect); if (endPos >= 0) { item->setSpeed(speed, strobe); item->updateRectGeometry(); if (item->cropDuration().frames(m_document->fps()) != endPos - 1) { item->resizeEnd((int) info.startPos.frames(m_document->fps()) + endPos ); } updatePositionEffects(item, info, false); } else { emit displayMessage(i18n("Invalid clip"), ErrorMessage); } } void CustomTrackView::cutSelectedClips() { QList itemList = scene()->selectedItems(); QList groups; GenTime currentPos = GenTime(m_cursorPos, m_document->fps()); for (int i = 0; i < itemList.count(); ++i) { if (!itemList.at(i)) continue; if (itemList.at(i)->type() == AVWidget) { ClipItem *item = static_cast (itemList.at(i)); if (item->parentItem() && item->parentItem() != m_selectionGroup) { AbstractGroupItem *group = static_cast (item->parentItem()); if (!groups.contains(group)) groups << group; } else if (currentPos > item->startPos() && currentPos < item->endPos()) { RazorClipCommand *command = new RazorClipCommand(this, item->info(), item->effectList(), currentPos); m_commandStack->push(command); // Select right part of the cut for further cuts ClipItem *dup = getClipItemAtStart(currentPos, item->track()); if (item->isSelected() && dup) { m_scene->clearSelection(); item->setMainSelectedClip(false); dup->setSelected(true); m_dragItem = dup; m_dragItem->setMainSelectedClip(true); emit clipItemSelected(dup, false); } } } else if (itemList.at(i)->type() == GroupWidget && itemList.at(i) != m_selectionGroup) { AbstractGroupItem *group = static_cast(itemList.at(i)); if (!groups.contains(group)) groups << group; } } for (int i = 0; i < groups.count(); ++i) razorGroup(groups.at(i), currentPos); } void CustomTrackView::razorGroup(AbstractGroupItem* group, GenTime cutPos) { if (group) { QList children = group->childItems(); QUndoCommand *command = new QUndoCommand; command->setText(i18n("Cut Group")); groupClips(false, children, command); QList clips1, transitions1; QList transitionsCut; QList clips2, transitions2; QVector clipsToCut; // Collect info for (int i = 0; i < children.count(); ++i) { children.at(i)->setSelected(false); AbstractClipItem *child = static_cast (children.at(i)); if (!child) continue; if (child->type() == AVWidget) { if (cutPos >= child->endPos()) clips1 << child->info(); else if (cutPos <= child->startPos()) clips2 << child->info(); else { clipsToCut << child; } } else { if (cutPos > child->endPos()) transitions1 << child->info(); else if (cutPos < child->startPos()) transitions2 << child->info(); else { //transitionsCut << child->info(); // Transition cut not implemented, leave it in first group... transitions1 << child->info(); } } } if (clipsToCut.isEmpty() && transitionsCut.isEmpty() && ((clips1.isEmpty() && transitions1.isEmpty()) || (clips2.isEmpty() && transitions2.isEmpty()))) { delete command; return; } // Process the cut for (int i = 0; i < clipsToCut.count(); ++i) { ClipItem *clip = static_cast(clipsToCut.at(i)); new RazorClipCommand(this, clip->info(), clip->effectList(), cutPos, false, command); ClipItem *secondClip = cutClip(clip->info(), cutPos, true); clips1 << clip->info(); clips2 << secondClip->info(); } new GroupClipsCommand(this, clips1, transitions1, true, command); new GroupClipsCommand(this, clips2, transitions2, true, command); m_commandStack->push(command); } } void CustomTrackView::groupClips(bool group, QList itemList, QUndoCommand *command) { if (itemList.isEmpty()) itemList = scene()->selectedItems(); QList clipInfos; QList transitionInfos; QList existingGroups; // Expand groups int max = itemList.count(); for (int i = 0; i < max; ++i) { if (itemList.at(i)->type() == GroupWidget) { if (!existingGroups.contains(itemList.at(i))) { existingGroups << itemList.at(i); } itemList += itemList.at(i)->childItems(); } } for (int i = 0; i < itemList.count(); ++i) { if (itemList.at(i)->type() == AVWidget) { AbstractClipItem *clip = static_cast (itemList.at(i)); if (!clip->isItemLocked()) clipInfos.append(clip->info()); } else if (itemList.at(i)->type() == TransitionWidget) { AbstractClipItem *clip = static_cast (itemList.at(i)); if (!clip->isItemLocked()) transitionInfos.append(clip->info()); } } if (clipInfos.count() > 0) { // break previous groups QUndoCommand *metaCommand = NULL; if (group && !command && !existingGroups.isEmpty()) { metaCommand = new QUndoCommand(); metaCommand->setText(i18n("Group clips")); } for (int i = 0; group && i < existingGroups.count(); ++i) { AbstractGroupItem *grp = static_cast (existingGroups.at(i)); QList clipGrpInfos; QList transitionGrpInfos; QList items = grp->childItems(); for (int j = 0; j < items.count(); ++j) { if (items.at(j)->type() == AVWidget) { AbstractClipItem *clip = static_cast (items.at(j)); if (!clip->isItemLocked()) clipGrpInfos.append(clip->info()); } else if (items.at(j)->type() == TransitionWidget) { AbstractClipItem *clip = static_cast (items.at(j)); if (!clip->isItemLocked()) transitionGrpInfos.append(clip->info()); } } if (!clipGrpInfos.isEmpty() || !transitionGrpInfos.isEmpty()) { if (command) { new GroupClipsCommand(this, clipGrpInfos, transitionGrpInfos, false, command); } else { new GroupClipsCommand(this, clipGrpInfos, transitionGrpInfos, false, metaCommand); } } } if (command) { // Create new group new GroupClipsCommand(this, clipInfos, transitionInfos, group, command); } else { if (metaCommand) { new GroupClipsCommand(this, clipInfos, transitionInfos, group, metaCommand); m_commandStack->push(metaCommand); } else { GroupClipsCommand *command = new GroupClipsCommand(this, clipInfos, transitionInfos, group); m_commandStack->push(command); } } } } void CustomTrackView::doGroupClips(QList clipInfos, QList transitionInfos, bool group) { resetSelectionGroup(); m_scene->clearSelection(); if (!group) { // ungroup, find main group to destroy it... for (int i = 0; i < clipInfos.count(); ++i) { ClipItem *clip = getClipItemAtStart(clipInfos.at(i).startPos, clipInfos.at(i).track); if (clip == NULL) continue; if (clip->parentItem() && clip->parentItem()->type() == GroupWidget) { AbstractGroupItem *grp = static_cast (clip->parentItem()); m_document->clipManager()->removeGroup(grp); if (grp == m_selectionGroup) m_selectionGroup = NULL; scene()->destroyItemGroup(grp); } clip->setFlag(QGraphicsItem::ItemIsMovable, true); } for (int i = 0; i < transitionInfos.count(); ++i) { Transition *tr = getTransitionItemAt(transitionInfos.at(i).startPos, transitionInfos.at(i).track); if (tr == NULL) continue; if (tr->parentItem() && tr->parentItem()->type() == GroupWidget) { AbstractGroupItem *grp = static_cast (tr->parentItem()); m_document->clipManager()->removeGroup(grp); if (grp == m_selectionGroup) m_selectionGroup = NULL; scene()->destroyItemGroup(grp); grp = NULL; } tr->setFlag(QGraphicsItem::ItemIsMovable, true); } return; } QList list; for (int i = 0; i < clipInfos.count(); ++i) { ClipItem *clip = getClipItemAtStart(clipInfos.at(i).startPos, clipInfos.at(i).track); if (clip) { list.append(clip); //clip->setSelected(true); } } for (int i = 0; i < transitionInfos.count(); ++i) { Transition *clip = getTransitionItemAt(transitionInfos.at(i).startPos, transitionInfos.at(i).track); if (clip) { list.append(clip); //clip->setSelected(true); } } groupSelectedItems(list, true, true); } void CustomTrackView::slotInfoProcessingFinished() { m_producerNotReady.wakeAll(); } void CustomTrackView::addClip(const QString &clipId, ItemInfo info, EffectsList effects, PlaylistState::ClipState state, bool refresh) { qDebug()<<" / / /ADDING CLIP: "<getBinClip(clipId); if (!binClip) { emit displayMessage(i18n("Cannot insert clip..."), ErrorMessage); return; } if (!binClip->isReady()) { // If the clip has no producer, we must wait until it is created... emit displayMessage(i18n("Waiting for clip..."), InformationMessage); m_document->forceProcessing(clipId); // If the clip is not ready, give it 10x3 seconds to complete the task... for (int i = 0; i < 10; ++i) { if (!binClip->isReady()) { m_mutex.lock(); m_producerNotReady.wait(&m_mutex, 3000); m_mutex.unlock(); } else break; } if (!binClip->isReady()) { emit displayMessage(i18n("Cannot insert clip..."), ErrorMessage); return; } emit displayMessage(QString(), InformationMessage); } QLocale locale; locale.setNumberOptions(QLocale::OmitGroupSeparator); // Get speed and strobe values from effects double speed = 1.0; int strobe = 1; QDomElement speedEffect = effects.effectById("speed"); if (!speedEffect.isNull()) { QDomNodeList nodes = speedEffect.elementsByTagName("parameter"); for (int i = 0; i < nodes.count(); ++i) { QDomElement e = nodes.item(i).toElement(); if (e.attribute("name") == "speed") { speed = locale.toDouble(e.attribute("value", "1")); int factor = e.attribute("factor", "1").toInt(); speed /= factor; if (speed == 0) speed = 1; } else if (e.attribute("name") == "strobe") { strobe = e.attribute("value", "1").toInt(); } } } ClipItem *item = new ClipItem(binClip, info, m_document->fps(), speed, strobe, getFrameWidth()); item->setPos(info.startPos.frames(m_document->fps()), getPositionFromTrack(info.track) + 1 + item->itemOffset()); item->setState(state); item->setEffectList(effects); scene()->addItem(item); bool isLocked = m_timeline->getTrackInfo(info.track).isLocked; if (isLocked) item->setItemLocked(true); bool duplicate = true; Mlt::Producer *prod; if (speed != 1.0) { prod = m_document->renderer()->getBinProducer(clipId); QString url = QString::fromUtf8(prod->get("resource")); //url.append('?' + locale.toString(speed)); Track::SlowmoInfo slowInfo; slowInfo.speed = speed; slowInfo.strobe = strobe; slowInfo.state = state; Mlt::Producer *copy = m_document->renderer()->getSlowmotionProducer(slowInfo.toString(locale) + url); if (copy == NULL) { url.prepend(locale.toString(speed) + ":"); Mlt::Properties passProperties; Mlt::Properties original(prod->get_properties()); passProperties.pass_list(original, ClipController::getPassPropertiesList(false)); copy = m_timeline->track(info.track)->buildSlowMoProducer(passProperties, url, clipId, slowInfo); } if (copy == NULL) { emit displayMessage(i18n("Cannot insert clip..."), ErrorMessage); return; } prod = copy; duplicate = false; } else if (item->clipState() == PlaylistState::VideoOnly) { prod = m_document->renderer()->getBinVideoProducer(clipId); } else { prod = m_document->renderer()->getBinProducer(clipId); } binClip->addRef(); m_timeline->track(info.track)->add(info.startPos.seconds(), prod, info.cropStart.seconds(), (info.cropStart+info.cropDuration).seconds(), state, duplicate, m_scene->editMode()); for (int i = 0; i < item->effectsCount(); ++i) { m_document->renderer()->mltAddEffect(info.track, info.startPos, EffectsController::getEffectArgs(m_document->getProfileInfo(), item->effect(i)), false); } if (refresh) { m_document->renderer()->doRefresh(); } //TODO: manage placeholders /*if (!baseclip->isPlaceHolder()) m_waitingThumbs.append(item); */ //m_thumbsTimer.start(); } void CustomTrackView::slotUpdateClip(const QString &clipId, bool reload) { QMutexLocker locker(&m_mutex); QList list = scene()->items(); QList clipList; ClipItem *clip = NULL; //TODO: move the track replacement code in track.cpp Mlt::Tractor *tractor = m_document->renderer()->lockService(); for (int i = 0; i < list.size(); ++i) { if (list.at(i)->type() == AVWidget) { clip = static_cast (list.at(i)); if (clip->getBinId() == clipId) { //TODO: get audio / video only producers /*ItemInfo info = clip->info(); if (clip->isAudioOnly()) prod = baseClip->getTrackProducer(info.track); else if (clip->isVideoOnly()) prod = baseClip->getTrackProducer(info.track); else prod = baseClip->getTrackProducer(info.track);*/ if (reload) { /*Mlt::Producer *prod = m_document->renderer()->getTrackProducer(clipId, info.track, clip->isAudioOnly(), clip->isVideoOnly()); if (!m_document->renderer()->mltUpdateClip(tractor, info, clip->xml(), prod)) { emit displayMessage(i18n("Cannot update clip (time: %1, track: %2)", info.startPos.frames(m_document->fps()), info.track), ErrorMessage); }*/ } else clipList.append(clip); } } } for (int i = 0; i < clipList.count(); ++i) clipList.at(i)->refreshClip(true, true); m_document->renderer()->unlockService(tractor); } ClipItem *CustomTrackView::getClipItemAtEnd(GenTime pos, int track) { int framepos = (int)(pos.frames(m_document->fps())); QList list = scene()->items(QPointF(framepos - 1, getPositionFromTrack(track) + m_tracksHeight / 2)); ClipItem *clip = NULL; for (int i = 0; i < list.size(); ++i) { if (!list.at(i)->isEnabled()) continue; if (list.at(i)->type() == AVWidget) { ClipItem *test = static_cast (list.at(i)); if (test->endPos() == pos) clip = test; break; } } return clip; } ClipItem *CustomTrackView::getClipItemAtStart(GenTime pos, int track, GenTime end) { QList list = scene()->items(QPointF(pos.frames(m_document->fps()), getPositionFromTrack(track) + m_tracksHeight / 2)); ClipItem *clip = NULL; for (int i = 0; i < list.size(); ++i) { if (!list.at(i)->isEnabled()) continue; if (list.at(i)->type() == AVWidget) { ClipItem *test = static_cast (list.at(i)); if (test->startPos() == pos) { if (end > GenTime()) { if (test->endPos() != end) { continue; } } clip = test; break; } } } return clip; } ClipItem *CustomTrackView::getClipItemAtMiddlePoint(int pos, int track) { const QPointF p(pos, getPositionFromTrack(track) + m_tracksHeight / 2); QList list = scene()->items(p); ClipItem *clip = NULL; for (int i = 0; i < list.size(); ++i) { if (!list.at(i)->isEnabled()) continue; if (list.at(i)->type() == AVWidget) { clip = static_cast (list.at(i)); break; } } return clip; } Transition *CustomTrackView::getTransitionItemAt(int pos, int track) { const QPointF p(pos, getPositionFromTrack(track) + Transition::itemOffset() + 1); QList list = scene()->items(p); Transition *clip = NULL; for (int i = 0; i < list.size(); ++i) { if (!list.at(i)->isEnabled()) continue; if (list.at(i)->type() == TransitionWidget) { clip = static_cast (list.at(i)); break; } } return clip; } Transition *CustomTrackView::getTransitionItemAt(GenTime pos, int track) { return getTransitionItemAt(pos.frames(m_document->fps()), track); } Transition *CustomTrackView::getTransitionItemAtEnd(GenTime pos, int track) { int framepos = (int)(pos.frames(m_document->fps())); const QPointF p(framepos - 1, getPositionFromTrack(track) + Transition::itemOffset() + 1); QList list = scene()->items(p); Transition *clip = NULL; for (int i = 0; i < list.size(); ++i) { if (!list.at(i)->isEnabled()) continue; if (list.at(i)->type() == TransitionWidget) { Transition *test = static_cast (list.at(i)); if (test->endPos() == pos) clip = test; break; } } return clip; } Transition *CustomTrackView::getTransitionItemAtStart(GenTime pos, int track) { const QPointF p(pos.frames(m_document->fps()), getPositionFromTrack(track) + Transition::itemOffset() + 1); QList list = scene()->items(p); Transition *clip = NULL; for (int i = 0; i < list.size(); ++i) { if (!list.at(i)->isEnabled()) continue; if (list.at(i)->type() == TransitionWidget) { Transition *test = static_cast (list.at(i)); if (test->startPos() == pos) clip = test; break; } } return clip; } bool CustomTrackView::moveClip(const ItemInfo &start, const ItemInfo &end, bool refresh, ItemInfo *out_actualEnd) { if (m_selectionGroup) resetSelectionGroup(false); ClipItem *item = getClipItemAtStart(start.startPos, start.track); if (!item) { emit displayMessage(i18n("Cannot move clip at time: %1 on track %2", m_document->timecode().getTimecodeFromFrames(start.startPos.frames(m_document->fps())), start.track), ErrorMessage); //qDebug() << "---------------- ERROR, CANNOT find clip to move at.. "; return false; } #ifdef DEBUG qDebug() << "Moving item " << (long)item << " from .. to:"; qDebug() << item->info(); qDebug() << start; qDebug() << end; #endif bool success = m_timeline->moveClip(start.track, start.startPos.seconds(), end.track, end.startPos.seconds(), item->clipState(), m_scene->editMode(), item->needsDuplicate()); if (success) { bool snap = KdenliveSettings::snaptopoints(); KdenliveSettings::setSnaptopoints(false); item->setPos((int) end.startPos.frames(m_document->fps()), getPositionFromTrack(end.track) + 1); bool isLocked = m_timeline->getTrackInfo(end.track).isLocked; m_scene->clearSelection(); if (isLocked) item->setItemLocked(true); else { if (item->isItemLocked()) item->setItemLocked(false); item->setSelected(true); } //TODO /* if (item->baseClip()->isTransparent()) { // Also move automatic transition Transition *tr = getTransitionItemAt(start.startPos, start.track); if (tr && tr->isAutomatic()) { tr->updateTransitionEndTrack(getPreviousVideoTrack(end.track)); m_document->renderer()->mltMoveTransition(tr->transitionTag(), m_timeline->tracksCount() - start.track, m_timeline->tracksCount() - end.track, tr->transitionEndTrack(), start.startPos, start.endPos, end.startPos, end.endPos); tr->setPos((int) end.startPos.frames(m_document->fps()), (int)(end.track * m_tracksHeight + 1)); } }*/ KdenliveSettings::setSnaptopoints(snap); } else { // undo last move and emit error message emit displayMessage(i18n("Cannot move clip to position %1", m_document->timecode().getTimecodeFromFrames(end.startPos.frames(m_document->fps()))), ErrorMessage); } if (refresh) m_document->renderer()->doRefresh(); if (out_actualEnd != NULL) { *out_actualEnd = item->info(); #ifdef DEBUG qDebug() << "Actual end position updated:" << *out_actualEnd; #endif } #ifdef DEBUG qDebug() << item->info(); #endif return success; } void CustomTrackView::moveGroup(QList startClip, QList startTransition, const GenTime &offset, const int trackOffset, bool reverseMove) { // Group Items resetSelectionGroup(); m_scene->clearSelection(); m_selectionMutex.lock(); m_selectionGroup = new AbstractGroupItem(m_document->fps()); scene()->addItem(m_selectionGroup); m_document->renderer()->blockSignals(true); for (int i = 0; i < startClip.count(); ++i) { if (reverseMove) { startClip[i].startPos = startClip.at(i).startPos - offset; startClip[i].track = startClip.at(i).track - trackOffset; } ClipItem *clip = getClipItemAtStart(startClip.at(i).startPos, startClip.at(i).track); if (clip) { clip->setItemLocked(false); if (clip->parentItem()) { m_selectionGroup->addItem(clip->parentItem()); } else { m_selectionGroup->addItem(clip); } m_timeline->track(startClip.at(i).track)->del(startClip.at(i).startPos.seconds()); } else qDebug() << "//MISSING CLIP AT: " << startClip.at(i).startPos.frames(25)<<" / track: "<setItemLocked(false); if (tr->parentItem()) { m_selectionGroup->addItem(tr->parentItem()); } else { m_selectionGroup->addItem(tr); } m_timeline->transitionHandler->deleteTransition(tr->transitionTag(), tr->transitionEndTrack(), startTransition.at(i).track, startTransition.at(i).startPos, startTransition.at(i).endPos, tr->toXML()); } else qDebug() << "//MISSING TRANSITION AT: " << startTransition.at(i).startPos.frames(25); } m_document->renderer()->blockSignals(false); if (m_selectionGroup) { bool snap = KdenliveSettings::snaptopoints(); KdenliveSettings::setSnaptopoints(false); m_selectionGroup->setTransform(QTransform::fromTranslate(offset.frames(m_document->fps()), -trackOffset *(qreal) m_tracksHeight), true); //m_selectionGroup->moveBy(offset.frames(m_document->fps()), trackOffset *(qreal) m_tracksHeight); QList children = m_selectionGroup->childItems(); QList groupList; // Expand groups int max = children.count(); for (int i = 0; i < max; ++i) { if (children.at(i)->type() == GroupWidget) { children += children.at(i)->childItems(); //AbstractGroupItem *grp = static_cast(children.at(i)); //grp->moveBy(offset.frames(m_document->fps()), trackOffset *(qreal) m_tracksHeight); /*m_document->clipManager()->removeGroup(grp); m_scene->destroyItemGroup(grp);*/ AbstractGroupItem *group = static_cast(children.at(i)); if (!groupList.contains(group)) groupList.append(group); children.removeAll(children.at(i)); --i; } } for (int i = 0; i < children.count(); ++i) { // re-add items in correct place if (children.at(i)->type() != AVWidget && children.at(i)->type() != TransitionWidget) continue; AbstractClipItem *item = static_cast (children.at(i)); int realTrack = getTrackFromPos(item->scenePos().y()); item->updateItem(realTrack); ItemInfo info = item->info(); bool isLocked = m_timeline->getTrackInfo(info.track).isLocked; if (isLocked) item->setItemLocked(true); else if (item->isItemLocked()) item->setItemLocked(false); if (item->type() == AVWidget) { ClipItem *clip = static_cast (item); Mlt::Producer *prod; if (clip->clipState() == PlaylistState::VideoOnly) { prod = m_document->renderer()->getBinVideoProducer(clip->getBinId()); } else { prod = m_document->renderer()->getBinProducer(clip->getBinId()); } m_timeline->track(info.track)->add(info.startPos.seconds(), prod, info.cropStart.seconds(), (info.cropStart + info.cropDuration).seconds(), clip->clipState(), true, m_scene->editMode()); for (int i = 0; i < clip->effectsCount(); ++i) { m_document->renderer()->mltAddEffect(info.track, info.startPos, EffectsController::getEffectArgs(m_document->getProfileInfo(), clip->effect(i)), false); } } else if (item->type() == TransitionWidget) { Transition *tr = static_cast (item); int newTrack; if (!tr->forcedTrack()) { newTrack = getPreviousVideoTrack(info.track); } else { newTrack = tr->transitionEndTrack() + trackOffset; if (newTrack < 0 || newTrack > m_timeline->tracksCount()) newTrack = getPreviousVideoTrack(info.track); } tr->updateTransitionEndTrack(newTrack); m_timeline->transitionHandler->addTransition(tr->transitionTag(), newTrack, info.track, info.startPos, info.endPos, tr->toXML()); } } m_selectionMutex.unlock(); resetSelectionGroup(false); for (int i = 0; i < groupList.count(); ++i) { rebuildGroup(groupList.at(i)); } clearSelection(); KdenliveSettings::setSnaptopoints(snap); m_document->renderer()->doRefresh(); } else qDebug() << "///////// WARNING; NO GROUP TO MOVE"; } void CustomTrackView::moveTransition(const ItemInfo &start, const ItemInfo &end, bool refresh) { Transition *item = getTransitionItemAt(start.startPos, start.track); if (!item) { emit displayMessage(i18n("Cannot move transition at time: %1 on track %2", m_document->timecode().getTimecodeFromFrames(start.startPos.frames(m_document->fps())), start.track), ErrorMessage); //qDebug() << "---------------- ERROR, CANNOT find transition to move... ";// << startPos.x() * m_scale * FRAME_SIZE + 1 << ", " << startPos.y() * m_tracksHeight + m_tracksHeight / 2; return; } bool snap = KdenliveSettings::snaptopoints(); KdenliveSettings::setSnaptopoints(false); if (end.endPos - end.startPos == start.endPos - start.startPos) { // Transition was moved item->setPos((int) end.startPos.frames(m_document->fps()), getPositionFromTrack(end.track) + 1); } else if (end.endPos == start.endPos) { // Transition start resize item->resizeStart((int) end.startPos.frames(m_document->fps())); } else if (end.startPos == start.startPos) { // Transition end resize; //qDebug() << "// resize END: " << end.endPos.frames(m_document->fps()); item->resizeEnd((int) end.endPos.frames(m_document->fps())); } else { // Move & resize item->setPos((int) end.startPos.frames(m_document->fps()), getPositionFromTrack(end.track) + 1); item->resizeStart((int) end.startPos.frames(m_document->fps())); item->resizeEnd((int) end.endPos.frames(m_document->fps())); } //item->transitionHandler->moveTransition(GenTime((int) (endPos.x() - startPos.x()), m_document->fps())); KdenliveSettings::setSnaptopoints(snap); item->updateTransitionEndTrack(getPreviousVideoTrack(end.track)); m_timeline->transitionHandler->moveTransition(item->transitionTag(), start.track, item->track(), item->transitionEndTrack(), start.startPos, start.endPos, item->startPos(), item->endPos()); if (m_dragItem && m_dragItem == item) { QPoint p; ClipItem *transitionClip = getClipItemAtStart(item->startPos(), item->track()); if (transitionClip && transitionClip->binClip()) { int frameWidth = transitionClip->binClip()->getProducerIntProperty(QStringLiteral("meta.media.width")); int frameHeight = transitionClip->binClip()->getProducerIntProperty(QStringLiteral("meta.media.height")); double factor = transitionClip->binClip()->getProducerProperty(QStringLiteral("aspect_ratio")).toDouble(); if (factor == 0) factor = 1.0; p.setX((int)(frameWidth * factor + 0.5)); p.setY(frameHeight); } emit transitionItemSelected(item, getPreviousVideoTrack(item->track()), p); } if (refresh) m_document->renderer()->doRefresh(); } void CustomTrackView::resizeClip(const ItemInfo &start, const ItemInfo &end, bool dontWorry) { bool resizeClipStart = (start.startPos != end.startPos); ClipItem *item = getClipItemAtStart(start.startPos, start.track, start.startPos + start.cropDuration); if (!item) { if (dontWorry) return; emit displayMessage(i18n("Cannot move clip at time: %1 on track %2", m_document->timecode().getTimecodeFromFrames(start.startPos.frames(m_document->fps())), start.track), ErrorMessage); //qDebug() << "---------------- ERROR, CANNOT find clip to resize at... "; // << startPos; return; } if (item->parentItem()) { // Item is part of a group, reset group resetSelectionGroup(); } bool snap = KdenliveSettings::snaptopoints(); KdenliveSettings::setSnaptopoints(false); if (resizeClipStart) { if (m_timeline->track(start.track)->resize(start.startPos.seconds(), (end.startPos - start.startPos).seconds(), false)) item->resizeStart((int) end.startPos.frames(m_document->fps())); } else { if (m_timeline->track(start.track)->resize(start.startPos.seconds(), (end.endPos - start.endPos).seconds(), true)) item->resizeEnd((int) end.endPos.frames(m_document->fps())); } if (!resizeClipStart && end.cropStart != start.cropStart) { //qDebug() << "// RESIZE CROP, DIFF: " << (end.cropStart - start.cropStart).frames(25); ItemInfo clipinfo = end; clipinfo.track = end.track; bool success = m_document->renderer()->mltResizeClipCrop(clipinfo, end.cropStart); if (success) { item->setCropStart(end.cropStart); item->resetThumbs(true); } } m_document->renderer()->doRefresh(); if (item == m_dragItem) { // clip is selected, update effect stack emit clipItemSelected(item); } KdenliveSettings::setSnaptopoints(snap); } void CustomTrackView::prepareResizeClipStart(AbstractClipItem* item, ItemInfo oldInfo, int pos, bool check, QUndoCommand *command) { if (pos == oldInfo.startPos.frames(m_document->fps())) return; bool snap = KdenliveSettings::snaptopoints(); if (check) { KdenliveSettings::setSnaptopoints(false); item->resizeStart(pos); if (item->startPos().frames(m_document->fps()) != pos) { item->resizeStart(oldInfo.startPos.frames(m_document->fps())); emit displayMessage(i18n("Not possible to resize"), ErrorMessage); KdenliveSettings::setSnaptopoints(snap); return; } KdenliveSettings::setSnaptopoints(snap); } bool hasParentCommand = false; if (command) { hasParentCommand = true; } else { command = new QUndoCommand(); command->setText(i18n("Resize clip start")); } // do this here, too, because otherwise undo won't update the group if (item->parentItem() && item->parentItem() != m_selectionGroup) new RebuildGroupCommand(this, item->info().track, item->endPos() - GenTime(1, m_document->fps()), command); ItemInfo info = item->info(); if (item->type() == AVWidget) { bool success = m_timeline->track(oldInfo.track)->resize(oldInfo.startPos.seconds(), (item->startPos() - oldInfo.startPos).seconds(), false); if (success) { // Check if there is an automatic transition on that clip (lower track) Transition *transition = getTransitionItemAtStart(oldInfo.startPos, oldInfo.track); if (transition && transition->isAutomatic()) { ItemInfo trInfo = transition->info(); ItemInfo newTrInfo = trInfo; newTrInfo.startPos = item->startPos(); newTrInfo.cropDuration = trInfo.endPos - newTrInfo.startPos; if (newTrInfo.startPos < newTrInfo.endPos) { QDomElement old = transition->toXML(); if (transition->updateKeyframes(trInfo, newTrInfo)) { QDomElement xml = transition->toXML(); m_timeline->transitionHandler->updateTransition(xml.attribute("tag"), xml.attribute("tag"), xml.attribute("transition_btrack").toInt(), xml.attribute("transition_atrack").toInt(), newTrInfo.startPos, newTrInfo.endPos, xml); new EditTransitionCommand(this, transition->track(), transition->startPos(), old, xml, false, command); } new MoveTransitionCommand(this, trInfo, newTrInfo, true, command); } } // Check if there is an automatic transition on that clip (upper track) transition = getTransitionItemAtStart(oldInfo.startPos, oldInfo.track + 1); if (transition && transition->isAutomatic() && transition->transitionEndTrack() == oldInfo.track) { ItemInfo trInfo = transition->info(); ItemInfo newTrInfo = trInfo; newTrInfo.startPos = item->startPos(); newTrInfo.cropDuration = trInfo.endPos - newTrInfo.startPos; ClipItem * upperClip = getClipItemAtStart(oldInfo.startPos, oldInfo.track + 1); //TODO if ((!upperClip /*|| !upperClip->baseClip()->isTransparent()*/) && newTrInfo.startPos < newTrInfo.endPos) { QDomElement old = transition->toXML(); if (transition->updateKeyframes(trInfo, newTrInfo)) { QDomElement xml = transition->toXML(); m_timeline->transitionHandler->updateTransition(xml.attribute("tag"), xml.attribute("tag"), xml.attribute("transition_btrack").toInt(), xml.attribute("transition_atrack").toInt(), newTrInfo.startPos, newTrInfo.endPos, xml); new EditTransitionCommand(this, transition->track(), transition->startPos(), old, xml, false, command); } new MoveTransitionCommand(this, trInfo, newTrInfo, true, command); } } ClipItem *clip = static_cast < ClipItem * >(item); // Hack: // Since we must always resize clip before updating the keyframes, we // put a resize command before & after checking keyframes so that // we are sure the resize is performed before whenever we do or undo the action // TODO: find a way to apply adjusteffect after the resize command was done / undone new ResizeClipCommand(this, oldInfo, info, false, true, command); adjustEffects(clip, oldInfo, command); } else { KdenliveSettings::setSnaptopoints(false); item->resizeStart((int) oldInfo.startPos.frames(m_document->fps())); KdenliveSettings::setSnaptopoints(snap); emit displayMessage(i18n("Error when resizing clip"), ErrorMessage); } } else if (item->type() == TransitionWidget) { Transition *transition = static_cast (item); if (!m_timeline->transitionHandler->moveTransition(transition->transitionTag(), oldInfo.track, oldInfo.track, transition->transitionEndTrack(), oldInfo.startPos, oldInfo.endPos, info.startPos, info.endPos)) { // Cannot resize transition KdenliveSettings::setSnaptopoints(false); transition->resizeStart((int) oldInfo.startPos.frames(m_document->fps())); KdenliveSettings::setSnaptopoints(snap); emit displayMessage(i18n("Cannot resize transition"), ErrorMessage); } else { EditTransitionCommand *kfrCommand = NULL; QDomElement old = transition->toXML(); if (transition->updateKeyframes(oldInfo, info)) { QDomElement xml = transition->toXML(); m_timeline->transitionHandler->updateTransition(xml.attribute("tag"), xml.attribute("tag"), xml.attribute("transition_btrack").toInt(), xml.attribute("transition_atrack").toInt(), info.startPos, info.endPos, xml); kfrCommand = new EditTransitionCommand(this, transition->track(), transition->startPos(), old, xml, false, command); } MoveTransitionCommand *moveCommand = new MoveTransitionCommand(this, oldInfo, info, false, command); if (command == NULL) { if (kfrCommand) m_commandStack->push(kfrCommand); m_commandStack->push(moveCommand); } } } if (item->parentItem() && item->parentItem() != m_selectionGroup) new RebuildGroupCommand(this, item->info().track, item->endPos() - GenTime(1, m_document->fps()), command); if (!hasParentCommand) m_commandStack->push(command); } void CustomTrackView::prepareResizeClipEnd(AbstractClipItem* item, ItemInfo oldInfo, int pos, bool check, QUndoCommand *command) { if (pos == oldInfo.endPos.frames(m_document->fps())) return; bool snap = KdenliveSettings::snaptopoints(); if (check) { KdenliveSettings::setSnaptopoints(false); item->resizeEnd(pos); if (item->endPos().frames(m_document->fps()) != pos) { item->resizeEnd(oldInfo.endPos.frames(m_document->fps())); emit displayMessage(i18n("Not possible to resize"), ErrorMessage); KdenliveSettings::setSnaptopoints(snap); return; } KdenliveSettings::setSnaptopoints(snap); } bool hasParentCommand = false; if (command) { hasParentCommand = true; } else { command = new QUndoCommand(); } // do this here, too, because otherwise undo won't update the group if (item->parentItem() && item->parentItem() != m_selectionGroup) new RebuildGroupCommand(this, item->info().track, item->startPos(), command); ItemInfo info = item->info(); if (item->type() == AVWidget) { if (!hasParentCommand) command->setText(i18n("Resize clip end")); bool success = m_timeline->track(info.track)->resize(oldInfo.startPos.seconds(), (info.endPos - oldInfo.endPos).seconds(), true); if (success) { // Check if there is an automatic transition on that clip (lower track) Transition *tr = getTransitionItemAtEnd(oldInfo.endPos, oldInfo.track); if (tr && tr->isAutomatic()) { ItemInfo trInfo = tr->info(); ItemInfo newTrInfo = trInfo; newTrInfo.endPos = item->endPos(); newTrInfo.cropDuration = newTrInfo.endPos - trInfo.startPos; if (newTrInfo.endPos > newTrInfo.startPos) { QDomElement old = tr->toXML(); if (tr->updateKeyframes(trInfo, newTrInfo)) { QDomElement xml = tr->toXML(); m_timeline->transitionHandler->updateTransition(xml.attribute("tag"), xml.attribute("tag"), xml.attribute("transition_btrack").toInt(), xml.attribute("transition_atrack").toInt(), newTrInfo.startPos, newTrInfo.endPos, xml); new EditTransitionCommand(this, tr->track(), tr->startPos(), old, xml, false, command); } new MoveTransitionCommand(this, trInfo, newTrInfo, true, command); } } // Check if there is an automatic transition on that clip (upper track) tr = getTransitionItemAtEnd(oldInfo.endPos, oldInfo.track - 1); if (tr && tr->isAutomatic() && tr->transitionEndTrack() == oldInfo.track) { ItemInfo trInfo = tr->info(); ItemInfo newTrInfo = trInfo; newTrInfo.endPos = item->endPos(); newTrInfo.cropDuration = newTrInfo.endPos - trInfo.startPos; ClipItem * upperClip = getClipItemAtEnd(oldInfo.endPos, oldInfo.track + 1); //TODO if ((!upperClip /*|| !upperClip->baseClip()->isTransparent()*/) && newTrInfo.endPos > newTrInfo.startPos) { QDomElement old = tr->toXML(); if (tr->updateKeyframes(trInfo, newTrInfo)) { QDomElement xml = tr->toXML(); m_timeline->transitionHandler->updateTransition(xml.attribute("tag"), xml.attribute("tag"), xml.attribute("transition_btrack").toInt(), xml.attribute("transition_atrack").toInt(), newTrInfo.startPos, newTrInfo.endPos, xml); new EditTransitionCommand(this, tr->track(), tr->startPos(), old, xml, false, command); } new MoveTransitionCommand(this, trInfo, newTrInfo, true, command); } } ClipItem *clip = static_cast < ClipItem * >(item); // Hack: // Since we must always resize clip before updating the keyframes, we // put a resize command before & after checking keyframes so that // we are sure the resize is performed before whenever we do or undo the action // TODO: find a way to apply adjusteffect after the resize command was done / undone new ResizeClipCommand(this, oldInfo, info, false, true, command); adjustEffects(clip, oldInfo, command); } else { KdenliveSettings::setSnaptopoints(false); item->resizeEnd((int) oldInfo.endPos.frames(m_document->fps())); KdenliveSettings::setSnaptopoints(true); emit displayMessage(i18n("Error when resizing clip"), ErrorMessage); } } else if (item->type() == TransitionWidget) { if (!hasParentCommand) command->setText(i18n("Resize transition end")); Transition *transition = static_cast (item); if (!m_timeline->transitionHandler->moveTransition(transition->transitionTag(), oldInfo.track, oldInfo.track, transition->transitionEndTrack(), oldInfo.startPos, oldInfo.endPos, info.startPos, info.endPos)) { // Cannot resize transition KdenliveSettings::setSnaptopoints(false); transition->resizeEnd((int) oldInfo.endPos.frames(m_document->fps())); KdenliveSettings::setSnaptopoints(true); emit displayMessage(i18n("Cannot resize transition"), ErrorMessage); } else { // Check transition keyframes QDomElement old = transition->toXML(); ItemInfo info = transition->info(); if (transition->updateKeyframes(oldInfo, info)) { QDomElement xml = transition->toXML(); m_timeline->transitionHandler->updateTransition(xml.attribute(QStringLiteral("tag")), xml.attribute(QStringLiteral("tag")), xml.attribute(QStringLiteral("transition_btrack")).toInt(), xml.attribute(QStringLiteral("transition_atrack")).toInt(), transition->startPos(), transition->endPos(), xml); new EditTransitionCommand(this, transition->track(), transition->startPos(), old, xml, false, command); } QPoint p; ClipItem *transitionClip = getClipItemAtStart(info.startPos, info.track); if (transitionClip && transitionClip->binClip()) { int frameWidth = transitionClip->binClip()->getProducerIntProperty(QStringLiteral("meta.media.width")); int frameHeight = transitionClip->binClip()->getProducerIntProperty(QStringLiteral("meta.media.height")); double factor = transitionClip->binClip()->getProducerProperty(QStringLiteral("aspect_ratio")).toDouble(); if (factor == 0) factor = 1.0; p.setX((int)(frameWidth * factor + 0.5)); p.setY(frameHeight); } emit transitionItemSelected(transition, getPreviousVideoTrack(info.track), p, true); new MoveTransitionCommand(this, oldInfo, info, false, command); } } if (item->parentItem() && item->parentItem() != m_selectionGroup) new RebuildGroupCommand(this, item->info().track, item->startPos(), command); if (!hasParentCommand) { updateTrackDuration(oldInfo.track, command); m_commandStack->push(command); } } void CustomTrackView::updatePositionEffects(ClipItem* item, const ItemInfo &info, bool standalone) { int effectPos = item->hasEffect(QStringLiteral("volume"), QStringLiteral("fadein")); if (effectPos != -1) { QDomElement effect = item->getEffectAtIndex(effectPos); int start = item->cropStart().frames(m_document->fps()); int duration = EffectsList::parameter(effect, QStringLiteral("out")).toInt() - EffectsList::parameter(effect, QStringLiteral("in")).toInt(); int max = item->cropDuration().frames(m_document->fps()); if (duration > max) { // Make sure the fade effect is not longer than the clip duration = max; } duration += start; EffectsList::setParameter(effect, QStringLiteral("in"), QString::number(start)); EffectsList::setParameter(effect, QStringLiteral("out"), QString::number(duration)); if (!m_document->renderer()->mltEditEffect(item->track(), item->startPos(), EffectsController::getEffectArgs(m_document->getProfileInfo(), effect), false)) { emit displayMessage(i18n("Problem editing effect"), ErrorMessage); } // if fade effect is displayed, update the effect edit widget with new clip duration if (standalone && item->isSelected() && effectPos == item->selectedEffectIndex()) { emit clipItemSelected(item); } } effectPos = item->hasEffect(QStringLiteral("brightness"), QStringLiteral("fade_from_black")); if (effectPos != -1) { QDomElement effect = item->getEffectAtIndex(effectPos); int start = item->cropStart().frames(m_document->fps()); int duration = EffectsList::parameter(effect, QStringLiteral("out")).toInt() - EffectsList::parameter(effect, QStringLiteral("in")).toInt(); int max = item->cropDuration().frames(m_document->fps()); if (duration > max) { // Make sure the fade effect is not longer than the clip duration = max; } duration += start; EffectsList::setParameter(effect, QStringLiteral("in"), QString::number(start)); EffectsList::setParameter(effect, QStringLiteral("out"), QString::number(duration)); if (!m_document->renderer()->mltEditEffect(item->track(), item->startPos(), EffectsController::getEffectArgs(m_document->getProfileInfo(), effect), false)) { emit displayMessage(i18n("Problem editing effect"), ErrorMessage); } // if fade effect is displayed, update the effect edit widget with new clip duration if (standalone && item->isSelected() && effectPos == item->selectedEffectIndex()) { emit clipItemSelected(item); } } effectPos = item->hasEffect(QStringLiteral("volume"), QStringLiteral("fadeout")); if (effectPos != -1) { // there is a fade out effect QDomElement effect = item->getEffectAtIndex(effectPos); int max = item->cropDuration().frames(m_document->fps()); int end = max + item->cropStart().frames(m_document->fps()) - 1; int duration = EffectsList::parameter(effect, QStringLiteral("out")).toInt() - EffectsList::parameter(effect, QStringLiteral("in")).toInt(); if (duration > max) { // Make sure the fade effect is not longer than the clip duration = max; } int start = end - duration; EffectsList::setParameter(effect, QStringLiteral("in"), QString::number(start)); EffectsList::setParameter(effect, QStringLiteral("out"), QString::number(end)); if (!m_document->renderer()->mltEditEffect(item->track(), item->startPos(), EffectsController::getEffectArgs(m_document->getProfileInfo(), effect), false)) { emit displayMessage(i18n("Problem editing effect"), ErrorMessage); } // if fade effect is displayed, update the effect edit widget with new clip duration if (standalone && item->isSelected() && effectPos == item->selectedEffectIndex()) { emit clipItemSelected(item); } } effectPos = item->hasEffect(QStringLiteral("brightness"), QStringLiteral("fade_to_black")); if (effectPos != -1) { QDomElement effect = item->getEffectAtIndex(effectPos); int max = item->cropDuration().frames(m_document->fps()); int end = max + item->cropStart().frames(m_document->fps()) - 1; int duration = EffectsList::parameter(effect, QStringLiteral("out")).toInt() - EffectsList::parameter(effect, QStringLiteral("in")).toInt(); if (duration > max) { // Make sure the fade effect is not longer than the clip duration = max; } int start = end - duration; EffectsList::setParameter(effect, QStringLiteral("in"), QString::number(start)); EffectsList::setParameter(effect, QStringLiteral("out"), QString::number(end)); if (!m_document->renderer()->mltEditEffect(item->track(), item->startPos(), EffectsController::getEffectArgs(m_document->getProfileInfo(), effect), false)) { emit displayMessage(i18n("Problem editing effect"), ErrorMessage); } // if fade effect is displayed, update the effect edit widget with new clip duration if (standalone && item->isSelected() && effectPos == item->selectedEffectIndex()) { emit clipItemSelected(item); } } effectPos = item->hasEffect(QStringLiteral("freeze"), QStringLiteral("freeze")); if (effectPos != -1) { // Freeze effect needs to be adjusted with clip resize int diff = (info.startPos - item->startPos()).frames(m_document->fps()); QDomElement eff = item->getEffectAtIndex(effectPos); if (!eff.isNull() && diff != 0) { int freeze_pos = EffectsList::parameter(eff, QStringLiteral("frame")).toInt() + diff; EffectsList::setParameter(eff, QStringLiteral("frame"), QString::number(freeze_pos)); if (standalone && item->isSelected() && item->selectedEffect().attribute("id") == "freeze") { emit clipItemSelected(item); } } } } double CustomTrackView::getSnapPointForPos(double pos) { return m_scene->getSnapPointForPos(pos, KdenliveSettings::snaptopoints()); } void CustomTrackView::updateSnapPoints(AbstractClipItem *selected, QList offsetList, bool skipSelectedItems) { QList snaps; if (selected && offsetList.isEmpty()) offsetList.append(selected->cropDuration()); QList itemList = items(); for (int i = 0; i < itemList.count(); ++i) { if (itemList.at(i) == selected) continue; if (skipSelectedItems && itemList.at(i)->isSelected()) continue; if (itemList.at(i)->type() == AVWidget) { ClipItem *item = static_cast (itemList.at(i)); if (!item) continue; GenTime start = item->startPos(); GenTime end = item->endPos(); snaps.append(start); snaps.append(end); if (!offsetList.isEmpty()) { for (int j = 0; j < offsetList.size(); ++j) { if (start > offsetList.at(j)) snaps.append(start - offsetList.at(j)); if (end > offsetList.at(j)) snaps.append(end - offsetList.at(j)); } } // Add clip markers ClipController *controller = m_document->getClipController(item->getBinId()); if (!controller) { qDebug()<<" + + ++ WARN, NO CTLRR!!!"; } QList < GenTime > markers = item->snapMarkers(controller->snapMarkers()); for (int j = 0; j < markers.size(); ++j) { GenTime t = markers.at(j); snaps.append(t); if (!offsetList.isEmpty()) { for (int k = 0; k < offsetList.size(); ++k) { if (t > offsetList.at(k)) snaps.append(t - offsetList.at(k)); } } } } else if (itemList.at(i)->type() == TransitionWidget) { Transition *transition = static_cast (itemList.at(i)); if (!transition) continue; GenTime start = transition->startPos(); GenTime end = transition->endPos(); snaps.append(start); snaps.append(end); if (!offsetList.isEmpty()) { for (int j = 0; j < offsetList.size(); ++j) { if (start > offsetList.at(j)) snaps.append(start - offsetList.at(j)); if (end > offsetList.at(j)) snaps.append(end - offsetList.at(j)); } } } } // add cursor position GenTime pos = GenTime(m_cursorPos, m_document->fps()); snaps.append(pos); if (!offsetList.isEmpty()) { for (int j = 0; j < offsetList.size(); ++j) { snaps.append(pos - offsetList.at(j)); } } // add guides for (int i = 0; i < m_guides.count(); ++i) { snaps.append(m_guides.at(i)->position()); if (!offsetList.isEmpty()) { for (int j = 0; j < offsetList.size(); ++j) { snaps.append(m_guides.at(i)->position() - offsetList.at(j)); } } } // add render zone QPoint z = m_document->zone(); snaps.append(GenTime(z.x(), m_document->fps())); snaps.append(GenTime(z.y(), m_document->fps())); qSort(snaps); m_scene->setSnapList(snaps); //for (int i = 0; i < m_snapPoints.size(); ++i) // //qDebug() << "SNAP POINT: " << m_snapPoints.at(i).frames(25); } void CustomTrackView::slotSeekToPreviousSnap() { updateSnapPoints(NULL); seekCursorPos((int) m_scene->previousSnapPoint(GenTime(m_cursorPos, m_document->fps())).frames(m_document->fps())); checkScrolling(); } void CustomTrackView::slotSeekToNextSnap() { updateSnapPoints(NULL); seekCursorPos((int) m_scene->nextSnapPoint(GenTime(m_cursorPos, m_document->fps())).frames(m_document->fps())); checkScrolling(); } void CustomTrackView::clipStart() { AbstractClipItem *item = getMainActiveClip(); if (item == NULL) { item = m_dragItem; } if (item != NULL) { seekCursorPos((int) item->startPos().frames(m_document->fps())); checkScrolling(); } } void CustomTrackView::clipEnd() { AbstractClipItem *item = getMainActiveClip(); if (item == NULL) { item = m_dragItem; } if (item != NULL) { seekCursorPos((int) item->endPos().frames(m_document->fps()) - 1); checkScrolling(); } } int CustomTrackView::hasGuide(int pos, int offset) { for (int i = 0; i < m_guides.count(); ++i) { int guidePos = m_guides.at(i)->position().frames(m_document->fps()); if (qAbs(guidePos - pos) <= offset) return guidePos; else if (guidePos > pos) return -1; } return -1; } void CustomTrackView::buildGuidesMenu(QMenu *goMenu) const { goMenu->clear(); double fps = m_document->fps(); for (int i = 0; i < m_guides.count(); ++i) { QAction *act = goMenu->addAction(m_guides.at(i)->label() + '/' + Timecode::getStringTimecode(m_guides.at(i)->position().frames(fps), fps)); act->setData(m_guides.at(i)->position().frames(m_document->fps())); } goMenu->setEnabled(!m_guides.isEmpty()); } QMap CustomTrackView::guidesData() const { QMap data; for (int i = 0; i < m_guides.count(); ++i) { data.insert(m_guides.at(i)->position().seconds(), m_guides.at(i)->label()); } return data; } void CustomTrackView::editGuide(const GenTime &oldPos, const GenTime &pos, const QString &comment) { if (comment.isEmpty() && pos < GenTime()) { // Delete guide bool found = false; for (int i = 0; i < m_guides.count(); ++i) { if (m_guides.at(i)->position() == oldPos) { delete m_guides.takeAt(i); found = true; break; } } if (!found) emit displayMessage(i18n("No guide at cursor time"), ErrorMessage); } else if (oldPos >= GenTime()) { // move guide for (int i = 0; i < m_guides.count(); ++i) { if (m_guides.at(i)->position() == oldPos) { Guide *item = m_guides.at(i); item->updateGuide(pos, comment); break; } } } else addGuide(pos, comment); qSort(m_guides.begin(), m_guides.end(), sortGuidesList); emit guidesUpdated(); } bool CustomTrackView::addGuide(const GenTime &pos, const QString &comment, bool loadingProject) { for (int i = 0; i < m_guides.count(); ++i) { if (m_guides.at(i)->position() == pos) { emit displayMessage(i18n("A guide already exists at position %1", m_document->timecode().getTimecodeFromFrames(pos.frames(m_document->fps()))), ErrorMessage); return false; } } Guide *g = new Guide(this, pos, comment, m_tracksHeight * m_timeline->visibleTracksCount() * matrix().m22()); scene()->addItem(g); m_guides.append(g); qSort(m_guides.begin(), m_guides.end(), sortGuidesList); if (!loadingProject) { emit guidesUpdated(); } return true; } void CustomTrackView::slotAddGuide(bool dialog) { CommentedTime marker(GenTime(m_cursorPos, m_document->fps()), i18n("Guide")); if (dialog) { QPointer d = new MarkerDialog(NULL, marker, m_document->timecode(), i18n("Add Guide"), this); if (d->exec() != QDialog::Accepted) { delete d; return; } marker = d->newMarker(); delete d; } else { marker.setComment(m_document->timecode().getDisplayTimecodeFromFrames(m_cursorPos, false)); } if (addGuide(marker.time(), marker.comment())) { EditGuideCommand *command = new EditGuideCommand(this, GenTime(), QString(), marker.time(), marker.comment(), false); m_commandStack->push(command); } } void CustomTrackView::slotEditGuide(int guidePos, const QString newText) { GenTime pos; if (guidePos == -1) pos = GenTime(m_cursorPos, m_document->fps()); else pos = GenTime(guidePos, m_document->fps()); bool found = false; for (int i = 0; i < m_guides.count(); ++i) { if (m_guides.at(i)->position() == pos) { CommentedTime guide = m_guides.at(i)->info(); if (!newText.isEmpty()) { EditGuideCommand *command = new EditGuideCommand(this, guide.time(), guide.comment(), guide.time(), newText, true); m_commandStack->push(command); } else { slotEditGuide(guide); } found = true; break; } } if (!found) emit displayMessage(i18n("No guide at cursor time"), ErrorMessage); } void CustomTrackView::slotEditGuide(const CommentedTime &guide) { QPointer d = new MarkerDialog(NULL, guide, m_document->timecode(), i18n("Edit Guide"), this); if (d->exec() == QDialog::Accepted) { EditGuideCommand *command = new EditGuideCommand(this, guide.time(), guide.comment(), d->newMarker().time(), d->newMarker().comment(), true); m_commandStack->push(command); } delete d; } void CustomTrackView::slotEditTimeLineGuide() { if (m_dragGuide == NULL) return; CommentedTime guide = m_dragGuide->info(); QPointer d = new MarkerDialog(NULL, guide, m_document->timecode(), i18n("Edit Guide"), this); if (d->exec() == QDialog::Accepted) { EditGuideCommand *command = new EditGuideCommand(this, guide.time(), guide.comment(), d->newMarker().time(), d->newMarker().comment(), true); m_commandStack->push(command); } delete d; } void CustomTrackView::slotDeleteGuide(int guidePos) { GenTime pos; if (guidePos == -1) pos = GenTime(m_cursorPos, m_document->fps()); else pos = GenTime(guidePos, m_document->fps()); bool found = false; for (int i = 0; i < m_guides.count(); ++i) { if (m_guides.at(i)->position() == pos) { EditGuideCommand *command = new EditGuideCommand(this, m_guides.at(i)->position(), m_guides.at(i)->label(), GenTime(-1), QString(), true); m_commandStack->push(command); found = true; break; } } if (!found) emit displayMessage(i18n("No guide at cursor time"), ErrorMessage); } void CustomTrackView::slotDeleteTimeLineGuide() { if (m_dragGuide == NULL) return; EditGuideCommand *command = new EditGuideCommand(this, m_dragGuide->position(), m_dragGuide->label(), GenTime(-1), QString(), true); m_commandStack->push(command); } void CustomTrackView::slotDeleteAllGuides() { QUndoCommand *deleteAll = new QUndoCommand(); deleteAll->setText(QStringLiteral("Delete all guides")); for (int i = 0; i < m_guides.count(); ++i) { new EditGuideCommand(this, m_guides.at(i)->position(), m_guides.at(i)->label(), GenTime(-1), QString(), true, deleteAll); } m_commandStack->push(deleteAll); } void CustomTrackView::setTool(ProjectTool tool) { m_tool = tool; if (m_cutLine && tool != RazorTool) { delete m_cutLine; m_cutLine = NULL; } switch (m_tool) { case RazorTool: if (!m_cutLine) { m_cutLine = m_scene->addLine(0, 0, 0, m_tracksHeight * m_scene->scale().y()); m_cutLine->setZValue(1000); QPen pen1 = QPen(); pen1.setWidth(1); QColor line(Qt::red); pen1.setColor(line); m_cutLine->setPen(pen1); m_cutLine->setFlag(QGraphicsItem::ItemIgnoresTransformations, true); slotRefreshCutLine(); } setCursor(m_razorCursor); break; case SpacerTool: setCursor(m_spacerCursor); break; default: unsetCursor(); } } void CustomTrackView::setScale(double scaleFactor, double verticalScale) { QMatrix newmatrix; newmatrix = newmatrix.scale(scaleFactor, verticalScale); m_scene->isZooming = true; m_scene->setScale(scaleFactor, verticalScale); removeTipAnimation(); if (scaleFactor < 1.5) { m_cursorLine->setFlag(QGraphicsItem::ItemIgnoresTransformations, true); QPen p = m_cursorLine->pen(); QColor c = p.color(); c.setAlpha(255); p.setColor(c); m_cursorLine->setPen(p); m_cursorOffset = 0; } else { m_cursorLine->setFlag(QGraphicsItem::ItemIgnoresTransformations, false); QPen p = m_cursorLine->pen(); QColor c = p.color(); c.setAlpha(100); p.setColor(c); m_cursorLine->setPen(p); m_cursorOffset = 0.5; } bool adjust = false; if (verticalScale != matrix().m22()) adjust = true; setMatrix(newmatrix); if (adjust) { double newHeight = m_tracksHeight * m_timeline->visibleTracksCount() * matrix().m22(); m_cursorLine->setLine(0, 0, 0, newHeight - 1); for (int i = 0; i < m_guides.count(); ++i) { m_guides.at(i)->setLine(0, 0, 0, newHeight - 1); } setSceneRect(0, 0, sceneRect().width(), m_tracksHeight * m_timeline->visibleTracksCount()); } int diff = sceneRect().width() - m_projectDuration; if (diff * newmatrix.m11() < 50) { if (newmatrix.m11() < 0.4) setSceneRect(0, 0, (m_projectDuration + 100 / newmatrix.m11()), sceneRect().height()); else setSceneRect(0, 0, (m_projectDuration + 300), sceneRect().height()); } double verticalPos = mapToScene(QPoint(0, viewport()->height() / 2)).y(); centerOn(QPointF(cursorPos(), verticalPos)); slotRefreshCutLine(); m_scene->isZooming = false; } void CustomTrackView::slotRefreshGuides() { if (KdenliveSettings::showmarkers()) { for (int i = 0; i < m_guides.count(); ++i) m_guides.at(i)->update(); } } void CustomTrackView::drawBackground(QPainter * painter, const QRectF &rect) { //TODO: optimize, we currently redraw bg on every cursor move painter->setClipRect(rect); QPen pen1 = painter->pen(); QColor lineColor = palette().text().color(); lineColor.setAlpha(100); pen1.setColor(lineColor); painter->setPen(pen1); double min = rect.left(); double max = rect.right(); painter->drawLine(QPointF(min, 0), QPointF(max, 0)); int maxTrack = m_timeline->visibleTracksCount(); QColor lockedColor = palette().button().color(); QColor audioColor = palette().alternateBase().color(); for (int i = 1; i <= maxTrack; ++i) { TrackInfo info = m_timeline->getTrackInfo(i); if (info.isLocked || info.type == AudioTrack || i == m_selectedTrack) { const QRectF track(min, m_tracksHeight * (maxTrack - i), max - min, m_tracksHeight - 1); if (i == m_selectedTrack) painter->fillRect(track, m_selectedTrackColor); else painter->fillRect(track, info.isLocked ? lockedColor : audioColor); } painter->drawLine(QPointF(min, m_tracksHeight * (maxTrack - i) - 1), QPointF(max, m_tracksHeight * (maxTrack - i) - 1)); } } bool CustomTrackView::findString(const QString &text) { QString marker; for (int i = 0; i < m_searchPoints.size(); ++i) { marker = m_searchPoints.at(i).comment(); if (marker.contains(text, Qt::CaseInsensitive)) { seekCursorPos(m_searchPoints.at(i).time().frames(m_document->fps())); int vert = verticalScrollBar()->value(); int hor = cursorPos(); ensureVisible(hor, vert + 10, 2, 2, 50, 0); m_findIndex = i; return true; } } return false; } void CustomTrackView::selectFound(QString track, QString pos) { int hor = m_document->timecode().getFrameCount(pos); activateMonitor(); seekCursorPos(hor); slotSelectTrack(track.toInt()); selectClip(true, false, track.toInt(), hor); ensureVisible(hor, verticalScrollBar()->value() + 10, 2, 2, 50, 0); } bool CustomTrackView::findNextString(const QString &text) { QString marker; for (int i = m_findIndex + 1; i < m_searchPoints.size(); ++i) { marker = m_searchPoints.at(i).comment(); if (marker.contains(text, Qt::CaseInsensitive)) { seekCursorPos(m_searchPoints.at(i).time().frames(m_document->fps())); int vert = verticalScrollBar()->value(); int hor = cursorPos(); ensureVisible(hor, vert + 10, 2, 2, 50, 0); m_findIndex = i; return true; } } m_findIndex = -1; return false; } void CustomTrackView::initSearchStrings() { m_searchPoints.clear(); QList itemList = items(); for (int i = 0; i < itemList.count(); ++i) { // parse all clip names if (itemList.at(i)->type() == AVWidget) { ClipItem *item = static_cast (itemList.at(i)); GenTime start = item->startPos(); CommentedTime t(start, item->clipName()); m_searchPoints.append(t); // add all clip markers ClipController *controller = m_document->getClipController(item->getBinId()); QList < CommentedTime > markers = controller->commentedSnapMarkers(); m_searchPoints += markers; } } // add guides for (int i = 0; i < m_guides.count(); ++i) m_searchPoints.append(m_guides.at(i)->info()); qSort(m_searchPoints); } void CustomTrackView::clearSearchStrings() { m_searchPoints.clear(); m_findIndex = 0; } QList CustomTrackView::findId(const QString &clipId) { QList matchingInfo; QList itemList = items(); for (int i = 0; i < itemList.count(); ++i) { if (itemList.at(i)->type() == AVWidget) { ClipItem *item = static_cast(itemList.at(i)); if (item->getBinId() == clipId) matchingInfo << item->info(); } } return matchingInfo; } void CustomTrackView::copyClip() { qDeleteAll(m_copiedItems); m_copiedItems.clear(); QList itemList = scene()->selectedItems(); if (itemList.count() == 0) { emit displayMessage(i18n("Select a clip before copying"), ErrorMessage); return; } for (int i = 0; i < itemList.count(); ++i) { if (itemList.at(i)->type() == AVWidget) { ClipItem *clip = static_cast (itemList.at(i)); ClipItem *clone = clip->clone(clip->info()); m_copiedItems.append(clone); } else if (itemList.at(i)->type() == TransitionWidget) { Transition *dup = static_cast (itemList.at(i)); m_copiedItems.append(dup->clone()); } } } bool CustomTrackView::canBePastedTo(ItemInfo info, int type) const { if (m_scene->editMode() != NormalEdit) { // If we are in overwrite mode, always allow the move return true; } int height = m_tracksHeight - 2; int offset = 0; if (type == TransitionWidget) { height = Transition::itemHeight(); offset = Transition::itemOffset(); } else if (type == AVWidget) { height = ClipItem::itemHeight(); offset = ClipItem::itemOffset(); } QRectF rect((double) info.startPos.frames(m_document->fps()), (double)(getPositionFromTrack(info.track) + 1 + offset), (double)(info.endPos - info.startPos).frames(m_document->fps()), (double) height); QList collisions = scene()->items(rect, Qt::IntersectsItemBoundingRect); for (int i = 0; i < collisions.count(); ++i) { if (collisions.at(i)->type() == type) { return false; } } return true; } bool CustomTrackView::canBePastedTo(QList infoList, int type) const { QPainterPath path; for (int i = 0; i < infoList.count(); ++i) { const QRectF rect((double) infoList.at(i).startPos.frames(m_document->fps()), (double)(getPositionFromTrack(infoList.at(i).track) + 1), (double)(infoList.at(i).endPos - infoList.at(i).startPos).frames(m_document->fps()), (double)(m_tracksHeight - 1)); path.addRect(rect); } QList collisions = scene()->items(path); for (int i = 0; i < collisions.count(); ++i) { if (collisions.at(i)->type() == type) return false; } return true; } bool CustomTrackView::canBePasted(QList items, GenTime offset, int trackOffset) const { for (int i = 0; i < items.count(); ++i) { ItemInfo info = items.at(i)->info(); info.startPos += offset; info.endPos += offset; info.track += trackOffset; if (!canBePastedTo(info, items.at(i)->type())) return false; } return true; } void CustomTrackView::pasteClip() { if (m_copiedItems.count() == 0) { emit displayMessage(i18n("No clip copied"), ErrorMessage); return; } QPoint position; int track = -1; GenTime pos = GenTime(-1); if (m_menuPosition.isNull()) { position = mapFromGlobal(QCursor::pos()); if (!contentsRect().contains(position)) { track = m_selectedTrack; pos = GenTime(m_cursorPos, m_document->fps()); /*emit displayMessage(i18n("Cannot paste selected clips"), ErrorMessage); return;*/ } } else { position = m_menuPosition; } if (pos == GenTime(-1)) pos = GenTime((int)(mapToScene(position).x()), m_document->fps()); if (track == -1) track = getTrackFromPos(mapToScene(position).y()); GenTime leftPos = m_copiedItems.at(0)->startPos(); int lowerTrack = m_copiedItems.at(0)->track(); int upperTrack = m_copiedItems.at(0)->track(); for (int i = 1; i < m_copiedItems.count(); ++i) { if (m_copiedItems.at(i)->startPos() < leftPos) leftPos = m_copiedItems.at(i)->startPos(); if (m_copiedItems.at(i)->track() < lowerTrack) lowerTrack = m_copiedItems.at(i)->track(); if (m_copiedItems.at(i)->track() > upperTrack) upperTrack = m_copiedItems.at(i)->track(); } GenTime offset = pos - leftPos; int trackOffset = track - lowerTrack; if (upperTrack + trackOffset > m_timeline->tracksCount() - 1) trackOffset = m_timeline->tracksCount() - 1 -upperTrack; if (lowerTrack + trackOffset < 0 || !canBePasted(m_copiedItems, offset, trackOffset)) { emit displayMessage(i18n("Cannot paste selected clips"), ErrorMessage); return; } QUndoCommand *pasteClips = new QUndoCommand(); pasteClips->setText(QStringLiteral("Paste clips")); new RefreshMonitorCommand(this, false, true, pasteClips); for (int i = 0; i < m_copiedItems.count(); ++i) { // parse all clip names if (m_copiedItems.at(i) && m_copiedItems.at(i)->type() == AVWidget) { ClipItem *clip = static_cast (m_copiedItems.at(i)); ItemInfo info = clip->info(); info.startPos += offset; info.endPos += offset; info.track += trackOffset; if (canBePastedTo(info, AVWidget)) { new AddTimelineClipCommand(this, clip->getBinId(), info, clip->effectList(), clip->clipState(), true, false, pasteClips); } else emit displayMessage(i18n("Cannot paste clip to selected place"), ErrorMessage); } else if (m_copiedItems.at(i) && m_copiedItems.at(i)->type() == TransitionWidget) { Transition *tr = static_cast (m_copiedItems.at(i)); ItemInfo info; info.startPos = tr->startPos() + offset; info.endPos = tr->endPos() + offset; info.track = tr->track() + trackOffset; int transitionEndTrack; if (!tr->forcedTrack()) transitionEndTrack = getPreviousVideoTrack(info.track); else transitionEndTrack = tr->transitionEndTrack(); if (canBePastedTo(info, TransitionWidget)) { if (info.startPos >= info.endPos) { emit displayMessage(i18n("Invalid transition"), ErrorMessage); } else new AddTransitionCommand(this, info, transitionEndTrack, tr->toXML(), false, true, pasteClips); } else emit displayMessage(i18n("Cannot paste transition to selected place"), ErrorMessage); } } updateTrackDuration(-1, pasteClips); new RefreshMonitorCommand(this, false, false, pasteClips); m_commandStack->push(pasteClips); } void CustomTrackView::pasteClipEffects() { if (m_copiedItems.count() != 1 || m_copiedItems.at(0)->type() != AVWidget) { emit displayMessage(i18n("You must copy exactly one clip before pasting effects"), ErrorMessage); return; } ClipItem *clip = static_cast < ClipItem *>(m_copiedItems.at(0)); QUndoCommand *paste = new QUndoCommand(); paste->setText(QStringLiteral("Paste effects")); QList clips = scene()->selectedItems(); // expand groups for (int i = 0; i < clips.count(); ++i) { if (clips.at(i)->type() == GroupWidget) { QList children = clips.at(i)->childItems(); for (int j = 0; j < children.count(); ++j) { if (children.at(j)->type() == AVWidget && !clips.contains(children.at(j))) { clips.append(children.at(j)); } } } } for (int i = 0; i < clips.count(); ++i) { if (clips.at(i)->type() == AVWidget) { ClipItem *item = static_cast < ClipItem *>(clips.at(i)); for (int j = 0; j < clip->effectsCount(); ++j) { QDomElement eff = clip->effect(j); if (eff.attribute(QStringLiteral("unique"), QStringLiteral("0")) == QLatin1String("0") || item->hasEffect(eff.attribute(QStringLiteral("tag")), eff.attribute(QStringLiteral("id"))) == -1) { adjustKeyfames(clip->cropStart(), item->cropStart(), item->cropDuration(), eff); new AddEffectCommand(this, item->track(), item->startPos(), eff, true, paste); } } } } if (paste->childCount() > 0) m_commandStack->push(paste); else delete paste; //adjust effects (fades, ...) for (int i = 0; i < clips.count(); ++i) { if (clips.at(i)->type() == AVWidget) { ClipItem *item = static_cast < ClipItem *>(clips.at(i)); updatePositionEffects(item, item->info()); } } } void CustomTrackView::adjustKeyfames(GenTime oldstart, GenTime newstart, GenTime duration, QDomElement xml) { // parse parameters to check if we need to adjust to the new crop start QLocale locale; locale.setNumberOptions(QLocale::OmitGroupSeparator); QDomNodeList params = xml.elementsByTagName(QStringLiteral("parameter")); for (int i = 0; i < params.count(); ++i) { QDomElement e = params.item(i).toElement(); if (e.isNull()) continue; if (e.attribute(QStringLiteral("type")) == QLatin1String("keyframe") || e.attribute(QStringLiteral("type")) == QLatin1String("simplekeyframe")) { // Effect has a keyframe type parameter, we need to adjust the values QString adjusted = EffectsController::adjustKeyframes(e.attribute(QStringLiteral("keyframes")), oldstart.frames(m_document->fps()), newstart.frames(m_document->fps()), (newstart + duration).frames(m_document->fps()) - 1, m_document->getProfileInfo()); e.setAttribute(QStringLiteral("keyframes"), adjusted); } else if (e.attribute(QStringLiteral("type")) == QLatin1String("animated")) { if (xml.attribute(QStringLiteral("kdenlive:sync_in_out")) != QLatin1String("1")) { QString resizedAnim = KeyframeView::cutAnimation(e.attribute(QStringLiteral("value")), 0, duration.frames(m_document->fps()), duration.frames(m_document->fps())); e.setAttribute(QStringLiteral("value"), resizedAnim); } else if (xml.hasAttribute(QStringLiteral("kdenlive:sync_in_out"))) { // Effect attached to clip in, update xml.setAttribute("in", QString::number(newstart.frames(m_document->fps()))); xml.setAttribute("out", QString::number((newstart + duration).frames(m_document->fps()))); } } } } ClipItem *CustomTrackView::getClipUnderCursor() const { QRectF rect((double) seekPosition(), 0.0, 1.0, (double)(m_tracksHeight * m_timeline->visibleTracksCount())); QList collisions = scene()->items(rect, Qt::IntersectsItemBoundingRect); if (m_dragItem && !collisions.isEmpty() && collisions.contains(m_dragItem) && m_dragItem->type() == AVWidget && !m_dragItem->isItemLocked()) { return static_cast < ClipItem *>(m_dragItem); } for (int i = 0; i < collisions.count(); ++i) { if (collisions.at(i)->type() == AVWidget) { ClipItem *clip = static_cast < ClipItem *>(collisions.at(i)); if (!clip->isItemLocked()) return clip; } } return NULL; } const QString CustomTrackView::getClipUnderCursor(int *pos, QPoint *zone) const { - ClipItem *item = getClipUnderCursor(); + ClipItem *item = static_cast < ClipItem *>(getMainActiveClip()); if (item == NULL) { if (m_dragItem && m_dragItem->type() == AVWidget) { item = static_cast < ClipItem *>(m_dragItem); } } else { *pos = seekPosition() - (item->startPos() - item->cropStart()).frames(m_document->fps()); if (zone) { *zone = QPoint(item->cropStart().frames(m_document->fps()), (item->cropStart() + item->cropDuration()).frames(m_document->fps())); } } QString result; if (item) { result = item->getBinId(); } return result; } AbstractClipItem *CustomTrackView::getMainActiveClip() const { QList clips = scene()->selectedItems(); if (clips.isEmpty()) { return getClipUnderCursor(); } else { AbstractClipItem *item = NULL; for (int i = 0; i < clips.count(); ++i) { if (clips.at(i)->type() == AVWidget) { item = static_cast < AbstractClipItem *>(clips.at(i)); if (clips.count() > 1 && item->startPos().frames(m_document->fps()) <= seekPosition() && item->endPos().frames(m_document->fps()) >= seekPosition()) break; } } if (item) return item; } return NULL; } ClipItem *CustomTrackView::getActiveClipUnderCursor(bool allowOutsideCursor) const { QList clips = scene()->selectedItems(); if (clips.isEmpty()) { return getClipUnderCursor(); } else { ClipItem *item; // remove all items in the list that are not clips for (int i = 0; i < clips.count();) { if (clips.at(i)->type() != AVWidget) clips.removeAt(i); else ++i; } if (clips.count() == 1 && allowOutsideCursor) return static_cast < ClipItem *>(clips.at(0)); for (int i = 0; i < clips.count(); ++i) { if (clips.at(i)->type() == AVWidget) { item = static_cast < ClipItem *>(clips.at(i)); if (item->startPos().frames(m_document->fps()) <= seekPosition() && item->endPos().frames(m_document->fps()) >= seekPosition()) return item; } } } return NULL; } void CustomTrackView::expandActiveClip() { AbstractClipItem *item = getActiveClipUnderCursor(true); if (item == NULL || item->type() != AVWidget) { emit displayMessage(i18n("You must select one clip for this action"), ErrorMessage); return; } ClipItem *clip = static_cast < ClipItem *>(item); QUrl url = clip->binClip()->url(); if (clip->clipType() != Playlist || !url.isValid()) { emit displayMessage(i18n("You must select a playlist clip for this action"), ErrorMessage); return; } // Step 1: remove playlist clip QUndoCommand *expandCommand = new QUndoCommand(); expandCommand->setText(i18n("Expand Clip")); ItemInfo info = clip->info(); new AddTimelineClipCommand(this, clip->getBinId(), info, clip->effectList(), clip->clipState(), true, true, expandCommand); emit importPlaylistClips(info, url, expandCommand); } void CustomTrackView::setInPoint() { AbstractClipItem *clip = getActiveClipUnderCursor(true); if (clip == NULL) { if (m_dragItem && m_dragItem->type() == TransitionWidget) { clip = m_dragItem; } else { emit displayMessage(i18n("You must select one clip for this action"), ErrorMessage); return; } } AbstractGroupItem *parent = static_cast (clip->parentItem()); if (parent) { // Resizing a group QUndoCommand *resizeCommand = new QUndoCommand(); resizeCommand->setText(i18n("Resize group")); QList items = parent->childItems(); for (int i = 0; i < items.count(); ++i) { AbstractClipItem *item = static_cast(items.at(i)); if (item && item->type() == AVWidget) { prepareResizeClipStart(item, item->info(), seekPosition(), true, resizeCommand); } } if (resizeCommand->childCount() > 0) m_commandStack->push(resizeCommand); else { //TODO warn user of failed resize delete resizeCommand; } } else prepareResizeClipStart(clip, clip->info(), seekPosition(), true); } void CustomTrackView::setOutPoint() { AbstractClipItem *clip = getActiveClipUnderCursor(true); if (clip == NULL) { if (m_dragItem && m_dragItem->type() == TransitionWidget) { clip = m_dragItem; } else { emit displayMessage(i18n("You must select one clip for this action"), ErrorMessage); return; } } AbstractGroupItem *parent = static_cast (clip->parentItem()); if (parent) { // Resizing a group QUndoCommand *resizeCommand = new QUndoCommand(); resizeCommand->setText(i18n("Resize group")); QList items = parent->childItems(); for (int i = 0; i < items.count(); ++i) { AbstractClipItem *item = static_cast(items.at(i)); if (item && item->type() == AVWidget) { prepareResizeClipEnd(item, item->info(), seekPosition(), true, resizeCommand); } } if (resizeCommand->childCount() > 0) m_commandStack->push(resizeCommand); else { //TODO warn user of failed resize delete resizeCommand; } } else prepareResizeClipEnd(clip, clip->info(), seekPosition(), true); } void CustomTrackView::slotUpdateAllThumbs() { if (!isEnabled()) return; QList itemList = items(); //if (itemList.isEmpty()) return; ClipItem *item; const QString thumbBase = m_document->projectFolder().path() + "/thumbs/"; for (int i = 0; i < itemList.count(); ++i) { if (itemList.at(i)->type() == AVWidget) { item = static_cast (itemList.at(i)); if (item && item->isEnabled() && item->clipType() != Color && item->clipType() != Audio) { // Check if we have a cached thumbnail if (item->clipType() == Image || item->clipType() == Text) { QString thumb = thumbBase + item->getBinHash() + "#0.png"; if (QFile::exists(thumb)) { QPixmap pix(thumb); if (pix.isNull()) QFile::remove(thumb); item->slotSetStartThumb(pix); } } else { QString startThumb = thumbBase + item->getBinHash() + '#'; QString endThumb = startThumb; startThumb.append(QString::number((int) item->speedIndependantCropStart().frames(m_document->fps())) + ".png"); endThumb.append(QString::number((int) (item->speedIndependantCropStart() + item->speedIndependantCropDuration()).frames(m_document->fps()) - 1) + ".png"); if (QFile::exists(startThumb)) { QPixmap pix(startThumb); if (pix.isNull()) QFile::remove(startThumb); item->slotSetStartThumb(pix); } if (QFile::exists(endThumb)) { QPixmap pix(endThumb); if (pix.isNull()) QFile::remove(endThumb); item->slotSetEndThumb(pix); } } item->refreshClip(false, false); } } } viewport()->update(); } void CustomTrackView::saveThumbnails() { QList itemList = items(); ClipItem *item; QString thumbBase = m_document->projectFolder().path() + "/thumbs/"; for (int i = 0; i < itemList.count(); ++i) { if (itemList.at(i)->type() == AVWidget) { item = static_cast (itemList.at(i)); if (item->clipType() != Color) { // Check if we have a cached thumbnail if (item->clipType() == Image || item->clipType() == Text || item->clipType() == Audio) { QString thumb = thumbBase + item->getBinHash() + "#0.png"; if (!QFile::exists(thumb)) { QPixmap pix(item->startThumb()); pix.save(thumb); } } else { QString startThumb = thumbBase + item->getBinHash() + '#'; QString endThumb = startThumb; startThumb.append(QString::number((int) item->speedIndependantCropStart().frames(m_document->fps())) + ".png"); endThumb.append(QString::number((int) (item->speedIndependantCropStart() + item->speedIndependantCropDuration()).frames(m_document->fps()) - 1) + ".png"); if (!QFile::exists(startThumb)) { QPixmap pix(item->startThumb()); pix.save(startThumb); } if (!QFile::exists(endThumb)) { QPixmap pix(item->endThumb()); pix.save(endThumb); } } } } } } void CustomTrackView::slotInsertTrack(int ix) { QPointer d = new TrackDialog(m_timeline, parentWidget()); d->comboTracks->setCurrentIndex(m_timeline->visibleTracksCount() - ix); d->label->setText(i18n("Insert track")); d->track_name->setText(i18n("Video %1", ix)); d->setWindowTitle(i18n("Insert New Track")); if (d->exec() == QDialog::Accepted) { ix = m_timeline->visibleTracksCount() - d->comboTracks->currentIndex(); if (d->before_select->currentIndex() == 0) ix++; TrackInfo info; info.duration = 0; info.isMute = false; info.isLocked = false; info.effectsList = EffectsList(true); if (d->video_track->isChecked()) { info.type = VideoTrack; info.isBlind = false; } else { info.type = AudioTrack; info.isBlind = true; } info.trackName = d->track_name->text(); AddTrackCommand *addTrack = new AddTrackCommand(this, ix, info, true); m_commandStack->push(addTrack); } delete d; } void CustomTrackView::slotDeleteTrack(int ix) { if (m_timeline->tracksCount() <= 2) { // TODO: warn user that at least one track is necessary return; } QPointer d = new TrackDialog(m_timeline, parentWidget()); d->comboTracks->setCurrentIndex(m_timeline->visibleTracksCount() - ix); d->label->setText(i18n("Delete track")); d->track_name->setHidden(true); d->name_label->setHidden(true); d->before_select->setHidden(true); d->setWindowTitle(i18n("Delete Track")); d->video_track->setHidden(true); d->audio_track->setHidden(true); if (d->exec() == QDialog::Accepted) { ix = m_timeline->visibleTracksCount() - d->comboTracks->currentIndex(); TrackInfo info = m_timeline->getTrackInfo(ix); deleteTimelineTrack(ix, info); /*AddTrackCommand* command = new AddTrackCommand(this, ix, info, false); m_commandStack->push(command);*/ } delete d; } void CustomTrackView::slotConfigTracks(int ix) { QPointer d = new TracksConfigDialog(m_timeline, ix, parentWidget()); if (d->exec() == QDialog::Accepted) { configTracks(d->tracksList()); QList toDelete = d->deletedTracks(); for (int i = 0; i < toDelete.count(); ++i) { int track = m_timeline->visibleTracksCount() - toDelete.at(i); TrackInfo info = m_timeline->getTrackInfo(track); deleteTimelineTrack(track, info); } } delete d; } void CustomTrackView::deleteTimelineTrack(int ix, TrackInfo trackinfo) { if (m_timeline->tracksCount() < 2) return; // Clear effect stack clearSelection(); emit transitionItemSelected(NULL); double startY = getPositionFromTrack(ix) + 1 + m_tracksHeight / 2; QRectF r(0, startY, sceneRect().width(), m_tracksHeight / 2 - 1); QList selection = m_scene->items(r); QUndoCommand *deleteTrack = new QUndoCommand(); deleteTrack->setText(QStringLiteral("Delete track")); new RefreshMonitorCommand(this, false, true, deleteTrack); // Remove clips on that track from groups QList groupsToProceed; for (int i = 0; i < selection.count(); ++i) { if (selection.at(i)->type() == GroupWidget) { if (!groupsToProceed.contains(selection.at(i))) { groupsToProceed << selection.at(i); } } } for (int i = 0; i < groupsToProceed.count(); i++) { AbstractGroupItem *grp = static_cast(groupsToProceed.at(i)); QList items = grp->childItems(); QList clipGrpInfos; QList transitionGrpInfos; QList newClipGrpInfos; QList newTransitionGrpInfos; for (int j = 0; j < items.count(); ++j) { if (items.at(j)->type() == AVWidget) { AbstractClipItem *clip = static_cast (items.at(j)); clipGrpInfos.append(clip->info()); if (clip->track() != ix) { newClipGrpInfos.append(clip->info()); } } else if (items.at(j)->type() == TransitionWidget) { AbstractClipItem *clip = static_cast (items.at(j)); transitionGrpInfos.append(clip->info()); if (clip->track() != ix) { newTransitionGrpInfos.append(clip->info()); } } } if (!clipGrpInfos.isEmpty() || !transitionGrpInfos.isEmpty()) { // Break group new GroupClipsCommand(this, clipGrpInfos, transitionGrpInfos, false, deleteTrack); // Rebuild group without clips from track to delete if (!newClipGrpInfos.isEmpty() || !newTransitionGrpInfos.isEmpty()) { new GroupClipsCommand(this, newClipGrpInfos, newTransitionGrpInfos, true, deleteTrack); } } } // Delete all clips in selected track for (int i = 0; i < selection.count(); ++i) { if (selection.at(i)->type() == AVWidget) { ClipItem *item = static_cast (selection.at(i)); new AddTimelineClipCommand(this, item->getBinId(), item->info(), item->effectList(), item->clipState(), false, true, deleteTrack); m_waitingThumbs.removeAll(item); m_scene->removeItem(item); delete item; item = NULL; } else if (selection.at(i)->type() == TransitionWidget) { Transition *item = static_cast (selection.at(i)); new AddTransitionCommand(this, item->info(), item->transitionEndTrack(), item->toXML(), true, false, deleteTrack); m_scene->removeItem(item); delete item; item = NULL; } } new AddTrackCommand(this, ix, trackinfo, false, deleteTrack); new RefreshMonitorCommand(this, true, false, deleteTrack); m_commandStack->push(deleteTrack); } void CustomTrackView::autoTransition() { QList itemList = scene()->selectedItems(); if (itemList.count() != 1 || itemList.at(0)->type() != TransitionWidget) { emit displayMessage(i18n("You must select one transition for this action"), ErrorMessage); return; } Transition *tr = static_cast (itemList.at(0)); tr->setAutomatic(!tr->isAutomatic()); QDomElement transition = tr->toXML(); m_timeline->transitionHandler->updateTransition(transition.attribute(QStringLiteral("tag")), transition.attribute(QStringLiteral("tag")), transition.attribute(QStringLiteral("transition_btrack")).toInt(), transition.attribute(QStringLiteral("transition_atrack")).toInt(), tr->startPos(), tr->endPos(), transition); setDocumentModified(); } void CustomTrackView::clipNameChanged(const QString &id) { QList list = scene()->items(); ClipItem *clip = NULL; for (int i = 0; i < list.size(); ++i) { if (list.at(i)->type() == AVWidget) { clip = static_cast (list.at(i)); if (clip->getBinId() == id) { clip->update(); } } } //viewport()->update(); } void CustomTrackView::getClipAvailableSpace(AbstractClipItem *item, GenTime &minimum, GenTime &maximum) { minimum = GenTime(); maximum = GenTime(); QList selection; selection = m_scene->items(QRectF(0, getPositionFromTrack(item->track()) + m_tracksHeight / 2, sceneRect().width(), 2)); selection.removeAll(item); for (int i = 0; i < selection.count(); ++i) { AbstractClipItem *clip = static_cast (selection.at(i)); if (clip && clip->type() == AVWidget) { if (clip->endPos() <= item->startPos() && clip->endPos() > minimum) minimum = clip->endPos(); if (clip->startPos() > item->startPos() && (clip->startPos() < maximum || maximum == GenTime())) maximum = clip->startPos(); } } } void CustomTrackView::getTransitionAvailableSpace(AbstractClipItem *item, GenTime &minimum, GenTime &maximum) { minimum = GenTime(); maximum = GenTime(); QList selection; selection = m_scene->items(QRectF(0, getPositionFromTrack(item->track()) + m_tracksHeight, sceneRect().width(), 2)); selection.removeAll(item); for (int i = 0; i < selection.count(); ++i) { AbstractClipItem *clip = static_cast (selection.at(i)); if (clip && clip->type() == TransitionWidget) { if (clip->endPos() <= item->startPos() && clip->endPos() > minimum) minimum = clip->endPos(); if (clip->startPos() > item->startPos() && (clip->startPos() < maximum || maximum == GenTime())) maximum = clip->startPos(); } } } void CustomTrackView::loadGroups(const QDomNodeList &groups) { m_document->clipManager()->resetGroups(); for (int i = 0; i < groups.count(); ++i) { QDomNodeList children = groups.at(i).childNodes(); scene()->clearSelection(); QList list; for (int nodeindex = 0; nodeindex < children.count(); ++nodeindex) { QDomElement elem = children.item(nodeindex).toElement(); int track = elem.attribute(QStringLiteral("track")).toInt(); // Ignore items removed after track deletion if (track == -1) continue; int pos = elem.attribute(QStringLiteral("position")).toInt(); if (elem.tagName() == QLatin1String("clipitem")) { ClipItem *clip = getClipItemAtStart(GenTime(pos, m_document->fps()), track); if (clip) list.append(clip);//clip->setSelected(true); } else { Transition *clip = getTransitionItemAt(pos, track); if (clip) list.append(clip);//clip->setSelected(true); } } groupSelectedItems(list, true); } } void CustomTrackView::splitAudio(bool warn) { resetSelectionGroup(); QList selection = scene()->selectedItems(); if (selection.isEmpty()) { emit displayMessage(i18n("You must select at least one clip for this action"), ErrorMessage); return; } QUndoCommand *splitCommand = new QUndoCommand(); splitCommand->setText(i18n("Split audio")); for (int i = 0; i < selection.count(); ++i) { if (selection.at(i)->type() == AVWidget) { ClipItem *clip = static_cast (selection.at(i)); if (clip->clipType() == AV || clip->clipType() == Playlist) { if (clip->parentItem()) { emit displayMessage(i18n("Cannot split audio of grouped clips"), ErrorMessage); } else { EffectsList effects; effects.clone(clip->effectList()); new SplitAudioCommand(this, clip->track(), clip->startPos(), effects, splitCommand); } } } } if (splitCommand->childCount()) { updateTrackDuration(-1, splitCommand); m_commandStack->push(splitCommand); } else { if (warn) emit displayMessage(i18n("No clip to split"), ErrorMessage); delete splitCommand; } } void CustomTrackView::setAudioAlignReference() { QList selection = scene()->selectedItems(); if (selection.isEmpty() || selection.size() > 1) { emit displayMessage(i18n("You must select exactly one clip for the audio reference."), ErrorMessage); return; } if (m_audioCorrelator != NULL) { delete m_audioCorrelator; m_audioCorrelator = NULL; m_audioAlignmentReference = NULL; } if (selection.at(0)->type() == AVWidget) { ClipItem *clip = static_cast(selection.at(0)); if (clip->clipType() == AV || clip->clipType() == Audio) { m_audioAlignmentReference = clip; Mlt::Producer *prod = m_timeline->track(clip->track())->clipProducer(m_document->renderer()->getBinProducer(clip->getBinId()), clip->clipState()); if (!prod) { qWarning() << "couldn't load producer for clip " << clip->getBinId() << " on track " << clip->track(); return; } AudioEnvelope *envelope = new AudioEnvelope(clip->binClip()->url().path(), prod); m_audioCorrelator = new AudioCorrelation(envelope); connect(m_audioCorrelator, SIGNAL(gotAudioAlignData(int,int,int)), this, SLOT(slotAlignClip(int,int,int))); connect(m_audioCorrelator, SIGNAL(displayMessage(QString,MessageType)), this, SIGNAL(displayMessage(QString,MessageType))); emit displayMessage(i18n("Processing audio, please wait."), ProcessingJobMessage); } return; } emit displayMessage(i18n("Reference for audio alignment must contain audio data."), ErrorMessage); } void CustomTrackView::alignAudio() { bool referenceOK = true; if (m_audioCorrelator == NULL) { referenceOK = false; } if (referenceOK) { if (!scene()->items().contains(m_audioAlignmentReference)) { // The reference item has been deleted from the timeline (or so) referenceOK = false; } } if (!referenceOK) { emit displayMessage(i18n("Audio alignment reference not yet set."), InformationMessage); return; } QList selection = scene()->selectedItems(); foreach (QGraphicsItem *item, selection) { if (item->type() == AVWidget) { ClipItem *clip = static_cast(item); if (clip == m_audioAlignmentReference) { continue; } if (clip->clipType() == AV || clip->clipType() == Audio) { ItemInfo info = clip->info(); Mlt::Producer *prod = m_timeline->track(clip->track())->clipProducer(m_document->renderer()->getBinProducer(clip->getBinId()), clip->clipState()); if (!prod) { qWarning() << "couldn't load producer for clip " << clip->getBinId() << " on track " << clip->track(); return; } AudioEnvelope *envelope = new AudioEnvelope(clip->binClip()->url().path(), prod, info.cropStart.frames(m_document->fps()), info.cropDuration.frames(m_document->fps()), clip->track(), info.startPos.frames(m_document->fps())); m_audioCorrelator->addChild(envelope); } } } emit displayMessage(i18n("Processing audio, please wait."), ProcessingJobMessage); } void CustomTrackView::slotAlignClip(int track, int pos, int shift) { QUndoCommand *moveCommand = new QUndoCommand(); ClipItem *clip = getClipItemAtStart(GenTime(pos, m_document->fps()), track); if (!clip) { emit displayMessage(i18n("Cannot find clip to align."), ErrorMessage); //emit displayMessage(i18n("Auto-aligned %1 clips.", counter), InformationMessage); return; } GenTime add(shift, m_document->fps()); ItemInfo start = clip->info(); ItemInfo end = start; end.startPos = m_audioAlignmentReference->startPos() + add - m_audioAlignmentReference->cropStart(); end.endPos = end.startPos + start.cropDuration; if ( end.startPos < GenTime() ) { // Clip would start before 0, so crop it first GenTime cropBy = -end.startPos; if (cropBy > start.cropDuration) { delete moveCommand; emit displayMessage(i18n("Unable to move clip out of timeline."), ErrorMessage); return; } ItemInfo resized = start; resized.startPos += cropBy; resizeClip(start, resized); new ResizeClipCommand(this, start, resized, false, false, moveCommand); start = clip->info(); end.startPos += cropBy; } if (itemCollision(clip, end)) { delete moveCommand; emit displayMessage(i18n("Unable to move clip due to collision."), ErrorMessage); return; } emit displayMessage(i18n("Clip aligned."), OperationCompletedMessage); moveCommand->setText(i18n("Auto-align clip")); new MoveClipCommand(this, start, end, true, moveCommand); updateTrackDuration(clip->track(), moveCommand); m_commandStack->push(moveCommand); } void CustomTrackView::doSplitAudio(const GenTime &pos, int track, EffectsList effects, bool split) { ClipItem *clip = getClipItemAtStart(pos, track); if (clip == NULL) { //qDebug() << "// Cannot find clip to split!!!"; return; } if (split) { int start = pos.frames(m_document->fps()); int freetrack = track - 1; // do not split audio when we are on an audio track if (m_timeline->getTrackInfo(track).type == AudioTrack) return; for (; freetrack > 0; freetrack--) { TrackInfo info = m_timeline->getTrackInfo(freetrack); if (info.type == AudioTrack && !info.isLocked) { int blength = m_timeline->getTrackSpaceLength(freetrack, start, false); if (blength == -1 || blength >= clip->cropDuration().frames(m_document->fps())) { break; } } } if (freetrack == 0) { emit displayMessage(i18n("No empty space to put clip audio"), ErrorMessage); } else { ItemInfo info = clip->info(); info.track = freetrack; scene()->clearSelection(); addClip(clip->getBinId(), info, clip->effectList(), PlaylistState::AudioOnly, false); clip->setSelected(true); ClipItem *audioClip = getClipItemAtStart(pos, info.track); if (audioClip) { if (m_timeline->track(track)->replace(pos.seconds(), m_document->renderer()->getBinVideoProducer(clip->getBinId()))) { clip->setState(PlaylistState::VideoOnly); } else { emit displayMessage(i18n("Cannot update clip (time: %1, track: %2)", pos.frames(m_document->fps()), freetrack), ErrorMessage); } audioClip->setSelected(true); // keep video effects, move audio effects to audio clip int videoIx = 0; int audioIx = 0; for (int i = 0; i < effects.count(); ++i) { if (effects.at(i).attribute(QStringLiteral("type")) == QLatin1String("audio")) { deleteEffect(track, pos, clip->effect(videoIx)); audioIx++; } else { deleteEffect(freetrack, pos, audioClip->effect(audioIx)); videoIx++; } } groupSelectedItems(QList ()<parentItem() == NULL || clip->parentItem()->type() != GroupWidget) { //qDebug() << "//CANNOT FIND CLP GRP"; return; } AbstractGroupItem *grp = static_cast (clip->parentItem()); QList children = grp->childItems(); if (children.count() != 2) { //qDebug() << "//SOMETHING IS WRONG WITH CLP GRP"; return; } for (int i = 0; i < children.count(); ++i) { if (children.at(i) != clip) { ClipItem *clp = static_cast (children.at(i)); ItemInfo info = clip->info(); deleteClip(clp->info()); if (!m_timeline->track(info.track)->replace(info.startPos.seconds(), m_document->renderer()->getBinProducer(clip->getBinId()))) { emit displayMessage(i18n("Cannot update clip (time: %1, track: %2)", info.startPos.frames(m_document->fps()), info.track), ErrorMessage); return; } else { clip->setState(PlaylistState::Original); } // re-add audio effects for (int i = 0; i < effects.count(); ++i) { if (effects.at(i).attribute(QStringLiteral("type")) == QLatin1String("audio")) { addEffect(track, pos, effects.at(i)); } } break; } } clip->setFlag(QGraphicsItem::ItemIsMovable, true); m_document->clipManager()->removeGroup(grp); if (grp == m_selectionGroup) m_selectionGroup = NULL; scene()->destroyItemGroup(grp); } } void CustomTrackView::setClipType(PlaylistState::ClipState state) { QString text = i18n("Audio and Video"); if (state == PlaylistState::VideoOnly) text = i18n("Video only"); else if (state == PlaylistState::AudioOnly) text = i18n("Audio only"); resetSelectionGroup(); QList selection = scene()->selectedItems(); if (selection.isEmpty()) { emit displayMessage(i18n("You must select one clip for this action"), ErrorMessage); return; } QUndoCommand *videoCommand = new QUndoCommand(); videoCommand->setText(text); for (int i = 0; i < selection.count(); ++i) { if (selection.at(i)->type() == AVWidget) { ClipItem *clip = static_cast (selection.at(i)); if (clip->clipType() == AV || clip->clipType() == Playlist) { if (clip->parentItem()) { emit displayMessage(i18n("Cannot change grouped clips"), ErrorMessage); } else { new ChangeClipTypeCommand(this, clip->track(), clip->startPos(), state, clip->clipState(), videoCommand); } } } } m_commandStack->push(videoCommand); } void CustomTrackView::monitorRefresh() { m_document->renderer()->doRefresh(); } void CustomTrackView::doChangeClipType(const GenTime &pos, int track, PlaylistState::ClipState state) { ClipItem *clip = getClipItemAtStart(pos, track); if (clip == NULL) { emit displayMessage(i18n("Cannot find clip to edit (time: %1, track: %2)", pos.frames(m_document->fps()), m_timeline->getTrackInfo(track).trackName), ErrorMessage); return; } Mlt::Producer *prod; double speed = clip->speed(); if (state == PlaylistState::VideoOnly) { prod = m_document->renderer()->getBinVideoProducer(clip->getBinId()); } else { prod = m_document->renderer()->getBinProducer(clip->getBinId()); } if (speed != 1) { QLocale locale; QString url = prod->get("resource"); Track::SlowmoInfo info; info.speed = speed; info.strobe = 1; info.state = state; Mlt::Producer *copy = m_document->renderer()->getSlowmotionProducer(info.toString(locale) + url); if (copy == NULL) { // create mute slowmo producer url.prepend(locale.toString(speed) + ":"); Mlt::Properties passProperties; Mlt::Properties original(prod->get_properties()); passProperties.pass_list(original, ClipController::getPassPropertiesList(false)); copy = m_timeline->track(track)->buildSlowMoProducer(passProperties, url, clip->getBinId(), info); } if (copy == NULL) { // Failed to get slowmo producer, error qDebug()<<"Failed to get slowmo producer, error"; return; } prod = copy; } if (prod && prod->is_valid() && m_timeline->track(track)->replace(pos.seconds(), prod, state)) { clip->setState(state); } else { // Changing clip type failed emit displayMessage(i18n("Cannot update clip (time: %1, track: %2)", pos.frames(m_document->fps()), m_timeline->getTrackInfo(track).trackName), ErrorMessage); return; } clip->update(); } void CustomTrackView::updateClipTypeActions(ClipItem *clip) { bool hasAudio; bool hasAV; if (clip == NULL || (clip->clipType() != AV && clip->clipType() != Playlist)) { m_clipTypeGroup->setEnabled(false); hasAudio = clip != NULL && clip->clipType() == Audio; hasAV = false; } else { switch (clip->clipType()) { case AV: case Playlist: hasAudio = true; hasAV = true; break; case Audio: hasAudio = true; hasAV = false; break; default: hasAudio = false; hasAV = false; } m_clipTypeGroup->setEnabled(true); QList actions = m_clipTypeGroup->actions(); QString lookup; if (clip->clipState() == PlaylistState::AudioOnly) lookup = QStringLiteral("clip_audio_only"); else if (clip->clipState() == PlaylistState::VideoOnly) lookup = QStringLiteral("clip_video_only"); else lookup = QStringLiteral("clip_audio_and_video"); for (int i = 0; i < actions.count(); ++i) { if (actions.at(i)->data().toString() == lookup) { actions.at(i)->setChecked(true); break; } } } for (int i = 0; i < m_audioActions.count(); ++i) { m_audioActions.at(i)->setEnabled(hasAudio); } for (int i = 0; i < m_avActions.count(); ++i) { m_avActions.at(i)->setEnabled(hasAV); } } void CustomTrackView::slotGoToMarker(QAction *action) { int pos = action->data().toInt(); seekCursorPos(pos); } void CustomTrackView::reloadTransitionLumas() { QString lumaNames; QString lumaFiles; QDomElement lumaTransition = MainWindow::transitions.getEffectByTag(QStringLiteral("luma"), QStringLiteral("luma")); QDomNodeList params = lumaTransition.elementsByTagName(QStringLiteral("parameter")); for (int i = 0; i < params.count(); ++i) { QDomElement e = params.item(i).toElement(); if (e.attribute(QStringLiteral("tag")) == QLatin1String("resource")) { lumaNames = e.attribute(QStringLiteral("paramlistdisplay")); lumaFiles = e.attribute(QStringLiteral("paramlist")); break; } } QList itemList = items(); Transition *transitionitem; QDomElement transitionXml; for (int i = 0; i < itemList.count(); ++i) { if (itemList.at(i)->type() == TransitionWidget) { transitionitem = static_cast (itemList.at(i)); transitionXml = transitionitem->toXML(); if (transitionXml.attribute(QStringLiteral("id")) == QLatin1String("luma") && transitionXml.attribute(QStringLiteral("tag")) == QLatin1String("luma")) { QDomNodeList params = transitionXml.elementsByTagName(QStringLiteral("parameter")); for (int i = 0; i < params.count(); ++i) { QDomElement e = params.item(i).toElement(); if (e.attribute(QStringLiteral("tag")) == QLatin1String("resource")) { e.setAttribute(QStringLiteral("paramlistdisplay"), lumaNames); e.setAttribute(QStringLiteral("paramlist"), lumaFiles); break; } } } if (transitionXml.attribute(QStringLiteral("id")) == QLatin1String("composite") && transitionXml.attribute(QStringLiteral("tag")) == QLatin1String("composite")) { QDomNodeList params = transitionXml.elementsByTagName(QStringLiteral("parameter")); for (int i = 0; i < params.count(); ++i) { QDomElement e = params.item(i).toElement(); if (e.attribute(QStringLiteral("tag")) == QLatin1String("luma")) { e.setAttribute(QStringLiteral("paramlistdisplay"), lumaNames); e.setAttribute(QStringLiteral("paramlist"), lumaFiles); break; } } } } } emit transitionItemSelected(NULL); } double CustomTrackView::fps() const { return m_document->fps(); } void CustomTrackView::updateProjectFps() { // update all clips to the new fps resetSelectionGroup(); scene()->clearSelection(); if (m_dragItem) m_dragItem->setMainSelectedClip(false); m_dragItem = NULL; QList itemList = items(); for (int i = 0; i < itemList.count(); ++i) { // remove all items and re-add them one by one if (itemList.at(i) != m_cursorLine && itemList.at(i)->parentItem() == NULL) m_scene->removeItem(itemList.at(i)); } for (int i = 0; i < itemList.count(); ++i) { if (itemList.at(i)->parentItem() == 0 && (itemList.at(i)->type() == AVWidget || itemList.at(i)->type() == TransitionWidget)) { AbstractClipItem *clip = static_cast (itemList.at(i)); clip->updateFps(m_document->fps()); m_scene->addItem(clip); } else if (itemList.at(i)->type() == GroupWidget) { AbstractGroupItem *grp = static_cast (itemList.at(i)); QList children = grp->childItems(); for (int j = 0; j < children.count(); ++j) { if (children.at(j)->type() == AVWidget || children.at(j)->type() == TransitionWidget) { AbstractClipItem *clip = static_cast (children.at(j)); clip->setFlag(QGraphicsItem::ItemIsMovable, true); clip->updateFps(m_document->fps()); } } m_document->clipManager()->removeGroup(grp); m_scene->addItem(grp); if (grp == m_selectionGroup) m_selectionGroup = NULL; scene()->destroyItemGroup(grp); scene()->clearSelection(); /*for (int j = 0; j < children.count(); ++j) { if (children.at(j)->type() == AVWidget || children.at(j)->type() == TRANSITIONWIDGET) { //children.at(j)->setParentItem(0); children.at(j)->setSelected(true); } }*/ groupSelectedItems(children, true); } else if (itemList.at(i)->type() == GUIDEITEM) { Guide *g = static_cast(itemList.at(i)); g->updatePos(); m_scene->addItem(g); } } viewport()->update(); } void CustomTrackView::slotTrackDown() { int ix; if (m_selectedTrack > 1) ix = m_selectedTrack - 1; else ix = m_timeline->tracksCount() - 1; slotSelectTrack(ix); } void CustomTrackView::slotTrackUp() { int ix; if (m_selectedTrack < m_timeline->tracksCount() - 1) ix = m_selectedTrack + 1; else ix = 1; slotSelectTrack(ix); } int CustomTrackView::selectedTrack() const { return m_selectedTrack; } QStringList CustomTrackView::selectedClips() const { QStringList clipIds; QList selection = m_scene->selectedItems(); for (int i = 0; i < selection.count(); ++i) { if (selection.at(i)->type() == AVWidget) { ClipItem *item = static_cast(selection.at(i)); clipIds << item->getBinId(); } } return clipIds; } void CustomTrackView::slotSelectTrack(int ix) { m_selectedTrack = qMax(0, ix); m_selectedTrack = qMin(ix, m_timeline->tracksCount() - 1); emit updateTrackHeaders(); m_timeline->slotShowTrackEffects(ix); QRectF rect(mapToScene(QPoint(10, 0)).x(), getPositionFromTrack(ix) , 10, m_tracksHeight); ensureVisible(rect, 0, 0); viewport()->update(); } void CustomTrackView::slotSelectClipsInTrack() { QRectF rect(0, getPositionFromTrack(m_selectedTrack) + m_tracksHeight / 2, sceneRect().width(), m_tracksHeight / 2 - 1); resetSelectionGroup(); QList selection = m_scene->items(rect); m_scene->clearSelection(); QList list; for (int i = 0; i < selection.count(); ++i) { if (selection.at(i)->type() == AVWidget || selection.at(i)->type() == TransitionWidget || selection.at(i)->type() == GroupWidget) { list.append(selection.at(i)); } } groupSelectedItems(list, false, true); } void CustomTrackView::slotSelectAllClips() { m_scene->clearSelection(); resetSelectionGroup(); groupSelectedItems(m_scene->items(), false, true); } void CustomTrackView::selectClip(bool add, bool group, int track, int pos) { QRectF rect; if (track != -1 && pos != -1) rect = QRectF(pos, getPositionFromTrack(track) + m_tracksHeight / 2, 1, 1); else rect = QRectF(m_cursorPos, getPositionFromTrack(m_selectedTrack) + m_tracksHeight / 2, 1, 1); QList selection = m_scene->items(rect); resetSelectionGroup(group); if (!group) m_scene->clearSelection(); for (int i = 0; i < selection.count(); ++i) { if (selection.at(i)->type() == AVWidget) { selection.at(i)->setSelected(add); break; } } if (group) groupSelectedItems(); } void CustomTrackView::selectTransition(bool add, bool group) { QRectF rect(m_cursorPos, getPositionFromTrack(m_selectedTrack) + m_tracksHeight, 1, 1); QList selection = m_scene->items(rect); resetSelectionGroup(group); if (!group) m_scene->clearSelection(); for (int i = 0; i < selection.count(); ++i) { if (selection.at(i)->type() == TransitionWidget) { selection.at(i)->setSelected(add); break; } } if (group) groupSelectedItems(); } QStringList CustomTrackView::extractTransitionsLumas() { QStringList urls; QList itemList = items(); Transition *transitionitem; QDomElement transitionXml; for (int i = 0; i < itemList.count(); ++i) { if (itemList.at(i)->type() == TransitionWidget) { transitionitem = static_cast (itemList.at(i)); transitionXml = transitionitem->toXML(); // luma files in transitions are in "resource" property QString luma = EffectsList::parameter(transitionXml, QStringLiteral("resource")); if (!luma.isEmpty()) urls << QUrl(luma).path(); } } urls.removeDuplicates(); return urls; } void CustomTrackView::setEditMode(EditMode mode) { m_scene->setEditMode(mode); } void CustomTrackView::checkTrackSequence(int track) { QList times = m_document->renderer()->checkTrackSequence(track); QRectF rect(0, getPositionFromTrack(track) + m_tracksHeight / 2, sceneRect().width(), 2); QList selection = m_scene->items(rect); QList timelineList; timelineList.append(0); for (int i = 0; i < selection.count(); ++i) { if (selection.at(i)->type() == AVWidget) { ClipItem *clip = static_cast (selection.at(i)); int start = clip->startPos().frames(m_document->fps()); int end = clip->endPos().frames(m_document->fps()); if (!timelineList.contains(start)) timelineList.append(start); if (!timelineList.contains(end)) timelineList.append(end); } } qSort(timelineList); //qDebug() << "// COMPARE:\n" << times << '\n' << timelineList << "\n-------------------"; if (times != timelineList) KMessageBox::sorry(this, i18n("error"), i18n("TRACTOR")); } void CustomTrackView::insertZoneOverwrite(QStringList data, int in) { if (data.isEmpty()) return; ItemInfo info; info.startPos = GenTime(in, m_document->fps()); info.cropStart = GenTime(data.at(1).toInt(), m_document->fps()); info.endPos = info.startPos + GenTime(data.at(2).toInt(), m_document->fps()) - info.cropStart; info.cropDuration = info.endPos - info.startPos; info.track = m_selectedTrack; QUndoCommand *addCommand = new QUndoCommand(); addCommand->setText(i18n("Insert clip")); adjustTimelineClips(OverwriteEdit, NULL, info, addCommand); new AddTimelineClipCommand(this, data.at(0), info, EffectsList(), PlaylistState::Original, true, false, addCommand); updateTrackDuration(info.track, addCommand); m_commandStack->push(addCommand); selectClip(true, false, m_selectedTrack, in); // Automatic audio split if (KdenliveSettings::splitaudio()) splitAudio(); } void CustomTrackView::clearSelection(bool emitInfo) { if (m_dragItem) m_dragItem->setSelected(false); resetSelectionGroup(); scene()->clearSelection(); if (m_dragItem) m_dragItem->setMainSelectedClip(false); m_dragItem = NULL; if (emitInfo) emit clipItemSelected(NULL); } void CustomTrackView::updatePalette() { if (m_cursorLine) { QPen pen1 = m_cursorLine->pen(); QColor line(palette().text().color()); line.setAlpha(pen1.color().alpha()); pen1.setColor(line); m_cursorLine->setPen(pen1); } QIcon razorIcon = KoIconUtils::themedIcon(QStringLiteral("edit-cut")); m_razorCursor = QCursor(razorIcon.pixmap(32, 32)); KColorScheme scheme(palette().currentColorGroup(), KColorScheme::Window, KSharedConfig::openConfig(KdenliveSettings::colortheme())); m_selectedTrackColor = scheme.background(KColorScheme::ActiveBackground ).color(); m_selectedTrackColor.setAlpha(150); } void CustomTrackView::removeTipAnimation() { if (m_visualTip) { scene()->removeItem(m_visualTip); m_keyPropertiesTimer->stop(); delete m_keyProperties; m_keyProperties = NULL; delete m_visualTip; m_visualTip = NULL; } } void CustomTrackView::setTipAnimation(AbstractClipItem *clip, OperationType mode, const double size) { if (m_visualTip == NULL) { QRectF rect = clip->sceneBoundingRect(); m_keyProperties = new QGraphicsItemAnimation; m_keyProperties->setTimeLine(m_keyPropertiesTimer); m_keyProperties->setScaleAt(1, 1, 1); QPolygon polygon; switch (mode) { case FadeIn: case FadeOut: m_visualTip = new QGraphicsEllipseItem(-size, -size, size * 2, size * 2); ((QGraphicsEllipseItem*) m_visualTip)->setBrush(m_tipColor); ((QGraphicsEllipseItem*) m_visualTip)->setPen(m_tipPen); if (mode == FadeIn) m_visualTip->setPos(rect.x() + static_cast(clip)->fadeIn(), rect.y()); else m_visualTip->setPos(rect.right() - static_cast(clip)->fadeOut(), rect.y()); m_keyProperties->setScaleAt(.5, 2, 2); break; case ResizeStart: case ResizeEnd: polygon << QPoint(0, - size * 2); if (mode == ResizeStart) polygon << QPoint(size * 2, 0); else polygon << QPoint(- size * 2, 0); polygon << QPoint(0, size * 2); polygon << QPoint(0, - size * 2); m_visualTip = new QGraphicsPolygonItem(polygon); ((QGraphicsPolygonItem*) m_visualTip)->setBrush(m_tipColor); ((QGraphicsPolygonItem*) m_visualTip)->setPen(m_tipPen); if (mode == ResizeStart) m_visualTip->setPos(rect.x(), rect.y() + rect.height() / 2); else m_visualTip->setPos(rect.right(), rect.y() + rect.height() / 2); m_keyProperties->setScaleAt(.5, 2, 1); break; case TransitionStart: case TransitionEnd: polygon << QPoint(0, - size * 2); if (mode == TransitionStart) polygon << QPoint(size * 2, 0); else polygon << QPoint(- size * 2, 0); polygon << QPoint(0, 0); polygon << QPoint(0, - size * 2); m_visualTip = new QGraphicsPolygonItem(polygon); ((QGraphicsPolygonItem*) m_visualTip)->setBrush(m_tipColor); ((QGraphicsPolygonItem*) m_visualTip)->setPen(m_tipPen); if (mode == TransitionStart) m_visualTip->setPos(rect.x(), rect.bottom()); else m_visualTip->setPos(rect.right(), rect.bottom()); m_keyProperties->setScaleAt(.5, 2, 2); break; default: delete m_keyProperties; return; } m_visualTip->setFlags(QGraphicsItem::ItemIgnoresTransformations); m_visualTip->setZValue(100); scene()->addItem(m_visualTip); m_keyProperties->setItem(m_visualTip); m_keyPropertiesTimer->start(); } } bool CustomTrackView::hasAudio(int track) const { QRectF rect(0, (double)(getPositionFromTrack(track) + 1), (double) sceneRect().width(), (double)(m_tracksHeight - 1)); QList collisions = scene()->items(rect, Qt::IntersectsItemBoundingRect); for (int i = 0; i < collisions.count(); ++i) { QGraphicsItem *item = collisions.at(i); if (!item->isEnabled()) continue; if (item->type() == AVWidget) { ClipItem *clip = static_cast (item); if (clip->clipState() != PlaylistState::VideoOnly && (clip->clipType() == Audio || clip->clipType() == AV || clip->clipType() == Playlist)) return true; } } return false; } void CustomTrackView::slotAddTrackEffect(const QDomElement &effect, int ix) { QUndoCommand *effectCommand = new QUndoCommand(); QString effectName; if (effect.tagName() == QLatin1String("effectgroup")) { effectName = effect.attribute(QStringLiteral("name")); } else { QDomElement namenode = effect.firstChildElement(QStringLiteral("name")); if (!namenode.isNull()) effectName = i18n(namenode.text().toUtf8().data()); else effectName = i18n("effect"); } effectCommand->setText(i18n("Add %1", effectName)); if (effect.tagName() == QLatin1String("effectgroup")) { QDomNodeList effectlist = effect.elementsByTagName(QStringLiteral("effect")); for (int j = 0; j < effectlist.count(); ++j) { QDomElement trackeffect = effectlist.at(j).toElement(); if (trackeffect.attribute(QStringLiteral("unique"), QStringLiteral("0")) != QLatin1String("0") && m_timeline->hasTrackEffect(ix, trackeffect.attribute(QStringLiteral("tag")), trackeffect.attribute(QStringLiteral("id"))) != -1) { emit displayMessage(i18n("Effect already present in track"), ErrorMessage); continue; } new AddEffectCommand(this, ix, GenTime(-1), trackeffect, true, effectCommand); } } else { if (effect.attribute(QStringLiteral("unique"), QStringLiteral("0")) != QLatin1String("0") && m_timeline->hasTrackEffect(ix, effect.attribute(QStringLiteral("tag")), effect.attribute(QStringLiteral("id"))) != -1) { emit displayMessage(i18n("Effect already present in track"), ErrorMessage); delete effectCommand; return; } new AddEffectCommand(this, ix, GenTime(-1), effect, true, effectCommand); } if (effectCommand->childCount() > 0) { m_commandStack->push(effectCommand); } else delete effectCommand; } void CustomTrackView::updateTrackNames(int track, bool added) { QList tracks = m_timeline->getTracksInfo(); int max = tracks.count(); int docTrack = max - track - 1; // count number of tracks of each type int videoTracks = 0; int audioTracks = 0; for (int i = max - 1; i >= 0; --i) { TrackInfo info = tracks.at(i); if (info.type == VideoTrack) videoTracks++; else audioTracks++; if (i <= docTrack) { QString type = (info.type == VideoTrack ? "Video " : "Audio "); int typeNumber = (info.type == VideoTrack ? videoTracks : audioTracks); if (added) { if (i == docTrack || info.trackName == type + QString::number(typeNumber - 1)) { info.trackName = type + QString::number(typeNumber); m_timeline->setTrackInfo(i, info); } } else { if (info.trackName == type + QString::number(typeNumber + 1)) { info.trackName = type + QString::number(typeNumber); m_timeline->setTrackInfo(i, info); } } } } emit tracksChanged(); } void CustomTrackView::updateTrackDuration(int track, QUndoCommand *command) { Q_UNUSED(command) QList tracks; if (track >= 0) { tracks << track; } else { // negative track number -> update all tracks for (int i = 1; i < m_timeline->tracksCount(); ++i) tracks << i; } /*for (int i = 0; i < tracks.count(); ++i) { int t = tracks.at(i); // t + 1 because of black background track int duration = m_document->renderer()->mltTrackDuration(t + 1); if (duration != m_document->trackDuration(t)) { m_document->setTrackDuration(t, duration); // update effects EffectsList effects = m_document->getTrackEffects(t); for (int j = 0; j < effects.count(); ++j) { // TODO // - lookout for keyframable parameters and update them so all keyframes are in the new range (0 - duration) // - update the effectstack if necessary // } } }*/ } void CustomTrackView::slotRefreshThumbs(const QString &id, bool resetThumbs) { QList list = scene()->items(); ClipItem *clip = NULL; for (int i = 0; i < list.size(); ++i) { if (list.at(i)->type() == AVWidget) { clip = static_cast (list.at(i)); if (clip->getBinId() == id) { clip->refreshClip(true, resetThumbs); } } } } void CustomTrackView::adjustEffects(ClipItem* item, ItemInfo oldInfo, QUndoCommand* command) { QMap effects = item->adjustEffectsToDuration(oldInfo); if (!effects.isEmpty()) { QMap::const_iterator i = effects.constBegin(); while (i != effects.constEnd()) { new EditEffectCommand(this, item->track(), item->startPos(), i.value(), item->effect(i.key()), i.value().attribute(QStringLiteral("kdenlive_ix")).toInt(), true, true, command); ++i; } } if (item == m_dragItem) { // clip is selected, update effect stack emit clipItemSelected(item); } } void CustomTrackView::slotGotFilterJobResults(const QString &/*id*/, int startPos, int track, stringMap filterParams, stringMap extra) { ClipItem *clip = getClipItemAtStart(GenTime(startPos, m_document->fps()), track); if (clip == NULL) { emit displayMessage(i18n("Cannot find clip for effect update %1.", extra.value("finalfilter")), ErrorMessage); return; } QDomElement newEffect; QDomElement effect = clip->getEffectAtIndex(clip->selectedEffectIndex()); if (effect.attribute(QStringLiteral("id")) == extra.value(QStringLiteral("finalfilter"))) { newEffect = effect.cloneNode().toElement(); QMap::const_iterator i = filterParams.constBegin(); while (i != filterParams.constEnd()) { EffectsList::setParameter(newEffect, i.key(), i.value()); //qDebug()<<"// RESULT FILTER: "<track(), clip->startPos(), effect, newEffect, clip->selectedEffectIndex(), true, true); m_commandStack->push(command); emit clipItemSelected(clip); } } void CustomTrackView::slotImportClipKeyframes(GraphicsRectItem type, ItemInfo info, QDomElement xml, QMap data) { ClipItem *item = NULL; ItemInfo srcInfo; if (type == TransitionWidget && data.isEmpty()) { // We want to import keyframes to a transition if (!m_selectionGroup) { emit displayMessage(i18n("You need to select one clip and one transition"), ErrorMessage); return; } // Make sure there is no collision QList children = m_selectionGroup->childItems(); for (int i = 0; i < children.count(); ++i) { if (children.at(i)->type() == AVWidget) { item = static_cast(children.at(i)); srcInfo = item->info(); break; } } } else if (data.isEmpty()) { // Import keyframes from current clip to its effect if (m_dragItem && m_dragItem->type() == AVWidget) item = static_cast (m_dragItem); } if (!item && data.isEmpty()) { emit displayMessage(i18n("No clip found"), ErrorMessage); return; } if (data.isEmpty()) { // Load it from clip data = item->binClip()->analysisData(); } if (data.isEmpty()) { emit displayMessage(i18n("No keyframe data found in clip"), ErrorMessage); return; } KeyframeImport *import = new KeyframeImport(srcInfo, info, data, m_document->timecode(), xml, m_document->getProfileInfo(), this); if (import->exec() != QDialog::Accepted) { // Aborted by user delete import; return; } QString keyframeData = import->selectedData(); QString tag = import->selectedTarget(); delete import; emit importKeyframes(type, tag, keyframeData); } void CustomTrackView::slotReplaceTimelineProducer(const QString &id) { Mlt::Producer *prod = m_document->renderer()->getBinProducer(id); Mlt::Producer *videoProd = m_document->renderer()->getBinVideoProducer(id); QList allSlows; for (int i = 1; i < m_timeline->tracksCount(); i++) { allSlows << m_timeline->track(i)->getSlowmotionInfos(id); } QLocale locale; QString url = prod->get("resource"); // generate all required slowmo producers QStringList processedUrls; QMap newSlowMos; for (int i = 0; i < allSlows.count(); i++) { // find out speed and strobe values Track::SlowmoInfo info = allSlows.at(i); QString wrapUrl = QStringLiteral("timewrap:") + locale.toString(info.speed) + ':' + url; // build slowmo producer if (processedUrls.contains(wrapUrl)) continue; processedUrls << wrapUrl; Mlt::Producer *slowProd = new Mlt::Producer(*prod->profile(), 0, wrapUrl.toUtf8().constData()); if (!slowProd->is_valid()) { qDebug()<<"++++ FAILED TO CREATE SLOWMO PROD"; continue; } //if (strobe > 1) slowProd->set("strobe", strobe); QString producerid = "slowmotion:" + id + ':' + info.toString(locale); slowProd->set("id", producerid.toUtf8().constData()); newSlowMos.insert(info.toString(locale), slowProd); } for (int i = 1; i < m_timeline->tracksCount(); i++) { m_timeline->track(i)->replaceAll(id, prod, videoProd, newSlowMos); } // update slowmotion storage QMapIterator i(newSlowMos); while (i.hasNext()) { i.next(); Mlt::Producer *sprod = i.value(); m_document->renderer()->storeSlowmotionProducer(i.key() + url, sprod, true); } m_timeline->refreshTractor(); } void CustomTrackView::slotPrepareTimelineReplacement(const QString &id) { for (int i = 1; i < m_timeline->tracksCount(); i++) { m_timeline->track(i)->replaceId(id); } } void CustomTrackView::slotUpdateTimelineProducer(const QString &id) { Mlt::Producer *prod = m_document->renderer()->getBinProducer(id); for (int i = 1; i < m_timeline->tracksCount(); i++) { m_timeline->track(i)->updateEffects(id, prod); } } bool CustomTrackView::hasSelection() const { return (m_selectionGroup || m_dragItem); } void CustomTrackView::exportTimelineSelection(QString path) { if (!m_selectionGroup && !m_dragItem) { qDebug()<<"/// ARGH, NO SELECTION GRP"; return; } if (path.isEmpty()) { QString clipFolder = KRecentDirs::dir(QStringLiteral(":KdenliveClipFolder")); if (clipFolder.isEmpty()) { clipFolder = QDir::homePath(); } path = QFileDialog::getSaveFileName(this, i18n("Save Zone"), clipFolder, i18n("MLT playlist (*.mlt)")); if (path.isEmpty()) return; } QList children; QRectF bounding; if (m_selectionGroup) { children = m_selectionGroup->childItems(); bounding = m_selectionGroup->sceneBoundingRect(); } else { // only one clip selected children << m_dragItem; bounding = m_dragItem->sceneBoundingRect(); } int firstTrack = getTrackFromPos(bounding.bottom()); int lastTrack = getTrackFromPos(bounding.top()); // Build export playlist Mlt::Tractor *newTractor = new Mlt::Tractor(*(m_document->renderer()->getProducer()->profile())); Mlt::Field *field = newTractor->field(); for (int i = firstTrack; i <= lastTrack; i++) { QScopedPointer newTrackPlaylist(new Mlt::Playlist(*newTractor->profile())); newTractor->set_track(*newTrackPlaylist, i - firstTrack); } int startOffest = m_projectDuration; // Find first frame of selection for (int i = 0; i < children.count(); ++i) { QGraphicsItem *item = children.at(i); if (item->type() != TransitionWidget && item->type() != AVWidget) { continue; } AbstractClipItem *it = static_cast(item); if (!it) continue; int pos = it->startPos().frames(m_document->fps()); if (pos < startOffest) startOffest = pos; } for (int i = 0; i < children.count(); ++i) { QGraphicsItem *item = children.at(i); if (item->type() == AVWidget) { ClipItem *clip = static_cast(item); int track = clip->track() - firstTrack; m_timeline->duplicateClipOnPlaylist(clip->track(), clip->startPos().seconds(), startOffest, newTractor->track(track)); } else if (item->type() == TransitionWidget) { Transition *tr = static_cast(item); int a_track = qBound(0, tr->transitionEndTrack() - firstTrack, lastTrack - firstTrack + 1); int b_track = qBound(0, tr->track() - firstTrack, lastTrack - firstTrack + 1);; m_timeline->transitionHandler->duplicateTransitionOnPlaylist(tr->startPos().frames(m_document->fps()) - startOffest, tr->endPos().frames(m_document->fps()) - startOffest, tr->transitionTag(), tr->toXML(), a_track, b_track, field); } } Mlt::Consumer xmlConsumer(*newTractor->profile(), ("xml:" + path).toUtf8().constData()); xmlConsumer.set("terminate_on_pause", 1); xmlConsumer.connect(*newTractor); xmlConsumer.run(); delete newTractor; } int CustomTrackView::getTrackFromPos(double y) const { int totalTracks = m_timeline->tracksCount() - 1; return qMax(1, totalTracks - ((int)(y / m_tracksHeight))); } int CustomTrackView::getPositionFromTrack(int track) const { int totalTracks = m_timeline->tracksCount() - 1; return m_tracksHeight * (totalTracks - track); } void CustomTrackView::importPlaylist(ItemInfo info, QMap processedUrl, QMap idMaps, QDomDocument doc, QUndoCommand *command) { Mlt::Producer *import = new Mlt::Producer(*m_document->renderer()->getProducer()->profile(), "xml-string", doc.toString().toUtf8().constData()); if (!import || !import->is_valid()) { delete command; qDebug()<<" / / /CANNOT open playlist to import "; return; } Mlt::Service service(import->parent().get_service()); if (service.type() != tractor_type) { delete command; qDebug()<<" / / /CANNOT playlist file: "< m_timeline->tracksCount()) { lowerTrack = m_timeline->tracksCount() - playlistTracks; } if (lowerTrack <1) { qWarning()<<" / / / TOO many tracks in playlist for our timeline "; delete command; return; } // Check if there are no objects on the way double startY = getPositionFromTrack(lowerTrack + playlistTracks) + 1; QRectF r(info.startPos.frames(m_document->fps()), startY, (info.endPos - info.startPos).frames(m_document->fps()), m_tracksHeight * playlistTracks); QList selection = m_scene->items(r); ClipItem *playlistToExpand = getClipItemAtStart(info.startPos, info.track); if (playlistToExpand) selection.removeAll(playlistToExpand); for (int i = 0; i < selection.count(); ++i) { if (selection.at(i)->type() == TransitionWidget || selection.at(i)->type() == AVWidget) { qWarning()<<" / / /There are clips in timeline preventing expand actions"; delete command; return; } } for (int i = 0; i < playlistTracks; i++) { int startPos = info.startPos.frames(m_document->fps()); Mlt::Producer trackProducer(tractor.track(i)); Mlt::Playlist trackPlaylist((mlt_playlist) trackProducer.get_service()); for (int j = 0; j < trackPlaylist.count(); j++) { QScopedPointer original(trackPlaylist.get_clip(j)); if (original == NULL || !original->is_valid()) { // invalid clip continue; } if (original->is_blank()) { startPos += original->get_playtime(); continue; } // Found a producer, import it QString resource = original->parent().get("resource"); QString service = original->parent().get("mlt_service"); if (service == QLatin1String("timewarp")) { resource = original->parent().get("warp_resource"); } else if (service == QLatin1String("framebuffer")) { resource = resource.section(QStringLiteral("?"), 0, -2); } QString originalId = processedUrl.value(resource); // Title clips cannot be identified by resource, so use a map of previous / current ids instead of an url / id map if (originalId.isEmpty()) originalId = idMaps.value(original->parent().get("id")); if (originalId.isEmpty()) { qDebug()<<" / /WARNING, MISSING PRODUCER FOR: "<get_playtime(); continue; } // Ready, insert clip ItemInfo insertInfo; insertInfo.startPos = GenTime(startPos, m_document->fps()); int in = original->get_in(); int out = original->get_out(); insertInfo.cropStart = GenTime(in, m_document->fps()); insertInfo.cropDuration = GenTime(out - in + 1, m_document->fps()); insertInfo.endPos = insertInfo.startPos + insertInfo.cropDuration; insertInfo.track = lowerTrack + i; EffectsList effects = ClipController::xmlEffectList(original->profile(), *original); new AddTimelineClipCommand(this, originalId, insertInfo, effects, PlaylistState::Original, true, false, command); startPos += original->get_playtime(); } updateTrackDuration(lowerTrack + i, command); } // Paste transitions QScopedPointer serv(tractor.field()); while (serv && serv->is_valid()) { if (serv->type() == transition_type) { Mlt::Transition t((mlt_transition) serv->get_service()); if (t.get_int("internal_added") > 0) { // This is an auto transition, skip } else { Mlt::Properties prop(t.get_properties()); ItemInfo transitionInfo; transitionInfo.startPos = info.startPos + GenTime(t.get_in(), m_document->fps()); transitionInfo.endPos = info.startPos + GenTime(t.get_out(), m_document->fps()); transitionInfo.track = t.get_b_track() + lowerTrack; int endTrack = t.get_a_track() + lowerTrack; if (prop.get("kdenlive_id") == NULL && QString(prop.get("mlt_service")) == "composite" && Timeline::isSlide(prop.get("geometry"))) prop.set("kdenlive_id", "slide"); QDomElement base = MainWindow::transitions.getEffectByTag(prop.get("mlt_service"), prop.get("kdenlive_id")).cloneNode().toElement(); QDomNodeList params = base.elementsByTagName("parameter"); for (int i = 0; i < params.count(); ++i) { QDomElement e = params.item(i).toElement(); QString paramName = e.hasAttribute("tag") ? e.attribute("tag") : e.attribute("name"); QString value; if (paramName == "a_track") { value = QString::number(transitionInfo.track); } else if (paramName == "b_track") { value = QString::number(endTrack); } else value = prop.get(paramName.toUtf8().constData()); //int factor = e.attribute("factor").toInt(); if (value.isEmpty()) continue; e.setAttribute("value", value); } base.setAttribute("force_track", prop.get_int("force_track")); base.setAttribute("automatic", prop.get_int("automatic")); new AddTransitionCommand(this, transitionInfo, endTrack, base, false, true, command); } } serv.reset(serv->producer()); } if (command->childCount() > 0) { m_commandStack->push(command); } else delete command; } void CustomTrackView::slotRefreshCutLine() { if (m_cutLine) { QPointF pos = mapToScene(mapFromGlobal(QCursor::pos())); int mappedXPos = qMax((int)(pos.x()), 0); m_cutLine->setPos(mappedXPos, getPositionFromTrack(getTrackFromPos(pos.y()))); } } diff --git a/src/titler/titledocument.cpp b/src/titler/titledocument.cpp index d24d53f41..a285ec66c 100644 --- a/src/titler/titledocument.cpp +++ b/src/titler/titledocument.cpp @@ -1,696 +1,699 @@ /*************************************************************************** titledocument.h - description ------------------- begin : Feb 28 2008 copyright : (C) 2008 by Marco Gittler email : g.marco@freenet.de ***************************************************************************/ /*************************************************************************** * * * 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. * * * ***************************************************************************/ #include "titledocument.h" #include "gradientwidget.h" #include "kdenlivesettings.h" #include "timecode.h" #include "effectstack/graphicsscenerectmove.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef Q_OS_MAC #include #endif #include #include #include QByteArray fileToByteArray(const QString& filename) { QByteArray ret; QFile file(filename); if (file.open(QIODevice::ReadOnly)) { while (!file.atEnd()) { ret.append(file.readLine()); } } return ret; } TitleDocument::TitleDocument() { m_scene = NULL; m_width = 0; m_height = 0; } void TitleDocument::setScene(QGraphicsScene* _scene, int width, int height) { m_scene = _scene; m_width = width; m_height = height; } int TitleDocument::base64ToUrl(QGraphicsItem* item, QDomElement& content, bool embed) { if (embed) { if (!item->data(Qt::UserRole + 1).toString().isEmpty()) { content.setAttribute(QStringLiteral("base64"), item->data(Qt::UserRole + 1).toString()); } else if (!item->data(Qt::UserRole).toString().isEmpty()) { content.setAttribute(QStringLiteral("base64"), fileToByteArray(item->data(Qt::UserRole).toString()).toBase64().data()); } content.removeAttribute(QStringLiteral("url")); } else { // save for project files to disk QString base64 = item->data(Qt::UserRole + 1).toString(); if (!base64.isEmpty()) { QString titlePath; if (!m_projectPath.isEmpty()) { titlePath = m_projectPath; } else { titlePath = QStringLiteral("/tmp/titles"); } QString filename = extractBase64Image(titlePath, base64); if (!filename.isEmpty()) { content.setAttribute(QStringLiteral("url"), filename); content.removeAttribute(QStringLiteral("base64")); } } else { return 1; } } return 0; } //static const QString TitleDocument::extractBase64Image(const QString &titlePath, const QString &data) { QString filename = titlePath + QString(QCryptographicHash::hash(data.toLatin1(), QCryptographicHash::Md5).toHex().append(".titlepart")); QDir dir; dir.mkpath(titlePath); QFile f(filename); if (f.open(QIODevice::WriteOnly)) { f.write(QByteArray::fromBase64(data.toLatin1())) ; f.close(); return filename; } return QString(); } QDomDocument TitleDocument::xml(QGraphicsRectItem* startv, QGraphicsRectItem* endv, bool embed) { QDomDocument doc; QDomElement main = doc.createElement(QStringLiteral("kdenlivetitle")); main.setAttribute(QStringLiteral("width"), m_width); main.setAttribute(QStringLiteral("height"), m_height); // Save locale #ifndef Q_OS_MAC const char *locale = setlocale(LC_NUMERIC, NULL); #else const char *locale = setlocale(LC_NUMERIC_MASK, NULL); #endif main.setAttribute(QStringLiteral("LC_NUMERIC"), locale); doc.appendChild(main); QTextCursor cur; QTextBlockFormat format; foreach(QGraphicsItem * item, m_scene->items()) { + if (!(item->flags() & QGraphicsItem::ItemIsSelectable)) { + continue; + } QDomElement e = doc.createElement(QStringLiteral("item")); QDomElement content = doc.createElement(QStringLiteral("content")); QFont font; QString gradient; MyTextItem *t; double xPosition = item->pos().x(); switch (item->type()) { case 7: e.setAttribute(QStringLiteral("type"), QStringLiteral("QGraphicsPixmapItem")); content.setAttribute(QStringLiteral("url"), item->data(Qt::UserRole).toString()); base64ToUrl(item, content, embed); break; case 13: e.setAttribute(QStringLiteral("type"), QStringLiteral("QGraphicsSvgItem")); content.setAttribute(QStringLiteral("url"), item->data(Qt::UserRole).toString()); base64ToUrl(item, content, embed); break; case 3: e.setAttribute(QStringLiteral("type"), QStringLiteral("QGraphicsRectItem")); content.setAttribute(QStringLiteral("rect"), rectFToString(static_cast(item)->rect().normalized())); content.setAttribute(QStringLiteral("pencolor"), colorToString(static_cast(item)->pen().color())); if (static_cast(item)->pen() == Qt::NoPen) { content.setAttribute(QStringLiteral("penwidth"), 0); } else { content.setAttribute(QStringLiteral("penwidth"), static_cast(item)->pen().width()); } content.setAttribute(QStringLiteral("brushcolor"), colorToString(static_cast(item)->brush().color())); gradient = item->data(TitleDocument::Gradient).toString(); if (!gradient.isEmpty()) { content.setAttribute(QStringLiteral("gradient"), gradient); } break; case 8: e.setAttribute(QStringLiteral("type"), QStringLiteral("QGraphicsTextItem")); t = static_cast(item); // Don't save empty text nodes if (t->toPlainText().simplified().isEmpty()) continue; //content.appendChild(doc.createTextNode(((QGraphicsTextItem*)item)->toHtml())); content.appendChild(doc.createTextNode(t->toPlainText())); font = t->font(); content.setAttribute(QStringLiteral("font"), font.family()); content.setAttribute(QStringLiteral("font-weight"), font.weight()); content.setAttribute(QStringLiteral("font-pixel-size"), font.pixelSize()); content.setAttribute(QStringLiteral("font-italic"), font.italic()); content.setAttribute(QStringLiteral("font-underline"), font.underline()); content.setAttribute(QStringLiteral("letter-spacing"), QString::number(font.letterSpacing())); gradient = item->data(TitleDocument::Gradient).toString(); if (!gradient.isEmpty()) { content.setAttribute(QStringLiteral("gradient"), gradient); } cur = QTextCursor(t->document()); cur.select(QTextCursor::Document); format = cur.blockFormat(); if (t->toPlainText() == QLatin1String("%s")) { // template text box, adjust size for later remplacement text if (t->alignment() == Qt::AlignHCenter) { // grow dimensions on both sides double xcenter = item->pos().x() + (t->baseBoundingRect().width()) / 2; double offset = qMin(xcenter, m_width - xcenter); xPosition = xcenter - offset; content.setAttribute(QStringLiteral("box-width"), QString::number(2 * offset)); } else if (t->alignment() == Qt::AlignRight) { // grow to the left double offset = item->pos().x() + (t->baseBoundingRect().width()); xPosition = 0; content.setAttribute(QStringLiteral("box-width"), QString::number(offset)); } else { // left align, grow on right side double offset = m_width - item->pos().x(); content.setAttribute(QStringLiteral("box-width"), QString::number(offset)); } } else { content.setAttribute(QStringLiteral("box-width"), QString::number(t->baseBoundingRect().width())); } content.setAttribute(QStringLiteral("box-height"), QString::number(t->baseBoundingRect().height())); if (!t->data(TitleDocument::LineSpacing).isNull()) content.setAttribute(QStringLiteral("line-spacing"), QString::number(t->data(TitleDocument::LineSpacing).toInt())); { QTextCursor cursor(t->document()); cursor.select(QTextCursor::Document); QColor fontcolor = cursor.charFormat().foreground().color(); content.setAttribute(QStringLiteral("font-color"), colorToString(fontcolor)); if (!t->data(TitleDocument::OutlineWidth).isNull()) content.setAttribute(QStringLiteral("font-outline"), QString::number(t->data(TitleDocument::OutlineWidth).toInt())); if (!t->data(TitleDocument::OutlineColor).isNull()) { QVariant variant = t->data(TitleDocument::OutlineColor); QColor outlineColor = variant.value(); content.setAttribute(QStringLiteral("font-outline-color"), colorToString(outlineColor)); } } if (!t->data(100).isNull()) { QStringList effectParams = t->data(100).toStringList(); QString effectName = effectParams.takeFirst(); content.setAttribute(QStringLiteral("textwidth"), QString::number(t->sceneBoundingRect().width())); content.setAttribute(effectName, effectParams.join(QStringLiteral(";"))); } // Only save when necessary. if (t->data(OriginXLeft).toInt() == AxisInverted) { content.setAttribute(QStringLiteral("kdenlive-axis-x-inverted"), t->data(OriginXLeft).toInt()); } if (t->data(OriginYTop).toInt() == AxisInverted) { content.setAttribute(QStringLiteral("kdenlive-axis-y-inverted"), t->data(OriginYTop).toInt()); } if (t->textWidth() != -1) { content.setAttribute(QStringLiteral("alignment"), (int) t->alignment()); } content.setAttribute(QStringLiteral("shadow"), t->shadowInfo().join(";")); break; default: continue; } // position QDomElement pos = doc.createElement(QStringLiteral("position")); pos.setAttribute(QStringLiteral("x"), QString::number(xPosition)); pos.setAttribute(QStringLiteral("y"), QString::number(item->pos().y())); QTransform transform = item->transform(); QDomElement tr = doc.createElement(QStringLiteral("transform")); if (!item->data(TitleDocument::ZoomFactor).isNull()) { tr.setAttribute(QStringLiteral("zoom"), QString::number(item->data(TitleDocument::ZoomFactor).toInt())); } if (!item->data(TitleDocument::RotateFactor).isNull()) { QList rotlist = item->data(TitleDocument::RotateFactor).toList(); tr.setAttribute(QStringLiteral("rotation"), QStringLiteral("%1,%2,%3").arg(rotlist[0].toDouble()).arg(rotlist[1].toDouble()).arg(rotlist[2].toDouble())); } tr.appendChild(doc.createTextNode( QStringLiteral("%1,%2,%3,%4,%5,%6,%7,%8,%9").arg( transform.m11()).arg(transform.m12()).arg(transform.m13()).arg(transform.m21()).arg(transform.m22()).arg(transform.m23()).arg(transform.m31()).arg(transform.m32()).arg(transform.m33()) ) ); e.setAttribute(QStringLiteral("z-index"), item->zValue()); pos.appendChild(tr); // effects QGraphicsEffect *eff = item->graphicsEffect(); if (eff) { QGraphicsBlurEffect *blur = static_cast (eff); QDomElement effect = doc.createElement(QStringLiteral("effect")); if (blur) { effect.setAttribute(QStringLiteral("type"), QStringLiteral("blur")); effect.setAttribute(QStringLiteral("blurradius"), QString::number(blur->blurRadius())); } /*else { //WARNING:those effects are anyways broken because they use QPixmaps which are not safe for MLT's threaded workflow QGraphicsDropShadowEffect *shadow = static_cast (eff); if (shadow) { effect.setAttribute("type", "shadow"); effect.setAttribute("blurradius", shadow->blurRadius()); effect.setAttribute("xoffset", shadow->xOffset()); effect.setAttribute("yoffset", shadow->yOffset()); } }*/ e.appendChild(effect); } e.appendChild(pos); e.appendChild(content); if (item->zValue() > -1000) main.appendChild(e); } if (startv && endv) { QDomElement endport = doc.createElement(QStringLiteral("endviewport")); QDomElement startport = doc.createElement(QStringLiteral("startviewport")); QRectF r(endv->pos().x(), endv->pos().y(), endv->rect().width(), endv->rect().height()); endport.setAttribute(QStringLiteral("rect"), rectFToString(r)); QRectF r2(startv->pos().x(), startv->pos().y(), startv->rect().width(), startv->rect().height()); startport.setAttribute(QStringLiteral("rect"), rectFToString(r2)); main.appendChild(startport); main.appendChild(endport); } QDomElement backgr = doc.createElement(QStringLiteral("background")); QColor color = getBackgroundColor(); backgr.setAttribute(QStringLiteral("color"), colorToString(color)); main.appendChild(backgr); return doc; } /** \brief Get the background color (incl. alpha) from the document, if possibly * \returns The background color of the document, inclusive alpha. If none found, returns (0,0,0,0) */ QColor TitleDocument::getBackgroundColor() const { QColor color(0, 0, 0, 0); if (m_scene) { QList items = m_scene->items(); for (int i = 0; i < items.size(); ++i) { if (items.at(i)->zValue() == -1100) { color = static_cast(items.at(i))->brush().color(); return color; } } } return color; } bool TitleDocument::saveDocument(const QUrl &url, QGraphicsRectItem* startv, QGraphicsRectItem* endv, int duration, bool embed) { if (!m_scene) return false; QDomDocument doc = xml(startv, endv, embed); doc.documentElement().setAttribute(QStringLiteral("duration"), duration); // keep some time for backwards compatibility (opening projects with older versions) - 26/12/12 doc.documentElement().setAttribute(QStringLiteral("out"), duration); QTemporaryFile tmpfile; if (!tmpfile.open()) { qWarning() << "///// CANNOT CREATE TMP FILE in: " << tmpfile.fileName(); return false; } QFile xmlf(tmpfile.fileName()); if (!xmlf.open(QIODevice::WriteOnly)) return false; xmlf.write(doc.toString().toUtf8()); if (xmlf.error() != QFile::NoError) { xmlf.close(); return false; } xmlf.close(); KIO::FileCopyJob *copyjob = KIO::file_copy(QUrl::fromLocalFile(tmpfile.fileName()), url); return copyjob->exec(); } int TitleDocument::loadFromXml(const QDomDocument& doc, QGraphicsRectItem* startv, QGraphicsRectItem* endv, int *duration, const QString& projectpath) { m_projectPath = projectpath; QDomNodeList titles = doc.elementsByTagName(QStringLiteral("kdenlivetitle")); //TODO: Check if the opened title size is equal to project size, otherwise warn user and rescale if (doc.documentElement().hasAttribute(QStringLiteral("width")) && doc.documentElement().hasAttribute(QStringLiteral("height"))) { int doc_width = doc.documentElement().attribute(QStringLiteral("width")).toInt(); int doc_height = doc.documentElement().attribute(QStringLiteral("height")).toInt(); if (doc_width != m_width || doc_height != m_height) { KMessageBox::information(QApplication::activeWindow(), i18n("This title clip was created with a different frame size."), i18n("Title Profile")); //TODO: convert using QTransform m_width = doc_width; m_height = doc_height; } } else { // Document has no size info, it is likely an old version title, so ignore viewport data QDomNodeList viewportlist = doc.documentElement().elementsByTagName(QStringLiteral("startviewport")); if (!viewportlist.isEmpty()) { doc.documentElement().removeChild(viewportlist.at(0)); } viewportlist = doc.documentElement().elementsByTagName(QStringLiteral("endviewport")); if (!viewportlist.isEmpty()) { doc.documentElement().removeChild(viewportlist.at(0)); } } if (doc.documentElement().hasAttribute(QStringLiteral("duration"))) *duration = doc.documentElement().attribute(QStringLiteral("duration")).toInt(); else if (doc.documentElement().hasAttribute(QStringLiteral("out"))) *duration = doc.documentElement().attribute(QStringLiteral("out")).toInt(); else *duration = Timecode().getFrameCount(KdenliveSettings::title_duration()); int maxZValue = 0; if (titles.size()) { QDomNodeList items = titles.item(0).childNodes(); for (int i = 0; i < items.count(); ++i) { QGraphicsItem *gitem = NULL; QDomNode itemNode = items.item(i); //qDebug() << items.item(i).attributes().namedItem("type").nodeValue(); int zValue = itemNode.attributes().namedItem(QStringLiteral("z-index")).nodeValue().toInt(); double xPosition = itemNode.namedItem(QStringLiteral("position")).attributes().namedItem(QStringLiteral("x")).nodeValue().toDouble(); if (zValue > -1000) { if (itemNode.attributes().namedItem(QStringLiteral("type")).nodeValue() == QLatin1String("QGraphicsTextItem")) { QDomNamedNodeMap txtProperties = itemNode.namedItem(QStringLiteral("content")).attributes(); QFont font(txtProperties.namedItem(QStringLiteral("font")).nodeValue()); QDomNode node = txtProperties.namedItem(QStringLiteral("font-bold")); if (!node.isNull()) { // Old: Bold/Not bold. font.setBold(node.nodeValue().toInt()); } else { // New: Font weight (QFont::) font.setWeight(txtProperties.namedItem(QStringLiteral("font-weight")).nodeValue().toInt()); } //font.setBold(txtProperties.namedItem("font-bold").nodeValue().toInt()); font.setItalic(txtProperties.namedItem(QStringLiteral("font-italic")).nodeValue().toInt()); font.setUnderline(txtProperties.namedItem(QStringLiteral("font-underline")).nodeValue().toInt()); // Older Kdenlive version did not store pixel size but point size if (txtProperties.namedItem(QStringLiteral("font-pixel-size")).isNull()) { KMessageBox::information(QApplication::activeWindow(), i18n("Some of your text clips were saved with size in points, which means different sizes on different displays. They will be converted to pixel size, making them portable, but you could have to adjust their size."), i18n("Text Clips Updated")); QFont f2; f2.setPointSize(txtProperties.namedItem(QStringLiteral("font-size")).nodeValue().toInt()); font.setPixelSize(QFontInfo(f2).pixelSize()); } else { font.setPixelSize(txtProperties.namedItem(QStringLiteral("font-pixel-size")).nodeValue().toInt()); } font.setLetterSpacing(QFont::AbsoluteSpacing, txtProperties.namedItem(QStringLiteral("letter-spacing")).nodeValue().toInt()); QColor col(stringToColor(txtProperties.namedItem(QStringLiteral("font-color")).nodeValue())); MyTextItem *txt = new MyTextItem(itemNode.namedItem(QStringLiteral("content")).firstChild().nodeValue(), NULL); m_scene->addItem(txt); txt->setFont(font); txt->setTextInteractionFlags(Qt::NoTextInteraction); QTextCursor cursor(txt->document()); cursor.select(QTextCursor::Document); QTextCharFormat cformat = cursor.charFormat(); if (txtProperties.namedItem(QStringLiteral("font-outline")).nodeValue().toDouble() > 0.0) { txt->setData(TitleDocument::OutlineWidth, txtProperties.namedItem(QStringLiteral("font-outline")).nodeValue().toDouble()); txt->setData(TitleDocument::OutlineColor, stringToColor(txtProperties.namedItem(QStringLiteral("font-outline-color")).nodeValue())); cformat.setTextOutline( QPen(QColor(stringToColor(txtProperties.namedItem(QStringLiteral("font-outline-color")).nodeValue())), txtProperties.namedItem(QStringLiteral("font-outline")).nodeValue().toDouble(), Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin) ); } if (!txtProperties.namedItem(QStringLiteral("line-spacing")).isNull()) { int lineSpacing = txtProperties.namedItem(QStringLiteral("line-spacing")).nodeValue().toInt(); QTextBlockFormat format = cursor.blockFormat(); format.setLineHeight(lineSpacing, QTextBlockFormat::LineDistanceHeight); cursor.setBlockFormat(format); txt->setData(TitleDocument::LineSpacing, lineSpacing); } cformat.setForeground(QBrush(col)); cursor.setCharFormat(cformat); if (txtProperties.namedItem(QStringLiteral("gradient")).isNull() == false) { // Gradient color QString data = txtProperties.namedItem(QStringLiteral("gradient")).nodeValue(); txt->setData(TitleDocument::Gradient, data); QLinearGradient gr = GradientWidget::gradientFromString(data, txt->boundingRect().width(), txt->boundingRect().height()); cformat.setForeground(QBrush(gr)); cursor.setCharFormat(cformat); } if (txtProperties.namedItem(QStringLiteral("alignment")).isNull() == false) { txt->setAlignment((Qt::Alignment) txtProperties.namedItem(QStringLiteral("alignment")).nodeValue().toInt()); } if (!txtProperties.namedItem(QStringLiteral("kdenlive-axis-x-inverted")).isNull()) { txt->setData(OriginXLeft, txtProperties.namedItem(QStringLiteral("kdenlive-axis-x-inverted")).nodeValue().toInt()); } if (!txtProperties.namedItem(QStringLiteral("kdenlive-axis-y-inverted")).isNull()) { txt->setData(OriginYTop, txtProperties.namedItem(QStringLiteral("kdenlive-axis-y-inverted")).nodeValue().toInt()); } if (!txtProperties.namedItem(QStringLiteral("shadow")).isNull()) { QString info = txtProperties.namedItem(QStringLiteral("shadow")).nodeValue(); txt->loadShadow(info.split(';')); } // Effects if (!txtProperties.namedItem(QStringLiteral("typewriter")).isNull()) { QStringList effData = QStringList() << QStringLiteral("typewriter") << txtProperties.namedItem(QStringLiteral("typewriter")).nodeValue(); txt->setData(100, effData); } if (txt->toPlainText() == QLatin1String("%s")) { // template text box, adjust size for later remplacement text if (txt->alignment() == Qt::AlignHCenter) { // grow dimensions on both sides double width = txtProperties.namedItem(QStringLiteral("box-width")).nodeValue().toDouble(); double xcenter = (width - xPosition) / 2.0; xPosition = xcenter - txt->boundingRect().width() / 2; } else if (txt->alignment() == Qt::AlignRight) { // grow to the left xPosition = xPosition + txtProperties.namedItem(QStringLiteral("box-width")).nodeValue().toDouble() - txt->boundingRect().width(); } else { // left align, grow on right side, nothing to do } } gitem = txt; } else if (itemNode.attributes().namedItem(QStringLiteral("type")).nodeValue() == QLatin1String("QGraphicsRectItem")) { QDomNamedNodeMap rectProperties = itemNode.namedItem(QStringLiteral("content")).attributes(); QString rect = rectProperties.namedItem(QStringLiteral("rect")).nodeValue(); QString br_str = rectProperties.namedItem(QStringLiteral("brushcolor")).nodeValue(); QString pen_str = rectProperties.namedItem(QStringLiteral("pencolor")).nodeValue(); double penwidth = rectProperties.namedItem(QStringLiteral("penwidth")).nodeValue().toDouble(); MyRectItem *rec = new MyRectItem(); rec->setRect(stringToRect(rect)); if (penwidth > 0) { rec->setPen(QPen(QBrush(stringToColor(pen_str)), penwidth, Qt::SolidLine, Qt::SquareCap, Qt::RoundJoin)); } else { rec->setPen(Qt::NoPen); } if (rectProperties.namedItem(QStringLiteral("gradient")).isNull() == false) { // Gradient color QString data = rectProperties.namedItem(QStringLiteral("gradient")).nodeValue(); rec->setData(TitleDocument::Gradient, data); QLinearGradient gr = GradientWidget::gradientFromString(data, rec->rect().width(), rec->rect().height()); rec->setBrush(QBrush(gr)); } else { rec->setBrush(QBrush(stringToColor(br_str))); } m_scene->addItem(rec); gitem = rec; } else if (itemNode.attributes().namedItem(QStringLiteral("type")).nodeValue() == QLatin1String("QGraphicsPixmapItem")) { QString url = itemNode.namedItem(QStringLiteral("content")).attributes().namedItem(QStringLiteral("url")).nodeValue(); QString base64 = itemNode.namedItem(QStringLiteral("content")).attributes().namedItem(QStringLiteral("base64")).nodeValue(); QPixmap pix; if (base64.isEmpty()) { pix.load(url); } else { pix.loadFromData(QByteArray::fromBase64(base64.toLatin1())); } QGraphicsPixmapItem *rec = m_scene->addPixmap(pix); rec->setShapeMode(QGraphicsPixmapItem::BoundingRectShape); rec->setData(Qt::UserRole, url); if (!base64.isEmpty()) { rec->setData(Qt::UserRole + 1, base64); } gitem = rec; } else if (itemNode.attributes().namedItem(QStringLiteral("type")).nodeValue() == QLatin1String("QGraphicsSvgItem")) { QString url = itemNode.namedItem(QStringLiteral("content")).attributes().namedItem(QStringLiteral("url")).nodeValue(); QString base64 = itemNode.namedItem(QStringLiteral("content")).attributes().namedItem(QStringLiteral("base64")).nodeValue(); QGraphicsSvgItem *rec = NULL; if (base64.isEmpty()) { rec = new QGraphicsSvgItem(url); } else { rec = new QGraphicsSvgItem(); QSvgRenderer *renderer = new QSvgRenderer(QByteArray::fromBase64(base64.toLatin1()), rec); rec->setSharedRenderer(renderer); //QString elem=rec->elementId(); //QRectF bounds = renderer->boundsOnElement(elem); } if (rec) { m_scene->addItem(rec); rec->setData(Qt::UserRole, url); if (!base64.isEmpty()) { rec->setData(Qt::UserRole + 1, base64); } gitem = rec; } } } //pos and transform if (gitem) { QPointF p(xPosition, itemNode.namedItem(QStringLiteral("position")).attributes().namedItem(QStringLiteral("y")).nodeValue().toDouble()); gitem->setPos(p); QDomElement trans = itemNode.namedItem(QStringLiteral("position")).firstChild().toElement(); gitem->setTransform(stringToTransform(trans.firstChild().nodeValue())); QString rotate = trans.attribute(QStringLiteral("rotation")); if (!rotate.isEmpty()) gitem->setData(TitleDocument::RotateFactor, stringToList(rotate)); QString zoom = trans.attribute(QStringLiteral("zoom")); if (!zoom.isEmpty()) gitem->setData(TitleDocument::ZoomFactor, zoom.toInt()); int zValue = itemNode.attributes().namedItem(QStringLiteral("z-index")).nodeValue().toInt(); if (zValue > maxZValue) maxZValue = zValue; gitem->setZValue(zValue); gitem->setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable); // effects QDomNode eff = itemNode.namedItem(QStringLiteral("effect")); if (!eff.isNull()) { QDomElement e = eff.toElement(); if (e.attribute(QStringLiteral("type")) == QLatin1String("blur")) { QGraphicsBlurEffect *blur = new QGraphicsBlurEffect(); blur->setBlurRadius(e.attribute(QStringLiteral("blurradius")).toInt()); gitem->setGraphicsEffect(blur); } else if (e.attribute(QStringLiteral("type")) == QLatin1String("shadow")) { QGraphicsDropShadowEffect *shadow = new QGraphicsDropShadowEffect(); shadow->setBlurRadius(e.attribute(QStringLiteral("blurradius")).toInt()); shadow->setOffset(e.attribute(QStringLiteral("xoffset")).toInt(), e.attribute(QStringLiteral("yoffset")).toInt()); gitem->setGraphicsEffect(shadow); } } } if (itemNode.nodeName() == QLatin1String("background")) { //qDebug() << items.item(i).attributes().namedItem("color").nodeValue(); QColor color = QColor(stringToColor(itemNode.attributes().namedItem(QStringLiteral("color")).nodeValue())); //color.setAlpha(itemNode.attributes().namedItem("alpha").nodeValue().toInt()); QList items = m_scene->items(); for (int i = 0; i < items.size(); ++i) { if (items.at(i)->zValue() == -1100) { static_cast(items.at(i))->setBrush(QBrush(color)); break; } } } else if (itemNode.nodeName() == QLatin1String("startviewport") && startv) { QString rect = itemNode.attributes().namedItem(QStringLiteral("rect")).nodeValue(); QRectF r = stringToRect(rect); startv->setRect(0, 0, r.width(), r.height()); startv->setPos(r.topLeft()); } else if (itemNode.nodeName() == QLatin1String("endviewport") && endv) { QString rect = itemNode.attributes().namedItem(QStringLiteral("rect")).nodeValue(); QRectF r = stringToRect(rect); endv->setRect(0, 0, r.width(), r.height()); endv->setPos(r.topLeft()); } } } return maxZValue; } QString TitleDocument::colorToString(const QColor& c) { QString ret = QStringLiteral("%1,%2,%3,%4"); ret = ret.arg(c.red()).arg(c.green()).arg(c.blue()).arg(c.alpha()); return ret; } QString TitleDocument::rectFToString(const QRectF& c) { QString ret = QStringLiteral("%1,%2,%3,%4"); ret = ret.arg(c.left()).arg(c.top()).arg(c.width()).arg(c.height()); return ret; } QRectF TitleDocument::stringToRect(const QString & s) { QStringList l = s.split(','); if (l.size() < 4) return QRectF(); return QRectF(l.at(0).toDouble(), l.at(1).toDouble(), l.at(2).toDouble(), l.at(3).toDouble()).normalized(); } QColor TitleDocument::stringToColor(const QString & s) { QStringList l = s.split(','); if (l.size() < 4) return QColor(); return QColor(l.at(0).toInt(), l.at(1).toInt(), l.at(2).toInt(), l.at(3).toInt());; } QTransform TitleDocument::stringToTransform(const QString& s) { QStringList l = s.split(QLatin1Char(',')); if (l.size() < 9) return QTransform(); return QTransform( l.at(0).toDouble(), l.at(1).toDouble(), l.at(2).toDouble(), l.at(3).toDouble(), l.at(4).toDouble(), l.at(5).toDouble(), l.at(6).toDouble(), l.at(7).toDouble(), l.at(8).toDouble() ); } QList TitleDocument::stringToList(const QString & s) { QStringList l = s.split(','); if (l.size() < 3) return QList(); return QList() << QVariant(l.at(0).toDouble()) << QVariant(l.at(1).toDouble()) << QVariant(l.at(2).toDouble()); } int TitleDocument::frameWidth() const { return m_width; } int TitleDocument::frameHeight() const { return m_height; } diff --git a/src/titler/titlewidget.cpp b/src/titler/titlewidget.cpp index ff4c50ee2..74ccc8440 100644 --- a/src/titler/titlewidget.cpp +++ b/src/titler/titlewidget.cpp @@ -1,2864 +1,2891 @@ /*************************************************************************** titlewidget.cpp - description ------------------- begin : Feb 28 2008 copyright : (C) 2008 by Marco Gittler email : g.marco@freenet.de ***************************************************************************/ /*************************************************************************** * * * 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. * * * ***************************************************************************/ #include "titlewidget.h" #include "gradientwidget.h" #include "kdenlivesettings.h" #include "doc/kthumb.h" #include "KoSliderCombo.h" #include "utils/KoIconUtils.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static QList titletemplates; // What exactly is this variable good for? int settingUp = false; const int IMAGEITEM = 7; const int RECTITEM = 3; const int TEXTITEM = 8; const int NOEFFECT = 0; const int BLUREFFECT = 1; const int SHADOWEFFECT = 2; const int TYPEWRITEREFFECT = 3; TitleWidget::TitleWidget(const QUrl &url, const Timecode &tc, const QString &projectTitlePath, Render *render, QWidget *parent) : QDialog(parent), Ui::TitleWidget_UI(), m_startViewport(NULL), m_endViewport(NULL), m_render(render), m_count(0), m_unicodeDialog(new UnicodeDialog(UnicodeDialog::InputHex)), m_projectTitlePath(projectTitlePath), m_tc(tc) { setupUi(this); setMinimumSize(200, 200); setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont)); frame_properties->setEnabled(false); frame_properties->setFixedHeight(frame_toolbar->height()); int size = style()->pixelMetric(QStyle::PM_SmallIconSize); QSize iconSize(size, size); rectBColor->setAlphaChannelEnabled(true); rectFColor->setAlphaChannelEnabled(true); fontColorButton->setAlphaChannelEnabled(true); textOutlineColor->setAlphaChannelEnabled(true); shadowColor->setAlphaChannelEnabled(true); QButtonGroup *colorGroup = new QButtonGroup(this); colorGroup->addButton(gradient_color); colorGroup->addButton(plain_color); QButtonGroup *alignGroup = new QButtonGroup(this); alignGroup->addButton(buttonAlignLeft); alignGroup->addButton(buttonAlignCenter); alignGroup->addButton(buttonAlignRight); textOutline->setMinimum(0); textOutline->setMaximum(200); //textOutline->setDecimals(0); textOutline->setValue(0); textOutline->setToolTip(i18n("Outline width")); backgroundAlpha->setMinimum(0); backgroundAlpha->setMaximum(255); //backgroundAlpha->setDecimals(0); backgroundAlpha->setValue(0); backgroundAlpha->setToolTip(i18n("Background color opacity")); itemrotatex->setMinimum(-360); itemrotatex->setMaximum(360); //itemrotatex->setDecimals(0); itemrotatex->setValue(0); itemrotatex->setToolTip(i18n("Rotation around the X axis")); itemrotatey->setMinimum(-360); itemrotatey->setMaximum(360); //itemrotatey->setDecimals(0); itemrotatey->setValue(0); itemrotatey->setToolTip(i18n("Rotation around the Y axis")); itemrotatez->setMinimum(-360); itemrotatez->setMaximum(360); //itemrotatez->setDecimals(0); itemrotatez->setValue(0); itemrotatez->setToolTip(i18n("Rotation around the Z axis")); rectLineWidth->setMinimum(0); rectLineWidth->setMaximum(500); //rectLineWidth->setDecimals(0); rectLineWidth->setValue(0); rectLineWidth->setToolTip(i18n("Border width")); itemzoom->setSuffix(i18n("%")); m_frameWidth = render->renderWidth(); m_frameHeight = render->renderHeight(); showToolbars(TITLE_SELECT); splitter->setStretchFactor(0, 20); //If project is drop frame, set the input mask as such. title_duration->setInputMask(m_tc.mask()); title_duration->setText(m_tc.reformatSeparators(KdenliveSettings::title_duration())); connect(backgroundColor, SIGNAL(changed(QColor)), this, SLOT(slotChangeBackground())) ; connect(backgroundAlpha, SIGNAL(valueChanged(int)), this, SLOT(slotChangeBackground())) ; connect(shadowBox, SIGNAL(toggled(bool)), this, SLOT(slotUpdateShadow())); connect(shadowColor, SIGNAL(changed(QColor)), this, SLOT(slotUpdateShadow())); connect(blur_radius, SIGNAL(valueChanged(int)), this, SLOT(slotUpdateShadow())); connect(shadowX, SIGNAL(valueChanged(int)), this, SLOT(slotUpdateShadow())); connect(shadowY, SIGNAL(valueChanged(int)), this, SLOT(slotUpdateShadow())); connect(fontColorButton, SIGNAL(changed(QColor)), this, SLOT(slotUpdateText())); connect(plain_color, SIGNAL(clicked(bool)), this, SLOT(slotUpdateText())); connect(gradient_color, SIGNAL(clicked(bool)), this, SLOT(slotUpdateText())); connect(gradients_combo, SIGNAL(currentIndexChanged(int)), this, SLOT(slotUpdateText())); connect(textOutlineColor, SIGNAL(changed(QColor)), this, SLOT(slotUpdateText())) ; connect(font_family, SIGNAL(currentFontChanged(QFont)), this, SLOT(slotUpdateText())) ; connect(font_size, SIGNAL(valueChanged(int)), this, SLOT(slotUpdateText())); connect(letter_spacing, SIGNAL(valueChanged(int)), this, SLOT(slotUpdateText())) ; connect(line_spacing, SIGNAL(valueChanged(int)), this, SLOT(slotUpdateText())) ; connect(textOutline, SIGNAL(valueChanged(int)), this, SLOT(slotUpdateText())); connect(font_weight_box, SIGNAL(currentIndexChanged(int)), this, SLOT(slotUpdateText())); connect(font_family, SIGNAL(editTextChanged(QString)), this, SLOT(slotFontText(QString))); connect(rectFColor, SIGNAL(changed(QColor)), this, SLOT(rectChanged())); connect(rectBColor, SIGNAL(changed(QColor)), this, SLOT(rectChanged())); connect(plain_rect, SIGNAL(clicked(bool)), this, SLOT(rectChanged())); connect(gradient_rect, SIGNAL(clicked(bool)), this, SLOT(rectChanged())); connect(gradients_rect_combo, SIGNAL(currentIndexChanged(int)), this, SLOT(rectChanged())); connect(rectLineWidth, SIGNAL(valueChanged(int)), this, SLOT(rectChanged())); // Fill effects, NOT SUPPORTED in titler version 2 /*effect_list->addItem(i18n("None"), NOEFFECT); if (render->getMltVersionInfo(QStringLiteral("kdenlivetitle")) > 1.0) { // there was a bug in MLT's kdenlivetitle module version 1 that crashed on typewriter effect effect_list->addItem(i18n("Typewriter"), TYPEWRITEREFFECT); } effect_list->addItem(i18n("Blur"), BLUREFFECT);*/ connect(zValue, SIGNAL(valueChanged(int)), this, SLOT(zIndexChanged(int))); connect(itemzoom, SIGNAL(valueChanged(int)), this, SLOT(itemScaled(int))); connect(itemrotatex, SIGNAL(valueChanged(int)), this, SLOT(itemRotateX(int))); connect(itemrotatey, SIGNAL(valueChanged(int)), this, SLOT(itemRotateY(int))); connect(itemrotatez, SIGNAL(valueChanged(int)), this, SLOT(itemRotateZ(int))); connect(itemhcenter, SIGNAL(clicked()), this, SLOT(itemHCenter())); connect(itemvcenter, SIGNAL(clicked()), this, SLOT(itemVCenter())); connect(itemtop, SIGNAL(clicked()), this, SLOT(itemTop())); connect(itembottom, SIGNAL(clicked()), this, SLOT(itemBottom())); connect(itemleft, SIGNAL(clicked()), this, SLOT(itemLeft())); connect(itemright, SIGNAL(clicked()), this, SLOT(itemRight())); connect(effect_list, SIGNAL(currentIndexChanged(int)), this, SLOT(slotAddEffect(int))); connect(typewriter_delay, SIGNAL(valueChanged(int)), this, SLOT(slotEditTypewriter(int))); connect(typewriter_start, SIGNAL(valueChanged(int)), this, SLOT(slotEditTypewriter(int))); connect(origin_x_left, SIGNAL(clicked()), this, SLOT(slotOriginXClicked())); connect(origin_y_top, SIGNAL(clicked()), this, SLOT(slotOriginYClicked())); connect(render, SIGNAL(frameUpdated(QImage)), this, SLOT(slotGotBackground(QImage))); // Position and size m_signalMapper = new QSignalMapper(this); m_signalMapper->setMapping(value_w, ValueWidth); m_signalMapper->setMapping(value_h, ValueHeight); m_signalMapper->setMapping(value_x, ValueX); m_signalMapper->setMapping(value_y, ValueY); connect(value_w, SIGNAL(valueChanged(int)), m_signalMapper, SLOT(map())); connect(value_h, SIGNAL(valueChanged(int)), m_signalMapper, SLOT(map())); connect(value_x, SIGNAL(valueChanged(int)), m_signalMapper, SLOT(map())); connect(value_y, SIGNAL(valueChanged(int)), m_signalMapper, SLOT(map())); connect(m_signalMapper, SIGNAL(mapped(int)), this, SLOT(slotValueChanged(int))); connect(buttonFitZoom, SIGNAL(clicked()), this, SLOT(slotAdjustZoom())); connect(buttonRealSize, SIGNAL(clicked()), this, SLOT(slotZoomOneToOne())); connect(buttonItalic, SIGNAL(clicked()), this, SLOT(slotUpdateText())); connect(buttonUnder, SIGNAL(clicked()), this, SLOT(slotUpdateText())); connect(buttonAlignLeft, SIGNAL(clicked()), this, SLOT(slotUpdateText())); connect(buttonAlignRight, SIGNAL(clicked()), this, SLOT(slotUpdateText())); connect(buttonAlignCenter, SIGNAL(clicked()), this, SLOT(slotUpdateText())); connect(edit_gradient, SIGNAL(clicked()), this, SLOT(slotEditGradient())); connect(edit_rect_gradient, SIGNAL(clicked()), this, SLOT(slotEditGradient())); connect(displayBg, SIGNAL(stateChanged(int)), this, SLOT(displayBackgroundFrame())); connect(m_unicodeDialog, SIGNAL(charSelected(QString)), this, SLOT(slotInsertUnicodeString(QString))); // mbd connect(this, SIGNAL(accepted()), this, SLOT(slotAccepted())); font_weight_box->blockSignals(true); font_weight_box->addItem(i18nc("Font style", "Light"), QFont::Light); font_weight_box->addItem(i18nc("Font style", "Normal"), QFont::Normal); font_weight_box->addItem(i18nc("Font style", "Demi-Bold"), QFont::DemiBold); font_weight_box->addItem(i18nc("Font style", "Bold"), QFont::Bold); font_weight_box->addItem(i18nc("Font style", "Black"), QFont::Black); font_weight_box->setToolTip(i18n("Font weight")); font_weight_box->setCurrentIndex(1); font_weight_box->blockSignals(false); buttonFitZoom->setIconSize(iconSize); buttonRealSize->setIconSize(iconSize); buttonItalic->setIconSize(iconSize); buttonUnder->setIconSize(iconSize); buttonAlignCenter->setIconSize(iconSize); buttonAlignLeft->setIconSize(iconSize); buttonAlignRight->setIconSize(iconSize); buttonFitZoom->setIcon(KoIconUtils::themedIcon(QStringLiteral("zoom-fit-best"))); buttonRealSize->setIcon(KoIconUtils::themedIcon(QStringLiteral("zoom-original"))); buttonItalic->setIcon(KoIconUtils::themedIcon(QStringLiteral("format-text-italic"))); buttonUnder->setIcon(KoIconUtils::themedIcon(QStringLiteral("format-text-underline"))); buttonAlignCenter->setIcon(KoIconUtils::themedIcon(QStringLiteral("format-justify-center"))); buttonAlignLeft->setIcon(KoIconUtils::themedIcon(QStringLiteral("format-justify-left"))); buttonAlignRight->setIcon(KoIconUtils::themedIcon(QStringLiteral("format-justify-right"))); edit_gradient->setIcon(KoIconUtils::themedIcon(QStringLiteral("document-edit"))); edit_rect_gradient->setIcon(KoIconUtils::themedIcon(QStringLiteral("document-edit"))); buttonAlignRight->setToolTip(i18n("Align right")); buttonAlignLeft->setToolTip(i18n("Align left")); buttonAlignCenter->setToolTip(i18n("Align center")); buttonAlignLeft->setChecked(true); m_unicodeAction = new QAction(KoIconUtils::themedIcon(QStringLiteral("kdenlive-insert-unicode")), QString(), this); m_unicodeAction->setShortcut(Qt::SHIFT + Qt::CTRL + Qt::Key_U); m_unicodeAction->setToolTip(getTooltipWithShortcut(i18n("Insert Unicode character"), m_unicodeAction)); connect(m_unicodeAction, SIGNAL(triggered()), this, SLOT(slotInsertUnicode())); buttonInsertUnicode->setDefaultAction(m_unicodeAction); m_zUp = new QAction(KoIconUtils::themedIcon(QStringLiteral("kdenlive-zindex-up")), QString(), this); m_zUp->setShortcut(Qt::Key_PageUp); m_zUp->setToolTip(i18n("Raise object")); connect(m_zUp, SIGNAL(triggered()), this, SLOT(slotZIndexUp())); zUp->setDefaultAction(m_zUp); m_zDown = new QAction(KoIconUtils::themedIcon(QStringLiteral("kdenlive-zindex-down")), QString(), this); m_zDown->setShortcut(Qt::Key_PageDown); m_zDown->setToolTip(i18n("Lower object")); connect(m_zDown, SIGNAL(triggered()), this, SLOT(slotZIndexDown())); zDown->setDefaultAction(m_zDown); m_zTop = new QAction(KoIconUtils::themedIcon(QStringLiteral("kdenlive-zindex-top")), QString(), this); // TODO mbt 1414: Shortcut should change z index only if // cursor is NOT in a text field ... //m_zTop->setShortcut(Qt::Key_Home); m_zTop->setToolTip(i18n("Raise object to top")); connect(m_zTop, SIGNAL(triggered()), this, SLOT(slotZIndexTop())); zTop->setDefaultAction(m_zTop); m_zBottom = new QAction(KoIconUtils::themedIcon(QStringLiteral("kdenlive-zindex-bottom")), QString(), this); // TODO mbt 1414 //m_zBottom->setShortcut(Qt::Key_End); m_zBottom->setToolTip(i18n("Lower object to bottom")); connect(m_zBottom, SIGNAL(triggered()), this, SLOT(slotZIndexBottom())); zBottom->setDefaultAction(m_zBottom); m_selectAll = new QAction(KoIconUtils::themedIcon(QStringLiteral("kdenlive-select-all")), QString(), this); m_selectAll->setShortcut(Qt::CTRL + Qt::Key_A); connect(m_selectAll, SIGNAL(triggered()), this, SLOT(slotSelectAll())); buttonSelectAll->setDefaultAction(m_selectAll); m_selectText = new QAction(KoIconUtils::themedIcon(QStringLiteral("kdenlive-select-texts")), QString(), this); m_selectText->setShortcut(Qt::CTRL + Qt::Key_T); connect(m_selectText, SIGNAL(triggered()), this, SLOT(slotSelectText())); buttonSelectText->setDefaultAction(m_selectText); buttonSelectText->setEnabled(false); m_selectRects = new QAction(KoIconUtils::themedIcon(QStringLiteral("kdenlive-select-rects")), QString(), this); m_selectRects->setShortcut(Qt::CTRL + Qt::Key_R); connect(m_selectRects, SIGNAL(triggered()), this, SLOT(slotSelectRects())); buttonSelectRects->setDefaultAction(m_selectRects); buttonSelectRects->setEnabled(false); m_selectImages = new QAction(KoIconUtils::themedIcon(QStringLiteral("kdenlive-select-images")), QString(), this); m_selectImages->setShortcut(Qt::CTRL + Qt::Key_I); connect(m_selectImages, SIGNAL(triggered()), this, SLOT(slotSelectImages())); buttonSelectImages->setDefaultAction(m_selectImages); buttonSelectImages->setEnabled(false); m_unselectAll = new QAction(KoIconUtils::themedIcon(QStringLiteral("kdenlive-unselect-all")), QString(), this); m_unselectAll->setShortcut(Qt::SHIFT + Qt::CTRL + Qt::Key_A); connect(m_unselectAll, SIGNAL(triggered()), this, SLOT(slotSelectNone())); buttonUnselectAll->setDefaultAction(m_unselectAll); buttonUnselectAll->setEnabled(false); zDown->setIconSize(iconSize); zTop->setIconSize(iconSize); zBottom->setIconSize(iconSize); zDown->setIcon(KoIconUtils::themedIcon(QStringLiteral("kdenlive-zindex-down"))); zTop->setIcon(KoIconUtils::themedIcon(QStringLiteral("kdenlive-zindex-top"))); zBottom->setIcon(KoIconUtils::themedIcon(QStringLiteral("kdenlive-zindex-bottom"))); connect(zDown, SIGNAL(clicked()), this, SLOT(slotZIndexDown())); connect(zTop, SIGNAL(clicked()), this, SLOT(slotZIndexTop())); connect(zBottom, SIGNAL(clicked()), this, SLOT(slotZIndexBottom())); origin_x_left->setToolTip(i18n("Invert x axis and change 0 point")); origin_y_top->setToolTip(i18n("Invert y axis and change 0 point")); rectBColor->setToolTip(i18n("Select fill color")); rectFColor->setToolTip(i18n("Select border color")); zoom_slider->setToolTip(i18n("Zoom")); buttonRealSize->setToolTip(i18n("Original size (1:1)")); buttonFitZoom->setToolTip(i18n("Fit zoom")); backgroundColor->setToolTip(i18n("Select background color")); backgroundAlpha->setToolTip(i18n("Background opacity")); buttonSelectAll->setToolTip(getTooltipWithShortcut(i18n("Select all"), m_selectAll)); buttonSelectText->setToolTip(getTooltipWithShortcut(i18n("Select text items in current selection"), m_selectText)); buttonSelectRects->setToolTip(getTooltipWithShortcut(i18n("Select rect items in current selection"), m_selectRects)); buttonSelectImages->setToolTip(getTooltipWithShortcut(i18n("Select image items in current selection"), m_selectImages)); buttonUnselectAll->setToolTip(getTooltipWithShortcut(i18n("Unselect all"), m_unselectAll)); itemhcenter->setIconSize(iconSize); itemvcenter->setIconSize(iconSize); itemtop->setIconSize(iconSize); itembottom->setIconSize(iconSize); itemright->setIconSize(iconSize); itemleft->setIconSize(iconSize); itemhcenter->setIcon(KoIconUtils::themedIcon(QStringLiteral("kdenlive-align-hor"))); itemhcenter->setToolTip(i18n("Align item horizontally")); itemvcenter->setIcon(KoIconUtils::themedIcon(QStringLiteral("kdenlive-align-vert"))); itemvcenter->setToolTip(i18n("Align item vertically")); itemtop->setIcon(KoIconUtils::themedIcon(QStringLiteral("kdenlive-align-top"))); itemtop->setToolTip(i18n("Align item to top")); itembottom->setIcon(KoIconUtils::themedIcon(QStringLiteral("kdenlive-align-bottom"))); itembottom->setToolTip(i18n("Align item to bottom")); itemright->setIcon(KoIconUtils::themedIcon(QStringLiteral("kdenlive-align-right"))); itemright->setToolTip(i18n("Align item to right")); itemleft->setIcon(KoIconUtils::themedIcon(QStringLiteral("kdenlive-align-left"))); itemleft->setToolTip(i18n("Align item to left")); QHBoxLayout *layout = new QHBoxLayout; frame_toolbar->setLayout(layout); layout->setContentsMargins(0, 0, 0, 0); QToolBar *m_toolbar = new QToolBar(QStringLiteral("titleToolBar"), this); m_toolbar->setIconSize(iconSize); m_buttonCursor = m_toolbar->addAction(KoIconUtils::themedIcon(QStringLiteral("transform-move")), i18n("Selection Tool")); m_buttonCursor->setCheckable(true); m_buttonCursor->setShortcut(Qt::ALT + Qt::Key_S); m_buttonCursor->setToolTip(i18n("Selection Tool") + ' ' + m_buttonCursor->shortcut().toString()); connect(m_buttonCursor, SIGNAL(triggered()), this, SLOT(slotSelectTool())); m_buttonText = m_toolbar->addAction(KoIconUtils::themedIcon(QStringLiteral("insert-text")), i18n("Add Text")); m_buttonText->setCheckable(true); m_buttonText->setShortcut(Qt::ALT + Qt::Key_T); m_buttonText->setToolTip(i18n("Add Text") + ' ' + m_buttonText->shortcut().toString()); connect(m_buttonText, SIGNAL(triggered()), this, SLOT(slotTextTool())); m_buttonRect = m_toolbar->addAction(KoIconUtils::themedIcon(QStringLiteral("kdenlive-insert-rect")), i18n("Add Rectangle")); m_buttonRect->setCheckable(true); m_buttonRect->setShortcut(Qt::ALT + Qt::Key_R); m_buttonRect->setToolTip(i18n("Add Rectangle") + ' ' + m_buttonRect->shortcut().toString()); connect(m_buttonRect, SIGNAL(triggered()), this, SLOT(slotRectTool())); m_buttonImage = m_toolbar->addAction(KoIconUtils::themedIcon(QStringLiteral("insert-image")), i18n("Add Image")); m_buttonImage->setCheckable(false); m_buttonImage->setShortcut(Qt::ALT + Qt::Key_I); m_buttonImage->setToolTip(i18n("Add Image") + ' ' + m_buttonImage->shortcut().toString()); connect(m_buttonImage, SIGNAL(triggered()), this, SLOT(slotImageTool())); m_toolbar->addSeparator(); m_buttonLoad = m_toolbar->addAction(KoIconUtils::themedIcon(QStringLiteral("document-open")), i18n("Open Document")); m_buttonLoad->setCheckable(false); m_buttonLoad->setShortcut(Qt::CTRL + Qt::Key_O); m_buttonLoad->setToolTip(i18n("Open Document") + ' ' + m_buttonLoad->shortcut().toString()); connect(m_buttonLoad, SIGNAL(triggered()), this, SLOT(loadTitle())); m_buttonSave = m_toolbar->addAction(KoIconUtils::themedIcon(QStringLiteral("document-save-as")), i18n("Save As")); m_buttonSave->setCheckable(false); m_buttonSave->setShortcut(Qt::CTRL + Qt::Key_S); m_buttonSave->setToolTip(i18n("Save As") + ' ' + m_buttonSave->shortcut().toString()); connect(m_buttonSave, SIGNAL(triggered()), this, SLOT(saveTitle())); layout->addWidget(m_toolbar); // initialize graphic scene m_scene = new GraphicsSceneRectMove(this); graphicsView->setScene(m_scene); graphicsView->setMouseTracking(true); graphicsView->setDragMode(QGraphicsView::RubberBandDrag); graphicsView->setRubberBandSelectionMode(Qt::ContainsItemBoundingRect); m_titledocument.setScene(m_scene, m_frameWidth, m_frameHeight); connect(m_scene, SIGNAL(changed(QList)), this, SLOT(slotChanged())); connect(font_size, SIGNAL(valueChanged(int)), m_scene, SLOT(slotUpdateFontSize(int))); + connect(use_grid, SIGNAL(toggled(bool)), m_scene, SLOT(slotUseGrid(bool))); - QPen framepen(Qt::DotLine); + // Video frame rect + QPen framepen; framepen.setColor(Qt::red); - m_frameBorder = new QGraphicsRectItem(QRectF(0, 0, m_frameWidth, m_frameHeight)); m_frameBorder->setPen(framepen); - m_frameBorder->setZValue(-1100); + m_frameBorder->setZValue(1000); m_frameBorder->setBrush(Qt::transparent); m_frameBorder->setFlags(0); graphicsView->scene()->addItem(m_frameBorder); + // semi transparent safe zones + framepen.setColor(QColor(255, 0, 0, 100)); + QGraphicsRectItem *safe1 = new QGraphicsRectItem(QRectF(m_frameWidth * 0.05, m_frameHeight * 0.05, m_frameWidth * 0.9, m_frameHeight * 0.9), m_frameBorder); + safe1->setBrush(Qt::transparent); + safe1->setPen(framepen); + safe1->setFlags(0); + QGraphicsRectItem *safe2 = new QGraphicsRectItem(QRectF(m_frameWidth * 0.1, m_frameHeight * 0.1, m_frameWidth * 0.8, m_frameHeight * 0.8), m_frameBorder); + safe2->setBrush(Qt::transparent); + safe2->setPen(framepen); + safe2->setFlags(0); + + m_frameBackground = new QGraphicsRectItem(QRectF(0, 0, m_frameWidth, m_frameHeight)); + m_frameBackground->setZValue(-1100); + m_frameBackground->setBrush(Qt::transparent); + m_frameBackground->setFlags(0); + graphicsView->scene()->addItem(m_frameBackground); + m_frameImage = new QGraphicsPixmapItem(); QTransform qtrans; qtrans.scale(2.0, 2.0); m_frameImage->setTransform(qtrans); m_frameImage->setZValue(-1200); m_frameImage->setFlags(0); displayBackgroundFrame(); graphicsView->scene()->addItem(m_frameImage); connect(m_scene, SIGNAL(selectionChanged()), this , SLOT(selectionChanged())); connect(m_scene, SIGNAL(itemMoved()), this , SLOT(selectionChanged())); connect(m_scene, SIGNAL(sceneZoom(bool)), this , SLOT(slotZoom(bool))); connect(m_scene, SIGNAL(actionFinished()), this , SLOT(slotSelectTool())); connect(m_scene, SIGNAL(newRect(QGraphicsRectItem*)), this , SLOT(slotNewRect(QGraphicsRectItem*))); connect(m_scene, SIGNAL(newText(MyTextItem*)), this , SLOT(slotNewText(MyTextItem*))); connect(zoom_slider, SIGNAL(valueChanged(int)), this , SLOT(slotUpdateZoom(int))); connect(zoom_spin, SIGNAL(valueChanged(int)), this, SLOT(slotUpdateZoom(int))); // mbd: load saved settings loadGradients(); readChoices(); if (render->getMltVersionInfo(QStringLiteral("kdenlivetitle")) < 2.0) { // Gradients and shadows are only supported since version 2, so disable shadowBox->setEnabled(false); gradient_color->setEnabled(false); gradients_combo->setEnabled(false); gradients_rect_combo->setEnabled(false); gradient_rect->setEnabled(false); edit_gradient->setEnabled(false); edit_rect_gradient->setEnabled(false); } // Hide effects not implemented tabWidget->removeTab(3); graphicsView->show(); graphicsView->setInteractive(true); //qDebug() << "// TITLE WIDGWT: " << graphicsView->viewport()->width() << 'x' << graphicsView->viewport()->height(); m_startViewport = new QGraphicsRectItem(QRectF(0, 0, m_frameWidth, m_frameHeight)); // Setting data at -1 so that the item is recognized as undeletable by graphicsscenerectmove m_startViewport->setData(-1, -1); m_endViewport = new QGraphicsRectItem(QRectF(0, 0, m_frameWidth, m_frameHeight)); m_endViewport->setData(-1, -1); m_startViewport->setData(0, m_frameWidth); m_startViewport->setData(1, m_frameHeight); m_endViewport->setData(0, m_frameWidth); m_endViewport->setData(1, m_frameHeight); // scale the view so that the title widget is not too big at startup graphicsView->scale(.5, .5); if (url.isValid()) { loadTitle(url); } else { prepareTools(NULL); slotTextTool(); QTimer::singleShot(200, this, SLOT(slotAdjustZoom())); } initAnimation(); + QColor color = backgroundColor->color(); + m_scene->setBackgroundBrush(QBrush(color)); + color.setAlpha(backgroundAlpha->value()); + m_frameBackground->setBrush(color); connect(anim_start, SIGNAL(toggled(bool)), this, SLOT(slotAnimStart(bool))); connect(anim_end, SIGNAL(toggled(bool)), this, SLOT(slotAnimEnd(bool))); connect(templateBox, SIGNAL(currentIndexChanged(int)), this, SLOT(templateIndexChanged(int))); buttonBox->button(QDialogButtonBox::Ok)->setEnabled(KdenliveSettings::hastitleproducer()); if (titletemplates.isEmpty()) { refreshTitleTemplates(m_projectTitlePath); } //templateBox->setIconSize(QSize(60,60)); templateBox->clear(); templateBox->addItem(QLatin1String("")); foreach(const TitleTemplate &t, titletemplates) { templateBox->addItem(t.icon, t.name, t.file); } lastDocumentHash = QCryptographicHash::hash(xml().toString().toLatin1(), QCryptographicHash::Md5).toHex(); } TitleWidget::~TitleWidget() { m_scene->blockSignals(true); delete m_buttonRect; delete m_buttonText; delete m_buttonImage; delete m_buttonCursor; delete m_buttonSave; delete m_buttonLoad; delete m_unicodeAction; delete m_zUp; delete m_zDown; delete m_zTop; delete m_zBottom; delete m_selectAll; delete m_selectText; delete m_selectRects; delete m_selectImages; delete m_unselectAll; delete m_unicodeDialog; delete m_frameBorder; delete m_frameImage; delete m_startViewport; delete m_endViewport; delete m_scene; delete m_signalMapper; } // static QStringList TitleWidget::extractImageList(const QString& xml) { QStringList result; if (xml.isEmpty()) return result; QDomDocument doc; doc.setContent(xml); QDomNodeList images = doc.elementsByTagName(QStringLiteral("content")); for (int i = 0; i < images.count(); ++i) { if (images.at(i).toElement().hasAttribute(QStringLiteral("url"))) result.append(images.at(i).toElement().attribute(QStringLiteral("url"))); } return result; } // static QStringList TitleWidget::extractFontList(const QString& xml) { QStringList result; if (xml.isEmpty()) return result; QDomDocument doc; doc.setContent(xml); QDomNodeList images = doc.elementsByTagName(QStringLiteral("content")); for (int i = 0; i < images.count(); ++i) { if (images.at(i).toElement().hasAttribute(QStringLiteral("font"))) result.append(images.at(i).toElement().attribute(QStringLiteral("font"))); } return result; } //static void TitleWidget::refreshTitleTemplates(const QString &projectPath) { QStringList filters = QStringList() << QStringLiteral("*.kdenlivetitle") ; titletemplates.clear(); // project templates QDir dir(projectPath); QStringList templateFiles = dir.entryList(filters, QDir::Files); foreach(const QString & fname, templateFiles) { TitleTemplate t; t.name = fname; t.file = dir.absoluteFilePath(fname); t.icon = QIcon(KThumb::getImage(QUrl::fromLocalFile(t.file), 0, 60, 60)); titletemplates.append(t); } // system templates QStringList titleTemplates = QStandardPaths::locateAll(QStandardPaths::DataLocation, QStringLiteral("titles/"), QStandardPaths::LocateDirectory); foreach(const QString & folderpath, titleTemplates) { QDir folder(folderpath); QStringList filesnames = folder.entryList(filters, QDir::Files); foreach(const QString & fname, filesnames) { TitleTemplate t; t.name = fname; t.file = folder.absoluteFilePath(fname); t.icon = QIcon(KThumb::getImage(QUrl::fromLocalFile(t.file), 0, 60, 60)); titletemplates.append(t); } } } void TitleWidget::templateIndexChanged(int index) { QString item = templateBox->itemData(index).toString(); if (!item.isEmpty()) { if (lastDocumentHash != QCryptographicHash::hash(xml().toString().toLatin1(), QCryptographicHash::Md5).toHex()) { if (KMessageBox::questionYesNo(this, i18n("Do you really want to load a new template? Changes in this title will be lost!")) == KMessageBox::No) return; } loadTitle(QUrl::fromLocalFile(item)); // mbt 1607: Add property to distinguish between unchanged template titles and user titles. // Text of unchanged template titles should be selected when clicked. QList list = graphicsView->scene()->items(); foreach(QGraphicsItem * qgItem, list) { if (qgItem->type() == TEXTITEM) { MyTextItem *i = static_cast(qgItem); i->setProperty("isTemplate", "true"); i->setProperty("templateText", i->toHtml()); } } lastDocumentHash = QCryptographicHash::hash(xml().toString().toLatin1(), QCryptographicHash::Md5).toHex(); } } //virtual void TitleWidget::resizeEvent(QResizeEvent * /*event*/) { //slotAdjustZoom(); } //virtual void TitleWidget::keyPressEvent(QKeyEvent *e) { if(e->key()!=Qt::Key_Escape && e->key()!=Qt::Key_Return && e->key()!=Qt::Key_Enter) QDialog::keyPressEvent(e); } void TitleWidget::slotTextTool() { m_scene->setTool(TITLE_TEXT); showToolbars(TITLE_TEXT); checkButton(TITLE_TEXT); } void TitleWidget::slotRectTool() { m_scene->setTool(TITLE_RECTANGLE); showToolbars(TITLE_RECTANGLE); checkButton(TITLE_RECTANGLE); // Disable dragging mode, would make dragging a rect impossible otherwise ;) graphicsView->setDragMode(QGraphicsView::NoDrag); } void TitleWidget::slotSelectTool() { m_scene->setTool(TITLE_SELECT); // Enable rubberband selecting mode. graphicsView->setDragMode(QGraphicsView::RubberBandDrag); // Find out which toolbars need to be shown, depending on selected item TITLETOOL t = TITLE_SELECT; QList l = graphicsView->scene()->selectedItems(); if (l.size() > 0) { switch (l.at(0)->type()) { case TEXTITEM: t = TITLE_TEXT; break; case RECTITEM: t = TITLE_RECTANGLE; break; case IMAGEITEM: t = TITLE_IMAGE; break; } } enableToolbars(t); if (t == TITLE_RECTANGLE && (l.at(0) == m_endViewport || l.at(0) == m_startViewport)) { //graphicsView->centerOn(l.at(0)); t = TITLE_SELECT; } showToolbars(t); if (l.size() > 0) { updateCoordinates(l.at(0)); updateDimension(l.at(0)); updateRotZoom(l.at(0)); } checkButton(TITLE_SELECT); } void TitleWidget::slotImageTool() { QList supported = QImageReader::supportedImageFormats(); QStringList mimeTypeFilters; QString allExtensions = i18n("All Images") + " ("; foreach (const QByteArray &mimeType, supported) { mimeTypeFilters.append(i18n("%1 Image", QString(mimeType)) + "( *." + QString(mimeType) + ")"); allExtensions.append("*." + mimeType + " "); } mimeTypeFilters.sort(); allExtensions.append(")"); mimeTypeFilters.prepend(allExtensions); QString clipFolder = KRecentDirs::dir(QStringLiteral(":KdenliveImageFolder")); if (clipFolder.isEmpty()) { clipFolder = QDir::homePath(); } QFileDialog dialog(this, i18n("Add Image"), clipFolder); dialog.setAcceptMode(QFileDialog::AcceptOpen); dialog.setNameFilters(mimeTypeFilters); if (dialog.exec() != QDialog::Accepted) return; QUrl url = QUrl::fromLocalFile(dialog.selectedFiles().at(0)); if (url.isValid()) { KRecentDirs::add(QStringLiteral(":KdenliveImageFolder"), url.adjusted(QUrl::RemoveFilename).path()); if (url.path().endsWith(QLatin1String(".svg"))) { - QGraphicsSvgItem *svg = new QGraphicsSvgItem(url.toLocalFile()); - svg->setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable); + MySvgItem *svg = new MySvgItem(url.toLocalFile()); + svg->setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable | QGraphicsItem::ItemSendsGeometryChanges); svg->setZValue(m_count++); svg->setData(Qt::UserRole, url.path()); - graphicsView->scene()->addItem(svg); + m_scene->addNewItem(svg); prepareTools(svg); } else { QPixmap pix(url.path()); - QGraphicsPixmapItem *image = new QGraphicsPixmapItem(pix); + MyPixmapItem *image = new MyPixmapItem(pix); image->setShapeMode(QGraphicsPixmapItem::BoundingRectShape); - image->setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable); + image->setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable | QGraphicsItem::ItemSendsGeometryChanges); image->setData(Qt::UserRole, url.path()); image->setZValue(m_count++); - graphicsView->scene()->addItem(image); + m_scene->addNewItem(image); prepareTools(image); } } m_scene->setTool(TITLE_SELECT); showToolbars(TITLE_SELECT); checkButton(TITLE_SELECT); } void TitleWidget::showToolbars(TITLETOOL toolType) { //toolbar_stack->setEnabled(toolType != TITLE_SELECT); switch (toolType) { case TITLE_IMAGE: toolbar_stack->setCurrentIndex(2); break; case TITLE_RECTANGLE: toolbar_stack->setCurrentIndex(1); break; case TITLE_TEXT: default: toolbar_stack->setCurrentIndex(0); break; } } void TitleWidget::enableToolbars(TITLETOOL toolType) { // TITLETOOL is defined in effectstack/graphicsscenerectmove.h bool enable = false; if (toolType == TITLE_RECTANGLE || toolType == TITLE_IMAGE) enable = true; value_w->setEnabled(enable); value_h->setEnabled(enable); } void TitleWidget::checkButton(TITLETOOL toolType) { bool bSelect = false; bool bText = false; bool bRect = false; bool bImage = false; switch (toolType) { case TITLE_SELECT: bSelect = true; break; case TITLE_TEXT: bText = true; break; case TITLE_RECTANGLE: bRect = true; break; case TITLE_IMAGE: bImage = true; break; default: break; } m_buttonCursor->setChecked(bSelect); m_buttonText->setChecked(bText); m_buttonRect->setChecked(bRect); m_buttonImage->setChecked(bImage); } void TitleWidget::displayBackgroundFrame() { QRectF r = m_frameBorder->sceneBoundingRect(); if (!displayBg->isChecked()) { QPixmap pattern(20, 20); pattern.fill(); QColor bgcolor(210, 210, 210); QPainter p(&pattern); p.fillRect(QRect(0, 0, 10, 10), bgcolor); p.fillRect(QRect(10, 10, 20, 20), bgcolor); p.end(); QBrush br(pattern); QPixmap bg((int) (r.width() / 2), (int) (r.height()/ 2)); QPainter p2(&bg); p2.fillRect(bg.rect(), br); p2.end(); m_frameImage->setPixmap(bg); } else { emit requestBackgroundFrame(); } } void TitleWidget::slotGotBackground(QImage img) { QRectF r = m_frameBorder->sceneBoundingRect(); m_frameImage->setPixmap(QPixmap::fromImage(img.scaled(r.width() / 2, r.height() / 2))); } void TitleWidget::initAnimation() { align_box->setEnabled(false); QPen startpen(Qt::DotLine); QPen endpen(Qt::DashDotLine); startpen.setColor(QColor(100, 200, 100, 140)); endpen.setColor(QColor(200, 100, 100, 140)); m_startViewport->setPen(startpen); m_endViewport->setPen(endpen); m_startViewport->setZValue(-1000); m_endViewport->setZValue(-1000); m_startViewport->setFlags(0); m_endViewport->setFlags(0); graphicsView->scene()->addItem(m_startViewport); graphicsView->scene()->addItem(m_endViewport); connect(keep_aspect, SIGNAL(toggled(bool)), this, SLOT(slotKeepAspect(bool))); connect(resize50, SIGNAL(clicked()), this, SLOT(slotResize50())); connect(resize100, SIGNAL(clicked()), this, SLOT(slotResize100())); connect(resize200, SIGNAL(clicked()), this, SLOT(slotResize200())); } void TitleWidget::slotUpdateZoom(int pos) { zoom_spin->setValue(pos); zoom_slider->setValue(pos); m_scene->setZoom((double) pos / 100); } void TitleWidget::slotZoom(bool up) { int pos = zoom_slider->value(); if (up) pos++; else pos--; zoom_slider->setValue(pos); } void TitleWidget::slotAdjustZoom() { /*double scalex = graphicsView->width() / (double)(m_frameWidth * 1.2); double scaley = graphicsView->height() / (double)(m_frameHeight * 1.2); if (scalex > scaley) scalex = scaley; int zoompos = (int)(scalex * 7 + 0.5);*/ graphicsView->fitInView(m_frameBorder, Qt::KeepAspectRatio); int zoompos = graphicsView->matrix().m11() * 100; zoom_slider->setValue(zoompos); graphicsView->centerOn(m_frameBorder); } void TitleWidget::slotZoomOneToOne() { zoom_slider->setValue(100); graphicsView->centerOn(m_frameBorder); } void TitleWidget::slotNewRect(QGraphicsRectItem * rect) { updateAxisButtons(rect); // back to default if (rectLineWidth->value() == 0) { rect->setPen(Qt::NoPen); } else { QPen penf(rectFColor->color()); penf.setWidth(rectLineWidth->value()); penf.setJoinStyle(Qt::RoundJoin); rect->setPen(penf); } if (plain_rect->isChecked()) { rect->setBrush(QBrush(rectBColor->color())); rect->setData(TitleDocument::Gradient, QVariant()); } else { // gradient QString gradientData = gradients_rect_combo->currentData().toString(); rect->setData(TitleDocument::Gradient, gradientData); QLinearGradient gr = GradientWidget::gradientFromString(gradientData, rect->boundingRect().width(), rect->boundingRect().height()); rect->setBrush(QBrush(gr)); } rect->setZValue(m_count++); rect->setData(TitleDocument::ZoomFactor, 100); prepareTools(rect); //setCurrentItem(rect); //graphicsView->setFocus(); } void TitleWidget::slotNewText(MyTextItem *tt) { updateAxisButtons(tt); // back to default letter_spacing->blockSignals(true); line_spacing->blockSignals(true); letter_spacing->setValue(0); line_spacing->setValue(0); letter_spacing->blockSignals(false); line_spacing->blockSignals(false); letter_spacing->setEnabled(true); line_spacing->setEnabled(true); QFont font = font_family->currentFont(); font.setPixelSize(font_size->value()); // mbd: issue 551: font.setWeight(font_weight_box->itemData(font_weight_box->currentIndex()).toInt()); font.setItalic(buttonItalic->isChecked()); font.setUnderline(buttonUnder->isChecked()); tt->setFont(font); QColor color = fontColorButton->color(); QColor outlineColor = textOutlineColor->color(); tt->setDefaultTextColor(color); tt->document()->setDocumentMargin(0); QTextCursor cur(tt->document()); cur.select(QTextCursor::Document); QTextBlockFormat format = cur.blockFormat(); QTextCharFormat cformat = cur.charFormat(); double outlineWidth = textOutline->value() / 10.0; tt->setData(TitleDocument::OutlineWidth, outlineWidth); tt->setData(TitleDocument::OutlineColor, outlineColor); if (outlineWidth > 0.0) cformat.setTextOutline(QPen(outlineColor, outlineWidth)); tt->updateShadow(shadowBox->isChecked(), blur_radius->value(), shadowX->value(), shadowY->value(), shadowColor->color()); if (gradient_color->isChecked()) { QString gradientData = gradients_combo->currentData().toString(); tt->setData(TitleDocument::Gradient, gradientData); QLinearGradient gr = GradientWidget::gradientFromString(gradientData, tt->boundingRect().width(), tt->boundingRect().height()); cformat.setForeground(QBrush(gr)); } else { cformat.setForeground(QBrush(color)); } cur.setCharFormat(cformat); cur.setBlockFormat(format); tt->setTextCursor(cur); tt->setZValue(m_count++); setCurrentItem(tt); prepareTools(tt); } void TitleWidget::setFontBoxWeight(int weight) { int index = font_weight_box->findData(weight); if (index < 0) { index = font_weight_box->findData(QFont::Normal); } font_weight_box->setCurrentIndex(index); } void TitleWidget::setCurrentItem(QGraphicsItem *item) { m_scene->setSelectedItem(item); } void TitleWidget::zIndexChanged(int v) { QList l = graphicsView->scene()->selectedItems(); for (int i = 0; i < l.size(); ++i) { l[i]->setZValue(v); } } void TitleWidget::selectionChanged() { if (m_scene->tool() != TITLE_SELECT) return; //qDebug() << "Number of selected items: " << graphicsView->scene()->selectedItems().length() << '\n'; QList l; // mbt 1607: One text item might have grabbed the keyboard. // Ungrab it for all items that are not selected, otherwise // text input would only work for the text item that grabbed // the keyboard last. l = graphicsView->scene()->items(); foreach(QGraphicsItem * item, l) { if (item->type() == TEXTITEM && !item->isSelected()) { MyTextItem *i = static_cast(item); i->clearFocus(); } } l = graphicsView->scene()->selectedItems(); if (l.size() > 0) { buttonUnselectAll->setEnabled(true); // Enable all z index buttons if items selected. // We can selectively disable them later. zUp->setEnabled(true); zDown->setEnabled(true); zTop->setEnabled(true); zBottom->setEnabled(true); } else { buttonUnselectAll->setEnabled(false); } if (l.size() >= 2) { buttonSelectText->setEnabled(true); buttonSelectRects->setEnabled(true); buttonSelectImages->setEnabled(true); } else { buttonSelectText->setEnabled(false); buttonSelectRects->setEnabled(false); buttonSelectImages->setEnabled(false); } if (l.size() == 0) { prepareTools(NULL); } else if (l.size() == 1) { prepareTools(l.at(0)); } else { /* For multiple selected objects we need to decide which tools to show. */ int firstType = l.at(0)->type(); bool allEqual = true; for (int i = 0; i < l.size(); ++i) { if (l.at(i)->type() != firstType) { allEqual = false; break; } } //qDebug() << "All equal? " << allEqual << ".\n"; if (allEqual) { prepareTools(l.at(0)); } else { // Get the default toolset, but enable the property frame (x,y,w,h) prepareTools(NULL); frame_properties->setEnabled(true); // Enable x/y/w/h if it makes sense. value_x->setEnabled(true); value_y->setEnabled(true); bool containsTextitem = false; for (int i = 0; i < l.size(); ++i) { if (l.at(i)->type() == TEXTITEM) { containsTextitem = true; break; } } if (!containsTextitem) { value_w->setEnabled(true); value_h->setEnabled(true); } } // Disable z index buttons if they don't make sense for the current selection int firstZindex = l.at(0)->zValue(); allEqual = true; for (int i = 0; i < l.size(); ++i) { if (l[i]->zValue() != firstZindex) { allEqual = false; break; } } if (!allEqual) { zUp->setEnabled(false); zDown->setEnabled(false); } } } void TitleWidget::slotValueChanged(int type) { /* type tells us which QSpinBox value has changed. */ QList l = graphicsView->scene()->selectedItems(); //qDebug() << l.size() << " items to be resized\n"; // Get the updated value here already to do less coding afterwards int val = 0; switch (type) { case ValueWidth: val = value_w->value(); break; case ValueHeight: val = value_h->value(); break; case ValueX: val = value_x->value(); break; case ValueY: val = value_y->value(); break; } for (int k = 0; k < l.size(); ++k) { //qDebug() << "Type of item " << k << ": " << l.at(k)->type() << '\n'; if (l.at(k)->type() == TEXTITEM) { // Just update the position. We don't allow setting width/height for text items yet. switch (type) { case ValueX: updatePosition(l.at(k), val, l.at(k)->pos().y()); break; case ValueY: updatePosition(l.at(k), l.at(k)->pos().x(), val); break; } } else if (l.at(k)->type() == RECTITEM) { QGraphicsRectItem *rec = static_cast (l.at(k)); switch (type) { case ValueX: updatePosition(l.at(k), val, l.at(k)->pos().y()); break; case ValueY: updatePosition(l.at(k), l.at(k)->pos().x(), val); break; case ValueWidth: rec->setRect(QRect(0, 0, val, rec->rect().height())); break; case ValueHeight: rec->setRect(QRect(0, 0, rec->rect().width(), val)); break; } } else if (l.at(k)->type() == IMAGEITEM) { if (type == ValueX) { updatePosition(l.at(k), val, l.at(k)->pos().y()); } else if (type == ValueY) { updatePosition(l.at(k), l.at(k)->pos().x(), val); } else { // Width/height has changed. This is more complex. QGraphicsItem *i = l.at(k); Transform t = m_transformations.value(i); // Ratio width:height double phi = (double) i->boundingRect().width() / i->boundingRect().height(); // TODO: proper calculation for rotation around 3 axes double alpha = (double) t.rotatez / 180.0 * M_PI; // New length double length; // Scaling factor double scale = 1; // We want to keep the aspect ratio of the image as the user does not yet have the possibility // to restore the original ratio. You rarely want to change it anyway. switch (type) { case ValueWidth: // Add 0.5 because otherwise incrementing by 1 might have no effect length = val / (cos(alpha) + 1 / phi * sin(alpha)) + 0.5; scale = length / i->boundingRect().width(); break; case ValueHeight: length = val / (phi * sin(alpha) + cos(alpha)) + 0.5; scale = length / i->boundingRect().height(); break; } t.scalex = scale; t.scaley = scale; QTransform qtrans; qtrans.scale(scale, scale); qtrans.rotate(t.rotatex, Qt::XAxis); qtrans.rotate(t.rotatey, Qt::YAxis); qtrans.rotate(t.rotatez, Qt::ZAxis); i->setTransform(qtrans); //qDebug() << "scale is: " << scale << '\n'; //qDebug() << i->boundingRect().width() << ": new width\n"; m_transformations[i] = t; if (l.size() == 1) { // Only update the w/h values if the selection contains just one item. // Otherwise, what should we do? ;) // (Use the values of the first item? Of the second? Of the x-th?) updateDimension(i); // Update rotation/zoom values. // These values are not yet able to handle multiple items! updateRotZoom(i); } } } } } void TitleWidget::updateDimension(QGraphicsItem *i) { bool wBlocked = value_w->signalsBlocked(); bool hBlocked = value_h->signalsBlocked(); bool zBlocked = zValue->signalsBlocked(); value_w->blockSignals(true); value_h->blockSignals(true); zValue->blockSignals(true); zValue->setValue((int) i->zValue()); if (i->type() == IMAGEITEM) { // Get multipliers for rotation/scaling /*Transform t = m_transformations.value(i); QRectF r = i->boundingRect(); int width = (int) ( abs(r.width()*t.scalex * cos(t.rotate/180.0*M_PI)) + abs(r.height()*t.scaley * sin(t.rotate/180.0*M_PI)) ); int height = (int) ( abs(r.height()*t.scaley * cos(t.rotate/180*M_PI)) + abs(r.width()*t.scalex * sin(t.rotate/180*M_PI)) );*/ value_w->setValue(i->sceneBoundingRect().width()); value_h->setValue(i->sceneBoundingRect().height()); } else if (i->type() == RECTITEM) { QGraphicsRectItem *r = static_cast (i); //qDebug() << "Rect width is: " << r->rect().width() << ", was: " << value_w->value() << '\n'; value_w->setValue((int) r->rect().width()); value_h->setValue((int) r->rect().height()); } else if (i->type() == TEXTITEM) { MyTextItem *t = static_cast (i); value_w->setValue((int) t->boundingRect().width()); value_h->setValue((int) t->boundingRect().height()); } zValue->blockSignals(zBlocked); value_w->blockSignals(wBlocked); value_h->blockSignals(hBlocked); } void TitleWidget::updateCoordinates(QGraphicsItem *i) { // Block signals emitted by this method value_x->blockSignals(true); value_y->blockSignals(true); if (i->type() == TEXTITEM) { MyTextItem *rec = static_cast (i); // Set the correct x coordinate value if (origin_x_left->isChecked()) { // Origin (0 point) is at m_frameWidth, coordinate axis is inverted value_x->setValue((int)(m_frameWidth - rec->pos().x() - rec->boundingRect().width())); } else { // Origin is at 0 (default) value_x->setValue((int) rec->pos().x()); } // Same for y if (origin_y_top->isChecked()) { value_y->setValue((int)(m_frameHeight - rec->pos().y() - rec->boundingRect().height())); } else { value_y->setValue((int) rec->pos().y()); } } else if (i->type() == RECTITEM) { QGraphicsRectItem *rec = static_cast (i); if (origin_x_left->isChecked()) { // Origin (0 point) is at m_frameWidth value_x->setValue((int)(m_frameWidth - rec->pos().x() - rec->rect().width())); } else { // Origin is at 0 (default) value_x->setValue((int) rec->pos().x()); } if (origin_y_top->isChecked()) { value_y->setValue((int)(m_frameHeight - rec->pos().y() - rec->rect().height())); } else { value_y->setValue((int) rec->pos().y()); } } else if (i->type() == IMAGEITEM) { if (origin_x_left->isChecked()) { value_x->setValue((int)(m_frameWidth - i->pos().x() - i->sceneBoundingRect().width())); } else { value_x->setValue((int) i->pos().x()); } if (origin_y_top->isChecked()) { value_y->setValue((int)(m_frameHeight - i->pos().y() - i->sceneBoundingRect().height())); } else { value_y->setValue((int) i->pos().y()); } } // Stop blocking signals now value_x->blockSignals(false); value_y->blockSignals(false); } void TitleWidget::updateRotZoom(QGraphicsItem *i) { itemzoom->blockSignals(true); itemrotatex->blockSignals(true); itemrotatey->blockSignals(true); itemrotatez->blockSignals(true); Transform t = m_transformations.value(i); if (!i->data(TitleDocument::ZoomFactor).isNull()) itemzoom->setValue(i->data(TitleDocument::ZoomFactor).toInt()); else itemzoom->setValue((int)(t.scalex * 100.0 + 0.5)); itemrotatex->setValue((int)(t.rotatex)); itemrotatey->setValue((int)(t.rotatey)); itemrotatez->setValue((int)(t.rotatez)); itemzoom->blockSignals(false); itemrotatex->blockSignals(false); itemrotatey->blockSignals(false); itemrotatez->blockSignals(false); } void TitleWidget::updatePosition(QGraphicsItem *i) { updatePosition(i, value_x->value(), value_y->value()); } void TitleWidget::updatePosition(QGraphicsItem *i, int x, int y) { if (i->type() == TEXTITEM) { MyTextItem *rec = static_cast (i); int posX; if (origin_x_left->isChecked()) { /* * Origin of the X axis is at m_frameWidth, and distance from right * border of the item to the right border of the frame is taken. See * comment to slotOriginXClicked(). */ posX = m_frameWidth - x - rec->boundingRect().width(); } else { posX = x; } int posY; if (origin_y_top->isChecked()) { /* Same for y axis */ posY = m_frameHeight - y - rec->boundingRect().height(); } else { posY = y; } rec->setPos(posX, posY); } else if (i->type() == RECTITEM) { QGraphicsRectItem *rec = static_cast (i); int posX; if (origin_x_left->isChecked()) { posX = m_frameWidth - x - rec->rect().width(); } else { posX = x; } int posY; if (origin_y_top->isChecked()) { posY = m_frameHeight - y - rec->rect().height(); } else { posY = y; } rec->setPos(posX, posY); } else if (i->type() == IMAGEITEM) { int posX; if (origin_x_left->isChecked()) { // Use the sceneBoundingRect because this also regards transformations like zoom posX = m_frameWidth - x - i->sceneBoundingRect().width(); } else { posX = x; } int posY; if (origin_y_top->isChecked()) { posY = m_frameHeight - y - i->sceneBoundingRect().height(); } else { posY = y; } i->setPos(posX, posY); } } void TitleWidget::updateTextOriginX() { if (origin_x_left->isChecked()) { origin_x_left->setText(i18n("\u2212X")); } else { origin_x_left->setText(i18n("+X")); } } void TitleWidget::slotOriginXClicked() { // Update the text displayed on the button. updateTextOriginX(); QList l = graphicsView->scene()->selectedItems(); if (l.size() >= 1) { updateCoordinates(l.at(0)); // Remember x axis setting l.at(0)->setData(TitleDocument::OriginXLeft, origin_x_left->isChecked() ? TitleDocument::AxisInverted : TitleDocument::AxisDefault); } graphicsView->setFocus(); } void TitleWidget::updateTextOriginY() { if (origin_y_top->isChecked()) { origin_y_top->setText(i18n("\u2212Y")); } else { origin_y_top->setText(i18n("+Y")); } } void TitleWidget::slotOriginYClicked() { // Update the text displayed on the button. updateTextOriginY(); QList l = graphicsView->scene()->selectedItems(); if (l.size() >= 1) { updateCoordinates(l.at(0)); l.at(0)->setData(TitleDocument::OriginYTop, origin_y_top->isChecked() ? TitleDocument::AxisInverted : TitleDocument::AxisDefault); } graphicsView->setFocus(); } void TitleWidget::updateAxisButtons(QGraphicsItem *i) { int xAxis = i->data(TitleDocument::OriginXLeft).toInt(); int yAxis = i->data(TitleDocument::OriginYTop).toInt(); origin_x_left->blockSignals(true); origin_y_top->blockSignals(true); if (xAxis == TitleDocument::AxisInverted) { origin_x_left->setChecked(true); } else { origin_x_left->setChecked(false); } updateTextOriginX(); if (yAxis == TitleDocument::AxisInverted) { origin_y_top->setChecked(true); } else { origin_y_top->setChecked(false); } updateTextOriginY(); origin_x_left->blockSignals(false); origin_y_top->blockSignals(false); } void TitleWidget::slotChangeBackground() { QColor color = backgroundColor->color(); m_scene->setBackgroundBrush(QBrush(color)); color.setAlpha(backgroundAlpha->value()); - m_frameBorder->setBrush(QBrush(color)); + m_frameBackground->setBrush(QBrush(color)); } void TitleWidget::slotChanged() { QList l = graphicsView->scene()->selectedItems(); if (l.size() >= 1 && l.at(0)->type() == TEXTITEM) { textChanged(static_cast (l.at(0))); } } void TitleWidget::textChanged(MyTextItem *i) { /* * If the user has set origin_x_left (the same for y), we need to look * whether a text element has been selected. If yes, we need to ensure that * the right border of the text field remains fixed also when some text has * been entered. * * This is also known as right-justified, with the difference that it is not * valid for text but for its boundingRect. Text may still be * left-justified. */ updateDimension(i); if (origin_x_left->isChecked() || origin_y_top->isChecked()) { if (!i->toPlainText().isEmpty()) updatePosition(i); else { /* * Don't do anything if the string is empty. If the position were * updated here, a newly created text field would be set to the * position of the last selected text field. */ } } // mbt 1607: Template text has changed; don't auto-select content anymore. if (i->property("isTemplate").isValid()) { if (i->property("templateText").isValid()) { if (i->property("templateText") == i->toHtml()) { // Unchanged, do nothing. } else { i->setProperty("isTemplate", QVariant::Invalid); i->setProperty("templateText", QVariant::Invalid); } } } } void TitleWidget::slotInsertUnicode() { m_unicodeDialog->exec(); } void TitleWidget::slotInsertUnicodeString(const QString &text) { QList l = graphicsView->scene()->selectedItems(); if (l.size() > 0) { if (l.at(0)->type() == TEXTITEM) { MyTextItem *t = static_cast (l.at(0)); t->textCursor().insertText(text); } } } void TitleWidget::slotUpdateText() { QFont font = font_family->currentFont(); font.setPixelSize(font_size->value()); font.setItalic(buttonItalic->isChecked()); font.setUnderline(buttonUnder->isChecked()); font.setWeight(font_weight_box->itemData(font_weight_box->currentIndex()).toInt()); font.setLetterSpacing(QFont::AbsoluteSpacing, letter_spacing->value()); QColor color = fontColorButton->color(); QColor outlineColor = textOutlineColor->color(); QString gradientData; if (gradient_color->isChecked()) { // user wants a gradient gradientData = gradients_combo->currentData().toString(); } double outlineWidth = textOutline->value() / 10.0; int i; QList l = graphicsView->scene()->selectedItems(); for (i = 0; i < l.length(); ++i) { MyTextItem* item = NULL; if (l.at(i)->type() == TEXTITEM) { item = static_cast (l.at(i)); } if (!item) { // No text item, try next one. continue; } // Set alignment of all text in the text item QTextCursor cur(item->document()); cur.select(QTextCursor::Document); QTextBlockFormat format = cur.blockFormat(); item->setData(TitleDocument::LineSpacing, line_spacing->value()); format.setLineHeight(line_spacing->value(), QTextBlockFormat::LineDistanceHeight); if (buttonAlignLeft->isChecked() || buttonAlignCenter->isChecked() || buttonAlignRight->isChecked()) { if (buttonAlignCenter->isChecked()) item->setAlignment(Qt::AlignHCenter); else if (buttonAlignRight->isChecked()) item->setAlignment(Qt::AlignRight); else if (buttonAlignLeft->isChecked()) item->setAlignment(Qt::AlignLeft); } else { item->setAlignment(Qt::AlignLeft); } // Set font properties item->setFont(font); QTextCharFormat cformat = cur.charFormat(); item->setData(TitleDocument::OutlineWidth, outlineWidth); item->setData(TitleDocument::OutlineColor, outlineColor); if (outlineWidth > 0.0) cformat.setTextOutline(QPen(outlineColor, outlineWidth, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin)); if (gradientData.isEmpty()) { cformat.setForeground(QBrush(color)); } else { QLinearGradient gr = GradientWidget::gradientFromString(gradientData, item->boundingRect().width(), item->boundingRect().height()); cformat.setForeground(QBrush(gr)); } // Store gradient in item properties item->setData(TitleDocument::Gradient, gradientData); cur.setCharFormat(cformat); cur.setBlockFormat(format); // item->setTextCursor(cur); cur.clearSelection(); item->setTextCursor(cur); item->setDefaultTextColor(color); } } void TitleWidget::rectChanged() { QList l = graphicsView->scene()->selectedItems(); for (int i = 0; i < l.length(); ++i) { if (l.at(i)->type() == RECTITEM && !settingUp) { QGraphicsRectItem *rec = static_cast(l.at(i)); QColor f = rectFColor->color(); if (rectLineWidth->value() == 0) { rec->setPen(Qt::NoPen); } else { QPen penf(f); penf.setWidth(rectLineWidth->value()); penf.setJoinStyle(Qt::RoundJoin); rec->setPen(penf); } if (plain_rect->isChecked()) { rec->setBrush(QBrush(rectBColor->color())); rec->setData(TitleDocument::Gradient, QVariant()); } else { // gradient QString gradientData = gradients_rect_combo->currentData().toString(); rec->setData(TitleDocument::Gradient, gradientData); QLinearGradient gr = GradientWidget::gradientFromString(gradientData, rec->boundingRect().width(), rec->boundingRect().height()); rec->setBrush(QBrush(gr)); } } } } void TitleWidget::itemScaled(int val) { QList l = graphicsView->scene()->selectedItems(); if (l.size() == 1) { Transform x = m_transformations.value(l.at(0)); x.scalex = (double)val / 100.0; x.scaley = (double)val / 100.0; QTransform qtrans; qtrans.scale(x.scalex, x.scaley); qtrans.rotate(x.rotatex, Qt::XAxis); qtrans.rotate(x.rotatey, Qt::YAxis); qtrans.rotate(x.rotatez, Qt::ZAxis); l[0]->setTransform(qtrans); l[0]->setData(TitleDocument::ZoomFactor, val); m_transformations[l.at(0)] = x; updateDimension(l.at(0)); } } void TitleWidget::itemRotateX(int val) { itemRotate(val, 0); } void TitleWidget::itemRotateY(int val) { itemRotate(val, 1); } void TitleWidget::itemRotateZ(int val) { itemRotate(val, 2); } void TitleWidget::itemRotate(int val, int axis) { QList l = graphicsView->scene()->selectedItems(); if (l.size() == 1) { Transform x = m_transformations[l.at(0)]; switch (axis) { case 0: x.rotatex = val; break; case 1: x.rotatey = val; break; case 2: x.rotatez = val; break; } l[0]->setData(TitleDocument::RotateFactor, QList() << QVariant(x.rotatex) << QVariant(x.rotatey) << QVariant(x.rotatez)); QTransform qtrans; qtrans.scale(x.scalex, x.scaley); qtrans.rotate(x.rotatex, Qt::XAxis); qtrans.rotate(x.rotatey, Qt::YAxis); qtrans.rotate(x.rotatez, Qt::ZAxis); l[0]->setTransform(qtrans); m_transformations[l.at(0)] = x; if (l[0]->data(TitleDocument::ZoomFactor).isNull()) l[0]->setData(TitleDocument::ZoomFactor, 100); updateDimension(l.at(0)); } } void TitleWidget::itemHCenter() { QList l = graphicsView->scene()->selectedItems(); if (l.size() == 1) { QGraphicsItem *item = l.at(0); QRectF br = item->sceneBoundingRect(); int width = (int)br.width(); int newPos = (int)((m_frameWidth - width) / 2); newPos += item->pos().x() - br.left(); // Check item transformation item->setPos(newPos, item->pos().y()); updateCoordinates(item); } } void TitleWidget::itemVCenter() { QList l = graphicsView->scene()->selectedItems(); if (l.size() == 1) { QGraphicsItem *item = l.at(0); QRectF br = item->sceneBoundingRect(); int height = (int)br.height(); int newPos = (int)((m_frameHeight - height) / 2); newPos += item->pos().y() - br.top(); // Check item transformation item->setPos(item->pos().x(), newPos); updateCoordinates(item); } } void TitleWidget::itemTop() { QList l = graphicsView->scene()->selectedItems(); if (l.size() == 1) { QGraphicsItem *item = l.at(0); QRectF br = item->sceneBoundingRect(); double diff; if (br.top() > 0) diff = -br.top(); else diff = -br.bottom(); item->moveBy(0, diff); updateCoordinates(item); } } void TitleWidget::itemBottom() { QList l = graphicsView->scene()->selectedItems(); if (l.size() == 1) { QGraphicsItem *item = l.at(0); QRectF br = item->sceneBoundingRect(); double diff; if (br.bottom() > m_frameHeight) diff = m_frameHeight - br.top(); else diff = m_frameHeight - br.bottom(); item->moveBy(0, diff); updateCoordinates(item); } } void TitleWidget::itemLeft() { QList l = graphicsView->scene()->selectedItems(); if (l.size() == 1) { QGraphicsItem *item = l.at(0); QRectF br = item->sceneBoundingRect(); double diff; if (br.left() > 0) diff = -br.left(); else diff = -br.right(); item->moveBy(diff, 0); updateCoordinates(item); } } void TitleWidget::itemRight() { QList l = graphicsView->scene()->selectedItems(); if (l.size() == 1) { QGraphicsItem *item = l.at(0); QRectF br = item->sceneBoundingRect(); double diff; if (br.right() < m_frameWidth) diff = m_frameWidth - br.right(); else diff = m_frameWidth - br.left(); item->moveBy(diff, 0); updateCoordinates(item); } } void TitleWidget::loadTitle(QUrl url) { if (!url.isValid()) url = QFileDialog::getOpenFileUrl(this, i18n("Load Title"), QUrl(m_projectTitlePath), i18n("Kdenlive title (*.kdenlivetitle)")); if (url.isValid()) { QList items = m_scene->items(); for (int i = 0; i < items.size(); ++i) { if (items.at(i)->zValue() > -1000) delete items.at(i); } m_scene->clearTextSelection(); QDomDocument doc; QFile file(url.path()); doc.setContent(&file, false); file.close(); setXml(doc); /*int out; m_count = m_titledocument.loadDocument(url, m_startViewport, m_endViewport, &out) + 1; adjustFrameSize(); title_duration->setText(m_tc.getTimecode(GenTime(out, m_render->fps()))); insertingValues = true; startViewportX->setValue(m_startViewport->data(0).toInt()); startViewportY->setValue(m_startViewport->data(1).toInt()); startViewportSize->setValue(m_startViewport->data(2).toInt()); endViewportX->setValue(m_endViewport->data(0).toInt()); endViewportY->setValue(m_endViewport->data(1).toInt()); endViewportSize->setValue(m_endViewport->data(2).toInt()); insertingValues = false; slotSelectTool(); slotAdjustZoom();*/ } } void TitleWidget::saveTitle(QUrl url) { if (anim_start->isChecked()) slotAnimStart(false); if (anim_end->isChecked()) slotAnimEnd(false); bool embed_image=false; // If we have images in the title, ask for embed QList list = graphicsView->scene()->items(); QGraphicsPixmapItem pix; int pixmapType = pix.type(); foreach(const QGraphicsItem *item, list) { if (item->type() == pixmapType && item != m_frameImage) { embed_image = true; break; } } if (embed_image && KMessageBox::questionYesNo(this, i18n("Do you want to embed Images into this TitleDocument?\nThis is most needed for sharing Titles.")) != KMessageBox::Yes) { embed_image=false; } if (!url.isValid()) { QPointer fs = new QFileDialog(this, i18n("Save Title"), m_projectTitlePath); fs->setMimeTypeFilters(QStringList() << QStringLiteral("application/x-kdenlivetitle")); fs->setFileMode(QFileDialog::AnyFile); fs->setAcceptMode(QFileDialog::AcceptSave); //TODO: KF5 porting? //fs->setConfirmOverwrite(true); //fs->setKeepLocation(true); if (fs->exec() && !fs->selectedUrls().isEmpty()) { url = fs->selectedUrls().first(); } delete fs; } if (url.isValid()) { if (m_titledocument.saveDocument(url, m_startViewport, m_endViewport, m_tc.getFrameCount(title_duration->text()), embed_image) == false) KMessageBox::error(this, i18n("Cannot write to file %1", url.path())); } } QDomDocument TitleWidget::xml() { QDomDocument doc = m_titledocument.xml(m_startViewport, m_endViewport); doc.documentElement().setAttribute(QStringLiteral("duration"), m_tc.getFrameCount(title_duration->text())); doc.documentElement().setAttribute(QStringLiteral("out"), m_tc.getFrameCount(title_duration->text())); return doc; } int TitleWidget::duration() const { return m_tc.getFrameCount(title_duration->text()); } void TitleWidget::setXml(const QDomDocument &doc) { int duration; m_count = m_titledocument.loadFromXml(doc, m_startViewport, m_endViewport, &duration, m_projectTitlePath); adjustFrameSize(); title_duration->setText(m_tc.getTimecode(GenTime(duration, m_render->fps()))); /*if (doc.documentElement().hasAttribute("out")) { GenTime duration = GenTime(doc.documentElement().attribute("out").toDouble() / 1000.0); title_duration->setText(m_tc.getTimecode(duration)); } else title_duration->setText(m_tc.getTimecode(GenTime(5000)));*/ QDomElement e = doc.documentElement(); m_transformations.clear(); QList items = graphicsView->scene()->items(); const double PI = 4.0 * atan(1.0); for (int i = 0; i < items.count(); ++i) { QTransform t = items.at(i)->transform(); Transform x; x.scalex = t.m11(); x.scaley = t.m22(); if (!items.at(i)->data(TitleDocument::RotateFactor).isNull()) { QList rotlist = items.at(i)->data(TitleDocument::RotateFactor).toList(); if (rotlist.count() >= 3) { x.rotatex = rotlist[0].toDouble(); x.rotatey = rotlist[1].toDouble(); x.rotatez = rotlist[2].toDouble(); // Try to adjust zoom t.rotate(x.rotatex *(-1), Qt::XAxis); t.rotate(x.rotatey *(-1), Qt::YAxis); t.rotate(x.rotatez *(-1), Qt::ZAxis); x.scalex = t.m11(); x.scaley = t.m22(); } else { x.rotatex = 0; x.rotatey = 0; x.rotatez = 0; } } else { x.rotatex = 0; x.rotatey = 0; x.rotatez = 180. / PI * atan2(-t.m21(), t.m11()); } m_transformations[items.at(i)] = x; } // mbd: Update the GUI color selectors to match the stuff from the loaded document QColor background_color = m_titledocument.getBackgroundColor(); backgroundAlpha->blockSignals(true); backgroundColor->blockSignals(true); backgroundAlpha->setValue(background_color.alpha()); background_color.setAlpha(255); backgroundColor->setColor(background_color); backgroundAlpha->blockSignals(false); backgroundColor->blockSignals(false); /*startViewportX->setValue(m_startViewport->data(0).toInt()); startViewportY->setValue(m_startViewport->data(1).toInt()); startViewportSize->setValue(m_startViewport->data(2).toInt()); endViewportX->setValue(m_endViewport->data(0).toInt()); endViewportY->setValue(m_endViewport->data(1).toInt()); endViewportSize->setValue(m_endViewport->data(2).toInt());*/ QTimer::singleShot(200, this, SLOT(slotAdjustZoom())); slotSelectTool(); selectionChanged(); } void TitleWidget::slotAccepted() { if (anim_start->isChecked()) slotAnimStart(false); if (anim_end->isChecked()) slotAnimEnd(false); writeChoices(); } void TitleWidget::writeChoices() { // Get a pointer to a shared configuration instance, then get the TitleWidget group. KSharedConfigPtr config = KSharedConfig::openConfig(); KConfigGroup titleConfig(config, "TitleWidget"); // Write the entries titleConfig.writeEntry("dialog_geometry", saveGeometry()); titleConfig.writeEntry("font_family", font_family->currentFont()); //titleConfig.writeEntry("font_size", font_size->value()); titleConfig.writeEntry("font_pixel_size", font_size->value()); titleConfig.writeEntry("font_color", fontColorButton->color()); titleConfig.writeEntry("font_outline_color", textOutlineColor->color()); titleConfig.writeEntry("font_outline", textOutline->value()); titleConfig.writeEntry("font_weight", font_weight_box->itemData(font_weight_box->currentIndex()).toInt()); titleConfig.writeEntry("font_italic", buttonItalic->isChecked()); titleConfig.writeEntry("font_underlined", buttonUnder->isChecked()); titleConfig.writeEntry("rect_background_color", rectBColor->color()); titleConfig.writeEntry("rect_foreground_color", rectFColor->color()); titleConfig.writeEntry("rect_background_alpha", rectBColor->color().alpha()); titleConfig.writeEntry("rect_foreground_alpha", rectFColor->color().alpha()); titleConfig.writeEntry("rect_line_width", rectLineWidth->value()); titleConfig.writeEntry("background_color", backgroundColor->color()); titleConfig.writeEntry("background_alpha", backgroundAlpha->value()); + titleConfig.writeEntry("use_grid", use_grid->isChecked()); + //! \todo Not sure if I should sync - it is probably safe to do it config->sync(); } void TitleWidget::readChoices() { // Get a pointer to a shared configuration instance, then get the TitleWidget group. KSharedConfigPtr config = KSharedConfig::openConfig(); KConfigGroup titleConfig(config, "TitleWidget"); // read the entries const QByteArray geometry = titleConfig.readEntry("dialog_geometry", QByteArray()); restoreGeometry(geometry); font_family->setCurrentFont(titleConfig.readEntry("font_family", font_family->currentFont())); font_size->setValue(titleConfig.readEntry("font_pixel_size", font_size->value())); m_scene->slotUpdateFontSize(font_size->value()); QColor fontColor = QColor(titleConfig.readEntry("font_color", fontColorButton->color())); QColor outlineColor = QColor(titleConfig.readEntry("font_outline_color", textOutlineColor->color())); fontColor.setAlpha(titleConfig.readEntry("font_alpha", fontColor.alpha())); outlineColor.setAlpha(titleConfig.readEntry("font_outline_alpha", outlineColor.alpha())); fontColorButton->setColor(fontColor); textOutlineColor->setColor(outlineColor); textOutline->setValue(titleConfig.readEntry("font_outline", textOutline->value())); int weight; if (titleConfig.readEntry("font_bold", false)) weight = QFont::Bold; else weight = titleConfig.readEntry("font_weight", font_weight_box->itemData(font_weight_box->currentIndex()).toInt()); setFontBoxWeight(weight); buttonItalic->setChecked(titleConfig.readEntry("font_italic", buttonItalic->isChecked())); buttonUnder->setChecked(titleConfig.readEntry("font_underlined", buttonUnder->isChecked())); QColor fgColor = QColor(titleConfig.readEntry("rect_foreground_color", rectFColor->color())); QColor bgColor = QColor(titleConfig.readEntry("rect_background_color", rectBColor->color())); fgColor.setAlpha(titleConfig.readEntry("rect_foreground_alpha", fgColor.alpha())); bgColor.setAlpha(titleConfig.readEntry("rect_background_alpha", bgColor.alpha())); rectFColor->setColor(fgColor); rectBColor->setColor(bgColor); rectLineWidth->setValue(titleConfig.readEntry("rect_line_width", rectLineWidth->value())); backgroundColor->setColor(titleConfig.readEntry("background_color", backgroundColor->color())); backgroundAlpha->setValue(titleConfig.readEntry("background_alpha", backgroundAlpha->value())); + use_grid->setChecked(titleConfig.readEntry("use_grid", false)); + m_scene->slotUseGrid(use_grid->isChecked()); } void TitleWidget::adjustFrameSize() { m_frameWidth = m_titledocument.frameWidth(); m_frameHeight = m_titledocument.frameHeight(); m_frameBorder->setRect(0, 0, m_frameWidth, m_frameHeight); displayBackgroundFrame(); } void TitleWidget::slotAnimStart(bool anim) { if (anim && anim_end->isChecked()) { anim_end->setChecked(false); m_endViewport->setZValue(-1000); m_endViewport->setBrush(QBrush()); } slotSelectTool(); QList list = m_scene->items(); for (int i = 0; i < list.count(); ++i) { if (list.at(i)->zValue() > -1000) { list.at(i)->setFlag(QGraphicsItem::ItemIsMovable, !anim); list.at(i)->setFlag(QGraphicsItem::ItemIsSelectable, !anim); } } align_box->setEnabled(anim); itemzoom->setEnabled(!anim); itemrotatex->setEnabled(!anim); itemrotatey->setEnabled(!anim); itemrotatez->setEnabled(!anim); frame_toolbar->setEnabled(!anim); toolbar_stack->setEnabled(!anim); if (anim) { keep_aspect->setChecked(!m_startViewport->data(0).isNull()); m_startViewport->setZValue(1100); QColor col = m_startViewport->pen().color(); col.setAlpha(100); m_startViewport->setBrush(col); m_startViewport->setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable); m_startViewport->setSelected(true); selectionChanged(); slotSelectTool(); if (m_startViewport->childItems().isEmpty()) addAnimInfoText(); } else { m_startViewport->setZValue(-1000); m_startViewport->setBrush(QBrush()); m_startViewport->setFlags(0); if (!anim_end->isChecked()) deleteAnimInfoText(); } } void TitleWidget::slotAnimEnd(bool anim) { if (anim && anim_start->isChecked()) { anim_start->setChecked(false); m_startViewport->setZValue(-1000); m_startViewport->setBrush(QBrush()); } slotSelectTool(); QList list = m_scene->items(); for (int i = 0; i < list.count(); ++i) { if (list.at(i)->zValue() > -1000) { list.at(i)->setFlag(QGraphicsItem::ItemIsMovable, !anim); list.at(i)->setFlag(QGraphicsItem::ItemIsSelectable, !anim); } } align_box->setEnabled(anim); itemzoom->setEnabled(!anim); itemrotatex->setEnabled(!anim); itemrotatey->setEnabled(!anim); itemrotatez->setEnabled(!anim); frame_toolbar->setEnabled(!anim); toolbar_stack->setEnabled(!anim); if (anim) { keep_aspect->setChecked(!m_endViewport->data(0).isNull()); m_endViewport->setZValue(1100); QColor col = m_endViewport->pen().color(); col.setAlpha(100); m_endViewport->setBrush(col); m_endViewport->setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable); m_endViewport->setSelected(true); selectionChanged(); slotSelectTool(); if (m_endViewport->childItems().isEmpty()) addAnimInfoText(); } else { m_endViewport->setZValue(-1000); m_endViewport->setBrush(QBrush()); m_endViewport->setFlags(0); if (!anim_start->isChecked()) deleteAnimInfoText(); } } void TitleWidget::addAnimInfoText() { // add text to anim viewport MyTextItem *t = new MyTextItem(i18nc("Indicates the start of an animation", "Start"), m_startViewport); MyTextItem *t2 = new MyTextItem(i18nc("Indicates the end of an animation", "End"), m_endViewport); QFont font = t->font(); font.setPixelSize(m_startViewport->rect().width() / 10); QColor col = m_startViewport->pen().color(); col.setAlpha(255); t->setDefaultTextColor(col); t->setFont(font); font.setPixelSize(m_endViewport->rect().width() / 10); col = m_endViewport->pen().color(); col.setAlpha(255); t2->setDefaultTextColor(col); t2->setFont(font); } void TitleWidget::updateInfoText() { // update info text font if (!m_startViewport->childItems().isEmpty()) { MyTextItem *item = static_cast (m_startViewport->childItems().at(0)); if (item) { QFont font = item->font(); font.setPixelSize(m_startViewport->rect().width() / 10); item->setFont(font); } } if (!m_endViewport->childItems().isEmpty()) { MyTextItem *item = static_cast (m_endViewport->childItems().at(0)); if (item) { QFont font = item->font(); font.setPixelSize(m_endViewport->rect().width() / 10); item->setFont(font); } } } void TitleWidget::deleteAnimInfoText() { // end animation editing, remove info text while (!m_startViewport->childItems().isEmpty()) { QGraphicsItem *item = m_startViewport->childItems().at(0); if (m_scene) m_scene->removeItem(item); } while (!m_endViewport->childItems().isEmpty()) { QGraphicsItem *item = m_endViewport->childItems().at(0); if (m_scene) m_scene->removeItem(item); } } void TitleWidget::slotKeepAspect(bool keep) { if (m_endViewport->zValue() == 1100) { m_endViewport->setData(0, keep == true ? m_frameWidth : QVariant()); m_endViewport->setData(1, keep == true ? m_frameHeight : QVariant()); } else { m_startViewport->setData(0, keep == true ? m_frameWidth : QVariant()); m_startViewport->setData(1, keep == true ? m_frameHeight : QVariant()); } } void TitleWidget::slotResize50() { if (m_endViewport->zValue() == 1100) { m_endViewport->setRect(0, 0, m_frameWidth / 2, m_frameHeight / 2); } else m_startViewport->setRect(0, 0, m_frameWidth / 2, m_frameHeight / 2); } void TitleWidget::slotResize100() { if (m_endViewport->zValue() == 1100) { m_endViewport->setRect(0, 0, m_frameWidth, m_frameHeight); } else m_startViewport->setRect(0, 0, m_frameWidth, m_frameHeight); } void TitleWidget::slotResize200() { if (m_endViewport->zValue() == 1100) { m_endViewport->setRect(0, 0, m_frameWidth * 2, m_frameHeight * 2); } else m_startViewport->setRect(0, 0, m_frameWidth * 2, m_frameHeight * 2); } void TitleWidget::slotAddEffect(int ix) { QList list = graphicsView->scene()->selectedItems(); int effect = effect_list->itemData(ix).toInt(); /* if (list.size() == 1) { if (effect == NOEFFECT) effect_stack->setHidden(true); else { effect_stack->setCurrentIndex(effect - 1); effect_stack->setHidden(false); } } else // Hide the effects stack when more than one element is selected. effect_stack->setHidden(true); foreach(QGraphicsItem * item, list) { switch (effect) { case NOEFFECT: item->setData(100, QVariant()); item->setGraphicsEffect(0); break; case TYPEWRITEREFFECT: if (item->type() == TEXTITEM) { QStringList effdata = QStringList() << QStringLiteral("typewriter") << QString::number(typewriter_delay->value()) + ';' + QString::number(typewriter_start->value()); item->setData(100, effdata); } break; // Do not remove the non-QGraphicsEffects. case BLUREFFECT: item->setGraphicsEffect(new QGraphicsBlurEffect()); break; case SHADOWEFFECT: item->setGraphicsEffect(new QGraphicsDropShadowEffect()); break; } }*/ } void TitleWidget::slotFontText(const QString& s) { const QFont f(s); if (f.exactMatch()) { // Font really exists (could also just be a «d» if the user // starts typing «dejavu» for example). font_family->setCurrentFont(f); } // Note: Typing dejavu serif does not recognize the font (takes sans) // in older Qt versions. Case must match there (except for first letter) } void TitleWidget::slotEditTypewriter(int /*ix*/) { QList l = graphicsView->scene()->selectedItems(); if (l.size() == 1) { QStringList effdata = QStringList() << QStringLiteral("typewriter") << QString::number(typewriter_delay->value()) + ';' + QString::number(typewriter_start->value()); l[0]->setData(100, effdata); } } void TitleWidget::slotEditBlur(int ix) { QList l = graphicsView->scene()->selectedItems(); if (l.size() == 1) { QGraphicsEffect *eff = l[0]->graphicsEffect(); QGraphicsBlurEffect *blur = static_cast (eff); if (blur) blur->setBlurRadius(ix); } } void TitleWidget::slotEditShadow() { /*QList l = graphicsView->scene()->selectedItems(); if (l.size() == 1) { QGraphicsEffect *eff = l[0]->graphicsEffect(); QGraphicsDropShadowEffect *shadow = static_cast (eff); if (shadow) { shadow->setBlurRadius(shadow_radius->value()); shadow->setOffset(shadow_x->value(), shadow_y->value()); } }*/ } qreal TitleWidget::zIndexBounds(bool maxBound, bool intersectingOnly) { qreal bound = maxBound ? -99 : 99; QList l = graphicsView->scene()->selectedItems(); if (l.size() > 0) { QList lItems; // Get items (all or intersecting only) if (intersectingOnly) { lItems = graphicsView->scene()->items(l[0]->sceneBoundingRect(), Qt::IntersectsItemShape); } else { lItems = graphicsView->scene()->items(); } if (lItems.size() > 0) { int n = lItems.size(); qreal z; if (maxBound) { for (int i = 0; i < n; ++i) { z = lItems[i]->zValue(); if (z > bound && !lItems[i]->isSelected()) { bound = z; } else if (z - 1 > bound) { // To get the maximum index even if it is of an item of the current selection. // Used when updating multiple items, to get all to the same level. // Otherwise, the maximum would stay at -99 if the highest item is in the selection. bound = z - 1; } } } else { // Get minimum z index. for (int i = 0; i < n; ++i) { z = lItems[i]->zValue(); if (z < bound && !lItems[i]->isSelected() && z > -999) { // There are items at the very bottom (background e.g.) with z-index < -1000. bound = z; } else if (z + 1 < bound && z > -999) { bound = z + 1; } } } } } return bound; } void TitleWidget::slotZIndexUp() { QList l = graphicsView->scene()->selectedItems(); if (l.size() >= 1) { qreal currentZ = l[0]->zValue(); qreal max = zIndexBounds(true, true); if (currentZ <= max) { l[0]->setZValue(currentZ + 1); updateDimension(l[0]); } } } void TitleWidget::slotZIndexTop() { QList l = graphicsView->scene()->selectedItems(); qreal max = zIndexBounds(true, false); //qDebug() << "Max z-index is " << max << ".\n"; for (int i = 0; i < l.size(); ++i) { qreal currentZ = l[i]->zValue(); if (currentZ <= max) { //qDebug() << "Updating item " << i << ", is " << currentZ << ".\n"; l[i]->setZValue(max + 1); } else { //qDebug() << "Not updating " << i << ", is " << currentZ << ".\n"; } } // Update the z index value in the GUI if (l.size() > 0) { updateDimension(l[0]); } } void TitleWidget::slotZIndexDown() { QList l = graphicsView->scene()->selectedItems(); if (l.size() >= 1) { qreal currentZ = l[0]->zValue(); qreal min = zIndexBounds(false, true); if (currentZ >= min) { l[0]->setZValue(currentZ - 1); updateDimension(l[0]); } } } void TitleWidget::slotZIndexBottom() { QList l = graphicsView->scene()->selectedItems(); qreal min = zIndexBounds(false, false); for (int i = 0; i < l.size(); ++i) { qreal currentZ = l[i]->zValue(); if (currentZ >= min) { l[i]->setZValue(min - 1); } } // Update the z index value in the GUI if (l.size() > 0) { updateDimension(l[0]); } } void TitleWidget::slotSelectAll() { QList l = graphicsView->scene()->items(); for (int i = 0; i < l.size(); ++i) { l.at(i)->setSelected(true); } } void TitleWidget::selectItems(int itemType) { QList l; if (graphicsView->scene()->selectedItems().size() > 0) { l = graphicsView->scene()->selectedItems(); for (int i = 0; i < l.size(); ++i) { if (l.at(i)->type() != itemType) { l.at(i)->setSelected(false); } } } else { l = graphicsView->scene()->items(); for (int i = 0; i < l.size(); ++i) { if (l.at(i)->type() == itemType) { l.at(i)->setSelected(true); } } } } void TitleWidget::slotSelectText() { selectItems(TEXTITEM); } void TitleWidget::slotSelectRects() { selectItems(RECTITEM); } void TitleWidget::slotSelectImages() { selectItems(IMAGEITEM); } void TitleWidget::slotSelectNone() { graphicsView->blockSignals(true); QList l = graphicsView->scene()->items(); for (int i = 0; i < l.size(); ++i) { l.at(i)->setSelected(false); } graphicsView->blockSignals(false); selectionChanged(); } QString TitleWidget::getTooltipWithShortcut(const QString &text, QAction *button) { return text + " " + button->shortcut().toString() + ""; } void TitleWidget::prepareTools(QGraphicsItem *referenceItem) { // Let some GUI elements block signals. We may want to change their values without any sideeffects. // Additionally, store the previous blocking state to avoid side effects when this function is called from within another one. // Note: Disabling an element also blocks signals. So disabled elements don't need to be set to blocking too. bool blockOX = origin_x_left->signalsBlocked(); bool blockOY = origin_y_top->signalsBlocked(); bool blockEff = effect_list->signalsBlocked(); bool blockRX = itemrotatex->signalsBlocked(); bool blockRY = itemrotatey->signalsBlocked(); bool blockRZ = itemrotatez->signalsBlocked(); bool blockZoom = itemzoom->signalsBlocked(); bool blockX = value_x->signalsBlocked(); bool blockY = value_y->signalsBlocked(); bool blockW = value_w->signalsBlocked(); bool blockH = value_h->signalsBlocked(); origin_x_left->blockSignals(true); origin_y_top->blockSignals(true); effect_list->blockSignals(true); itemrotatex->blockSignals(true); itemrotatey->blockSignals(true); itemrotatez->blockSignals(true); itemzoom->blockSignals(true); value_x->blockSignals(true); value_y->blockSignals(true); value_w->blockSignals(true); value_h->blockSignals(true); if (referenceItem == NULL) { //qDebug() << "NULL item.\n"; effect_list->setCurrentIndex(0); origin_x_left->setChecked(false); origin_y_top->setChecked(false); updateTextOriginX(); updateTextOriginY(); enableToolbars(TITLE_SELECT); showToolbars(TITLE_SELECT); itemzoom->setEnabled(false); itemrotatex->setEnabled(false); itemrotatey->setEnabled(false); itemrotatez->setEnabled(false); frame_properties->setEnabled(false); toolbar_stack->setEnabled(false); /*letter_spacing->setEnabled(false); line_spacing->setEnabled(false); letter_spacing->setValue(0); line_spacing->setValue(0);*/ } else { toolbar_stack->setEnabled(true); frame_properties->setEnabled(true); if (referenceItem != m_startViewport && referenceItem != m_endViewport) { itemzoom->setEnabled(true); itemrotatex->setEnabled(true); itemrotatey->setEnabled(true); itemrotatez->setEnabled(true); } else { itemzoom->setEnabled(false); itemrotatex->setEnabled(false); itemrotatey->setEnabled(false); itemrotatez->setEnabled(false); updateInfoText(); } letter_spacing->setEnabled(referenceItem->type() == TEXTITEM); line_spacing->setEnabled(referenceItem->type() == TEXTITEM); if (referenceItem->type() == TEXTITEM) { showToolbars(TITLE_TEXT); MyTextItem* i = static_cast (referenceItem); if (!i->toPlainText().isEmpty()) { // We have an existing text item selected if (!i->data(100).isNull()) { // Item has an effect QStringList effdata = i->data(100).toStringList(); QString effectName = effdata.takeFirst(); if (effectName == QLatin1String("typewriter")) { QStringList params = effdata.at(0).split(';'); typewriter_delay->setValue(params.at(0).toInt()); typewriter_start->setValue(params.at(1).toInt()); effect_list->setCurrentIndex(effect_list->findData((int) TYPEWRITEREFFECT)); } } else { /*if (i->graphicsEffect()) { QGraphicsBlurEffect *blur = static_cast (i->graphicsEffect()); if (blur) { effect_list->setCurrentIndex(effect_list->findData((int) BLUREFFECT)); int rad = (int) blur->blurRadius(); blur_radius->setValue(rad); effect_stack->setHidden(false); } else { QGraphicsDropShadowEffect *shad = static_cast (i->graphicsEffect()); if (shad) { effect_list->setCurrentIndex(effect_list->findData((int) SHADOWEFFECT)); shadow_radius->setValue(shad->blurRadius()); shadow_x->setValue(shad->xOffset()); shadow_y->setValue(shad->yOffset()); effect_stack->setHidden(false); } } } else { effect_list->setCurrentIndex(effect_list->findData((int) NOEFFECT)); effect_stack->setHidden(true); }*/ } font_size->blockSignals(true); font_family->blockSignals(true); font_weight_box->blockSignals(true); buttonItalic->blockSignals(true); buttonUnder->blockSignals(true); fontColorButton->blockSignals(true); buttonAlignLeft->blockSignals(true); buttonAlignRight->blockSignals(true); buttonAlignCenter->blockSignals(true); QFont font = i->font(); font_family->setCurrentFont(font); font_size->setValue(font.pixelSize()); m_scene->slotUpdateFontSize(font.pixelSize()); buttonItalic->setChecked(font.italic()); buttonUnder->setChecked(font.underline()); setFontBoxWeight(font.weight()); QTextCursor cursor(i->document()); cursor.select(QTextCursor::Document); QColor color = cursor.charFormat().foreground().color(); fontColorButton->setColor(color); if (!i->data(TitleDocument::OutlineWidth).isNull()) { textOutline->blockSignals(true); textOutline->setValue(i->data(TitleDocument::OutlineWidth).toDouble() * 10); textOutline->blockSignals(false); } else { textOutline->blockSignals(true); textOutline->setValue(0); textOutline->blockSignals(false); } if (!i->data(TitleDocument::OutlineColor).isNull()) { textOutlineColor->blockSignals(true); QVariant variant = i->data(TitleDocument::OutlineColor); color = variant.value(); textOutlineColor->setColor(color); textOutlineColor->blockSignals(false); } if (!i->data(TitleDocument::Gradient).isNull()) { gradients_combo->blockSignals(true); gradient_color->setChecked(true); QString gradientData = i->data(TitleDocument::Gradient).toString(); int ix = gradients_combo->findData(gradientData); if (ix == -1) { // This gradient does not exist in our settings, store it storeGradient(gradientData); ix = gradients_combo->findData(gradientData); } gradients_combo->setCurrentIndex(ix); gradients_combo->blockSignals(false); } else { plain_color->setChecked(true); } if (i->alignment() == Qt::AlignHCenter) buttonAlignCenter->setChecked(true); else if (i->alignment() == Qt::AlignRight) buttonAlignRight->setChecked(true); else buttonAlignLeft->setChecked(true); QStringList sInfo = i->shadowInfo(); if (sInfo.count() >= 5) { shadowBox->setChecked(sInfo.at(0).toInt() == true); shadowBox->blockSignals(true); shadowColor->setColor(QColor(sInfo.at(1))); blur_radius->setValue(sInfo.at(2).toInt()); shadowX->setValue(sInfo.at(3).toInt()); shadowY->setValue(sInfo.at(4).toInt()); shadowBox->blockSignals(false); } letter_spacing->blockSignals(true); line_spacing->blockSignals(true); QTextCursor cur = i->textCursor(); QTextBlockFormat format = cur.blockFormat(); letter_spacing->setValue(font.letterSpacing()); line_spacing->setValue(format.lineHeight()); letter_spacing->blockSignals(false); line_spacing->blockSignals(false); font_size->blockSignals(false); font_family->blockSignals(false); font_weight_box->blockSignals(false); buttonItalic->blockSignals(false); buttonUnder->blockSignals(false); fontColorButton->blockSignals(false); buttonAlignLeft->blockSignals(false); buttonAlignRight->blockSignals(false); buttonAlignCenter->blockSignals(false); // mbt 1607: Select text if the text item is an unchanged template item. if (i->property("isTemplate").isValid()) { cur.setPosition(0, QTextCursor::MoveAnchor); cur.select(QTextCursor::Document); i->setTextCursor(cur); // Make text editable now. i->grabKeyboard(); i->setTextInteractionFlags(Qt::TextEditorInteraction); } } updateAxisButtons(i); updateCoordinates(i); updateDimension(i); enableToolbars(TITLE_TEXT); } else if ((referenceItem)->type() == RECTITEM) { showToolbars(TITLE_RECTANGLE); settingUp = true; QGraphicsRectItem *rec = static_cast (referenceItem); if (rec == m_startViewport || rec == m_endViewport) { enableToolbars(TITLE_SELECT); } else { QColor fcol = rec->pen().color(); QColor bcol = rec->brush().color(); rectFColor->setColor(fcol); QString gradientData = rec->data(TitleDocument::Gradient).toString(); if (gradientData.isEmpty()) { plain_rect->setChecked(true); rectBColor->setColor(bcol); } else { gradient_rect->setChecked(true); gradients_rect_combo->blockSignals(true); int ix = gradients_rect_combo->findData(gradientData); if (ix == -1) { storeGradient(gradientData); ix = gradients_rect_combo->findData(gradientData); } gradients_rect_combo->setCurrentIndex(ix); gradients_rect_combo->blockSignals(false); } settingUp = false; if (rec->pen() == Qt::NoPen) { rectLineWidth->setValue(0); } else { rectLineWidth->setValue(rec->pen().width()); } enableToolbars(TITLE_RECTANGLE); } updateAxisButtons(referenceItem); updateCoordinates(rec); updateDimension(rec); } else if (referenceItem->type() == IMAGEITEM) { showToolbars(TITLE_IMAGE); updateCoordinates(referenceItem); updateDimension(referenceItem); enableToolbars(TITLE_IMAGE); } else { showToolbars(TITLE_SELECT); enableToolbars(TITLE_SELECT); frame_properties->setEnabled(false); } zValue->setValue((int)referenceItem->zValue()); if (!referenceItem->data(TitleDocument::ZoomFactor).isNull()) itemzoom->setValue(referenceItem->data(TitleDocument::ZoomFactor).toInt()); else itemzoom->setValue((int)(m_transformations.value(referenceItem).scalex * 100.0 + 0.5)); itemrotatex->setValue((int)(m_transformations.value(referenceItem).rotatex)); itemrotatey->setValue((int)(m_transformations.value(referenceItem).rotatey)); itemrotatez->setValue((int)(m_transformations.value(referenceItem).rotatez)); } effect_list->blockSignals(blockEff); itemrotatex->blockSignals(blockRX); itemrotatey->blockSignals(blockRY); itemrotatez->blockSignals(blockRZ); itemzoom->blockSignals(blockZoom); origin_x_left->blockSignals(blockOX); origin_y_top->blockSignals(blockOY); value_x->blockSignals(blockX); value_y->blockSignals(blockY); value_w->blockSignals(blockW); value_h->blockSignals(blockH); } void TitleWidget::slotEditGradient() { QToolButton *caller = qobject_cast(QObject::sender()); if (!caller) return; QComboBox *combo = NULL; if (caller == edit_gradient) { combo = gradients_combo; } else { combo = gradients_rect_combo; } QMap gradients; for (int i = 0; i < combo->count(); i++) { gradients.insert(combo->itemText(i), combo->itemData(i).toString()); } GradientWidget d(gradients, combo->currentIndex()); if (d.exec() == QDialog::Accepted) { // Save current gradients QMap gradients = d.gradients(); QList icons = d.icons(); QMap::const_iterator i = gradients.constBegin(); KSharedConfigPtr config = KSharedConfig::openConfig(); KConfigGroup group(config, "TitleGradients"); group.deleteGroup(); combo->clear(); gradients_rect_combo->clear(); int ix = 0; while (i != gradients.constEnd()) { group.writeEntry(i.key(), i.value()); gradients_combo->addItem(icons.at(ix), i.key(), i.value()); gradients_rect_combo->addItem(icons.at(ix), i.key(), i.value()); ++i; ix++; } group.sync(); combo->setCurrentIndex(d.selectedGradient()); } } void TitleWidget::storeGradient(const QString &gradientData) { KSharedConfigPtr config = KSharedConfig::openConfig(); KConfigGroup group(config, "TitleGradients"); QMap values = group.entryMap(); int ix = qMax(1, values.count()); QString gradName = i18n("Gradient %1", ix); while (values.contains(gradName)) { ix++; gradName = i18n("Gradient %1", ix); } group.writeEntry(gradName, gradientData); group.sync(); QPixmap pix(30, 30); QLinearGradient gr = GradientWidget::gradientFromString(gradientData, pix.width(), pix.height()); gr.setStart(0, pix.height() / 2); gr.setFinalStop(pix.width(), pix.height() / 2); QPainter painter(&pix); painter.fillRect(0, 0, pix.width(), pix.height(), QBrush(gr)); painter.end(); QIcon icon(pix); gradients_combo->addItem(icon, gradName, gradientData); gradients_rect_combo->addItem(icon, gradName, gradientData); } void TitleWidget::loadGradients() { QMap gradients; gradients_combo->blockSignals(true); gradients_rect_combo->blockSignals(true); QString data = gradients_combo->currentData().toString(); QString rect_data = gradients_rect_combo->currentData().toString(); gradients_combo->clear(); gradients_rect_combo->clear(); KSharedConfigPtr config = KSharedConfig::openConfig(); KConfigGroup group(config, "TitleGradients"); QMap values = group.entryMap(); if (values.isEmpty()) { // Ensure we at least always have one sample black to white gradient values.insert(i18n("Gradient"), QStringLiteral("#ffffffff;#ff000000;0;100;90")); } QMapIterator k(values); while (k.hasNext()) { k.next(); QPixmap pix(30, 30); QLinearGradient gr = GradientWidget::gradientFromString(k.value(), pix.width(), pix.height()); gr.setStart(0, pix.height() / 2); gr.setFinalStop(pix.width(), pix.height() / 2); QPainter painter(&pix); painter.fillRect(0, 0, pix.width(), pix.height(), QBrush(gr)); painter.end(); QIcon icon(pix); gradients_combo->addItem(icon, k.key(), k.value()); gradients_rect_combo->addItem(icon, k.key(), k.value()); } int ix = gradients_combo->findData(data); if (ix >= 0) { gradients_combo->setCurrentIndex(ix); } ix = gradients_rect_combo->findData(rect_data); if (ix >= 0) { gradients_rect_combo->setCurrentIndex(ix); } gradients_combo->blockSignals(false); gradients_rect_combo->blockSignals(false); } void TitleWidget::slotUpdateShadow() { QList l = graphicsView->scene()->selectedItems(); for (int i = 0; i < graphicsView->scene()->selectedItems().length(); ++i) { MyTextItem* item = NULL; if (l.at(i)->type() == TEXTITEM) { item = static_cast (l.at(i)); } if (!item) { // No text item, try next one. continue; } item->updateShadow(shadowBox->isChecked(), blur_radius->value(), shadowX->value(), shadowY->value(), shadowColor->color()); } } + diff --git a/src/titler/titlewidget.h b/src/titler/titlewidget.h index 70bf29a83..9eb956bac 100644 --- a/src/titler/titlewidget.h +++ b/src/titler/titlewidget.h @@ -1,358 +1,359 @@ /*************************************************************************** titlewidget.h - description ------------------- begin : Feb 28 2008 copyright : (C) 2008 by Marco Gittler email : g.marco@freenet.de ***************************************************************************/ /*************************************************************************** * * * 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. * * * ***************************************************************************/ #ifndef TITLEWIDGET_H #define TITLEWIDGET_H #include "ui_titlewidget_ui.h" #include "titler/titledocument.h" #include "renderer.h" #include "effectstack/graphicsscenerectmove.h" #include "titler/unicodedialog.h" #include "timecode.h" #include #include class TitleTemplate { public: QString file; QString name; QIcon icon; }; class Transform { public: Transform() { scalex = 1.0; scaley = 1.0; rotatex = 0.0; rotatey = 0.0; rotatez = 0.0; } double scalex, scaley; int rotatex, rotatey, rotatez; }; /*! \class TitleWidget \brief Title creation dialog Instances of TitleWidget classes are instansiated by KdenliveDoc::slotCreateTextClip () */ class TitleWidget : public QDialog , public Ui::TitleWidget_UI { Q_OBJECT public: /** @brief Draws the dialog and loads a title document (if any). * @param url title document to load * @param tc timecode of the project * @param projectPath default path to save to or load from title documents * @param render project renderer * @param parent (optional) parent widget */ explicit TitleWidget(const QUrl &url, const Timecode &tc, const QString &projectTitlePath, Render *render, QWidget *parent = 0); virtual ~TitleWidget(); QDomDocument xml(); void setXml(const QDomDocument& doc); /** @brief Checks for the images referenced by a title clip. * @param xml XML data representing the title * @return list of the image files */ static QStringList extractImageList(const QString &xml); /** @brief Checks for the fonts referenced by a title clip.\n * Called by DocumentChecker::hasErrorInClips () \n * @param xml XML data representing the title * @return list of the fonts in the title */ static QStringList extractFontList(const QString &xml); /** @brief Returns clip duration. */ int duration() const; /** @brief Retrieves a list of all available title templates. */ static void refreshTitleTemplates(const QString &projectPath); protected: void resizeEvent(QResizeEvent * event); void keyPressEvent(QKeyEvent *e); private: /** @brief Rectangle describing the animation start viewport. */ QGraphicsRectItem *m_startViewport; /** @brief Rectangle describing the animation end viewport. */ QGraphicsRectItem *m_endViewport; /** @brief Scene for the titler. */ GraphicsSceneRectMove *m_scene; /** @brief Initialises the animation properties (viewport size, etc.). */ void initAnimation(); QMap m_transformations; TitleDocument m_titledocument; QGraphicsRectItem *m_frameBorder; + QGraphicsRectItem *m_frameBackground; QGraphicsPixmapItem *m_frameImage; int m_frameWidth; int m_frameHeight; Render *m_render; // TODO Is NOT destroyed in the destructor. Deliberately? int m_count; QAction *m_buttonRect; QAction *m_buttonText; QAction *m_buttonImage; QAction *m_buttonCursor; QAction *m_buttonSave; QAction *m_buttonLoad; QAction *m_unicodeAction; QAction *m_zUp; QAction *m_zDown; QAction *m_zTop; QAction *m_zBottom; QAction *m_selectAll; QAction *m_selectText; QAction *m_selectRects; QAction *m_selectImages; QAction *m_unselectAll; /** @brief Dialog for entering Unicode characters in text fields. */ UnicodeDialog *m_unicodeDialog; /** @brief Project path for storing title documents. */ QString m_projectTitlePath; Timecode m_tc; QString lastDocumentHash; // See http://doc.trolltech.com/4.5/signalsandslots.html#advanced-signals-and-slots-usage. QSignalMapper *m_signalMapper; enum ValueType { ValueWidth = 1, ValueHeight = 2, ValueX = 4, ValueY = 8 }; /** @brief Sets the font weight value in the combo box. (#909) */ void setFontBoxWeight(int weight); /** @brief Stores the choices of font, background and rectangle values. */ void writeChoices(); /** @brief Reads the last stored choices into the dialog. */ void readChoices(); /** @brief Updates the displayed X/Y coordinates. */ void updateCoordinates(QGraphicsItem *i); /** @brief Updates the displayed width/height/zindex values. */ void updateDimension(QGraphicsItem *i); /** @brief Updates the displayed rotation/zoom values. Changes values of rotation/zoom GUI elements. */ void updateRotZoom(QGraphicsItem *i); /** @brief Updates the item position (position read directly from the GUI). Does not change GUI elements. */ void updatePosition(QGraphicsItem *i); /** @brief Updates the item position. Does not change GUI elements. */ void updatePosition(QGraphicsItem *i, int x, int y); void textChanged(MyTextItem *i); void updateAxisButtons(QGraphicsItem *i); void updateTextOriginX(); void updateTextOriginY(); /** @brief Enables the toolbars suiting to toolType. */ void enableToolbars(TITLETOOL toolType); /** @brief Shows the toolbars suiting to toolType. */ void showToolbars(TITLETOOL toolType); /** @brief Set up the tools suiting referenceItem */ void prepareTools(QGraphicsItem *referenceItem); /** @brief Checks a tool button. */ void checkButton(TITLETOOL toolType); void adjustFrameSize(); /** @brief Adds a "start" and "end" info text to the animation viewports. */ void addAnimInfoText(); /** @brief Updates the font for the "start" and "end" info text. */ void updateInfoText(); /** @brief Removes the "start" and "end" info text from animation viewports. */ void deleteAnimInfoText(); qreal maxZIndex(); /** @brief Gets the minimum/maximum Z index of items. * @param maxBound true: use maximum Z index; false: use minimum * @param intersectingOnly if true, consider only the items intersecting * with the currently selected item */ qreal zIndexBounds(bool maxBound, bool intersectingOnly); void itemRotate(int val, int axis); void selectItems(int itemType); /** @brief Appends the shortcut of a QAction to a tooltip text */ QString getTooltipWithShortcut(const QString& text, QAction *button); void loadGradients(); void storeGradient(const QString &gradientData); public slots: void slotNewText(MyTextItem *tt); void slotNewRect(QGraphicsRectItem *rect); void slotChangeBackground(); /** @brief Sets up the tools (toolbars etc.) according to the selected item. */ void selectionChanged(); void rectChanged(); void zIndexChanged(int); void itemScaled(int); void itemRotateX(int); void itemRotateY(int); void itemRotateZ(int); /** Save a title to a title file */ void saveTitle(QUrl url = QUrl()); /** Load a title from a title file */ void loadTitle(QUrl url = QUrl()); void slotGotBackground(QImage img); private slots: /** @brief Switches the origin of the X axis between left and right border. * * It's called when the origin of the X coordinate has been changed. The X * origin will either be at the left or at the right side of the frame. * * When the origin of the X axis is at the left side, the user can enter the * distance between an element's left border and the left side of the frame. * * When on the right, the distance from the right border of the frame to the * right border of the element can be entered. This would result in negative * values as long as the element's right border is at the left of the * frame's right border. As that is usually the case, I additionally invert * the X axis. * * Default value is left. * * |----l----->|#######|----r--->| * | |---w-->| | * | |#######| | * | | * |----------m_frameWidth------>| * | | * * Left selected: Value = l * Right selected: Value = r * * To calculate between the two coorindate systems: * l = m_frameWidth - w - r * r = m_frameWidth - w - l */ void slotOriginXClicked(); /** @brief Same as slotOriginXClicked(), but for the Y axis; default is top. * @ref slotOriginXClicked */ void slotOriginYClicked(); /** @brief Updates coordinates of text fields if necessary. * * It's called when something changes in the QGraphicsScene. */ void slotChanged(); /** * Reacts to changes of widht/height/x/y QSpinBox values. * @brief Updates width, height, and position of the selected items. * @param valueType of type ValueType */ void slotValueChanged(int valueType); void slotZoom(bool up); void slotUpdateZoom(int pos); void slotAdjustZoom(); void slotZoomOneToOne(); void slotSelectAll(); void slotSelectText(); void slotSelectRects(); void slotSelectImages(); void slotSelectNone(); /** Called whenever text properties change (font e.g.) */ void slotUpdateText(); void slotInsertUnicode(); void slotInsertUnicodeString(const QString&); void displayBackgroundFrame(); void setCurrentItem(QGraphicsItem *item); void slotTextTool(); void slotRectTool(); void slotSelectTool(); void slotImageTool(); void slotAnimStart(bool); void slotAnimEnd(bool); void slotKeepAspect(bool keep); void itemHCenter(); void itemVCenter(); void itemTop(); void itemBottom(); void itemLeft(); void itemRight(); void slotResize50(); void slotResize100(); void slotResize200(); /** @brief Called when accepted, stores user selections for next time use. * @ref writeChoices */ void slotAccepted(); void slotFontText(const QString& s); /** @brief Adds an effect to an element. * @param ix index of the effect in the effects menu * * The current implementation allows for one QGraphicsEffect to be added * along with the typewriter effect. This is not clear to the user: the * stack would help, and would permit us to make more QGraphicsEffects * coexist (with different layers of QGraphicsItems). */ void slotAddEffect(int ix); void slotEditBlur(int ix); void slotEditShadow(); void slotEditTypewriter(int ix); /** @brief Changes the Z index of objects. */ void slotZIndexUp(); void slotZIndexDown(); void slotZIndexTop(); void slotZIndexBottom(); /** Called when the user wants to apply a different template to the title */ void templateIndexChanged(int); void slotEditGradient(); void slotUpdateShadow(); signals: void requestBackgroundFrame(); }; #endif diff --git a/src/ui/titlewidget_ui.ui b/src/ui/titlewidget_ui.ui index 10e2340c4..9a5ce4c94 100644 --- a/src/ui/titlewidget_ui.ui +++ b/src/ui/titlewidget_ui.ui @@ -1,1446 +1,1450 @@ TitleWidget_UI 0 0 1051 - 1103 + 1182 Title Clip 0 0 QFrame::NoFrame QFrame::Plain 0 0 0 0 +X true false -5000 5000 +Y true false -5000 5000 W -1000 5000 -1000 5000 H Qt::Horizontal 94 20 -5000 5000 Z-Index: 0 V true V true Qt::Vertical 100 0 150 16777215 1 1000 30 Qt::Horizontal false % 1 1000 Qt::Vertical Show background Qt::Vertical Template: Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok 0 0 QFrame::StyledPanel QFrame::Raised 0 0 Qt::Horizontal - + 6 - - -1 - - - - - false + + + + Selects all items on the canvas. - I + A - - + + false - T + R false N - - - - Qt::Horizontal - - - - 40 - 20 - + + + + + 0 + 0 + - + - - - - Selects all items on the canvas. + + + + false - A + I - - + + false - R + T - - - - - 0 - 0 - + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Use grid Rotate Y: Rotate Z: Qt::Horizontal -360 360 0 0 QTabWidget::North 0 Qt::ElideNone Properties 0 0 0 Letter Spacing ... true false true ... P&lain Color true Line Spacing ... true ... ... true true ... true false true true -100 1000 5000 Outline Align 0 0 ... true 0 0 false - + 85 170 255 - + 0 0 0 -300 300 8 1000 20 &Gradient Qt::Vertical 20 40 Qt::Horizontal 40 20 false Shadow true false Blur 3 0 0 - + 0 0 0 - + true Offset -100 100 3 -100 100 3 P&lain Color true 0 0 - + 0 0 0 - + 0 0 0 &Gradient false ... Qt::Horizontal 40 20 Border 0 0 - + 0 0 0 - + 0 0 0 Border Width 5000 Qt::Vertical 20 40 false Preserve aspect ratio true Qt::Vertical 20 332 Background Qt::Vertical 20 40 0 0 - + 0 0 0 - + 0 0 0 256 Animation Edit start true Edit end true 0 0 0 Resize 50% 100% 200% Keep aspect ratio Qt::Vertical 20 17 Effect Effect 0 0 Delay 0 0 frames 1 5000 5 Start at frames 5000 Qt::Vertical 20 571 Zoom: -360 360 Rotate X: 0 0 QFrame::NoFrame QFrame::Plain ... ... ... ... ... ... Qt::Horizontal 40 20 -360 360 100000 1 Duration KComboBox QComboBox
kcombobox.h
KColorButton QPushButton
kcolorbutton.h
buttonBox accepted() TitleWidget_UI accept() 253 558 157 274 buttonBox rejected() TitleWidget_UI reject() 321 558 286 274 gradient_color toggled(bool) gradients_combo setEnabled(bool) 523 494 672 494 plain_color toggled(bool) fontColorButton setEnabled(bool) 523 454 652 454 plain_rect toggled(bool) rectBColor setEnabled(bool) 523 424 674 431 gradient_rect toggled(bool) gradients_rect_combo setEnabled(bool) 523 464 672 464