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

Make Undo/redo work on bin operations (delete folder, move clip, etc).

parent 45f8623b
......@@ -6,5 +6,6 @@ set(kdenlive_SRCS
bin/projectclip.cpp
bin/projectfolder.cpp
bin/projectsortproxymodel.cpp
bin/bincommands.cpp
PARENT_SCOPE
)
......@@ -117,15 +117,15 @@ public:
enum DataType {
DataName = 0,
DataDescription,
DataDate,
DataThumbnail,
DataDescription = 1,
DataDate = 2,
DataThumbnail = 3,
ItemTypeRole = 4,
DataDuration,
DataDuration = 5,
JobType = Qt::UserRole + 1,
JobProgress,
JobMessage,
ClipStatus
JobProgress = Qt::UserRole + 2,
JobMessage = Qt::UserRole + 3,
ClipStatus = Qt::UserRole + 4,
};
enum CLIPSTATUS {
......
......@@ -37,6 +37,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "mltcontroller/clippropertiescontroller.h"
#include "project/projectcommands.h"
#include "projectsortproxymodel.h"
#include "bincommands.h"
#include "mlt++/Mlt.h"
......@@ -218,23 +219,31 @@ void Bin::deleteClip(const QString &id)
ProjectClip *clip = m_rootFolder->clip(id);
if (!clip) return;
m_jobManager->discardJobs(id);
m_rootFolder->removeChild(clip);
AbstractProjectItem *parent = clip->parent();
parent->removeChild(clip);
delete clip;
}
void Bin::slotDeleteClip()
{
QModelIndexList indexes = m_proxyModel->selectionModel()->selectedIndexes();
QStringList ids;
QStringList clipIds;
QStringList foldersIds;
foreach (const QModelIndex &ix, indexes) {
ProjectClip *currentItem = static_cast<ProjectClip *>(m_proxyModel->mapToSource(ix).internalPointer());
AbstractProjectItem *currentItem = static_cast<AbstractProjectItem *>(m_proxyModel->mapToSource(ix).internalPointer());
if (currentItem) {
ids << currentItem->clipId();
if (currentItem->isFolder()) {
//TODO: check for non empty folders
foldersIds << currentItem->clipId();
}
else {
clipIds << currentItem->clipId();
}
}
}
// For some reason, we get duplicates, which is not expected
//ids.removeDuplicates();
pCore->projectManager()->deleteProjectClips(ids);
pCore->projectManager()->deleteProjectClips(clipIds, foldersIds);
}
void Bin::slotReloadClip()
......@@ -317,10 +326,10 @@ void Bin::setDocument(KdenliveDoc* project)
void Bin::createClip(QDomElement xml)
{
// Check if clip should be in a folder
QString groupId = xml.attribute("groupid");
QString groupId = ProjectClip::getXmlProperty(xml, "kdenlive.groupid");
ProjectFolder *parentFolder = m_rootFolder;
if (!groupId.isEmpty()) {
QString groupName = xml.attribute("group");
QString groupName = ProjectClip::getXmlProperty(xml, "kdenlive.groupname");
parentFolder = m_rootFolder->folder(groupId);
if (!parentFolder) {
// parent folder does not exist, put in root folder
......@@ -333,8 +342,6 @@ void Bin::createClip(QDomElement xml)
void Bin::slotAddFolder()
{
// Check parent item
QString folderName;
QString folderId;
QModelIndex ix = m_proxyModel->selectionModel()->currentIndex();
ProjectFolder *parentFolder = m_rootFolder;
if (ix.isValid() && m_proxyModel->selectionModel()->isSelected(ix)) {
......@@ -345,15 +352,43 @@ void Bin::slotAddFolder()
if (currentItem->isFolder()) {
parentFolder = static_cast<ProjectFolder *>(currentItem);
}
if (parentFolder != m_rootFolder) {
// clip was added to a sub folder, set info
folderName = currentItem->name();
folderId = currentItem->clipId();
}
}
ProjectFolder *newItem = new ProjectFolder(QString::number(getFreeFolderId()), i18n("Folder"), parentFolder);
AddBinFolderCommand *command = new AddBinFolderCommand(this, QString::number(getFreeFolderId()), i18n("Folder"), parentFolder->clipId());
m_doc->commandStack()->push(command);
}
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;
}
ProjectFolder *newItem = new ProjectFolder(id, name, parentFolder);
}
void Bin::removeFolder(const QString &id, QUndoCommand *deleteCommand)
{
// Check parent item
ProjectFolder *folder = m_rootFolder->folder(id);
AbstractProjectItem *parent = folder->parent();
new AddBinFolderCommand(this, folder->clipId(), folder->name(), parent->clipId(), true, 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);
delete folder;
}
void Bin::emitAboutToAddItem(AbstractProjectItem* item)
{
m_itemModel->onAboutToAddItem(item);
......@@ -733,10 +768,10 @@ void Bin::slotProducerReady(requestClipInfo info, ClipController *controller)
}
else {
// Clip not found, create it
QString groupId = controller->property("groupid");
QString groupId = controller->property("kdenlive.groupid");
ProjectFolder *parentFolder;
if (!groupId.isEmpty()) {
QString groupName = controller->property("group");
QString groupName = controller->property("kdenlive.groupname");
parentFolder = m_rootFolder->folder(groupId);
if (!parentFolder) {
// parent folder does not exist, put in root folder
......@@ -899,15 +934,27 @@ void Bin::slotItemDropped(QStringList ids, const QModelIndex &parent)
else {
parentItem = m_rootFolder;
}
QUndoCommand *moveCommand = new QUndoCommand();
moveCommand->setText(i18np("Move Clip", "Move Clips", ids.count()));
foreach(const QString &id, ids) {
ProjectClip *currentItem = m_rootFolder->clip(id);
AbstractProjectItem *currentParent = currentItem->parent();
if (currentParent != parentItem) {
// Item was dropped on a different folder
currentParent->removeChild(currentItem);
currentItem->setParent(parentItem);
new MoveBinClipCommand(this, id, currentParent->clipId(), parentItem->clipId(), moveCommand);
}
}
m_doc->commandStack()->push(moveCommand);
}
void Bin::doMoveClip(const QString &id, const QString &newParentId)
{
ProjectClip *currentItem = m_rootFolder->clip(id);
AbstractProjectItem *currentParent = currentItem->parent();
ProjectFolder *newParent = m_rootFolder->folder(newParentId);
currentParent->removeChild(currentItem);
currentItem->setParent(newParent);
currentItem->updateParentInfo(newParentId, newParent->name());
}
void Bin::slotItemDropped(const QList<QUrl>&urls, const QModelIndex &parent)
......
......@@ -37,6 +37,7 @@ class QSplitter;
class KToolBar;
class KSplitterCollapserButton;
class QMenu;
class QUndoCommand;
class ProjectItemModel;
class ProjectClip;
class ProjectFolder;
......@@ -293,16 +294,16 @@ public:
/** @brief Ask MLT to reload this clip's producer */
void reloadClip(const QString &id);
/** @brief Defines the values for data roles */
enum DATATYPE {
ItemTypeRole = 4,
JobType = Qt::UserRole + 1,
JobProgress,
JobMessage
};
/** @brief Create a folder */
void doAddFolder(const QString &id, const QString &name, const QString &parentId);
/** @brief Delete a folder */
void doRemoveFolder(const QString &id);
void removeFolder(const QString &id, QUndoCommand *deleteCommand);
void doMoveClip(const QString &id, const QString &newParentId);
private slots:
void slotAddClip();
void slotReloadClip();
......
/***************************************************************************
* Copyright (C) 2015 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 "bincommands.h"
#include "bin.h"
#include <klocalizedstring.h>
AddBinFolderCommand::AddBinFolderCommand(Bin *bin, const QString &id, const QString &name, const QString &parentId, bool remove, QUndoCommand *parent) :
QUndoCommand(parent),
m_bin(bin),
m_id(id),
m_name(name),
m_parentId(parentId),
m_remove(remove)
{
if (remove) setText(i18n("Remove Folder"));
else setText(i18n("Add Folder"));
}
// virtual
void AddBinFolderCommand::undo()
{
if (m_remove)
m_bin->doAddFolder(m_id, m_name, m_parentId);
else
m_bin->doRemoveFolder(m_id);
}
// virtual
void AddBinFolderCommand::redo()
{
if (m_remove)
m_bin->doRemoveFolder(m_id);
else
m_bin->doAddFolder(m_id, m_name, m_parentId);
}
MoveBinClipCommand::MoveBinClipCommand(Bin *bin, const QString &clipId, const QString &oldParentId, const QString &newParentId, QUndoCommand *parent) :
QUndoCommand(parent),
m_bin(bin),
m_clipId(clipId),
m_oldParentId(oldParentId),
m_newParentId(newParentId)
{
setText(i18n("Move Clip"));
}
// virtual
void MoveBinClipCommand::undo()
{
m_bin->doMoveClip(m_clipId, m_oldParentId);
}
// virtual
void MoveBinClipCommand::redo()
{
m_bin->doMoveClip(m_clipId, m_newParentId);
}
/***************************************************************************
* Copyright (C) 2015 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 BINCOMMANDS_H
#define BINCOMMANDS_H
#include <QUndoCommand>
class Bin;
class AddBinFolderCommand : public QUndoCommand
{
public:
explicit AddBinFolderCommand(Bin *bin, const QString &id, const QString &name, const QString &parentId, bool remove = false, QUndoCommand * parent = 0);
void undo();
void redo();
private:
Bin *m_bin;
QString m_id;
QString m_name;
QString m_parentId;
bool m_remove;
};
class MoveBinClipCommand : public QUndoCommand
{
public:
explicit MoveBinClipCommand(Bin *bin, const QString &clipId, const QString &oldParentId, const QString &newParentId, QUndoCommand *parent = 0);
void undo();
void redo();
private:
Bin *m_bin;
QString m_clipId;
QString m_oldParentId;
QString m_newParentId;
};
#endif
......@@ -57,10 +57,14 @@ ProjectClip::ProjectClip(const QDomElement& description, ProjectFolder* parent)
Q_ASSERT(description.hasAttribute("id"));
m_clipStatus = StatusWaiting;
QString resource = getXmlProperty(description, "resource");
if (!resource.isEmpty()) {
QString clipName = getXmlProperty(description, "kdenlive.clipname");
if (!clipName.isEmpty()) {
m_name = clipName;
}
else if (!resource.isEmpty()) {
m_name = QUrl::fromLocalFile(resource).fileName();
}
else m_name = description.attribute("name");
else m_name = i18n("Untitled");
if (description.hasAttribute("zone"))
m_zone = QPoint(description.attribute("zone").section(':', 0, 0).toInt(), description.attribute("zone").section(':', 1, 1).toInt());
setParent(parent);
......@@ -450,3 +454,9 @@ ClipPropertiesController *ProjectClip::buildProperties(QWidget *parent)
return panel;
}
void ProjectClip::updateParentInfo(const QString &folderid, const QString &foldername)
{
m_controller->setProperty("kdenlive.groupid", folderid);
m_controller->setProperty("kdenlive.groupname", foldername);
}
......@@ -164,7 +164,8 @@ public:
/** format is frame -> channel ->bytes */
QMap<int, QMap<int, QByteArray> > audioFrameCache;
bool audioThumbCreated() const;
void updateParentInfo(const QString &folderid, const QString &foldername);
void setWaitingStatus(const QString &id);
public slots:
......@@ -183,7 +184,7 @@ private:
/** @brief Generate and store file hash if not available. */
const QString getFileHash() const;
bool m_audioThumbCreated;
signals:
void gotAudioData();
void refreshPropertiesPanel();
......
......@@ -78,7 +78,7 @@ QVariant ProjectItemModel::data(const QModelIndex& index, int role) const
AbstractProjectItem *item = static_cast<AbstractProjectItem *>(index.internalPointer());
return item->data(AbstractProjectItem::DataDuration);
}
if (role == Bin::JobType || role == Bin::JobProgress || role == Bin::JobMessage || role == Bin::ItemTypeRole) {
if (role == AbstractProjectItem::JobType || role == AbstractProjectItem::JobProgress || role == AbstractProjectItem::JobMessage || role == AbstractProjectItem::ItemTypeRole || role == AbstractProjectItem::ItemId) {
AbstractProjectItem *item = static_cast<AbstractProjectItem *>(index.internalPointer());
return item->data((AbstractProjectItem::DataType) role);
}
......
......@@ -64,18 +64,21 @@ void ClipCreationDialogDialog::createColorClip(KdenliveDoc *doc, QStringList gro
QDomDocument xml;
QDomElement prod = xml.createElement("producer");
xml.appendChild(prod);
prod.setAttribute("mlt_service", "colour");
prod.setAttribute("colour", color);
prod.setAttribute("type", (int) Color);
uint id = bin->getFreeClipId();
prod.setAttribute("id", QString::number(id));
prod.setAttribute("in", "0");
prod.setAttribute("out", doc->getFramePos(doc->timecode().getTimecode(t->gentime())) - 1);
prod.setAttribute("name", dia_ui.clip_name->text());
QMap <QString, QString> properties;
properties.insert("resource", color);
properties.insert("kdenlive.clipname", dia_ui.clip_name->text());
properties.insert("mlt_service", "color");
if (!groupInfo.isEmpty()) {
prod.setAttribute("group", groupInfo.at(0));
prod.setAttribute("groupid", groupInfo.at(1));
properties.insert("kdenlive.groupname", groupInfo.at(0));
properties.insert("kdenlive.groupid", groupInfo.at(1));
}
addXmlProperties(prod, properties);
AddClipCommand *command = new AddClipCommand(doc, xml.documentElement(), QString::number(id), true);
doc->commandStack()->push(command);
}
......@@ -93,24 +96,26 @@ void ClipCreationDialogDialog::createSlideshowClip(KdenliveDoc *doc, QStringList
QDomDocument xml;
QDomElement prod = xml.createElement("producer");
xml.appendChild(prod);
prod.setAttribute("name", dia->clipName());
prod.setAttribute("resource", dia->selectedPath());
prod.setAttribute("in", "0");
prod.setAttribute("out", QString::number(doc->getFramePos(dia->clipDuration()) * dia->imageCount() - 1));
prod.setAttribute("ttl", QString::number(doc->getFramePos(dia->clipDuration())));
prod.setAttribute("loop", QString::number(dia->loop()));
prod.setAttribute("crop", QString::number(dia->crop()));
prod.setAttribute("fade", QString::number(dia->fade()));
prod.setAttribute("luma_duration", dia->lumaDuration());
prod.setAttribute("luma_file", dia->lumaFile());
prod.setAttribute("softness", QString::number(dia->softness()));
prod.setAttribute("animation", dia->animation());
prod.setAttribute("type", (int) SlideShow);
uint id = bin->getFreeClipId();
QMap <QString, QString> properties;
properties.insert("kdenlive.clipname", dia->clipName());
properties.insert("resource", dia->selectedPath());
properties.insert("ttl", QString::number(doc->getFramePos(dia->clipDuration())));
properties.insert("loop", QString::number(dia->loop()));
properties.insert("crop", QString::number(dia->crop()));
properties.insert("fade", QString::number(dia->fade()));
properties.insert("luma_duration", dia->lumaDuration());
properties.insert("luma_file", dia->lumaFile());
properties.insert("softness", QString::number(dia->softness()));
properties.insert("animation", dia->animation());
if (!groupInfo.isEmpty()) {
prod.setAttribute("group", groupInfo.at(0));
prod.setAttribute("groupid", groupInfo.at(1));
properties.insert("kdenlive.groupname", groupInfo.at(0));
properties.insert("kdenlive.groupid", groupInfo.at(1));
}
addXmlProperties(prod, properties);
uint id = bin->getFreeClipId();
AddClipCommand *command = new AddClipCommand(doc, xml.documentElement(), QString::number(id), true);
doc->commandStack()->push(command);
}
......@@ -130,14 +135,17 @@ void ClipCreationDialogDialog::createTitleClip(KdenliveDoc *doc, QStringList gro
QDomElement prod = xml.createElement("producer");
xml.appendChild(prod);
//prod.setAttribute("resource", imagePath);
prod.setAttribute("name", i18n("Title clip"));
prod.setAttribute("xmldata", dia_ui->xml().toString());
uint id = bin->getFreeClipId();
prod.setAttribute("id", QString::number(id));
QMap <QString, QString> properties;
properties.insert("xmldata", dia_ui->xml().toString());
properties.insert("kdenlive.clipname", i18n("Title clip"));
if (!groupInfo.isEmpty()) {
prod.setAttribute("group", groupInfo.at(0));
prod.setAttribute("groupid", groupInfo.at(1));
properties.insert("kdenlive.groupname", groupInfo.at(0));
properties.insert("kdenlive.groupid", groupInfo.at(1));
}
addXmlProperties(prod, properties);
prod.setAttribute("type", (int) Text);
prod.setAttribute("transparency", "1");
prod.setAttribute("in", "0");
......@@ -148,6 +156,19 @@ void ClipCreationDialogDialog::createTitleClip(KdenliveDoc *doc, QStringList gro
delete dia_ui;
}
void ClipCreationDialogDialog::addXmlProperties(QDomElement &producer, QMap <QString, QString> &properties)
{
QMapIterator<QString, QString> i(properties);
while (i.hasNext()) {
i.next();
QDomElement prop = producer.ownerDocument().createElement("property");
prop.setAttribute("name", i.key());
QDomText value = producer.ownerDocument().createTextNode(i.value());
prop.appendChild(value);
producer.appendChild(prop);
}
}
void ClipCreationDialogDialog::createClipsCommand(KdenliveDoc *doc, const QList<QUrl> &urls, QStringList groupInfo, Bin *bin)
{
QUndoCommand *addClips = new QUndoCommand();
......@@ -176,18 +197,16 @@ void ClipCreationDialogDialog::createClipsCommand(KdenliveDoc *doc, const QList<
QDomDocument xml;
QDomElement prod = xml.createElement("producer");
xml.appendChild(prod);
QDomElement prop = xml.createElement("property");
prop.setAttribute("name", "resource");
QDomText value = xml.createTextNode(file.path());
prop.appendChild(value);
prod.appendChild(prop);
QMap <QString, QString> properties;
properties.insert("resource", file.path());
if (!groupInfo.isEmpty()) {
properties.insert("kdenlive.groupname", groupInfo.at(0));
properties.insert("kdenlive.groupid", groupInfo.at(1));
}
addXmlProperties(prod, properties);
//prod.setAttribute("resource", file.path());
uint id = bin->getFreeClipId();
prod.setAttribute("id", QString::number(id));
if (!groupInfo.isEmpty()) {
prod.setAttribute("group", groupInfo.at(0));
prod.setAttribute("groupid", groupInfo.at(1));
}
QMimeDatabase db;
QMimeType type = db.mimeTypeForUrl(file);
if (type.name().startsWith(QLatin1String("image/"))) {
......@@ -310,20 +329,22 @@ void ClipCreationDialogDialog::createClipsCommand(KdenliveDoc *doc, QStringList
QDomDocument xml;
QDomElement prod = xml.createElement("producer");
xml.appendChild(prod);
prod.setAttribute("name", fileName);
prod.setAttribute("resource", pattern);
prod.setAttribute("in", "0");
QString duration = doc->timecode().reformatSeparators(KdenliveSettings::sequence_duration());
prod.setAttribute("out", QString::number(doc->getFramePos(duration) * count));
prod.setAttribute("ttl", QString::number(doc->getFramePos(duration)));
prod.setAttribute("loop", QString::number(false));
prod.setAttribute("crop", QString::number(false));
prod.setAttribute("fade", QString::number(false));
prod.setAttribute("luma_duration", QString::number(doc->getFramePos(doc->timecode().getTimecodeFromFrames(int(ceil(doc->timecode().fps()))))));
QMap <QString, QString> properties;
properties.insert("resource", pattern);
properties.insert("kdenlive.clipname", fileName);
properties.insert("ttl", QString::number(doc->getFramePos(duration)));
properties.insert("loop", QString::number(false));
properties.insert("crop", QString::number(false));
properties.insert("fade", QString::number(false));
properties.insert("luma_duration", QString::number(doc->getFramePos(doc->timecode().getTimecodeFromFrames(int(ceil(doc->timecode().fps()))))));
if (!groupInfo.isEmpty()) {
prod.setAttribute("group", groupInfo.at(0));
prod.setAttribute("groupid", groupInfo.at(1));
properties.insert("kdenlive.groupname", groupInfo.at(0));
properties.insert("kdenlive.groupid", groupInfo.at(1));
}
addXmlProperties(prod, properties);
uint id = bin->getFreeClipId();
AddClipCommand *command = new AddClipCommand(doc, xml.documentElement(), QString::number(id), true);
doc->commandStack()->push(command);
......@@ -337,4 +358,4 @@ void ClipCreationDialogDialog::createClipsCommand(KdenliveDoc *doc, QStringList
if (!list.isEmpty()) {
ClipCreationDialogDialog::createClipsCommand(doc, list, groupInfo, bin);
}
}
\ No newline at end of file
}
......@@ -44,6 +44,7 @@ public:
static void createTitleClip(KdenliveDoc *doc, QStringList groupInfo, QString templatePath, Bin *bin);
static void createClipsCommand(KdenliveDoc *doc, const QList<QUrl> &urls, QStringList groupInfo, Bin *bin);
static void createClipsCommand(KdenliveDoc *doc, QStringList groupInfo, Bin *bin);
static void addXmlProperties(QDomElement &producer, QMap <QString, QString> &properties);
};