1 #include "base/NotationTypes.h"
2 #include "base/Segment.h"
3 #include "base/Selection.h"
4 #include <QTest>
5 #include "gui/seqmanager/SequenceManager.h"
6 #include "document/RosegardenDocument.h"
7 #include "gui/editors/notation/NotationView.h"
8 
9 int init()
10 {
11     qputenv("QT_FATAL_WARNINGS", "1");
12     return 0;
13 }
14 Q_CONSTRUCTOR_FUNCTION(init)
15 
16 using namespace Rosegarden;
17 
18 // This test opens data/examples/test_selection.rg and simulates using the keyboard to navigate and select in the notation view
19 class TestNotationViewSelection : public QObject
20 {
21     Q_OBJECT
22 
23 public:
24     TestNotationViewSelection()
25         : m_doc(nullptr, {}, true /*skip autoload*/, true, false /*no sound*/),
26           m_view(nullptr) {}
27 
28 private Q_SLOTS:
29     void initTestCase();
30     void cleanupTestCase();
31     void init();
32     void testNavigate();
33     void testKeyboardSelection_data();
34     void testKeyboardSelection();
35     void testSelectForwardAndBackward();
36 
37 private:
38     QString selectedNotes() const;
39 
40     RosegardenDocument m_doc;
41     Segment *m_segment;
42     NotationView *m_view;
43     SequenceManager m_seqManager;
44 };
45 
46 void TestNotationViewSelection::initTestCase()
47 {
48     // Loading from a file
49     const QString input = QFINDTESTDATA("../data/examples/test_selection.rg");
50     QVERIFY(!input.isEmpty()); // file not found
51     m_doc.openDocument(input, false /*not permanent, i.e. don't create midi devices*/, true /*no progress dlg*/);
52 
53     const SegmentMultiSet segments = m_doc.getComposition().getSegments();
54     QVERIFY(!segments.empty());
55     m_segment = *segments.begin();
56     std::vector<Segment *> segmentsVec;
57     segmentsVec.push_back(m_segment);
58 
59     m_view = new NotationView(&m_doc, segmentsVec);
60 
61     QVERIFY(!m_view->getSelection());
62     QCOMPARE(m_view->getCurrentSegment(), m_segment);
63     QCOMPARE(m_segment->getStartTime(), timeT(0));
64 
65     m_seqManager.setDocument(&m_doc);
66 
67     // The mainwindow connects fast-forward and rewind (to intercept them when recording), so we need to do it ourselves here.
68     connect(m_view, &NotationView::fastForwardPlayback,
69             &m_seqManager, &SequenceManager::fastforward);
70     connect(m_view, &NotationView::rewindPlayback,
71             &m_seqManager, &SequenceManager::rewind);
72     connect(m_view, &NotationView::fastForwardPlaybackToEnd,
73             &m_seqManager, &SequenceManager::fastForwardToEnd);
74     connect(m_view, &NotationView::rewindPlaybackToBeginning,
75             &m_seqManager, &SequenceManager::rewindToBeginning);
76 
77 }
78 
79 void TestNotationViewSelection::cleanupTestCase()
80 {
81     delete m_view;
82 }
83 
84 // Returns the notes in the selection, as a string of note names. Ex: "ABBDG".
85 QString TestNotationViewSelection::selectedNotes() const
86 {
87     EventSelection *selection = m_view->getSelection();
88     if (!selection) {
89         return QString();
90     }
91     const EventContainer &eventContainer = selection->getSegmentEvents();
92     QString ret;
93     Key defaultKey;
94     for (EventContainer::const_iterator it = eventContainer.begin(); it != eventContainer.end(); ++it) {
95         Event *ev = *it;
96         if (ev->isa(Note::EventType)) {
97             ret += Pitch(*ev).getNoteName(defaultKey);
98         } else if (ev->isa(Note::EventRestType)) {
99             ret += 'R';
100         }
101     }
102     return ret;
103 }
104 
105 void TestNotationViewSelection::init()
106 {
107     // Before each test, unselect all and go back to position 0
108     m_view->setSelection(nullptr, false);
109     m_doc.slotSetPointerPosition(0);
110 }
111 
112 void TestNotationViewSelection::testNavigate()
113 {
114     // Go right one note
115     m_view->slotStepForward();
116     QCOMPARE(m_doc.getComposition().getPosition(), timeT(960)); // one quarter
117 
118     // Go to next bar
119     m_seqManager.fastforward();
120     QCOMPARE(m_doc.getComposition().getPosition(), timeT(3840)); // one quarter
121 
122     QVector<timeT> expectedPositions;
123     expectedPositions << 960       // one quarter
124                       << 960 * 2   // one rest
125                       << 960 * 2.5 // one eighth
126                       << 960 * 3   // another
127                       << 960 * 3.5 // another
128                       << 3840      // second bar
129                       << 3840 + 480
130                       << 3840 + 960
131                       << 3840 + 960 * 2
132                       << 7680
133                       << 7680 + 3840
134                       << 15360
135                       << 15360 + 480
136                       << 15360 + 480 * 2
137                       << 15360 + 480 * 3
138                       << 15360 + 480 * 4
139                          ;
140     for (int i = 0 ; i < expectedPositions.size(); ++i) {
141         m_view->slotStepForward();
142         QCOMPARE(m_doc.getComposition().getPosition(), timeT(3840 + expectedPositions.at(i)));
143     }
144 }
145 
146 void TestNotationViewSelection::testKeyboardSelection_data()
147 {
148     QTest::addColumn<QString>("keysPressed");
149     QTest::addColumn<QStringList>("expectedSelections");
150 
151     // Syntax for keyPressed:
152     // l = left, r = right, L = shift-left, R = shift right
153     // n = next bar (ctrl+right), N = select until next bar (ctrl+shift+right)
154     // p = prev bar (ctrl+left), P = select until previous bar (ctrl+shift+left)
155 
156     // To understand expectedSelections, note that the beginning of the file says: ABCDG[rest]...
157     QTest::newRow("1-3-5") << "RrRrR" << (QStringList() << "A" << "A" << "AC" << "AC" << "ACG");
158     QTest::newRow("shift_change_direction_1") << "RRLR" << (QStringList() << "A" << "AB" << "A" << "AB");
159     QTest::newRow("shift_change_direction_2") << "nLLRL" << (QStringList() << "" << "D" << "CD" << "D" << "CD");
160     QTest::newRow("bug_1519_testcase_2") << "RrL" << (QStringList() << "A" << "A" << "AB");
161     QTest::newRow("shift_right_again_same_note") << "RRlR" << (QStringList() << "A" << "AB" << "AB" << "A");
162     QTest::newRow("shift_left_again_same_note") << "nLLrL" << (QStringList() << "" << "D" << "CD" << "CD" << "D");
163     QTest::newRow("select_unselect_bar") << "NprNP" << (QStringList() << "ABCD" << "ABCD" << "ABCD" << "A" << "ABCD");
164 }
165 
166 void TestNotationViewSelection::testKeyboardSelection()
167 {
168     QFETCH(QString, keysPressed);
169     QFETCH(QStringList, expectedSelections);
170     for (int i = 0 ; i < keysPressed.size(); ++i) {
171         const QChar key = keysPressed.at(i);
172         switch (key.toLatin1()) {
173         case 'l':
174             m_view->slotStepBackward();
175             break;
176         case 'r':
177             m_view->slotStepForward();
178             break;
179         case 'L':
180             m_view->slotExtendSelectionBackward();
181             break;
182         case 'R':
183             m_view->slotExtendSelectionForward();
184             break;
185         case 'n':
186             m_seqManager.fastforward();
187             break;
188         case 'N':
189             m_view->slotExtendSelectionForwardBar();
190             break;
191         case 'p':
192             m_seqManager.rewind();
193             break;
194         case 'P':
195             m_view->slotExtendSelectionBackwardBar();
196             break;
197         }
198         const QString prefix = QString("step %1, key %2: ").arg(i).arg(key); // more info in case of failure
199         QCOMPARE(prefix + selectedNotes(), prefix + expectedSelections.at(i));
200     }
201 }
202 
203 void TestNotationViewSelection::testSelectForwardAndBackward()
204 {
205     m_seqManager.fastforward();
206 
207     QStringList expectedSelections;
208     expectedSelections << "G"
209                        << "G" // the rest doesn't get selected, currently
210                        << "GB"
211                        << "GBCC" // tied notes get selected together
212                        << "GBCCBB"
213                        << "GBCCBBGGG"
214                        << "GBCCBBGGGCC"
215                        << "GBCCBBGGGCCG"
216                        << "GBCCBBGGGCCGBDBD"
217                        << "GBCCBBGGGCCGBDBDG"
218                        << "GBCCBBGGGCCGBDBDGC"
219                        ;
220 
221     // select forward
222     for (int i = 0 ; i < expectedSelections.size(); ++i) {
223         m_view->slotExtendSelectionForward();
224         QCOMPARE(selectedNotes(), expectedSelections.at(i));
225     }
226 
227     const int pos = m_doc.getComposition().getPosition();
228 
229     // unselect backward
230     QStringList expectedSelectionsBack = expectedSelections;
231     std::reverse(expectedSelectionsBack.begin(), expectedSelectionsBack.end());
232     expectedSelectionsBack.append(QString());
233 
234     for (int i = 0 ; i < expectedSelectionsBack.size(); ++i) {
235         if (i > 0)
236             m_view->slotExtendSelectionBackward();
237         QCOMPARE(selectedNotes(), expectedSelectionsBack.at(i));
238     }
239     QCOMPARE(selectedNotes(), QString());
240 
241     // select everything backward, check at end
242     m_doc.slotSetPointerPosition(pos);
243     for (int i = 0 ; i < expectedSelections.size(); ++i) {
244         m_view->slotExtendSelectionBackward();
245     }
246     QCOMPARE(m_doc.getComposition().getPosition(), timeT(3840)); // one quarter
247     QCOMPARE(selectedNotes(), QString("GBCCBBGGGCCGDBDBGC")); // order of notes in the chords is reversed, doesn't matter
248 }
249 
250 QTEST_MAIN(TestNotationViewSelection)
251 
252 #include "test_notationview_selection.moc"
253