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