1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
2 
3 /*
4     Rosegarden
5     A MIDI and audio sequencer and musical notation editor.
6     Copyright 2000-2021 the Rosegarden development team.
7 
8     Other copyrights also apply to some parts of this work.  Please
9     see the AUTHORS file and individual file headers for details.
10 
11     This program is free software; you can redistribute it and/or
12     modify it under the terms of the GNU General Public License as
13     published by the Free Software Foundation; either version 2 of the
14     License, or (at your option) any later version.  See the file
15     COPYING included with this distribution for more information.
16 */
17 
18 
19 #include "SetLyricsCommand.h"
20 
21 #include "base/Event.h"
22 #include "misc/Strings.h"
23 #include "misc/Debug.h"
24 #include "base/Composition.h"
25 #include "base/NotationTypes.h"
26 #include "base/Segment.h"
27 #include "base/BaseProperties.h"
28 #include <QRegularExpression>
29 #include <QString>
30 #include <QStringList>
31 
32 
33 namespace Rosegarden
34 {
35 
36 using namespace BaseProperties;
37 
SetLyricsCommand(Segment * segment,int verse,QString newLyricData)38 SetLyricsCommand::SetLyricsCommand(Segment *segment, int verse, QString newLyricData) :
39         NamedCommand(getGlobalName()),
40         m_segment(segment),
41         m_verse(verse),
42         m_newLyricData(newLyricData)
43 {
44     // nothing
45 }
46 
~SetLyricsCommand()47 SetLyricsCommand::~SetLyricsCommand()
48 {
49     for (std::vector<Event *>::iterator i = m_oldLyricEvents.begin();
50             i != m_oldLyricEvents.end(); ++i) {
51         delete *i;
52     }
53 }
54 
55 void
execute()56 SetLyricsCommand::execute()
57 {
58     // This and LyricEditDialog::unparse() are opposites that will
59     // need to be kept in sync with any changes to one another.  (They
60     // should really both be in a common lyric management class.)
61 
62     // first remove old lyric events
63 
64     Segment::iterator i = m_segment->begin();
65 
66     while (i != m_segment->end()) {
67 
68         Segment::iterator j = i;
69         ++j;
70 
71         if ((*i)->isa(Text::EventType)) {
72             std::string textType;
73             if ((*i)->get<String>(Text::TextTypePropertyName, textType) &&
74                 textType == Text::Lyric) {
75                 long verse = 0;
76                 (*i)->get<Int>(Text::LyricVersePropertyName, verse);
77                 if (verse == m_verse) {
78                     m_oldLyricEvents.push_back(new Event(**i));
79                     m_segment->erase(i);
80                 }
81             }
82         }
83 
84         i = j;
85     }
86 
87     // now parse the new string
88 
89 #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
90     QStringList barStrings =
91         m_newLyricData.split("/", Qt::KeepEmptyParts); // empties ok
92 #else
93     QStringList barStrings =
94         m_newLyricData.split("/", QString::KeepEmptyParts); // empties ok
95 #endif
96 
97     Composition *comp = m_segment->getComposition();
98     int barNo = comp->getBarNumber(m_segment->getStartTime());
99 
100     QStringList::Iterator bsi = barStrings.begin();
101     while ( bsi != barStrings.end() ) {
102         NOTATION_DEBUG << "Parsing lyrics for bar number " << barNo << ": \"" << *bsi << "\"";
103 
104         std::pair<timeT, timeT> barRange = comp->getBarRange(barNo++);
105         QString syllables = *bsi;
106         syllables.replace(QRegularExpression("\\[\\d+\\] "), " ");
107         syllables.replace(QRegularExpression("\n"), " ");
108 #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
109         QStringList syllableList = syllables.split(" ", Qt::SkipEmptyParts); // no empties
110 #else
111         QStringList syllableList = syllables.split(" ", QString::SkipEmptyParts); // no empties
112 #endif
113 
114         i = m_segment->findTime(barRange.first);
115         timeT laterThan = barRange.first - 1;
116 
117 	++bsi; // update here in order to check whether we are in the last bar string
118         bool isLastBSI = (bsi == barStrings.end());
119 
120         for (QStringList::Iterator ssi = syllableList.begin();
121                 ssi != syllableList.end(); ++ssi) {
122 
123 	    // As a rule, syllables belong to a certain bar. However, from the
124 	    // last barString list syllables may flow to the following bars.
125 	    // As a result, one may copy&paste the full syllable list of a verse.
126             while (m_segment->isBeforeEndMarker(i) &&
127                     (isLastBSI || (*i)->getAbsoluteTime() < barRange.second) &&
128                     (!(*i)->isa(Note::EventType) ||
129                      (*i)->getNotationAbsoluteTime() <= laterThan ||
130                      ((*i)->has(TIED_BACKWARD) &&
131                       (*i)->get
132                       <Bool>(TIED_BACKWARD)))) ++i;
133 
134             timeT time = m_segment->getEndMarkerTime();
135             timeT notationTime = time;
136             if (m_segment->isBeforeEndMarker(i)) {
137                 time = (*i)->getAbsoluteTime();
138                 notationTime = (*i)->getNotationAbsoluteTime();
139             }
140 
141             QString syllable = *ssi;
142             syllable.replace(QRegularExpression("~"), " ");
143             syllable = syllable.simplified();
144             if (syllable == "")
145                 continue;
146             laterThan = notationTime + 1;
147             if (syllable == ".")
148                 continue;
149 
150             NOTATION_DEBUG << "Syllable \"" << syllable << "\" at time " << time;
151 
152             Text text(qstrtostr(syllable), Text::Lyric);
153             Event *event = text.getAsEvent(time);
154             event->set<Int>(Text::LyricVersePropertyName, m_verse);
155             m_segment->insert(event);
156         }
157     }
158 
159     // The verse count changed
160     m_segment->invalidateVerseCount();
161 }
162 
163 void
unexecute()164 SetLyricsCommand::unexecute()
165 {
166     // Before we inserted the new lyric events (in execute()), we
167     // removed all the existing ones.  That means we know any lyric
168     // events found now must have been inserted by execute(), so we
169     // can safely remove them before restoring the old ones.
170 
171     Segment::iterator i = m_segment->begin();
172 
173     while (i != m_segment->end()) {
174 
175         Segment::iterator j = i;
176         ++j;
177 
178         if ((*i)->isa(Text::EventType)) {
179             std::string textType;
180             if ((*i)->get<String>(Text::TextTypePropertyName, textType) &&
181                 textType == Text::Lyric) {
182                 long verse = 0;
183                 (*i)->get<Int>(Text::LyricVersePropertyName, verse);
184                 if (verse == m_verse) {
185                     m_segment->erase(i);
186                 }
187             }
188         }
189 
190         i = j;
191     }
192 
193     // Now restore the old ones and clear out the vector.
194 
195     for (std::vector<Event *>::iterator i = m_oldLyricEvents.begin();
196             i != m_oldLyricEvents.end(); ++i) {
197         m_segment->insert(*i);
198     }
199 
200     m_oldLyricEvents.clear();
201 }
202 
203 }
204