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 "AddIndicationCommand.h"
20
21 #include "misc/Strings.h"
22 #include "base/Event.h"
23 #include "base/NotationTypes.h"
24 #include "base/Segment.h"
25 #include "base/SegmentNotationHelper.h"
26 #include "base/Selection.h"
27 #include "base/BaseProperties.h"
28 #include "document/BasicCommand.h"
29 #include "document/CommandRegistry.h"
30 #include "gui/editors/notation/NotationProperties.h"
31 #include <vector>
32 #include <QString>
33
34
35 namespace Rosegarden
36 {
37
getStandardIndications()38 static std::vector<std::string> getStandardIndications()
39 {
40 std::vector<std::string> v;
41 v.push_back(Indication::Slur);
42 v.push_back(Indication::PhrasingSlur);
43 v.push_back(Indication::Glissando);
44 v.push_back(Indication::Crescendo);
45 v.push_back(Indication::Decrescendo);
46 v.push_back(Indication::QuindicesimaUp);
47 v.push_back(Indication::OttavaUp);
48 v.push_back(Indication::OttavaDown);
49 v.push_back(Indication::QuindicesimaDown);
50 v.push_back(Indication::TrillLine);
51 v.push_back(Indication::FigParameterChord);
52 v.push_back(Indication::Figuration);
53 return v;
54 }
55
56 static const char *actionNames[] = {
57 "slur",
58 "phrasing_slur",
59 "glissando",
60 "crescendo",
61 "decrescendo",
62 "octave_2up",
63 "octave_up",
64 "octave_down",
65 "octave_2down",
66 "trill_line",
67 "parameter_chord",
68 "figuration"
69 };
70
71 void
registerCommand(CommandRegistry * r)72 AddIndicationCommand::registerCommand(CommandRegistry *r)
73 {
74 std::vector<std::string> standardIndications = getStandardIndications();
75
76 for (size_t i = 0; i < standardIndications.size(); ++i) {
77 r->registerCommand
78 (actionNames[i],
79 new ArgumentAndSelectionCommandBuilder<AddIndicationCommand>());
80 }
81 }
82
83 std::string
getArgument(QString actionName,CommandArgumentQuerier &)84 AddIndicationCommand::getArgument(QString actionName, CommandArgumentQuerier &)
85 {
86 std::vector<std::string> standardIndications = getStandardIndications();
87
88 for (size_t i = 0; i < standardIndications.size(); ++i) {
89 if (actionName == actionNames[i]) return standardIndications[i];
90 }
91 throw CommandCancelled();
92 }
93
AddIndicationCommand(std::string indicationType,EventSelection & selection)94 AddIndicationCommand::AddIndicationCommand(std::string indicationType,
95 EventSelection &selection) :
96 BasicCommand(getGlobalName(indicationType),
97 selection.getSegment(),
98 std::min(selection.getStartTime(), selection.getNotationStartTime()),
99 std::max(selection.getEndTime(), selection.getNotationEndTime())),
100 m_indicationType(indicationType),
101 m_indicationStart(selection.getNotationStartTime()),
102 m_indicationDuration(selection.getTotalNotationDuration()),
103 m_lastInsertedEvent(nullptr)
104 {
105 if (!canExecute()) {
106 throw CommandFailed
107 //!!! need to use text from trunk/src/gui/editors/notation/NotationView.cpp (but this requires an informal human-readable version of the indication name)
108 (qstrtostr(tr("Can't add identical overlapping indications")));
109 }
110 }
111
~AddIndicationCommand()112 AddIndicationCommand::~AddIndicationCommand()
113 {
114 // empty
115 }
116
117 EventSelection *
getSubsequentSelection()118 AddIndicationCommand::getSubsequentSelection()
119 {
120 EventSelection *selection = new EventSelection(getSegment());
121 //!!! EXPERIMENTAL: Let's see if any users notice and complain about this.
122 // The behavior up to now has been to leave the indication that was just
123 // created the only thing selected in the segment after adding it. The nice
124 // thing about that is that it shows you in blue what you just did, and
125 // primes you to do something else with the indication. However, I'm facing
126 // several hundred groups of notes I have to add slurs to, and using the
127 // keyboard for this is the only sane way to go. With the old behavior, I
128 // have to remember to hit escape in between groups to clear the selection
129 // away from the slur I created on the last iteration. If I don't, the next
130 // slur will begin in the same place the last one did. Having to hit escape
131 // in between iterations is awkward, and not sitting well with my fingers at
132 // all. I keep messing it up, and it made me crazy enough to hunt this down
133 // and make it stop. So let's turn the following line off, and see if there
134 // are any negative repercussions:
135 // selection->addEvent(getLastInsertedEvent());
136 return selection;
137 }
138
139 bool
canExecute()140 AddIndicationCommand::canExecute()
141 {
142 Segment &s(getSegment());
143
144 for (Segment::iterator i = s.begin(); s.isBeforeEndMarker(i); ++i) {
145
146 if ((*i)->getNotationAbsoluteTime() >=
147 m_indicationStart + m_indicationDuration) {
148 return true;
149 }
150
151 if ((*i)->isa(Indication::EventType)) {
152
153 try {
154 Indication indication(**i);
155
156 if ((*i)->getNotationAbsoluteTime() +
157 indication.getIndicationDuration() <= m_indicationStart)
158 continue;
159
160 std::string type = indication.getIndicationType();
161
162 if (type == m_indicationType) {
163 // for all indications (including slur), we reject an
164 // exact overlap
165 if ((*i)->getAbsoluteTime() == m_indicationStart &&
166 indication.getIndicationDuration() == m_indicationDuration) {
167 return false;
168 }
169 } else if (m_indicationType == Indication::Slur) {
170 continue;
171 }
172
173 // for non-slur indications we reject a partial
174 // overlap such as this one, if it's an overlap with
175 // an indication of the same "sort"
176
177 if (m_indicationType == Indication::Crescendo ||
178 m_indicationType == Indication::Decrescendo) {
179 if (type == Indication::Crescendo ||
180 type == Indication::Decrescendo) return false;
181 }
182
183 if (m_indicationType == Indication::QuindicesimaUp ||
184 m_indicationType == Indication::OttavaUp ||
185 m_indicationType == Indication::OttavaDown ||
186 m_indicationType == Indication::QuindicesimaDown) {
187 if (indication.isOttavaType()) return false;
188 }
189 } catch (...) {}
190 }
191 }
192
193 return true;
194 }
195
196 void
modifySegment()197 AddIndicationCommand::modifySegment()
198 {
199 SegmentNotationHelper helper(getSegment());
200 Segment::iterator i, j;
201 int actualSubordering = Indication::EventSubOrdering;
202
203 helper.segment().getTimeSlice(getStartTime(), i, j);
204 for (Segment::iterator k = i; k != j; ++k) {
205 if ((*k)->has(BaseProperties::IS_GRACE_NOTE)) {
206 // If a grace note is inserted before an indication, the
207 // subordering is minor than Indication::EventSubOrdering,
208 // therefore we have to decrement the subordering to insert
209 // the new indication before the grace note.
210 if ((*k)->getSubOrdering() <= actualSubordering) {
211 actualSubordering = (*k)->getSubOrdering() - 1;
212 }
213 }
214 }
215
216 Indication indication(m_indicationType, m_indicationDuration);
217 Event *e;
218
219 e = new Event(Indication::EventType, m_indicationStart, m_indicationDuration,
220 actualSubordering);
221 e->set<String>(Indication::IndicationTypePropertyName, m_indicationType);
222 e->set<Int>("indicationduration", m_indicationDuration);
223 helper.segment().insert(e);
224 m_lastInsertedEvent = e;
225
226 if (indication.isOttavaType()) {
227 for (Segment::iterator i = getSegment().findTime(getStartTime());
228 i != getSegment().findTime(getStartTime() + m_indicationDuration);
229 ++i) {
230 if ((*i)->isa(Note::EventType)) {
231 (*i)->setMaybe<Int>(NotationProperties::OTTAVA_SHIFT,
232 indication.getOttavaShift());
233 }
234 }
235 }
236 }
237
238 QString
getGlobalName(std::string indicationType)239 AddIndicationCommand::getGlobalName(std::string indicationType)
240 {
241 if (indicationType == Indication::Slur) {
242 return tr("Add S&lur");
243 } else if (indicationType == Indication::PhrasingSlur) {
244 return tr("Add &Phrasing Slur");
245 } else if (indicationType == Indication::QuindicesimaUp) {
246 return tr("Add Double-Octave Up");
247 } else if (indicationType == Indication::OttavaUp) {
248 return tr("Add Octave &Up");
249 } else if (indicationType == Indication::OttavaDown) {
250 return tr("Add Octave &Down");
251 } else if (indicationType == Indication::QuindicesimaDown) {
252 return tr("Add Double Octave Down");
253
254 // We used to generate these ones from the internal names plus
255 // caps, but that makes them untranslateable:
256 } else if (indicationType == Indication::Crescendo) {
257 return tr("Add &Crescendo");
258 } else if (indicationType == Indication::Decrescendo) {
259 return tr("Add &Decrescendo");
260 } else if (indicationType == Indication::Glissando) {
261 return tr("Add &Glissando");
262 } else if (indicationType == Indication::TrillLine) {
263 return tr("Add Tri&ll With Line");
264 } else if (indicationType == Indication::FigParameterChord) {
265 return tr("Add Parameter Chord");
266 } else if (indicationType == Indication::Figuration) {
267 return tr("Add Figuration");
268 }
269
270 QString n = tr("Add &%1%2").arg((char)toupper(indicationType[0])).arg(strtoqstr(indicationType.substr(1)));
271 return n;
272 }
273
274 }
275