Commit a14537eb authored by Jean-Baptiste Mardelle's avatar Jean-Baptiste Mardelle
Browse files

Start implementing subclips (clip zones) in project bin

parent 9169efd8
......@@ -4,6 +4,7 @@ set(kdenlive_SRCS
bin/projectitemmodel.cpp
bin/abstractprojectitem.cpp
bin/projectclip.cpp
bin/projectsubclip.cpp
bin/projectfolder.cpp
bin/projectsortproxymodel.cpp
bin/bincommands.cpp
......
......@@ -53,7 +53,8 @@ public:
enum PROJECTITEMTYPE {
FolderItem = 0,
ClipItem = 1
ClipItem = 1,
SubClipItem = 2
};
/**
......
......@@ -24,6 +24,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "mainwindow.h"
#include "projectitemmodel.h"
#include "projectclip.h"
#include "projectsubclip.h"
#include "projectfolder.h"
#include "kdenlivesettings.h"
#include "project/projectmanager.h"
......@@ -246,7 +247,8 @@ Bin::Bin(QWidget* parent) :
connect(m_itemModel, SIGNAL(itemDropped(QStringList, const QModelIndex &)), this, SLOT(slotItemDropped(QStringList, const QModelIndex &)));
connect(m_itemModel, SIGNAL(itemDropped(const QList<QUrl>&, const QModelIndex &)), this, SLOT(slotItemDropped(const QList<QUrl>&, const QModelIndex &)));
connect(m_itemModel, SIGNAL(dataChanged(QModelIndex,QModelIndex,QVector<int>)), this, SLOT(slotItemEdited(QModelIndex,QModelIndex,QVector<int>)));
connect(m_itemModel, SIGNAL(addClipCut(QString,int,int)), this, SLOT(slotAddClipCut(QString,int,int)));
// Zoom slider
m_slider = new QSlider(Qt::Horizontal, this);
m_slider->setMaximumWidth(100);
......@@ -385,9 +387,10 @@ AbstractProjectItem *Bin::getFirstSelectedClip()
return NULL;
}
foreach (const QModelIndex &ix, indexes) {
AbstractProjectItem *currentItem = static_cast<AbstractProjectItem *>(m_proxyModel->mapToSource(ix).internalPointer());
if (!currentItem->isFolder()) {
return currentItem;
AbstractProjectItem *item = static_cast<AbstractProjectItem*>(m_proxyModel->mapToSource(ix).internalPointer());
ProjectClip *clip = qobject_cast<ProjectClip*>(item);
if (clip) {
return clip;
}
}
return NULL;
......@@ -397,18 +400,26 @@ void Bin::slotDeleteClip()
{
QModelIndexList indexes = m_proxyModel->selectionModel()->selectedIndexes();
QStringList clipIds;
QStringList subClipIds;
QStringList foldersIds;
foreach (const QModelIndex &ix, indexes) {
AbstractProjectItem *currentItem = static_cast<AbstractProjectItem *>(m_proxyModel->mapToSource(ix).internalPointer());
if (currentItem) {
if (currentItem->isFolder()) {
//TODO: check for non empty folders
foldersIds << currentItem->clipId();
AbstractProjectItem *item = static_cast<AbstractProjectItem*>(m_proxyModel->mapToSource(ix).internalPointer());
ProjectClip *clip = qobject_cast<ProjectClip*>(item);
if (clip) {
clipIds << clip->clipId();
} else {
ProjectFolder *folder = qobject_cast<ProjectFolder*>(item);
if (folder) {
foldersIds << folder->clipId();
}
else {
clipIds << currentItem->clipId();
else {
//TODO
ProjectSubClip *sub = qobject_cast<ProjectSubClip*>(item);
if (sub) {
subClipIds << sub->clipId();
}
}
}
}
}
// For some reason, we get duplicates, which is not expected
//ids.removeDuplicates();
......@@ -422,8 +433,9 @@ void Bin::slotReloadClip()
if (!ix.isValid()) {
continue;
}
AbstractProjectItem *currentItem = static_cast<AbstractProjectItem *>(m_proxyModel->mapToSource(ix).internalPointer());
if (currentItem && !currentItem->isFolder()) {
AbstractProjectItem *item = static_cast<AbstractProjectItem*>(m_proxyModel->mapToSource(ix).internalPointer());
ProjectClip *currentItem = qobject_cast<ProjectClip*>(item);
if (currentItem) {
m_monitor->openClip(NULL);
QDomDocument doc;
QDomElement xml = currentItem->toXml(doc);
......@@ -556,7 +568,7 @@ QModelIndex Bin::getIndexForId(const QString &id, bool folderWanted) const
// We found our folder
return items.at(i);
}
else if (!folderWanted && !currentItem->isFolder()) {
else if (!folderWanted && qobject_cast< ProjectClip* >(currentItem)) {
// We found our clip
return items.at(i);
}
......@@ -736,7 +748,8 @@ QList <ProjectClip *> Bin::selectedClips()
QModelIndexList indexes = m_proxyModel->selectionModel()->selectedIndexes();
QList <ProjectClip *> list;
foreach (const QModelIndex &ix, indexes) {
ProjectClip *currentItem = static_cast<ProjectClip *>(m_proxyModel->mapToSource(ix).internalPointer());
AbstractProjectItem *item = static_cast<AbstractProjectItem*>(m_proxyModel->mapToSource(ix).internalPointer());
ProjectClip *currentItem = qobject_cast<ProjectClip*>(item);
if (currentItem) {
list << currentItem;
}
......@@ -849,7 +862,8 @@ void Bin::contextMenuEvent(QContextMenuEvent *event)
if (currentItem) {
enableClipActions = true;
m_proxyAction->blockSignals(true);
if (!currentItem->isFolder()) m_proxyAction->setChecked(((ProjectClip *)currentItem)->hasProxy());
ProjectClip *clip = qobject_cast<ProjectClip*>(currentItem);
if (clip) m_proxyAction->setChecked(clip->hasProxy());
m_proxyAction->blockSignals(false);
}
}
......@@ -883,15 +897,17 @@ void Bin::slotSwitchClipProperties(const QModelIndex &ix)
{
if (ix.isValid()) {
if (m_collapser->isWidgetCollapsed()) {
ProjectClip *clip = static_cast<ProjectClip *>(m_proxyModel->mapToSource(ix).internalPointer());
if (clip && !clip->isFolder()) {
AbstractProjectItem *item = static_cast<AbstractProjectItem*>(m_proxyModel->mapToSource(ix).internalPointer());
ProjectClip *clip = qobject_cast<ProjectClip*>(item);
if (clip) {
m_collapser->restore();
showClipProperties(clip);
}
else if (clip->isFolder() && m_listType == BinIconView) {
else m_collapser->collapse();
/*else if (clip->isFolder() && m_listType == BinIconView) {
// Double clicking on a folder enters it in icon view
m_itemView->setRootIndex(ix);
}
}*/
}
else m_collapser->collapse();
}
......@@ -1031,6 +1047,11 @@ void Bin::openProducer(ClipController *controller)
m_monitor->openClip(controller);
}
void Bin::openProducer(ClipController *controller, int in, int out)
{
m_monitor->openClipZone(controller, in, out);
}
void Bin::emitItemUpdated(AbstractProjectItem* item)
{
emit itemUpdated(item);
......@@ -1384,4 +1405,28 @@ void Bin::slotPrepareJobsMenu()
}
}
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::addClipCut(const QString&id, int in, int out)
{
ProjectClip *clip = getBinClip(id);
if (!clip) return;
ProjectSubClip *sub = new ProjectSubClip(clip, in, out);
}
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) {
delete sub;
}
}
......@@ -111,7 +111,16 @@ public:
QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
{
QSize hint = QStyledItemDelegate::sizeHint(option, index);
return QSize(hint.width(), qMax(option.fontMetrics.lineSpacing() * 2 + 4, hint.height()));
int type = index.data(AbstractProjectItem::ItemTypeRole).toInt();
if (type == AbstractProjectItem::FolderItem) {
return QSize(hint.width(), qMin(option.fontMetrics.lineSpacing() + 4, hint.height()));
}
if (type == AbstractProjectItem::ClipItem) {
return QSize(hint.width(), qMax(option.fontMetrics.lineSpacing() * 2 + 4, hint.height()));
}
if (type == AbstractProjectItem::SubClipItem) {
return QSize(hint.width(), qMin((int) (option.fontMetrics.lineSpacing() * 1.5) + 4, hint.height()));
}
QIcon icon = qvariant_cast<QIcon>(index.data(Qt::DecorationRole));
QString line1 = index.data(Qt::DisplayRole).toString();
......@@ -130,17 +139,17 @@ public:
painter->setClipRect(r1);
QStyleOptionViewItemV4 opt(option);
initStyleOption(&opt, index);
int type = index.data(AbstractProjectItem::ItemTypeRole).toInt();
QStyle *style = opt.widget ? opt.widget->style() : QApplication::style();
const int textMargin = style->pixelMetric(QStyle::PM_FocusFrameHMargin) + 1;
QRect r = QStyle::alignedRect(opt.direction, Qt::AlignVCenter | Qt::AlignLeft,
opt.decorationSize, r1);
//QRect r = QStyle::alignedRect(opt.direction, Qt::AlignVCenter | Qt::AlignLeft, opt.decorationSize, r1);
style->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, painter, opt.widget);
if (option.state & QStyle::State_Selected) {
painter->setPen(option.palette.highlightedText().color());
}
QRect r = r1;
r.setWidth(opt.decorationSize.width());
// Draw thumbnail
opt.icon.paint(painter, r);
......@@ -157,60 +166,66 @@ public:
QFont font = painter->font();
font.setBold(true);
painter->setFont(font);
int mid = (int)((r1.height() / 2));
r1.adjust(decoWidth, 0, 0, -mid);
QRect r2 = option.rect;
r2.adjust(decoWidth, mid, 0, 0);
QRectF bounding;
painter->drawText(r1, Qt::AlignLeft | Qt::AlignTop, index.data(AbstractProjectItem::DataName).toString(), &bounding);
font.setBold(false);
painter->setFont(font);
QString subText = index.data(AbstractProjectItem::DataDuration).toString();
//int usage = index.data(UsageRole).toInt();
//if (usage != 0) subText.append(QString(" (%1)").arg(usage));
//if (option.state & (QStyle::State_Selected)) painter->setPen(option.palette.color(QPalette::Mid));
r2.adjust(0, bounding.bottom() - r2.top(), 0, 0);
QColor subTextColor = painter->pen().color();
subTextColor.setAlphaF(.5);
painter->setPen(subTextColor);
painter->drawText(r2, Qt::AlignLeft | Qt::AlignTop , subText, &bounding);
int jobProgress = index.data(AbstractProjectItem::JobProgress).toInt();
if (jobProgress != 0 && jobProgress != JobDone && jobProgress != JobAborted) {
if (jobProgress != JobCrashed) {
// Draw job progress bar
QColor color = option.palette.alternateBase().color();
painter->setPen(Qt::NoPen);
color.setAlpha(180);
painter->setBrush(QBrush(color));
QRect progress(r1.x() + 1, opt.rect.bottom() - 12, r1.width() / 2, 8);
painter->drawRect(progress);
painter->setBrush(option.palette.text());
if (jobProgress > 0) {
progress.adjust(1, 1, 0, -1);
progress.setWidth((progress.width() - 4) * jobProgress / 100);
painter->drawRect(progress);
} else if (jobProgress == JobWaiting) {
// Draw kind of a pause icon
progress.adjust(1, 1, 0, -1);
progress.setWidth(2);
painter->drawRect(progress);
progress.moveLeft(progress.right() + 2);
painter->drawRect(progress);
}
} else if (jobProgress == JobCrashed) {
QString jobText = index.data(AbstractProjectItem::JobMessage).toString();
if (!jobText.isEmpty()) {
QRectF txtBounding = painter->boundingRect(r2, Qt::AlignRight | Qt::AlignVCenter, " " + jobText + " ");
if (type == AbstractProjectItem::ClipItem) {
int mid = (int)((r1.height() / 2));
r1.adjust(decoWidth, 0, 0, -mid);
QRect r2 = option.rect;
r2.adjust(decoWidth, mid, 0, 0);
QRectF bounding;
painter->drawText(r1, Qt::AlignLeft | Qt::AlignTop, index.data(AbstractProjectItem::DataName).toString(), &bounding);
font.setBold(false);
painter->setFont(font);
QString subText = index.data(AbstractProjectItem::DataDuration).toString();
//int usage = index.data(UsageRole).toInt();
//if (usage != 0) subText.append(QString(" (%1)").arg(usage));
//if (option.state & (QStyle::State_Selected)) painter->setPen(option.palette.color(QPalette::Mid));
r2.adjust(0, bounding.bottom() - r2.top(), 0, 0);
QColor subTextColor = painter->pen().color();
subTextColor.setAlphaF(.5);
painter->setPen(subTextColor);
painter->drawText(r2, Qt::AlignLeft | Qt::AlignTop , subText, &bounding);
int jobProgress = index.data(AbstractProjectItem::JobProgress).toInt();
if (jobProgress != 0 && jobProgress != JobDone && jobProgress != JobAborted) {
if (jobProgress != JobCrashed) {
// Draw job progress bar
QColor color = option.palette.alternateBase().color();
painter->setPen(Qt::NoPen);
painter->setBrush(option.palette.highlight());
painter->drawRoundedRect(txtBounding, 2, 2);
painter->setPen(option.palette.highlightedText().color());
painter->drawText(txtBounding, Qt::AlignCenter, jobText);
color.setAlpha(180);
painter->setBrush(QBrush(color));
QRect progress(r1.x() + 1, opt.rect.bottom() - 12, r1.width() / 2, 8);
painter->drawRect(progress);
painter->setBrush(option.palette.text());
if (jobProgress > 0) {
progress.adjust(1, 1, 0, -1);
progress.setWidth((progress.width() - 4) * jobProgress / 100);
painter->drawRect(progress);
} else if (jobProgress == JobWaiting) {
// Draw kind of a pause icon
progress.adjust(1, 1, 0, -1);
progress.setWidth(2);
painter->drawRect(progress);
progress.moveLeft(progress.right() + 2);
painter->drawRect(progress);
}
} else if (jobProgress == JobCrashed) {
QString jobText = index.data(AbstractProjectItem::JobMessage).toString();
if (!jobText.isEmpty()) {
QRectF txtBounding = painter->boundingRect(r2, Qt::AlignRight | Qt::AlignVCenter, " " + jobText + " ");
painter->setPen(Qt::NoPen);
painter->setBrush(option.palette.highlight());
painter->drawRoundedRect(txtBounding, 2, 2);
painter->setPen(option.palette.highlightedText().color());
painter->drawText(txtBounding, Qt::AlignCenter, jobText);
}
}
}
}
else {
r1.adjust(decoWidth, 0, 0, 0);
QRectF bounding;
painter->drawText(r1, Qt::AlignLeft | Qt::AlignTop, index.data(AbstractProjectItem::DataName).toString(), &bounding);
}
painter->restore();
} else {
QStyledItemDelegate::paint(painter, option, index);
......@@ -281,6 +296,7 @@ public:
/** @brief Open a producer in the clip monitor */
void openProducer(ClipController *controller);
void openProducer(ClipController *controller, int in, int out);
/** @brief Trigger deletion of an item */
void deleteClip(const QString &id);
......@@ -350,6 +366,9 @@ public:
void startClipJob(const QStringList &params);
void droppedUrls(QList <QUrl> urls, const QMap<QString,QString> properties = QMap<QString,QString>());
void displayMessage(const QString &text, KMessageWidget::MessageType type);
void addClipCut(const QString&id, int in, int out);
void removeClipCut(const QString&id, int in, int out);
private slots:
void slotAddClip();
......@@ -376,6 +395,8 @@ private slots:
void slotAddUrl(QString url, QString,QString);
void slotPrepareJobsMenu();
void slotShowJobLog();
/** @brief Add a sub clip */
void slotAddClipCut(const QString&id, int in, int out);
public slots:
void slotThumbnailReady(const QString &id, const QImage &img);
......
......@@ -72,3 +72,35 @@ void MoveBinClipCommand::redo()
m_bin->doMoveClip(m_clipId, m_newParentId);
}
AddBinClipCutCommand::AddBinClipCutCommand(Bin *bin, const QString &clipId, int in, int out, bool add, QUndoCommand *parent) :
QUndoCommand(parent)
, m_bin(bin)
, m_clipId(clipId)
, m_in(in)
, m_out(out)
, m_addCut(add)
{
setText(i18n("Add Sub Clip"));
}
// virtual
void AddBinClipCutCommand::undo()
{
if (m_addCut) {
m_bin->removeClipCut(m_clipId, m_in, m_out);
}
else {
m_bin->addClipCut(m_clipId, m_in, m_out);
}
}
// virtual
void AddBinClipCutCommand::redo()
{
if (m_addCut) {
m_bin->addClipCut(m_clipId, m_in, m_out);
}
else {
m_bin->removeClipCut(m_clipId, m_in, m_out);
}
}
......@@ -54,6 +54,19 @@ private:
QString m_newParentId;
};
class AddBinClipCutCommand : public QUndoCommand
{
public:
explicit AddBinClipCutCommand(Bin *bin, const QString &clipId, int in, int out, bool add, QUndoCommand *parent = 0);
void undo();
void redo();
private:
Bin *m_bin;
QString m_clipId;
int m_in;
int m_out;
bool m_addCut;
};
#endif
......@@ -22,6 +22,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "projectclip.h"
#include "projectfolder.h"
#include "projectsubclip.h"
#include "bin.h"
#include "mltcontroller/clipcontroller.h"
#include "mltcontroller/clippropertiescontroller.h"
......@@ -42,6 +43,9 @@ ProjectClip::ProjectClip(const QString &id, ClipController *controller, ProjectF
, m_audioThumbCreated(false)
{
m_clipStatus = StatusReady;
QPixmap pix(64, 36);
pix.fill(Qt::lightGray);
m_thumbnail = QIcon(pix);
m_name = m_controller->clipName();
m_duration = m_controller->getStringDuration();
getFileHash();
......@@ -56,6 +60,9 @@ ProjectClip::ProjectClip(const QDomElement& description, ProjectFolder* parent)
{
Q_ASSERT(description.hasAttribute("id"));
m_clipStatus = StatusWaiting;
QPixmap pix(64, 36);
pix.fill(Qt::lightGray);
m_thumbnail = QIcon(pix);
QString resource = getXmlProperty(description, "resource");
QString clipName = getXmlProperty(description, "kdenlive:clipname");
if (!clipName.isEmpty()) {
......@@ -132,6 +139,18 @@ ProjectFolder* ProjectClip::folder(const QString &id)
return NULL;
}
ProjectSubClip* ProjectClip::getSubClip(int in, int out)
{
ProjectSubClip *clip;
for (int i = 0; i < count(); ++i) {
clip = static_cast<ProjectSubClip *>(at(i))->subClip(in, out);
if (clip) {
return clip;
}
}
return NULL;
}
ProjectClip* ProjectClip::clipAt(int ix)
{
if (ix == index()) {
......@@ -251,6 +270,11 @@ Mlt::Producer *ProjectClip::producer()
return m_controller->masterProducer();
}
ClipController *ProjectClip::controller()
{
return m_controller;
}
bool ProjectClip::isReady() const
{
return m_controller!= NULL;
......
......@@ -34,6 +34,7 @@ class ProjectFolder;
class QDomElement;
class ClipController;
class ClipPropertiesController;
class ProjectSubClip;
namespace Mlt {
class Producer;
......@@ -73,6 +74,8 @@ public:
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);
......@@ -131,6 +134,8 @@ public:
/** @brief Returns this clip's producer. */
Mlt::Producer *producer();
ClipController *controller();
/** @brief Set properties on this clip. TODO: should we store all in MLT or use extra m_properties ?. */
void setProperties(QMap <QString, QString> properties, bool refreshPanel = false);
......
......@@ -71,6 +71,7 @@ ProjectClip* ProjectFolder::clip(const QString &id)
return NULL;
}
QString ProjectFolder::getToolTip() const
{
return QString(i18np("%1 clip", "%1 clips", size()));
......
......@@ -23,6 +23,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "projectitemmodel.h"
#include "abstractprojectitem.h"
#include "projectclip.h"
#include "projectsubclip.h"
#include "projectfolder.h"
#include "bin.h"
......@@ -67,11 +68,6 @@ QVariant ProjectItemModel::data(const QModelIndex& index, int role) const
// Data has to be returned as icon to allow the view to scale it
AbstractProjectItem *item = static_cast<AbstractProjectItem *>(index.internalPointer());
QIcon icon = item->data(AbstractProjectItem::DataThumbnail).value<QIcon>();
if (icon.isNull()) {
QPixmap pix(m_iconSize);
pix.fill(Qt::lightGray);
icon = QIcon(pix);
}
return icon;
}
else {
......@@ -118,6 +114,11 @@ bool ProjectItemModel::dropMimeData(const QMimeData *data, Qt::DropAction action
emit itemDropped(ids, parent);
return true;
}
if (data->hasFormat("kdenlive/clip")) {
QStringList list = QString(data->data("kdenlive/clip")).split(';');
emit addClipCut(list.at(0), list.at(1).toInt(), list.at(2).toInt());
}
return false;
}
......@@ -213,7 +214,7 @@ Qt::DropActions ProjectItemModel::supportedDropActions() const
QStringList ProjectItemModel::mimeTypes() const
{
QStringList types;
types << QLatin1String("kdenlive/producerslist") << QLatin1String("text/uri-list");
types << QLatin1String("kdenlive/producerslist") << QLatin1String("text/uri-list") << QLatin1String("kdenlive/clip");
return types;
}
......@@ -230,6 +231,19 @@ QMimeData* ProjectItemModel::mimeData(const QModelIndexList& indices) const
data.append(list.join(QLatin1String(";")).toUtf8());
mimeData->setData(QLatin1String("kdenlive/producerslist"), data);
return mimeData;
} else {
ProjectSubClip *sub = qobject_cast<ProjectSubClip*>(item);
if (sub) {
QStringList list;
list << sub->clipId();
QPoint p = sub->zone();
list.append(QString::number(p.x()));
list.append(QString::number(p.y()));
QByteArray data;
data.append(list.join(QLatin1String(";")).toUtf8());
mimeData->setData(QLatin1String("kdenlive/clip"), data);
return mimeData;
}
}
}
}
......
......@@ -91,6 +91,7 @@ signals:
void markersNeedUpdate(const QString &id,const QList<int>&);
void itemDropped(QStringList, const QModelIndex &);
void itemDropped(const QList <QUrl> &, const QModelIndex &);
void addClipCut(const QString &,int,int);
};
#endif
/*
Copyright (C) 2015 Jean-Baptiste Mardelle <jb@kdenlive.org>
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.