New 'Grab item' feature (Shift+G) allowing to select the current timeline item...

New 'Grab item' feature (Shift+G) allowing to select the current timeline item and move it in timeline with keyboard arrows (wip)
parent cf1b592e
<!DOCTYPE kpartgui SYSTEM "kpartgui.dtd">
<kpartgui name="kdenlive" version="156" translationDomain="kdenlive">
<kpartgui name="kdenlive" version="157" translationDomain="kdenlive">
<MenuBar>
<Menu name="file" >
<Action name="dvd_wizard" />
......@@ -108,6 +108,7 @@
<Action name="clip_in_project_tree" />
<Action name="expand_timeline_clip" />
</Menu>
<Action name="grab_item" />
<Menu name="guide_menu" ><text>Guides</text>
<Action name="add_guide" />
<Action name="edit_guide" />
......
......@@ -1247,6 +1247,9 @@ void MainWindow::setupActions()
addAction(QStringLiteral("align_playhead"), i18n("Align Playhead to Mouse Position"), this, SLOT(slotAlignPlayheadToMousePos()), QIcon(), Qt::Key_P);
addAction(QStringLiteral("grab_item"), i18n("Grab Current Item"), this, SLOT(slotGrabItem()),
QIcon::fromTheme(QStringLiteral("transform-move")), Qt::SHIFT + Qt::Key_G);
QAction *stickTransition = new QAction(i18n("Automatic Transition"), this);
stickTransition->setData(QStringLiteral("auto"));
stickTransition->setCheckable(true);
......@@ -4059,6 +4062,11 @@ void MainWindow::slotSwitchTimelineZone(bool active)
m_useTimelineZone->setActive(active);
}
void MainWindow::slotGrabItem()
{
getCurrentTimeline()->controller()->grabCurrent();
}
#ifdef DEBUG_MAINW
#undef DEBUG_MAINW
#endif
......@@ -465,6 +465,8 @@ private slots:
void updateAction();
/** @brief Request adjust of timeline track height */
void resetTimelineTracks();
/** @brief Set keyboard grabbing on current timeline item */
void slotGrabItem();
signals:
Q_SCRIPTABLE void abortRenderJob(const QString &url);
......
......@@ -77,6 +77,10 @@ public:
*/
virtual const QString getProperty(const QString &name) const = 0;
/* Set if the item is in grab state */
bool isGrabbed() const;
void setGrab(bool grab);
protected:
/* @brief Returns a pointer to the service. It may be used but do NOT store it*/
virtual Service *service() const = 0;
......@@ -112,6 +116,7 @@ protected:
int m_id; // this is the creation id of the item, used for book-keeping
int m_position;
int m_currentTrackId;
bool m_grabbed;
mutable QReadWriteLock m_lock; // This is a lock that ensures safety in case of concurrent access
};
......
......@@ -26,6 +26,7 @@ MoveableItem<Service>::MoveableItem(std::weak_ptr<TimelineModel> parent, int id)
, m_id(id == -1 ? TimelineModel::getNextId() : id)
, m_position(-1)
, m_currentTrackId(-1)
, m_grabbed(false)
, m_lock(QReadWriteLock::Recursive)
{
}
......@@ -89,3 +90,15 @@ template <typename Service> void MoveableItem<Service>::setInOut(int in, int out
QWriteLocker locker(&m_lock);
service()->set_in_and_out(in, out);
}
template <typename Service> bool MoveableItem<Service>::isGrabbed() const
{
READ_LOCK();
return m_grabbed;
}
template <typename Service> void MoveableItem<Service>::setGrab(bool grab)
{
QWriteLocker locker(&m_lock);
m_grabbed = grab;
}
......@@ -202,6 +202,7 @@ QHash<int, QByteArray> TimelineItemModel::roleNames() const
roles[CanBeVideoRole] = "canBeVideo";
roles[ReloadThumbRole] = "reloadThumb";
roles[ThumbsFormatRole] = "thumbsFormat";
roles[GrabbedRole] = "isGrabbed";
return roles;
}
......@@ -295,6 +296,8 @@ QVariant TimelineItemModel::data(const QModelIndex &index, int role) const
return clip->forceThumbReload;
case SpeedRole:
return clip->getSpeed();
case GrabbedRole:
return clip->isGrabbed();
default:
break;
}
......@@ -370,6 +373,8 @@ QVariant TimelineItemModel::data(const QModelIndex &index, int role) const
}
case IsCompositionRole:
return true;
case GrabbedRole:
return compo->isGrabbed();
default:
break;
}
......
......@@ -152,7 +152,8 @@ public:
ReloadThumbRole, /// clip only
ItemATrack, /// composition only
ItemIdRole,
ThumbsFormatRole /// track only
ThumbsFormatRole, /// track only
GrabbedRole /// clip+composition only
};
virtual ~TimelineModel();
......
......@@ -40,6 +40,7 @@ Rectangle {
property bool isAudio: false
property bool isComposition: false
property bool showKeyframes: false
property bool isGrabbed: false
property bool grouped: false
property var audioLevels
property var markers
......@@ -86,6 +87,13 @@ Rectangle {
ColorAnimation { from: "#ff3300"; to: Qt.darker(getColor()); duration: 100 }
}
onIsGrabbedChanged: {
if (clipRoot.isGrabbed) {
clipRoot.forceActiveFocus();
mouseArea.focus = true
}
}
onInPointChanged: {
if (parentTrack.isAudio) {
thumbsLoader.item.generateWaveform()
......@@ -148,7 +156,7 @@ Rectangle {
color: Qt.darker(getColor())
border.color: selected? 'red' : grouped ? 'yellowgreen' : borderColor
border.width: 1.5
border.width: isGrabbed ? 8 : 1.5
Drag.active: mouseArea.drag.active
Drag.proposedAction: Qt.MoveAction
opacity: Drag.active? 0.5 : 1.0
......@@ -235,6 +243,7 @@ Rectangle {
}
if (mouse.button == Qt.LeftButton) {
drag.target = clipRoot
focus = true
} else if (mouse.button == Qt.RightButton) {
drag.target = undefined
clipMenu.item.clipId = clipRoot.clipId
......@@ -246,6 +255,15 @@ Rectangle {
clipMenu.item.popup()
}
}
Keys.onShortcutOverride: event.accepted = clipRoot.isGrabbed && (event.key === Qt.Key_Left || event.key === Qt.Key_Right)
Keys.onLeftPressed: {
console.log('left key pressed')
controller.requestClipMove(clipRoot.clipId, clipRoot.trackId, clipRoot.modelStart - 1, true, true, true);
}
Keys.onRightPressed: {
console.log('left key pressed')
controller.requestClipMove(clipRoot.clipId, clipRoot.trackId, clipRoot.modelStart + 1, true, true, true);
}
onPositionChanged: {
if (pressed && mouse.buttons === Qt.LeftButton) {
var trackIndex = Logic.getTrackIndexFromId(clipRoot.trackId)
......
......@@ -120,6 +120,12 @@ Column{
value: model.showKeyframes
when: loader.status == Loader.Ready
}
Binding {
target: loader.item
property: "isGrabbed"
value: model.isGrabbed
when: loader.status == Loader.Ready
}
Binding {
target: loader.item
property: "keyframeModel"
......
......@@ -1731,3 +1731,20 @@ const QString TimelineController::getAssetName(const QString &assetId, bool isTr
{
return isTransition ? TransitionsRepository::get()->getName(assetId) : EffectsRepository::get()->getName(assetId);
}
void TimelineController::grabCurrent()
{
if (m_selection.selectedItems.isEmpty()) {
//TODO: error displayMessage
return;
}
int id = m_selection.selectedItems.constFirst();
if (m_model->isClip(id)) {
std::shared_ptr<ClipModel> clip = m_model->getClipPtr(id);
clip->setGrab(!clip->isGrabbed());
QModelIndex ix = m_model->makeClipIndexFromID(id);
if (ix.isValid()) {
m_model->dataChanged(ix, ix, {TimelineItemModel::GrabbedRole});
}
}
}
......@@ -380,7 +380,10 @@ public:
void selectCurrentItem(ObjectType type, bool select, bool addToCurrent = false);
/** @brief Set target tracks (video, audio) */
void setTargetTracks(QPair<int, int> targets);
/** @brief Return asset's display name from it's id (effect or composition) */
Q_INVOKABLE const QString getAssetName(const QString &assetId, bool isTransition);
/** @brief Set keyboard grabbing on current selection */
void grabCurrent();
public slots:
void selectMultitrack();
......
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