Skip to content

Commit 0e9df9c

Browse files
Merge pull request #21033 from Eism/notation_view_range_start_playback_fix
fixed #11493: Playback incorrectly starts at the end of a selection
2 parents 971082a + fd3d467 commit 0e9df9c

File tree

4 files changed

+190
-22
lines changed

4 files changed

+190
-22
lines changed

src/notation/tests/notationviewinputcontroller_tests.cpp

Lines changed: 134 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,7 @@ TEST_F(NotationViewInputControllerTests, DISABLED_WheelEvent_Zoom)
215215
/**
216216
* @brief Mouse_Press_Range_Start_Drag_From_Selected_Element
217217
* @details User pressed left mouse button on already selected note
218+
* The new note should be seeked and played, but no selected again
218219
*/
219220
TEST_F(NotationViewInputControllerTests, Mouse_Press_Range_Start_Drag_From_Selected_Element)
220221
{
@@ -224,9 +225,6 @@ TEST_F(NotationViewInputControllerTests, Mouse_Press_Range_Start_Drag_From_Selec
224225
//! [GIVEN] Previous selected note
225226
INotationInteraction::HitElementContext oldContext = hitContext(score, true /*first note*/);
226227

227-
EXPECT_CALL(*m_interaction, hitElementContext())
228-
.WillOnce(ReturnRef(oldContext));
229-
230228
//! [GIVEN] User selected new note that was already selected
231229
INotationInteraction::HitElementContext newContext = hitContext(score, false /*last note*/);
232230
newContext.element->setSelected(true);
@@ -241,6 +239,11 @@ TEST_F(NotationViewInputControllerTests, Mouse_Press_Range_Start_Drag_From_Selec
241239
EXPECT_CALL(*m_interaction, setHitElementContext(newContext))
242240
.Times(1);
243241

242+
EXPECT_CALL(*m_interaction, hitElementContext())
243+
.Times(2)
244+
.WillOnce(ReturnRef(oldContext))
245+
.WillOnce(ReturnRef(newContext));
246+
244247
//! [GIVEN] There is a range selection
245248
ON_CALL(*m_selection, isRange())
246249
.WillByDefault(Return(true));
@@ -252,18 +255,143 @@ TEST_F(NotationViewInputControllerTests, Mouse_Press_Range_Start_Drag_From_Selec
252255
EXPECT_CALL(*m_playbackController, isPlaying())
253256
.WillOnce(Return(false));
254257

255-
//! [THEN] We will seek and play selected note
258+
//! [THEN] We will seek and play selected note, but no select again
256259
EXPECT_CALL(*m_playbackController, seekElement(newContext.element))
257260
.Times(1);
258261

259262
std::vector<const EngravingItem*> elements = { newContext.element };
260263
EXPECT_CALL(*m_playbackController, playElements(elements))
261264
.Times(1);
262265

263-
//! [THEN] We will not select already selected note
264-
EXPECT_CALL(*m_interaction, select(_, _, _))
266+
std::vector<EngravingItem*> selectElements = { newContext.element };
267+
EXPECT_CALL(*m_interaction, select(selectElements, _, _))
265268
.Times(0);
266269

267270
//! [WHEN] User pressed left mouse button
268271
m_controller->mousePressEvent(make_mousePressEvent(Qt::LeftButton, Qt::NoModifier, QPoint(100, 100)));
269272
}
273+
274+
/**
275+
* @brief Mouse_Press_Range_Start_Play_From_First_Playable_Element
276+
* @details User selected a range in a note that is located after the previous selected note
277+
* The new note should be selected and played, but no seeked
278+
* The first note from a range should be seeked
279+
*/
280+
TEST_F(NotationViewInputControllerTests, Mouse_Press_Range_Start_Play_From_First_Playable_Element)
281+
{
282+
//! [GIVEN] There is a test score
283+
engraving::MasterScore* score = engraving::ScoreRW::readScore(TEST_SCORE_PATH);
284+
285+
//! [GIVEN] Previous selected note
286+
INotationInteraction::HitElementContext oldContext = hitContext(score, true /*first note*/);
287+
288+
//! [GIVEN] User selected new note that is located after the previous selected note
289+
INotationInteraction::HitElementContext newContext = hitContext(score, false /*last note*/);
290+
291+
EXPECT_CALL(*m_interaction, hitElement(_, _))
292+
.WillOnce(Return(newContext.element));
293+
294+
EXPECT_CALL(*m_interaction, hitStaff(_))
295+
.WillOnce(Return(newContext.element->staff()));
296+
297+
//! [GIVEN] The hew hit element context with new note will be set
298+
EXPECT_CALL(*m_interaction, setHitElementContext(newContext))
299+
.Times(1);
300+
301+
EXPECT_CALL(*m_interaction, hitElementContext())
302+
.Times(2)
303+
.WillOnce(ReturnRef(oldContext))
304+
.WillOnce(ReturnRef(newContext));
305+
306+
//! [GIVEN] No note enter mode, no playing
307+
EXPECT_CALL(m_view, isNoteEnterMode())
308+
.WillOnce(Return(false));
309+
310+
EXPECT_CALL(*m_playbackController, isPlaying())
311+
.WillOnce(Return(false));
312+
313+
//! [THEN] We will select and play selected note, but no seek
314+
std::vector<EngravingItem*> selectElements = { newContext.element };
315+
EXPECT_CALL(*m_interaction, select(selectElements, _, _))
316+
.Times(1);
317+
318+
std::vector<const EngravingItem*> playElements = { newContext.element };
319+
EXPECT_CALL(*m_playbackController, playElements(playElements))
320+
.Times(1);
321+
322+
EXPECT_CALL(*m_playbackController, seekElement(newContext.element))
323+
.Times(0);
324+
325+
//! [GIVEN] There is a range selection with two notes
326+
ON_CALL(*m_selection, isRange())
327+
.WillByDefault(Return(true));
328+
329+
selectElements.push_back(oldContext.element);
330+
EXPECT_CALL(*m_selection, elements())
331+
.WillOnce(Return(selectElements));
332+
333+
//! [THEN] We will seek first note in the range
334+
EXPECT_CALL(*m_playbackController, seekElement(oldContext.element))
335+
.Times(1);
336+
337+
//! [WHEN] User pressed left mouse button with ShiftModifier on the new note
338+
m_controller->mousePressEvent(make_mousePressEvent(Qt::LeftButton, Qt::ShiftModifier, QPoint(100, 100)));
339+
}
340+
341+
/**
342+
* @brief Mouse_Press_On_Already_Selected_Element
343+
* @details User pressed on already selected note
344+
* The selected note should not be selected again, but should be played and seeked
345+
*/
346+
TEST_F(NotationViewInputControllerTests, Mouse_Press_On_Already_Selected_Element)
347+
{
348+
//! [GIVEN] There is a test score
349+
engraving::MasterScore* score = engraving::ScoreRW::readScore(TEST_SCORE_PATH);
350+
351+
//! [GIVEN] Previous selected note
352+
INotationInteraction::HitElementContext oldContext = hitContext(score, true /*first note*/);
353+
oldContext.element->setSelected(true);
354+
355+
//! [GIVEN] User pressed on the previous selected note
356+
INotationInteraction::HitElementContext newContext = oldContext;
357+
358+
EXPECT_CALL(*m_interaction, hitElement(_, _))
359+
.WillOnce(Return(newContext.element));
360+
361+
EXPECT_CALL(*m_interaction, hitStaff(_))
362+
.WillOnce(Return(newContext.element->staff()));
363+
364+
EXPECT_CALL(*m_interaction, setHitElementContext(newContext))
365+
.Times(1);
366+
367+
EXPECT_CALL(*m_interaction, hitElementContext())
368+
.Times(2)
369+
.WillOnce(ReturnRef(oldContext))
370+
.WillOnce(ReturnRef(newContext));
371+
372+
//! [GIVEN] No note enter mode, no playing
373+
EXPECT_CALL(m_view, isNoteEnterMode())
374+
.WillOnce(Return(false));
375+
376+
EXPECT_CALL(*m_playbackController, isPlaying())
377+
.WillOnce(Return(false));
378+
379+
//! [THEN] We will no select already selected note, but play and seek
380+
std::vector<EngravingItem*> selectElements = { newContext.element };
381+
EXPECT_CALL(*m_interaction, select(selectElements, _, _))
382+
.Times(0);
383+
384+
std::vector<const EngravingItem*> playElements = { newContext.element };
385+
EXPECT_CALL(*m_playbackController, playElements(playElements))
386+
.Times(1);
387+
388+
EXPECT_CALL(*m_playbackController, seekElement(newContext.element))
389+
.Times(1);
390+
391+
//! [GIVEN] There is no a range selection
392+
ON_CALL(*m_selection, isRange())
393+
.WillByDefault(Return(false));
394+
395+
//! [WHEN] User pressed left mouse button with ShiftModifier on the new note
396+
m_controller->mousePressEvent(make_mousePressEvent(Qt::LeftButton, Qt::NoModifier, QPoint(100, 100)));
397+
}

src/notation/view/notationviewinputcontroller.cpp

Lines changed: 54 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,23 @@ using namespace mu::commonscene;
3939

4040
static constexpr int PIXELSSTEPSFACTOR = 5;
4141

42+
static bool seekAllowed(const mu::engraving::EngravingItem* element)
43+
{
44+
if (!element) {
45+
return false;
46+
}
47+
48+
static const ElementTypeSet playableTypes = {
49+
ElementType::NOTE,
50+
ElementType::REST,
51+
ElementType::MMREST,
52+
ElementType::MEASURE,
53+
ElementType::BAR_LINE
54+
};
55+
56+
return contains(playableTypes, element->type());
57+
}
58+
4259
NotationViewInputController::NotationViewInputController(IControlledView* view)
4360
: m_view(view)
4461
{
@@ -579,21 +596,11 @@ void NotationViewInputController::mousePressEvent(QMouseEvent* event)
579596
m_physicalBeginPoint = event->pos();
580597
m_logicalBeginPoint = logicPos;
581598

582-
if (hitElement) {
583-
switch (hitElement->type()) {
584-
case ElementType::NOTE:
585-
case ElementType::REST:
586-
case ElementType::MMREST:
587-
case ElementType::MEASURE:
588-
case ElementType::BAR_LINE: {
599+
if (playbackController()->isPlaying()) {
600+
if (seekAllowed(hitElement)) {
589601
playbackController()->seekElement(hitElement);
590-
break;
591-
}
592-
default: break;
593602
}
594-
}
595603

596-
if (playbackController()->isPlaying()) {
597604
return;
598605
}
599606

@@ -622,6 +629,11 @@ void NotationViewInputController::mousePressEvent(QMouseEvent* event)
622629
viewInteraction()->select({ hitElement }, selectType, hitStaffIndex);
623630
}
624631

632+
EngravingItem* playbackStartElement = resolveStartPlayableElement();
633+
if (playbackStartElement) {
634+
playbackController()->seekElement(playbackStartElement);
635+
}
636+
625637
if (button == Qt::LeftButton) {
626638
handleLeftClick(ctx);
627639
} else if (button == Qt::RightButton) {
@@ -1129,3 +1141,33 @@ void NotationViewInputController::togglePopupForItemIfSupports(const EngravingIt
11291141
m_view->toggleElementPopup(type, item->canvasBoundingRect());
11301142
}
11311143
}
1144+
1145+
EngravingItem* NotationViewInputController::resolveStartPlayableElement() const
1146+
{
1147+
EngravingItem* hitElement = hitElementContext().element;
1148+
1149+
INotationSelectionPtr selection = viewInteraction()->selection();
1150+
if (!selection->isRange()) {
1151+
return hitElement;
1152+
}
1153+
1154+
EngravingItem* playbackStartElement = hitElement;
1155+
1156+
for (EngravingItem* element: selection->elements()) {
1157+
if (!element || element == playbackStartElement) {
1158+
continue;
1159+
}
1160+
1161+
if (!seekAllowed(element)) {
1162+
continue;
1163+
}
1164+
1165+
if (playbackStartElement && playbackStartElement->tick() <= element->tick()) {
1166+
continue;
1167+
}
1168+
1169+
playbackStartElement = element;
1170+
}
1171+
1172+
return playbackStartElement;
1173+
}

src/notation/view/notationviewinputcontroller.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,8 @@ class NotationViewInputController : public actions::Actionable, public async::As
178178
bool startTextEditingAllowed() const;
179179
void updateTextCursorPosition();
180180

181+
EngravingItem* resolveStartPlayableElement() const;
182+
181183
IControlledView* m_view = nullptr;
182184

183185
QList<int> m_possibleZoomPercentages;

src/playback/internal/playbackcontroller.cpp

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1164,10 +1164,6 @@ void PlaybackController::setupSequencePlayer()
11641164
return;
11651165
}
11661166

1167-
if (!isPlaying()) {
1168-
return;
1169-
}
1170-
11711167
setCurrentPlaybackTime(msecs);
11721168
m_tickPlayed.send(m_currentTick);
11731169
});

0 commit comments

Comments
 (0)