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 "[SegmentResizer]" 19 20 #include "SegmentResizer.h" 21 22 #include "base/Event.h" 23 #include "misc/Debug.h" 24 #include "base/Composition.h" 25 #include "base/NotationTypes.h" 26 #include "base/Segment.h" 27 #include "base/Track.h" 28 #include "base/SnapGrid.h" 29 #include "commands/segment/AudioSegmentResizeFromStartCommand.h" 30 #include "commands/segment/AudioSegmentRescaleCommand.h" 31 #include "commands/segment/SegmentRescaleCommand.h" 32 #include "commands/segment/SegmentReconfigureCommand.h" 33 #include "commands/segment/SegmentResizeFromStartCommand.h" 34 #include "commands/segment/SegmentLinkToCopyCommand.h" 35 #include "CompositionModelImpl.h" 36 #include "CompositionView.h" 37 #include "document/RosegardenDocument.h" 38 #include "gui/general/BaseTool.h" 39 #include "gui/application/RosegardenMainWindow.h" 40 #include "gui/application/TransportStatus.h" 41 #include "gui/general/RosegardenScrollView.h" 42 #include "gui/seqmanager/SequenceManager.h" 43 #include "SegmentTool.h" 44 #include "document/Command.h" 45 #include "document/CommandHistory.h" 46 47 #include <QMessageBox> 48 #include <QCursor> 49 #include <QEvent> 50 #include <QPoint> 51 #include <QRect> 52 #include <QString> 53 #include <QMouseEvent> 54 55 56 namespace Rosegarden 57 { 58 59 60 QString SegmentResizer::ToolName() { return "segmentresizer"; } 61 62 SegmentResizer::SegmentResizer(CompositionView *c, RosegardenDocument *d) : 63 SegmentTool(c, d), 64 m_resizeStart(false) 65 { 66 //RG_DEBUG << "ctor"; 67 } 68 69 void SegmentResizer::ready() 70 { 71 m_canvas->viewport()->setCursor(Qt::SizeHorCursor); 72 setContextHelp2(); 73 } 74 75 void SegmentResizer::stow() 76 { 77 } 78 79 void SegmentResizer::mousePressEvent(QMouseEvent *e) 80 { 81 //RG_DEBUG << "mousePressEvent()"; 82 83 // Let the baseclass have a go. 84 SegmentTool::mousePressEvent(e); 85 86 // We only care about the left mouse button. 87 if (e->button() != Qt::LeftButton) 88 return; 89 90 // Can't rescale a segment while playing, so just refuse to 91 // resize or rescale. 92 if (RosegardenMainWindow::self()->getSequenceManager()-> 93 getTransportStatus() == PLAYING) 94 return; 95 96 // No need to propagate. 97 e->accept(); 98 99 QPoint pos = m_canvas->viewportToContents(e->pos()); 100 101 ChangingSegmentPtr item = m_canvas->getModel()->getSegmentAt(pos); 102 103 if (item) { 104 //RG_DEBUG << "mousePressEvent() - got item"; 105 setChangingSegment(item); 106 107 // Are we resizing from start or end? 108 if (item->rect().x() + item->rect().width() / 2 > pos.x()) { 109 m_resizeStart = true; 110 } else { 111 m_resizeStart = false; 112 } 113 114 m_canvas->getModel()->startChange(item, 115 m_resizeStart ? CompositionModelImpl::ChangeResizeFromStart : 116 CompositionModelImpl::ChangeResizeFromEnd); 117 118 setSnapTime(e, SnapGrid::SnapToBeat); 119 } 120 121 setContextHelp2(e->modifiers()); 122 } 123 124 void SegmentResizer::resizeAudioSegment( 125 Segment *segment, 126 double ratio, 127 timeT newStartTime, 128 timeT newEndTime) 129 { 130 try { 131 m_doc->getAudioFileManager().testAudioPath(); 132 } catch (const AudioFileManager::BadAudioPathException &) { 133 if (QMessageBox::warning(nullptr, tr("Warning"), //tr("Set audio file path"), 134 tr("The audio file path does not exist or is not writable.\nYou must set the audio file path to a valid directory in Document Properties before rescaling an audio file.\nWould you like to set it now?"), 135 QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Cancel) == 136 QMessageBox::Yes) { 137 RosegardenMainWindow::self()->slotOpenAudioPathSettings(); 138 } 139 } 140 141 AudioSegmentRescaleCommand *command = 142 new AudioSegmentRescaleCommand(m_doc, segment, ratio, 143 newStartTime, newEndTime); 144 145 // Progress Dialog 146 // Note: The label text and range will be set later as needed. 147 QProgressDialog progressDialog( 148 tr("Rescaling audio file..."), // labelText 149 tr("Cancel"), // cancelButtonText 150 0, 100, // min, max 151 RosegardenMainWindow::self()); // parent 152 153 progressDialog.setWindowTitle(tr("Rosegarden")); 154 progressDialog.setWindowModality(Qt::WindowModal); 155 // Don't want to auto close since this is a multi-step 156 // process. Any of the steps may set progress to 100. We 157 // will close anyway when this object goes out of scope. 158 progressDialog.setAutoClose(false); 159 // Just force the progress dialog up. 160 // Both Qt4 and Qt5 have bugs related to delayed showing of progress 161 // dialogs. In Qt4, the dialog sometimes won't show. In Qt5, KDE 162 // based distros might lock up. See Bug #1546. 163 progressDialog.show(); 164 165 command->setProgressDialog(&progressDialog); 166 167 CommandHistory::getInstance()->addCommand(command); 168 169 if (progressDialog.wasCanceled()) 170 return; 171 172 int fileId = command->getNewAudioFileId(); 173 if (fileId < 0) 174 return; 175 176 // Add to sequencer 177 RosegardenMainWindow::self()->slotAddAudioFile(fileId); 178 179 m_doc->getAudioFileManager().setProgressDialog(&progressDialog); 180 m_doc->getAudioFileManager().generatePreview(fileId); 181 } 182 183 void SegmentResizer::mouseReleaseEvent(QMouseEvent *e) 184 { 185 //RG_DEBUG << "mouseReleaseEvent()"; 186 187 // We only care about the left mouse button. 188 if (e->button() != Qt::LeftButton) 189 return; 190 191 // No need to propagate. 192 e->accept(); 193 194 bool rescale = (e->modifiers() & Qt::ControlModifier); 195 196 if (getChangingSegment()) { 197 198 Segment* segment = getChangingSegment()->getSegment(); 199 200 // We only want to snap the end that we were actually resizing. 201 202 timeT oldStartTime, oldEndTime; 203 204 oldStartTime = segment->getStartTime(); 205 oldEndTime = segment->getEndMarkerTime(false); 206 207 timeT newStartTime, newEndTime; 208 209 if (m_resizeStart) { 210 newStartTime = getChangingSegment()->getStartTime(m_canvas->grid()); 211 newEndTime = oldEndTime; 212 } else { 213 newEndTime = getChangingSegment()->getEndTime(m_canvas->grid()); 214 newStartTime = oldStartTime; 215 } 216 217 // If something has changed 218 if (newStartTime != oldStartTime || newEndTime != oldEndTime) { 219 220 if (newStartTime > newEndTime) std::swap(newStartTime, newEndTime); 221 222 if (rescale) { 223 224 if (segment->getType() == Segment::Audio) { 225 226 double ratio = 227 static_cast<double>(newEndTime - newStartTime) / 228 (oldEndTime - oldStartTime); 229 230 resizeAudioSegment(segment, ratio, newStartTime, newEndTime); 231 232 } else { 233 234 SegmentRescaleCommand *command = 235 new SegmentRescaleCommand(segment, 236 newEndTime - newStartTime, 237 oldEndTime - oldStartTime, 238 newStartTime); 239 CommandHistory::getInstance()->addCommand(command); 240 } 241 } else { 242 243 if (m_resizeStart) { 244 245 if (segment->getType() == Segment::Audio) { 246 CommandHistory::getInstance()->addCommand( 247 new AudioSegmentResizeFromStartCommand( 248 segment, newStartTime)); 249 } else { 250 SegmentLinkToCopyCommand* unlinkCmd = 251 new SegmentLinkToCopyCommand(segment); 252 SegmentResizeFromStartCommand* resizeCmd = 253 new SegmentResizeFromStartCommand(segment, newStartTime); 254 255 MacroCommand* command = new MacroCommand( 256 SegmentResizeFromStartCommand::getGlobalName()); 257 258 command->addCommand(unlinkCmd); 259 command->addCommand(resizeCmd); 260 261 CommandHistory::getInstance()->addCommand(command); 262 } 263 264 } else { 265 266 Composition &comp = m_doc->getComposition(); 267 268 SegmentReconfigureCommand *command = 269 new SegmentReconfigureCommand(tr("Resize Segment"), &comp); 270 271 int trackPos = getChangingSegment()->getTrackPos(m_canvas->grid()); 272 273 Track *track = comp.getTrackByPosition(trackPos); 274 275 command->addSegment(segment, 276 newStartTime, 277 newEndTime, 278 track->getId()); 279 CommandHistory::getInstance()->addCommand(command); 280 } 281 } 282 } 283 } 284 285 m_canvas->getModel()->endChange(); 286 // m_canvas->updateContents(); 287 m_canvas->update(); 288 289 //setChangeMade(false); 290 setChangingSegment(ChangingSegmentPtr()); 291 292 setContextHelp2(e->modifiers()); 293 } 294 295 int SegmentResizer::mouseMoveEvent(QMouseEvent *e) 296 { 297 //RG_DEBUG << "SegmentResizer::mouseMoveEvent"; 298 299 // No need to propagate. 300 e->accept(); 301 302 QPoint pos = m_canvas->viewportToContents(e->pos()); 303 304 setContextHelp2(e->modifiers()); 305 306 if (!getChangingSegment()) { 307 return NO_FOLLOW; 308 } 309 310 Segment* segment = getChangingSegment()->getSegment(); 311 312 // Don't allow Audio segments to resize yet 313 // 314 /*!!! 315 if (segment->getType() == Segment::Audio) 316 { 317 setChangingSegment(nullptr); 318 QMessageBox::information(m_canvas, 319 tr("You can't yet resize an audio segment!")); 320 return NO_FOLLOW; 321 } 322 */ 323 324 QRect oldRect = getChangingSegment()->rect(); 325 326 setSnapTime(e, SnapGrid::SnapToBeat); 327 328 // Convert X coord to time 329 timeT time = m_canvas->grid().snapX(pos.x()); 330 331 // Get the "snap size" of the grid at the current X coord. It can change 332 // with certain snap modes and different time signatures. 333 // ??? rename getSnapTime() -> getSnapTimeForX() 334 timeT snapSize = m_canvas->grid().getSnapTime(double(pos.x())); 335 336 // If snap to grid is off 337 if (snapSize == 0) { 338 // Use the shortest note duration. 339 snapSize = Note(Note::Shortest).getDuration(); 340 } 341 342 if (m_resizeStart) { 343 344 timeT itemEndTime = segment->getEndMarkerTime(); 345 346 timeT duration = itemEndTime - time; 347 348 //RG_DEBUG << "mouseMoveEvent() resize start : duration = " << duration 349 // << " - snap = " << snapSize 350 // << " - itemEndTime : " << itemEndTime 351 // << " - time : " << time; 352 353 timeT newStartTime = time; 354 355 if (duration < snapSize) { 356 357 // Make sure the segment can never be smaller than the snap size. 358 newStartTime = itemEndTime - snapSize; 359 360 } 361 362 // Change the size of the segment on the canvas. 363 getChangingSegment()->setStartTime(newStartTime, m_canvas->grid()); 364 365 } else { // resize end 366 367 timeT itemStartTime = segment->getStartTime(); 368 369 timeT duration = time - itemStartTime; 370 371 timeT newEndTime = time; 372 373 //RG_DEBUG << "mouseMoveEvent() resize end : duration = " << duration 374 // << " - snap = " << snapSize 375 // << " - itemStartTime : " << itemStartTime 376 // << " - time : " << time; 377 378 if (duration < snapSize) { 379 380 // Make sure the segment can't be resized smaller than the snap 381 // size. 382 newEndTime = itemStartTime + snapSize; 383 384 } 385 386 // Change the size of the segment on the canvas. 387 getChangingSegment()->setEndTime(newEndTime, m_canvas->grid()); 388 } 389 390 // Redraw the canvas 391 m_canvas->slotAllNeedRefresh(getChangingSegment()->rect() | oldRect); 392 393 return FOLLOW_HORIZONTAL; 394 } 395 396 void SegmentResizer::keyPressEvent(QKeyEvent *e) 397 { 398 // In case shift or ctrl were pressed, update the context help. 399 setContextHelp2(e->modifiers()); 400 } 401 402 void SegmentResizer::keyReleaseEvent(QKeyEvent *e) 403 { 404 // In case shift or ctrl were released, update the context help. 405 setContextHelp2(e->modifiers()); 406 } 407 408 void SegmentResizer::setContextHelp2(Qt::KeyboardModifiers modifiers) 409 { 410 const bool ctrl = ((modifiers & Qt::ControlModifier) != 0); 411 412 // If we're resizing something 413 if (getChangingSegment()) { 414 const bool shift = ((modifiers & Qt::ShiftModifier) != 0); 415 416 if (ctrl) { 417 // If shift isn't being held down 418 if (!shift) { 419 setContextHelp(tr("Hold Shift to avoid snapping to beat grid")); 420 } else { 421 clearContextHelp(); 422 } 423 } else { 424 // If shift isn't being held down 425 if (!shift) { 426 setContextHelp(tr("Hold Shift to avoid snapping to beat grid; hold Ctrl as well to rescale contents")); 427 } else { 428 setContextHelp(tr("Hold Ctrl to rescale contents")); 429 } 430 } 431 432 return; 433 } 434 435 if (!ctrl) { 436 setContextHelp(tr("Click and drag to resize a segment; hold Ctrl as well to rescale its contents")); 437 } else { 438 setContextHelp(tr("Click and drag to rescale segment")); 439 } 440 } 441 442 443 } 444