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 #define RG_MODULE_STRING "[PasteEventsCommand]" 19 #define RG_NO_DEBUG_PRINT 1 20 21 #include "PasteEventsCommand.h" 22 23 #include "misc/Debug.h" 24 #include "base/Clipboard.h" 25 #include "base/Event.h" 26 #include "base/NotationTypes.h" 27 #include "base/Segment.h" 28 #include "base/SegmentNotationHelper.h" 29 #include "document/BasicCommand.h" 30 #include <QString> 31 #include "base/BaseProperties.h" MoveAcrossSegmentsCommand(Segment &,Segment & secondSegment,timeT newStartTime,bool notation,EventSelection & selection)32 33 34 namespace Rosegarden 35 { 36 37 using namespace BaseProperties; 38 39 PasteEventsCommand::PasteEventsCommand(Segment &segment, 40 Clipboard *clipboard, 41 timeT pasteTime, 42 PasteType pasteType) : 43 BasicCommand(getGlobalName(), segment, pasteTime, 44 getEffectiveEndTime(segment, clipboard, pasteTime)), 45 m_relayoutEndTime(getEndTime()), 46 m_clipboard(new Clipboard(*clipboard)), 47 m_pasteType(pasteType) 48 { 49 if (pasteType != OpenAndPaste) { 50 51 // paste clef or key -> relayout to end 52 53 if (clipboard->isSingleSegment()) { 54 55 Segment *s(clipboard->getSingleSegment()); 56 for (Segment::iterator i = s->begin(); i != s->end(); ++i) { 57 if ((*i)->isa(Clef::EventType) || 58 (*i)->isa(Key::EventType)) { 59 m_relayoutEndTime = s->getEndTime(); 60 break; 61 } 62 } 63 } 64 } 65 } 66 67 PasteEventsCommand::PasteEventsCommand(const QString& marking, 68 Clipboard *clipboard, 69 timeT pasteTime, 70 PasteType pasteType) : 71 BasicCommand(getGlobalName(), pasteTime, marking), 72 m_relayoutEndTime(getEndTime()), 73 m_clipboard(new Clipboard(*clipboard)), 74 m_pasteType(pasteType) 75 { 76 if (pasteType != OpenAndPaste) { 77 78 // paste clef or key -> relayout to end 79 80 if (clipboard->isSingleSegment()) { 81 82 Segment *s(clipboard->getSingleSegment()); 83 for (Segment::iterator i = s->begin(); i != s->end(); ++i) { 84 if ((*i)->isa(Clef::EventType) || 85 (*i)->isa(Key::EventType)) { 86 m_relayoutEndTime = s->getEndTime(); 87 break; 88 } 89 } 90 } 91 } 92 } 93 94 PasteEventsCommand::PasteEventsCommand(Segment &segment, 95 Clipboard *clipboard, 96 timeT pasteTime, 97 timeT pasteEndTime, 98 PasteType pasteType) : 99 BasicCommand(getGlobalName(), segment, pasteTime, pasteEndTime), 100 m_relayoutEndTime(getEndTime()), 101 m_clipboard(new Clipboard(*clipboard)), 102 m_pasteType(pasteType) 103 { 104 } 105 106 PasteEventsCommand::~PasteEventsCommand() 107 { 108 delete m_clipboard; 109 } 110 111 PasteEventsCommand::PasteTypeMap 112 113 PasteEventsCommand::getPasteTypes() 114 { 115 static PasteTypeMap types; 116 static bool haveTypes = false; 117 if (!haveTypes) { 118 types[Restricted] = 119 tr("Paste into an existing gap [\"restricted\"]"); 120 types[Simple] = 121 tr("Erase existing events to make room [\"simple\"]"); 122 types[OpenAndPaste] = 123 tr("Move existing events out of the way [\"open-n-paste\"]"); 124 types[NoteOverlay] = 125 tr("Overlay notes, tying against present notes [\"note-overlay\"]"); 126 types[MatrixOverlay] = 127 tr("Overlay notes, ignoring present notes [\"matrix-overlay\"]"); 128 } 129 return types; 130 } 131 132 timeT 133 PasteEventsCommand::getEffectiveEndTime(Segment &segment, 134 Clipboard *clipboard, 135 timeT pasteTime) 136 { 137 if (!clipboard->isSingleSegment()) { 138 RG_DEBUG << "PasteEventsCommand::getEffectiveEndTime: not single segment"; 139 return pasteTime; 140 } 141 142 RG_DEBUG << "PasteEventsCommand::getEffectiveEndTime: clipboard " 143 << clipboard->getSingleSegment()->getStartTime() 144 << " -> " 145 << clipboard->getSingleSegment()->getEndTime(); 146 147 timeT d = clipboard->getSingleSegment()->getEndTime() - 148 clipboard->getSingleSegment()->getStartTime(); 149 150 if (m_pasteType == OpenAndPaste) { 151 return segment.getEndTime() + d; 152 } else { 153 Segment::iterator i = segment.findTime(pasteTime + d); 154 if (i == segment.end()) 155 return segment.getEndTime(); 156 else 157 return (*i)->getAbsoluteTime(); 158 } 159 } 160 161 timeT 162 PasteEventsCommand::getRelayoutEndTime() 163 { 164 return m_relayoutEndTime; 165 } 166 167 bool 168 PasteEventsCommand::isPossible() 169 { 170 if (m_clipboard->isEmpty() || !m_clipboard->isSingleSegment()) { 171 return false; 172 } 173 174 Segment *source = m_clipboard->getSingleSegment(); 175 Segment *destination(&getSegment()); 176 177 timeT pasteTime = std::max(getStartTime(), destination->getStartTime()); 178 timeT origin = source->getStartTime(); 179 timeT duration = source->getEndTime() - origin; 180 181 if (m_pasteType == MatrixOverlay) { 182 // Compute the duration of the source without the rests at the end. 183 Segment::iterator last = source->end(); 184 for (--last ; last != source->begin(); --last) { 185 if (!(*last)->isa(Note::EventRestType)) { 186 break; 187 } 188 duration = (*last)->getAbsoluteTime() - origin; 189 } 190 } 191 192 RG_DEBUG << "PasteEventsCommand::isPossible: paste time is " << pasteTime << ", origin is " << origin << ", duration is " << duration; 193 194 if (pasteTime + duration > destination->getEndTime()) { 195 return false; 196 } 197 198 if (m_pasteType == OpenAndPaste && 199 destination->begin() != destination->end()) { 200 timeT lastEnd = destination->getEndTime(); 201 // Ignore the rests at the end of the destination segment. 202 Segment::iterator last = destination->end(); 203 for (--last ; last != destination->begin(); --last) { 204 if (!(*last)->isa(Note::EventRestType)) { 205 break; 206 } 207 lastEnd = (*last)->getAbsoluteTime(); 208 } 209 210 if (lastEnd + duration > destination->getEndTime()) { 211 return false; 212 } 213 } 214 215 if (m_pasteType != Restricted) { 216 return true; 217 } 218 219 SegmentNotationHelper helper(getSegment()); 220 return helper.removeRests(pasteTime, duration, true); 221 } 222 223 void 224 PasteEventsCommand::modifySegment() 225 { 226 RG_DEBUG << "PasteEventsCommand::modifySegment" << getSegment(); 227 228 if (!m_clipboard->isSingleSegment()) 229 return ; 230 231 Segment *source = m_clipboard->getSingleSegment(); 232 Segment *destination(&getSegment()); 233 234 RG_DEBUG << "segment source"; 235 RG_DEBUG << *source; 236 RG_DEBUG << "segment source end"; 237 RG_DEBUG << "segment destination"; 238 RG_DEBUG << *destination; 239 RG_DEBUG << "segment destination end"; 240 241 timeT destEndTime = destination->getEndTime(); 242 timeT pasteTime = std::max(getStartTime(), destination->getStartTime()); 243 timeT origin = source->getStartTime(); 244 timeT duration = source->getEndTime() - origin; 245 246 RG_DEBUG << "pasteTime" << pasteTime << "origin" << origin; 247 248 SegmentNotationHelper helper(*destination); 249 bool possible = helper.removeRests(pasteTime, duration, true); 250 if (! possible) RG_WARNING << "pasting when not possible"; 251 252 RG_DEBUG << "PasteEventsCommand::modifySegment() : paste type = " 253 << m_pasteType << " - pasteTime = " 254 << pasteTime << " - origin = " << origin; 255 256 // First check for group IDs, which we want to make unique in the 257 // copies in the destination segment 258 259 std::map<long, long> groupIdMap; 260 for (Segment::iterator i = source->begin(); i != source->end(); ++i) { 261 long groupId = -1; 262 if ((*i)->get 263 <Int>(BEAMED_GROUP_ID, groupId)) { 264 if (groupIdMap.find(groupId) == groupIdMap.end()) { 265 groupIdMap[groupId] = destination->getNextId(); 266 } 267 } 268 } 269 270 switch (m_pasteType) { 271 272 // Do some preliminary work to make space or whatever; 273 // we do the actual paste after this switch statement 274 // (except where individual cases do the work and return) 275 276 case Restricted: { 277 // removeRests() changes the duration destructively but the 278 // variable "duration" is used by normalizeRests() 279 timeT d = duration; 280 if (!helper.removeRests(pasteTime, d)) { 281 return; 282 } 283 break; 284 } 285 286 case Simple: 287 destination->erase(destination->findTime(pasteTime), 288 destination->findTime(pasteTime + duration)); 289 break; 290 291 case OpenAndPaste: { 292 timeT endTime = pasteTime + duration; 293 std::vector<Event *> copies, toErase; 294 for (Segment::iterator i = destination->findTime(pasteTime); 295 i != destination->end(); ++i) { 296 Event *e = (*i)->copyMoving(duration); 297 timeT myTime = 298 e->getAbsoluteTime() + e->getGreaterDuration() + duration; 299 300 toErase.push_back(*i); 301 302 if (e->isa(Note::EventRestType)) { 303 if (myTime > destEndTime) { 304 continue; 305 } 306 } 307 if (e->has(BEAMED_GROUP_ID)) { 308 e->set 309 <Int>(BEAMED_GROUP_ID, groupIdMap[e->get 310 <Int>(BEAMED_GROUP_ID)]); 311 } 312 if (myTime > endTime) { 313 endTime = myTime; 314 } 315 copies.push_back(e); 316 } 317 318 for (size_t i = 0; i < toErase.size(); ++i) { 319 destination->eraseSingle(toErase[i]); 320 } 321 322 for (size_t i = 0; i < copies.size(); ++i) { 323 destination->insert(copies[i]); 324 } 325 326 endTime = std::min(destEndTime, destination->getBarEndForTime(endTime)); 327 duration = endTime - pasteTime; 328 break; 329 } 330 331 case NoteOverlay: { 332 for (Segment::iterator i = source->begin(); i != source->end(); ++i) { 333 if ((*i)->isa(Note::EventRestType)) { 334 continue; 335 } 336 337 Event *e = (*i)->copyMoving(pasteTime - origin); 338 339 if (e->has(BEAMED_GROUP_ID)) { 340 e->set<Int>(BEAMED_GROUP_ID, 341 groupIdMap[e->get<Int>(BEAMED_GROUP_ID)]); 342 } 343 344 if ((*i)->isa(Note::EventType)) { 345 RG_DEBUG << "NoteOverlay insert" << 346 e->getNotationAbsoluteTime(); 347 // e is model event: we retain ownership of it 348 helper.insertNote(e); 349 delete e; 350 } else { 351 destination->insert(e); 352 } 353 } 354 355 RG_DEBUG << "segment after modify"; 356 RG_DEBUG << *destination; 357 RG_DEBUG << "segment after modify end"; 358 359 return; 360 } 361 362 case MatrixOverlay: 363 364 for (Segment::iterator i = source->begin(); i != source->end(); ++i) { 365 if ((*i)->isa(Note::EventRestType)) { 366 continue; 367 } 368 369 Event *e = (*i)->copyMoving(pasteTime - origin); 370 371 if (e->has(BEAMED_GROUP_TYPE) && 372 e->get 373 <String>(BEAMED_GROUP_TYPE) == GROUP_TYPE_BEAMED) { 374 e->unset(BEAMED_GROUP_ID); 375 e->unset(BEAMED_GROUP_TYPE); 376 } 377 378 if (e->has(BEAMED_GROUP_ID)) { 379 e->set 380 <Int>(BEAMED_GROUP_ID, groupIdMap[e->get 381 <Int>(BEAMED_GROUP_ID)]); 382 } 383 384 destination->insert(e); 385 } 386 387 timeT endTime = pasteTime + duration; 388 if (endTime > destEndTime) { 389 endTime = destEndTime; 390 } 391 392 destination->normalizeRests(pasteTime, endTime); 393 394 return ; 395 } 396 397 RG_DEBUG << "PasteEventsCommand::modifySegment() - inserting\n"; 398 399 for (Segment::iterator i = source->begin(); i != source->end(); ++i) { 400 Event *e = (*i)->copyMoving(pasteTime - origin); 401 if (e->has(BEAMED_GROUP_ID)) { 402 e->set 403 <Int>(BEAMED_GROUP_ID, groupIdMap[e->get 404 <Int>(BEAMED_GROUP_ID)]); 405 } 406 destination->insert(e); 407 } 408 409 destination->normalizeRests(pasteTime, pasteTime + duration); 410 } 411 412 } 413