Skip to content

Commit 115f2bc

Browse files
committed
Add timeline context menu
1 parent f61dca8 commit 115f2bc

File tree

10 files changed

+192
-10
lines changed

10 files changed

+192
-10
lines changed

src/libs/application/dspxmodel/src/Model.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,12 @@ namespace dspx {
312312
return d->createObject<Source>(handle);
313313
}
314314

315+
void Model::destroyItem(EntityObject *object) {
316+
Q_D(Model);
317+
// TODO do some checks
318+
object->deleteLater();
319+
}
320+
315321
void Model::handleSetEntityProperty(int property, const QVariant &value) {
316322
Q_D(Model);
317323
switch (property) {

src/libs/application/dspxmodel/src/Model.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ namespace dspx {
7777
Q_INVOKABLE Param *createParam();
7878
Q_INVOKABLE Source *createSource();
7979

80+
Q_INVOKABLE void destroyItem(EntityObject *object);
81+
8082
protected:
8183
void handleSetEntityProperty(int property, const QVariant &value) override;
8284

src/plugins/coreplugin/project/DspxDocument.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ namespace Core {
2121
QML_ELEMENT
2222
Q_DECLARE_PRIVATE(DspxDocument)
2323
Q_PROPERTY(dspx::Model *model READ model CONSTANT)
24+
Q_PROPERTY(TransactionController *transactionController READ transactionController CONSTANT)
2425
public:
2526
explicit DspxDocument(QObject *parent = nullptr);
2627
~DspxDocument() override;

src/plugins/coreplugin/project/EditTempoTimeSignatureScenario.cpp

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include <QQmlComponent>
77
#include <QQuickItem>
88
#include <QQuickWindow>
9+
#include <QCursor>
910

1011
#include <CoreApi/runtimeinterface.h>
1112

@@ -44,12 +45,18 @@ namespace Core {
4445
}
4546
auto width = dialog->property("width").toDouble();
4647
auto height = dialog->property("height").toDouble();
47-
dialog->setProperty("x", window->width() / 2.0 - width / 2);
48-
if (auto popupTopMarginHint = window->property("popupTopMarginHint"); popupTopMarginHint.isValid()) {
49-
// Assume it is project window
50-
dialog->setProperty("y", popupTopMarginHint);
48+
if (shouldDialogPopupAtCursor) {
49+
auto pos = window->mapFromGlobal(QCursor::pos()).toPointF();
50+
dialog->setProperty("x", qBound(0.0, pos.x(), window->width() - width));
51+
dialog->setProperty("y", qBound(0.0, pos.y(), window->height() - height));
5152
} else {
52-
dialog->setProperty("y", window->height() / 2.0 - height / 2);
53+
dialog->setProperty("x", window->width() / 2.0 - width / 2);
54+
if (auto popupTopMarginHint = window->property("popupTopMarginHint"); popupTopMarginHint.isValid()) {
55+
// Assume it is project window
56+
dialog->setProperty("y", popupTopMarginHint);
57+
} else {
58+
dialog->setProperty("y", window->height() / 2.0 - height / 2);
59+
}
5360
}
5461
return dialog;
5562

@@ -110,6 +117,20 @@ namespace Core {
110117
Q_EMIT documentChanged();
111118
}
112119
}
120+
121+
bool EditTempoTimeSignatureScenario::shouldDialogPopupAtCursor() const {
122+
Q_D(const EditTempoTimeSignatureScenario);
123+
return d->shouldDialogPopupAtCursor;
124+
}
125+
126+
void EditTempoTimeSignatureScenario::setShouldDialogPopupAtCursor(bool shouldDialogPopupAtCursor) {
127+
Q_D(EditTempoTimeSignatureScenario);
128+
if (d->shouldDialogPopupAtCursor != shouldDialogPopupAtCursor) {
129+
d->shouldDialogPopupAtCursor = shouldDialogPopupAtCursor;
130+
Q_EMIT shouldDialogPopupAtCursorChanged();
131+
}
132+
}
133+
113134
void EditTempoTimeSignatureScenario::editTempo() const {
114135
Q_D(const EditTempoTimeSignatureScenario);
115136
if (!d->projectTimeline)
@@ -153,14 +174,14 @@ namespace Core {
153174
for (auto redundantTempoItem : currentTempos) {
154175
if (redundantTempoItem != tempoItem) {
155176
tempoSequence->removeItem(redundantTempoItem);
156-
redundantTempoItem->deleteLater();
177+
d->document->model()->destroyItem(redundantTempoItem);
157178
}
158179
}
159180
tempoItem->setValue(tempo);
160181
}
161182
return true;
162183
}, [] {
163-
qCWarning(lcEditTempoTimeSignatureScenario) << "Failed to edit tempo in exclusive transaction";
184+
qCCritical(lcEditTempoTimeSignatureScenario) << "Failed to edit tempo in exclusive transaction";
164185
});
165186

166187

@@ -224,15 +245,15 @@ namespace Core {
224245
for (auto redundantTimeSignatureItem : currentTimeSignatures) {
225246
if (redundantTimeSignatureItem != timeSignatureItem) {
226247
timeSignatureSequence->removeItem(redundantTimeSignatureItem);
227-
redundantTimeSignatureItem->deleteLater();
248+
d->document->model()->destroyItem(redundantTimeSignatureItem);
228249
}
229250
}
230251
timeSignatureItem->setNumerator(numerator);
231252
timeSignatureItem->setDenominator(denominator);
232253
}
233254
return true;
234255
}, [] {
235-
qCWarning(lcEditTempoTimeSignatureScenario) << "Failed to edit tempo in exclusive transaction";
256+
qCCritical(lcEditTempoTimeSignatureScenario) << "Failed to edit tempo in exclusive transaction";
236257
});
237258
}
238259
void EditTempoTimeSignatureScenario::insertTimeSignatureAt(int position) const {

src/plugins/coreplugin/project/EditTempoTimeSignatureScenario.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ namespace Core {
2323
Q_PROPERTY(QQuickWindow *window READ window WRITE setWindow NOTIFY windowChanged)
2424
Q_PROPERTY(ProjectTimeline *projectTimeline READ projectTimeline WRITE setProjectTimeline NOTIFY projectTimelineChanged)
2525
Q_PROPERTY(DspxDocument *document READ document WRITE setDocument NOTIFY documentChanged)
26+
Q_PROPERTY(bool shouldDialogPopupAtCursor READ shouldDialogPopupAtCursor WRITE setShouldDialogPopupAtCursor NOTIFY shouldDialogPopupAtCursorChanged)
2627

2728
public:
2829
explicit EditTempoTimeSignatureScenario(QObject *parent = nullptr);
@@ -37,6 +38,9 @@ namespace Core {
3738
DspxDocument *document() const;
3839
void setDocument(DspxDocument *document);
3940

41+
bool shouldDialogPopupAtCursor() const;
42+
void setShouldDialogPopupAtCursor(bool shouldDialogPopupAtCursor);
43+
4044
Q_INVOKABLE void editTempo() const;
4145
Q_INVOKABLE void editTempo(int position, bool doInsertNew, double initialTempo) const;
4246
Q_INVOKABLE void insertTempoAt(int position) const;
@@ -52,6 +56,7 @@ namespace Core {
5256
void windowChanged();
5357
void projectTimelineChanged();
5458
void documentChanged();
59+
void shouldDialogPopupAtCursorChanged();
5560

5661
private:
5762
QScopedPointer<EditTempoTimeSignatureScenarioPrivate> d_ptr;

src/plugins/coreplugin/project/EditTempoTimeSignatureScenario_p.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ namespace Core {
1414
QQuickWindow *window = nullptr;
1515
ProjectTimeline *projectTimeline = nullptr;
1616
DspxDocument *document = nullptr;
17+
bool shouldDialogPopupAtCursor = false;
1718

1819
QObject *createAndPositionDialog(QQmlComponent *component, int position, bool doInsertNew) const;
1920
bool execDialog(QObject *dialog) const;

src/plugins/coreplugin/project/ProjectTimeline.cpp

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
#include "ProjectTimeline.h"
22
#include "ProjectTimeline_p.h"
33

4+
#include <QQmlComponent>
5+
6+
#include <CoreApi/runtimeinterface.h>
7+
48
#include <SVSCraftCore/MusicTimeline.h>
59
#include <SVSCraftCore/MusicTimeSignature.h>
610

@@ -109,7 +113,19 @@ namespace Core {
109113
Q_D(ProjectTimeline);
110114
d->q_ptr = this;
111115
d->document = document;
112-
d->musicTimeline = new SVS::MusicTimeline(this);
116+
{
117+
QQmlComponent component(RuntimeInterface::qmlEngine(), "DiffScope.Core", "MusicTimelineHelper");
118+
if (component.isError()) {
119+
qFatal() << component.errorString();
120+
}
121+
auto musicTimeline = component.create();
122+
if (!musicTimeline) {
123+
qFatal() << component.errorString();
124+
}
125+
musicTimeline->setParent(this);
126+
d->musicTimeline = qobject_cast<SVS::MusicTimeline *>(musicTimeline);
127+
Q_ASSERT(d->musicTimeline);
128+
}
113129
auto tempoSequence = document->model()->timeline()->tempos();
114130
auto timeSignatureSequence = document->model()->timeline()->timeSignatures();
115131
for (auto item : tempoSequence->asRange()) {
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import SVSCraft
2+
3+
MusicTimeline {
4+
id: musicTimeline
5+
}

src/plugins/visualeditor/qml/ArrangementView.qml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,13 @@ Item {
2424

2525
readonly property Timeline timeline: timeline
2626

27+
TimelineContextMenuHelper {
28+
timeline: view.timeline
29+
window: view.Window.window
30+
projectTimeline: view.arrangementPanelInterface?.windowHandle.projectTimeline ?? null
31+
document: view.arrangementPanelInterface?.windowHandle.projectDocumentContext.document ?? null
32+
}
33+
2734
SplitView {
2835
id: splitView
2936
anchors.fill: parent
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import QtQml
2+
import QtQml.Models
3+
import QtQuick
4+
import QtQuick.Controls
5+
import QtQuick.Layouts
6+
7+
import SVSCraft
8+
import SVSCraft.UIComponents
9+
10+
import dev.sjimo.ScopicFlow
11+
import dev.sjimo.ScopicFlow.Views
12+
13+
import DiffScope.Core
14+
15+
EditTempoTimeSignatureScenario {
16+
id: helper
17+
18+
shouldDialogPopupAtCursor: true
19+
20+
required property Timeline timeline
21+
22+
readonly property MusicTimeline musicTimeline: projectTimeline?.musicTimeline ?? null
23+
24+
readonly property TimeManipulator timeManipulator: TimeManipulator {
25+
id: timeManipulator
26+
target: helper.timeline
27+
timeLayoutViewModel: helper.timeline.timeLayoutViewModel
28+
timeViewModel: helper.timeline.timeViewModel
29+
}
30+
31+
readonly property LoggingCategory lcTimelineContextMenuHelper: LoggingCategory {
32+
id: lcTimelineContextMenuHelper
33+
name: "diffscope.visualeditor.qml.timelinecontextmenuhelper"
34+
}
35+
36+
readonly property Component newTimeSignatureMenu: Menu {
37+
id: menu
38+
required property int position
39+
Action {
40+
text: helper.musicTimeline.create(0, 0, menu.position).toString()
41+
enabled: false
42+
}
43+
MenuSeparator {
44+
}
45+
Action {
46+
text: qsTr("Insert Time Signature")
47+
onTriggered: {
48+
Qt.callLater(() => helper.insertTimeSignatureAt(menu.position))
49+
}
50+
}
51+
MenuSeparator {
52+
}
53+
Action {
54+
text: qsTr("Move Playhead Here")
55+
onTriggered: {
56+
helper.projectTimeline.goTo(menu.position)
57+
}
58+
}
59+
}
60+
61+
readonly property Component existingTimeSignatureMenu: Menu {
62+
id: menu
63+
required property int position
64+
Action {
65+
text: helper.musicTimeline.create(0, 0, menu.position).toString()
66+
enabled: false
67+
}
68+
MenuSeparator {
69+
}
70+
Action {
71+
text: qsTr("Edit Time Signature")
72+
onTriggered: {
73+
Qt.callLater(() => helper.modifyExistingTimeSignatureAt(menu.position))
74+
}
75+
}
76+
Action {
77+
text: qsTr("Remove Time Signature")
78+
enabled: helper.musicTimeline.create(0, 0, menu.position).measure !== 0
79+
onTriggered: () => {
80+
let measure = helper.musicTimeline.create(0, 0, menu.position).measure
81+
if (measure === 0)
82+
return
83+
let transactionId = helper.document.transactionController.beginTransaction()
84+
if (!transactionId) {
85+
console.error(lcTimelineContextMenuHelper, "Failed to remove time signature in exclusive transaction")
86+
return
87+
}
88+
let currentTimeSignatures = helper.document.model.timeline.timeSignatures.slice(measure, 1)
89+
for (let item of currentTimeSignatures) {
90+
helper.document.model.timeline.timeSignatures.removeItem(item)
91+
helper.document.model.destroyItem(item)
92+
}
93+
helper.document.transactionController.commitTransaction(transactionId, "Remove time signature")
94+
}
95+
}
96+
MenuSeparator {
97+
}
98+
Action {
99+
text: qsTr("Move Playhead Here")
100+
onTriggered: {
101+
helper.projectTimeline.goTo(menu.position)
102+
}
103+
}
104+
}
105+
106+
readonly property Connections controllerConnections: Connections {
107+
target: helper.timeline.timelineInteractionController
108+
function onContextMenuRequested(timeline_, position) {
109+
if (timeline_ !== helper.timeline)
110+
return
111+
position = timeManipulator.alignPosition(position, ScopicFlow.AO_Visible)
112+
let measure = helper.musicTimeline.create(0, 0, position).measure
113+
let isTimeSignatureExisting = helper.musicTimeline.nearestBarWithTimeSignatureTo(measure) === measure
114+
let menu = (isTimeSignatureExisting ? helper.existingTimeSignatureMenu : helper.newTimeSignatureMenu).createObject(helper.window, { position })
115+
menu.popup()
116+
}
117+
}
118+
}

0 commit comments

Comments
 (0)