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