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