Members of the KDE Community are recommended to subscribe to the kde-community mailing list at https://mail.kde.org/mailman/listinfo/kde-community to allow them to participate in important discussions and receive other important announcements

Commit 38f932a7 authored by Nicolas Carion's avatar Nicolas Carion

[Timeline2][Model] Implement clip move and pave way for Undo/Redo operations + tests

parent 4efa8316
......@@ -21,6 +21,7 @@
#include "clipmodel.hpp"
#include "timelinemodel.hpp"
#include "trackmodel.hpp"
#include "undohelper.hpp"
#include <mlt++/MltProducer.h>
#include <QDebug>
......@@ -74,22 +75,25 @@ int ClipModel::getPosition() const
return m_position;
}
std::pair<int, int> ClipModel::getInOut() const
{
return {m_producer->get_in(), m_producer->get_out()};
}
bool ClipModel::isValid()
{
return m_producer->is_valid();
}
bool ClipModel::requestResize(int size, bool right, bool dry)
bool ClipModel::requestResize(int size, bool right, Fun& undo, Fun& redo)
{
if (!dry && !requestResize(size, right, true)) {
return false;
}
if (size < 0 || size > m_producer->get_length()) {
return false;
}
int delta = getPlaytime() - size;
int in = m_producer->get_in();
int out = m_producer->get_out();
int old_in = in, old_out = out;
//check if there is enough space on the chosen side
if ((!right && in + delta < 0) || (right && out - delta >= m_producer->get_length())) {
return false;
......@@ -102,7 +106,7 @@ bool ClipModel::requestResize(int size, bool right, bool dry)
if (m_currentTrackId != -1) {
if (auto ptr = m_parent.lock()) {
bool ok = ptr->getTrackById(m_currentTrackId)->requestClipResize(m_id, in, out, right, dry);
bool ok = ptr->getTrackById(m_currentTrackId)->requestClipResize(m_id, in, out, right, undo, redo);
if (!ok) {
return false;
}
......@@ -111,10 +115,16 @@ bool ClipModel::requestResize(int size, bool right, bool dry)
Q_ASSERT(false);
}
}
if (!dry) {
auto operation = [this, in, out]() {
m_producer.reset(m_producer->cut(in, out));
}
return true;
return true;
};
auto reverse = [this, old_in, old_out]() {
m_producer.reset(m_producer->cut(old_in, old_out));
return true;
};
UPDATE_UNDO_REDO(operation, reverse, undo, redo);
return operation();
}
int ClipModel::getPlaytime()
......
......@@ -21,6 +21,7 @@
#include <memory>
#include <QObject>
#include "undohelper.hpp"
namespace Mlt{
class Producer;
......@@ -31,9 +32,8 @@ class TrackModel;
/* @brief This class represents a Clip object, as viewed by the backend.
In general, the Gui associated with it will send modification queries (such as resize or move), and this class authorize them or not depending on the validity of the modifications
*/
class ClipModel : public QObject
class ClipModel
{
Q_OBJECT
ClipModel() = delete;
protected:
......@@ -61,13 +61,19 @@ public:
/* @brief returns the length of the clip on the timeline
*/
int getPlaytime();
/* @brief returns the id of the track in which this clips is inserted (-1 if none)
*/
int getCurrentTrackId() const;
/* @brief returns the current position of the clip (-1 if not inserted)
*/
int getPosition() const;
/* @brief returns the in and out times of the clip
*/
std::pair<int, int> getInOut() const;
friend class TrackModel;
friend class TimelineModel;
/* Implicit conversion operator to access the underlying producer
......@@ -86,9 +92,10 @@ protected:
If a snap point is within reach, the operation will be coerced to use it.
@param size is the new size of the clip
@param right is true if we change the right side of the clip, false otherwise
@param dry If this parameter is true, no action is actually executed, but we return true if it would be possible to do it.
@param undo Lambda function containing the current undo stack. Will be updated with current operation
@param redo Lambda function containing the current redo queue. Will be updated with current operation
*/
bool requestResize(int size, bool right, bool dry = false);
bool requestResize(int size, bool right, Fun& undo, Fun& redo);
/* Split the current clip at the given position
*/
......
......@@ -54,8 +54,8 @@ std::shared_ptr<TimelineModel> TimelineModel::construct(bool populate)
int ix2 = TrackModel::construct(ptr);
int clipId = ClipModel::construct(ptr, prod);
int clipId2 = ClipModel::construct(ptr, prod);
ptr->requestClipChangeTrack(clipId, ix, 100);
ptr->requestClipChangeTrack(clipId2, ix2, 50);
ptr->requestClipMove(clipId, ix, 100);
ptr->requestClipMove(clipId2, ix2, 50);
ptr->getTrackById(ix)->setProperty("kdenlive:trackheight", "60");
ptr->getTrackById(ix2)->setProperty("kdenlive:trackheight", "140");
}
......@@ -272,36 +272,45 @@ int TimelineModel::getClipPosition(int cid) const
return clip->getPosition();
}
bool TimelineModel::moveClip(int fromTrack, int toTrack, int clipIndex, int position)
{
// Get Clip id
return true;
}
bool TimelineModel::requestClipChangeTrack(int cid, int tid, int position, bool dry)
#include <QDebug>
bool TimelineModel::requestClipMove(int cid, int tid, int position)
{
if (!dry && !requestClipChangeTrack(cid, tid, position, true)) {
return false;
}
std::function<bool (void)> undo = [](){return true;};
std::function<bool (void)> redo = [](){return true;};
qDebug() << "Requesting timeline clip move "<< cid<<tid<<position;
Q_ASSERT(m_allClips.count(cid) > 0);
bool ok = true;
int old_tid = m_allClips[cid]->getCurrentTrackId();
if (old_tid != -1) {
ok = getTrackById(old_tid)->requestClipDeletion(cid, dry);
ok = getTrackById(old_tid)->requestClipDeletion(cid, undo, redo);
if (!ok) {
Q_ASSERT(undo());
return false;
}
}
ok = getTrackById(tid)->requestClipInsertion(m_allClips[cid], position, dry);
if (ok && !dry) {
m_allClips[cid]->setCurrentTrackId(tid);
qDebug() << "in timeline, deletion worked";
ok = getTrackById(tid)->requestClipInsertion(m_allClips[cid], position, undo, redo);
if (!ok) {
Q_ASSERT(undo());
return false;
}
return ok;
auto operation = [cid, tid, this]() {
m_allClips[cid]->setCurrentTrackId(tid);
return true;
};
auto reverse = [cid, old_tid, this]() {
m_allClips[cid]->setCurrentTrackId(old_tid);
return true;
};
UPDATE_UNDO_REDO(operation, reverse, undo, redo);
return operation();
}
bool TimelineModel::requestClipResize(int cid, int size, bool right, bool dry)
bool TimelineModel::requestClipResize(int cid, int size, bool right)
{
return m_allClips[cid]->requestResize(size, right, dry);
std::function<bool (void)> undo = [](){return true;};
std::function<bool (void)> redo = [](){return true;};
return m_allClips[cid]->requestResize(size, right, undo, redo);
}
......
......@@ -28,6 +28,7 @@
#include <QVector>
#include <QAbstractItemModel>
#include <mlt++/MltTractor.h>
#include "undohelper.hpp"
class TrackModel;
class ClipModel;
......@@ -38,7 +39,7 @@ class GroupsModel;
This class also serves to keep track of all objects. It holds pointers to all tracks and clips, and gives them unique IDs on creation. These Ids are used in any interactions with the objects and have nothing to do with Melt IDs.
This is the entry point for any modifications that has to be made on an element. The dataflow beyond this entry point may vary, for example when the user request a clip resize, the call is deferred to the clip itself, that check if there is enough data to extend by the requested amount, compute the new in and out, and then asks the track if there is enough room for extension. To avoid any confusion on which function to call first, rembember to always call the version in timeline.
This is the entry point for any modifications that has to be made on an element. The dataflow beyond this entry point may vary, for example when the user request a clip resize, the call is deferred to the clip itself, that check if there is enough data to extend by the requested amount, compute the new in and out, and then asks the track if there is enough room for extension. To avoid any confusion on which function to call first, rembember to always call the version in timeline. This is also required to generate the Undo/Redo operators
It derives from AbstractItemModel to provide the model to the QML interface. An itemModel is organized with row and columns that contain the data. It can be hierarchical, meaning that a given index (row,column) can contain another level of rows and column.
......@@ -102,7 +103,6 @@ public:
QModelIndex index(int row, int column = 0, const QModelIndex &parent = QModelIndex()) const;
QModelIndex makeIndex(int trackIndex, int clipIndex) const;
QModelIndex parent(const QModelIndex &index) const;
bool moveClip(int fromTrack, int toTrack, int clipIndex, int position);
Mlt::Tractor* tractor() const { return m_tractor; }
/* @brief returns the number of tracks */
......@@ -134,21 +134,20 @@ public:
/* @brief Change the track in which the clip is included
Returns true on success. If it fails, nothing is modified.
If the clip is not in inserted in a track yet, it gets inserted for the first time.
@param cid is the ID of the clip
@param tid is the ID of the target track
@param position is the position where we want to insert
@param dry If this parameter is true, no action is actually executed, but we return true if it would be possible to do it.
*/
bool requestClipChangeTrack(int cid, int tid, int position, bool dry = false);
bool requestClipMove(int cid, int tid, int position);
/* @brief Change the track in which the clip is included
/* @brief Change the duration of a clip
Returns true on success. If it fails, nothing is modified.
@param cid is the ID of the clip
@param size is the new size of the clip
@param right is true if we change the right side of the clip, false otherwise
@param dry If this parameter is true, no action is actually executed, but we return true if it would be possible to do it.
*/
bool requestClipResize(int cid, int size, bool right, bool dry = false);
bool requestClipResize(int cid, int size, bool right);
/* @brief Group together a set of ids
Typically, ids would be ids of clips, but for convenience, some of them can be ids of groups as well.
......
This diff is collapsed.
......@@ -23,6 +23,7 @@
#include <QSharedPointer>
#include <unordered_map>
#include <mlt++/MltPlaylist.h>
#include "undohelper.hpp"
class TimelineModel;
class ClipModel;
......@@ -74,26 +75,35 @@ protected:
@param in is the new starting on the clip
@param out is the new ending on the clip
@param right is true if we change the right side of the clip, false otherwise
@param dry If this parameter is true, no action is actually executed, but we return true if it would be possible to do it.
@param undo Lambda function containing the current undo stack. Will be updated with current operation
@param redo Lambda function containing the current redo queue. Will be updated with current operation
*/
bool requestClipResize(int cid, int in, int out, bool right, bool dry = false);
bool requestClipResize(int cid, int in, int out, bool right, Fun& undo, Fun& redo);
/* @brief This function returns a lambda that performs the requested operation */
Fun requestClipResize_lambda(int cid, int in, int out, bool right);
/* @brief Performs an insertion of the given clip.
Returns true if the operation succeeded, and otherwise, the track is not modified.
This method is protected because it shouldn't be called directly. Call the function in the timeline instead.
@param clip is a shared pointer to the clip
@param position is the position where to insert the clip
@param dry If this parameter is true, no action is actually executed, but we return true if it would be possible to do it.
@param undo Lambda function containing the current undo stack. Will be updated with current operation
@param redo Lambda function containing the current redo queue. Will be updated with current operation
*/
bool requestClipInsertion(std::shared_ptr<ClipModel> clip, int position, bool dry = false);
bool requestClipInsertion(std::shared_ptr<ClipModel> clip, int position, Fun& undo, Fun& redo);
/* @brief This function returns a lambda that performs the requested operation */
Fun requestClipInsertion_lambda(std::shared_ptr<ClipModel> clip, int position);
/* @brief Performs an deletion of the given clip.
Returns true if the operation succeeded, and otherwise, the track is not modified.
This method is protected because it shouldn't be called directly. Call the function in the timeline instead.
@param cid is the id of the clip
@param dry If this parameter is true, no action is actually executed, but we return true if it would be possible to do it.
@param undo Lambda function containing the current undo stack. Will be updated with current operation
@param redo Lambda function containing the current redo queue. Will be updated with current operation
*/
bool requestClipDeletion(int cid, bool dry = false);
bool requestClipDeletion(int cid, Fun& undo, Fun& redo);
/* @brief This function returns a lambda that performs the requested operation */
Fun requestClipDeletion_lambda(int cid);
/*@brief Returns the (unique) construction id of the track*/
int getId() const;
......
using Fun = std::function<bool (void)>;
#define UPDATE_UNDO_REDO(operation, reverse, undo, redo) \
undo = [reverse, undo]() { \
bool v = reverse(); \
return undo() && v; \
}; \
redo = [operation, redo]() { \
bool v = redo(); \
return operation() && v; \
};
......@@ -133,25 +133,9 @@ bool TimelineWidget::scrub()
return false;
}
bool TimelineWidget::moveClip(int fromTrack, int toTrack, int clipIndex, int position)
bool TimelineWidget::moveClip(int /*fromTrack*/, int toTrack, int clipIndex, int position)
{
//TODO: only allow move when no overlap
//if (m_model.moveClipValid(fromTrack, toTrack, clipIndex, position)) {
/*if (fromTrack != toTrack)
// Workaround bug #326 moving clips between tracks stops allowing drag-n-drop
// into Timeline, which appeared with Qt 5.6 upgrade.
emit clipMoved(fromTrack, toTrack, clipIndex, position);
else*/
//onClipMoved(fromTrack, toTrack, clipIndex, position);
m_model->moveClip(fromTrack, toTrack, clipIndex, position);
return true;
/*} else if (m_model.addTransitionValid(fromTrack, toTrack, clipIndex, position)) {
MAIN.undoStack()->push(
new Timeline::AddTransitionCommand(m_model, fromTrack, clipIndex, position));
return true;
} else {
return false;
}*/
return m_model->requestClipMove(clipIndex, toTrack, position);
}
QString TimelineWidget::timecode(int frames)
......
......@@ -45,7 +45,7 @@ public:
Q_INVOKABLE int selectedTrack() const { return m_selection.selectedTrack; }
Q_INVOKABLE double scaleFactor() const;
Q_INVOKABLE void setScaleFactor(double scale);
Q_INVOKABLE bool moveClip(int fromTrack, int toTrack, int clipIndex, int position);
Q_INVOKABLE bool moveClip(int fromTrack, int toTrack, int clipIndex, int position);
int duration() const;
int position() const { return m_position; }
void setPosition(int);
......
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment