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 "[CompositionView]"
19 
20 #include "CompositionView.h"
21 
22 #include "misc/Debug.h"
23 #include "AudioPeaksThread.h"
24 #include "base/RulerScale.h"
25 #include "base/Segment.h"
26 #include "base/SnapGrid.h"
27 #include "base/Profiler.h"
28 #include "base/Instrument.h"
29 #include "base/InstrumentStaticSignals.h"
30 #include "CompositionColourCache.h"
31 #include "ChangingSegment.h"
32 #include "SegmentRect.h"
33 #include "AudioPreviewPainter.h"
34 #include "document/RosegardenDocument.h"
35 #include "misc/ConfigGroups.h"
36 #include "gui/general/GUIPalette.h"
37 #include "gui/general/IconLoader.h"
38 #include "gui/general/RosegardenScrollView.h"
39 #include "SegmentSelector.h"
40 #include "SegmentToolBox.h"
41 #include "sound/Midi.h"
42 
43 
44 #include <QBrush>
45 #include <QColor>
46 #include <QEvent>
47 #include <QFont>
48 #include <QFontMetrics>
49 #include <QMessageBox>
50 #include <QMouseEvent>
51 #include <QPainter>
52 #include <QPen>
53 #include <QPixmap>
54 #include <QPoint>
55 #include <QRect>
56 //#include <QScrollBar>
57 #include <QSettings>
58 #include <QSize>
59 #include <QString>
60 #include <QTimer>
61 #include <QVector>
62 #include <QWidget>
63 
64 #include <algorithm>  // std::min, std::max, std::find
65 
66 
67 namespace Rosegarden
68 {
69 
70 
CompositionView(RosegardenDocument * doc,CompositionModelImpl * model,QWidget * parent)71 CompositionView::CompositionView(RosegardenDocument *doc,
72                                  CompositionModelImpl *model,
73                                  QWidget *parent) :
74     RosegardenScrollView(parent),
75     m_model(model),
76     m_lastContentsX(0),
77     m_lastContentsY(0),
78     m_segmentsRefresh(0, 0, viewport()->width(), viewport()->height()),
79     //m_backgroundPixmap(),
80     m_trackDividerColor(GUIPalette::getColour(GUIPalette::TrackDivider)),
81     m_showPreviews(false),
82     m_showSegmentLabels(true),
83     m_segmentsLayer(viewport()->width(), viewport()->height()),
84     //m_audioPreview(),
85     //m_notationPreview(),
86     //m_updateTimer(),
87     m_deleteAudioPreviewsNeeded(false),
88     m_updateNeeded(false),
89     //m_updateRect()
90     m_drawTextFloat(false),
91     //m_textFloatText(),
92     //m_textFloatPos(),
93     m_pointerPos(0),
94     m_pointerPen(GUIPalette::getColour(GUIPalette::Pointer), 4),
95     //m_newSegmentRect(),
96     //m_newSegmentColor(),
97     //m_splitLinePos(),
98     m_drawGuides(false),
99     m_guideColor(GUIPalette::getColour(GUIPalette::MovementGuide)),
100     m_guideX(0),
101     m_guideY(0),
102     m_drawSelectionRect(false),
103     //m_selectionRect(),
104     m_toolBox(new SegmentToolBox(this, doc)),
105     m_currentTool(nullptr),
106     //m_toolContextHelp(),
107     m_contextHelpShown(false),
108     m_enableDrawing(true)
109 {
110     if (!doc)
111         return;
112     if (!m_model)
113         return;
114 
115     // Causing slow refresh issues on RG Main Window -- 10-12-2011 - JAS
116     // ??? This appears to have no effect positive or negative now (2015).
117     //viewport()->setAttribute(Qt::WA_PaintOnScreen);
118 
119     // Disable background erasing.  We redraw everything.  This would
120     // just waste time.  (It's hard to measure any improvement here.)
121     viewport()->setAttribute(Qt::WA_OpaquePaintEvent);
122 
123     QSettings settings;
124 
125     // If background textures are enabled, load the texture pixmap.
126     if (settings.value(
127             QString(GeneralOptionsConfigGroup) + "/backgroundtextures",
128             "true").toBool()) {
129 
130         m_backgroundPixmap = IconLoader::loadPixmap("bg-segmentcanvas");
131     }
132 
133     slotUpdateSize();
134 
135     // *** Connections
136 
137     connect(m_toolBox, &BaseToolBox::showContextHelp,
138             this, &CompositionView::slotToolHelpChanged);
139 
140     connect(m_model, SIGNAL(needUpdate()),
141             this, SLOT(slotUpdateAll()));
142     connect(m_model, SIGNAL(needUpdate(const QRect&)),
143             this, SLOT(slotAllNeedRefresh(const QRect&)));
144     connect(m_model, &CompositionModelImpl::needArtifactsUpdate,
145             this, &CompositionView::slotUpdateArtifacts);
146     connect(m_model, &CompositionModelImpl::needSizeUpdate,
147             this, &CompositionView::slotUpdateSize);
148 
149     connect(doc, &RosegardenDocument::docColoursChanged,
150             this, &CompositionView::slotRefreshColourCache);
151 
152     // recording-related signals
153     connect(doc, &RosegardenDocument::newMIDIRecordingSegment,
154             this, &CompositionView::slotNewMIDIRecordingSegment);
155     connect(doc, &RosegardenDocument::newAudioRecordingSegment,
156             this, &CompositionView::slotNewAudioRecordingSegment);
157     connect(doc, &RosegardenDocument::stoppedAudioRecording,
158             this, &CompositionView::slotStoppedRecording);
159     connect(doc, &RosegardenDocument::stoppedMIDIRecording,
160             this, &CompositionView::slotStoppedRecording);
161     connect(doc, &RosegardenDocument::audioFileFinalized,
162             m_model, &CompositionModelImpl::slotAudioFileFinalized);
163 
164     // Connect for high-frequency control change notifications.
165     connect(Instrument::getStaticSignals().data(),
166                 &InstrumentStaticSignals::controlChange,
167             this, &CompositionView::slotControlChange);
168 
169     // Audio Preview Thread
170     m_model->setAudioPeaksThread(&doc->getAudioPeaksThread());
171     doc->getAudioPeaksThread().setEmptyQueueListener(this);
172 
173     // Update timer
174     connect(&m_updateTimer, &QTimer::timeout, this, &CompositionView::slotUpdateTimer);
175     m_updateTimer.start(100);
176 
177     // Init the halo offsets table.
178     m_haloOffsets.push_back(QPoint(-1,-1));
179     m_haloOffsets.push_back(QPoint(-1, 0));
180     m_haloOffsets.push_back(QPoint(-1,+1));
181     m_haloOffsets.push_back(QPoint( 0,-1));
182     m_haloOffsets.push_back(QPoint( 0,+1));
183     m_haloOffsets.push_back(QPoint(+1,-1));
184     m_haloOffsets.push_back(QPoint(+1, 0));
185     m_haloOffsets.push_back(QPoint(+1,+1));
186 
187     // The various tools expect this.
188     setMouseTracking(true);
189     setFocusPolicy(Qt::StrongFocus);
190 
191     // *** Debugging
192 
193     settings.beginGroup("Performance_Testing");
194 
195     m_enableDrawing = (settings.value("CompositionView", 1).toInt() != 0);
196 
197     // Write it to the file to make it easier to find.
198     settings.setValue("CompositionView", m_enableDrawing ? 1 : 0);
199 
200     settings.endGroup();
201 }
202 
endAudioPreviewGeneration()203 void CompositionView::endAudioPreviewGeneration()
204 {
205     if (m_model) {
206         m_model->setAudioPeaksThread(nullptr);
207     }
208 }
209 
slotUpdateSize()210 void CompositionView::slotUpdateSize()
211 {
212     const int height =
213             std::max(m_model->getCompositionHeight(), viewport()->height());
214 
215     const RulerScale *rulerScale = grid().getRulerScale();
216     const int compositionWidth = (int)ceil(rulerScale->getTotalWidth());
217     const int minWidth = sizeHint().width();
218     const int width = std::max(compositionWidth, minWidth);
219 
220     // If the width or height need to change...
221     if (contentsWidth() != width  ||  contentsHeight() != height)
222         resizeContents(width, height);
223 }
224 
drawSelectionRectPos1(const QPoint & pos)225 void CompositionView::drawSelectionRectPos1(const QPoint &pos)
226 {
227     m_drawSelectionRect = true;
228 
229     // Update the selection rect used for drawing the rubber band.
230     m_selectionRect.setRect(pos.x(), pos.y(), 0, 0);
231     // Pass on to CompositionModelImpl which will adjust the selected
232     // segments and redraw them.
233     m_model->setSelectionRect(m_selectionRect);
234 }
235 
drawSelectionRectPos2(const QPoint & pos)236 void CompositionView::drawSelectionRectPos2(const QPoint &pos)
237 {
238     m_drawSelectionRect = true;
239 
240     // Update the selection rect used for drawing the rubber band.
241     m_selectionRect.setBottomRight(pos);
242 
243     // Pass on to CompositionModelImpl which will adjust the selected
244     // segments and redraw them.
245     m_model->setSelectionRect(m_selectionRect);
246 }
247 
hideSelectionRect()248 void CompositionView::hideSelectionRect()
249 {
250     // If it's already hidden, bail.
251     if (!m_drawSelectionRect)
252         return;
253 
254     m_drawSelectionRect = false;
255 
256     // Redraw the selection rect.
257     slotUpdateArtifacts();
258 }
259 
deleteCachedPreviews()260 void CompositionView::deleteCachedPreviews()
261 {
262     m_model->deleteCachedPreviews();
263 }
264 
265 SegmentSelection
getSelectedSegments()266 CompositionView::getSelectedSegments()
267 {
268     return m_model->getSelectedSegments();
269 }
270 
updateSelectedSegments()271 void CompositionView::updateSelectedSegments()
272 {
273     if (!m_model->haveSelection())
274         return;
275 
276     updateContents(m_model->getSelectedSegmentsRect());
277 }
278 
setTool(const QString & toolName)279 void CompositionView::setTool(const QString &toolName)
280 {
281     if (m_currentTool)
282         m_currentTool->stow();
283 
284     m_toolContextHelp = "";
285 
286     m_currentTool = m_toolBox->getTool(toolName);
287 
288     if (!m_currentTool) {
289         QMessageBox::critical(nullptr, tr("Rosegarden"), QString("CompositionView::setTool() : unknown tool name %1").arg(toolName));
290         return;
291     }
292 
293     m_currentTool->ready();
294 }
295 
selectSegments(const SegmentSelection & segments)296 void CompositionView::selectSegments(const SegmentSelection &segments)
297 {
298     m_model->selectSegments(segments);
299 }
300 
drawSplitLine(int x,int y)301 void CompositionView::drawSplitLine(int x, int y)
302 {
303     m_splitLinePos.setX(x);
304     m_splitLinePos.setY(y);
305     viewport()->update();
306 }
307 
hideSplitLine()308 void CompositionView::hideSplitLine()
309 {
310     m_splitLinePos.setX( -1);
311     m_splitLinePos.setY( -1);
312     viewport()->update();
313 }
314 
slotExternalWheelEvent(QWheelEvent * e)315 void CompositionView::slotExternalWheelEvent(QWheelEvent *e)
316 {
317     // Pass it up to RosegardenScrollView.
318     wheelEvent(e);
319     // We've got this.  No need to propagate.
320     e->accept();
321 }
322 
slotUpdateAll()323 void CompositionView::slotUpdateAll()
324 {
325     Profiler profiler("CompositionView::slotUpdateAll()");
326 
327     // ??? This routine gets hit really hard when recording.
328     //     Just holding down a single note results in 50 calls
329     //     per second.
330 
331     // Redraw the segments and artifacts.
332 
333 #if 1
334     // Since we might be reacting to a user change (e.g. zoom), we
335     // need to react immediately.
336     updateAll();
337 #else
338     QRect viewportContentsRect(
339             contentsX(), contentsY(),
340             viewport()->rect().width(), viewport()->rect().height());
341 
342     // Redraw the segments and artifacts.
343     // Uses 55% less CPU than updateAll() when recording.  But introduces a
344     // delay of up to 1/10 second for user interaction like zoom.  We need to
345     // untangle user updates and automatic updates (like recording) so that we
346     // can treat them differently.
347     slotAllNeedRefresh(viewportContentsRect);
348 #endif
349 }
350 
slotUpdateTimer()351 void CompositionView::slotUpdateTimer()
352 {
353     if (m_deleteAudioPreviewsNeeded) {
354         m_model->deleteCachedAudioPreviews();
355         m_deleteAudioPreviewsNeeded = false;
356     }
357 
358     if (m_updateNeeded) {
359         updateAll2(m_updateRect);
360         m_updateNeeded = false;
361     }
362 }
363 
updateAll2(const QRect & rect)364 void CompositionView::updateAll2(const QRect &rect)
365 {
366     Profiler profiler("CompositionView::updateAll2(rect)");
367 
368     //RG_DEBUG << "updateAll2() rect:" << rect << ", valid:" << rect.isValid();
369 
370     // If the incoming rect is invalid, just do everything.
371     // ??? This is probably not necessary as an invalid rect
372     //     cannot get in here.  See slotAllNeedRefresh(rect).
373     if (!rect.isValid()) {
374         updateAll();
375         return;
376     }
377 
378     segmentsNeedRefresh(rect);
379     updateArtifacts(rect);
380 }
381 
slotAllNeedRefresh(const QRect & rect)382 void CompositionView::slotAllNeedRefresh(const QRect &rect)
383 {
384     // Bail if drawing is turned off in the settings.
385     if (!m_enableDrawing)
386         return;
387 
388     // This one gets hit pretty hard while recording.
389     Profiler profiler("CompositionView::slotAllNeedRefresh(const QRect &rect)");
390 
391     // Note: This new approach normalizes the incoming rect.  This means
392     //   that it will never trigger a full refresh given an invalid rect
393     //   like it used to.  See updateAll2(rect).
394     if (!rect.isValid())
395         RG_DEBUG << "slotAllNeedRefresh(rect): Invalid rect";
396 
397     // If an update is now needed, set m_updateRect, otherwise accumulate it.
398     if (!m_updateNeeded) {
399         // Let slotUpdateTimer() know an update is needed next time.
400         m_updateNeeded = true;
401         m_updateRect = rect.normalized();
402     } else {
403         // Accumulate the update rect
404         m_updateRect |= rect.normalized();
405     }
406 }
407 
slotRefreshColourCache()408 void CompositionView::slotRefreshColourCache()
409 {
410     CompositionColourCache::getInstance()->init();
411     updateAll();
412 }
413 
slotNewMIDIRecordingSegment(Segment * s)414 void CompositionView::slotNewMIDIRecordingSegment(Segment *s)
415 {
416     m_model->addRecordingItem(ChangingSegmentPtr(
417             new ChangingSegment(*s, QRect())));
418 }
419 
slotNewAudioRecordingSegment(Segment * s)420 void CompositionView::slotNewAudioRecordingSegment(Segment *s)
421 {
422     m_model->addRecordingItem(ChangingSegmentPtr(
423             new ChangingSegment(*s, QRect())));
424 }
425 
slotStoppedRecording()426 void CompositionView::slotStoppedRecording()
427 {
428     m_model->clearRecordingItems();
429 }
430 
resizeEvent(QResizeEvent * e)431 void CompositionView::resizeEvent(QResizeEvent *e)
432 {
433     RosegardenScrollView::resizeEvent(e);
434 
435     // Resize the contents if needed.
436     slotUpdateSize();
437 
438     // If the viewport has grown larger than the segments layer
439     if (e->size().width() > m_segmentsLayer.width()  ||
440         e->size().height() > m_segmentsLayer.height()) {
441 
442         // Reallocate the segments layer
443         m_segmentsLayer = QPixmap(e->size().width(), e->size().height());
444     }
445 
446     updateAll();
447 }
448 
paintEvent(QPaintEvent *)449 void CompositionView::paintEvent(QPaintEvent *)
450 {
451     Profiler profiler("CompositionView::paintEvent()");
452 
453     // Just redraw the entire viewport.  Turns out that for the most
454     // critical use case, recording, this is actually slightly faster
455     // than trying to be frugal about drawing small parts of the viewport.
456     // The code is certainly easier to read.
457     drawAll();
458 }
459 
drawAll()460 void CompositionView::drawAll()
461 {
462     Profiler profiler("CompositionView::drawAll()");
463 
464     // Scroll and refresh the segments layer.
465     scrollSegmentsLayer();
466 
467     // ??? Try putting the artifacts on their own layer pixmap.  This might
468     //     simplify the code a bit.  Should also allow us to avoid
469     //     redrawing the artifacts just because the segments need to
470     //     be updated.  This might help the recording use case a little.
471     // ??? There are two key use cases that need to be optimized.
472     //     The first is recording.  This is the most important as it uses
473     //     a lot of CPU (and shouldn't).  The second is auto-scrolling.
474     //     It's not quite as important since it is relatively rare.
475 
476     // Copy the entire segments layer to the viewport
477 
478     QRect viewportRect = viewport()->rect();
479     // Copy the segments to the viewport.
480     QPainter viewportPainter(viewport());
481     viewportPainter.drawPixmap(
482             viewportRect, m_segmentsLayer, viewportRect);
483     viewportPainter.end();
484 
485     // Redraw all of the artifacts on the viewport.
486 
487     drawArtifacts();
488 }
489 
scrollSegmentsLayer()490 void CompositionView::scrollSegmentsLayer()
491 {
492     Profiler profiler("CompositionView::scrollSegmentsLayer()");
493 
494     // Portion of the segments layer that needs to be redrawn.
495     QRect refreshRect = m_segmentsRefresh;
496 
497     const int w = viewport()->width();
498     const int h = viewport()->height();
499     const int cx = contentsX();
500     const int cy = contentsY();
501     // The entire viewport in contents coords.
502     const QRect viewportContentsRect(cx, cy, w, h);
503 
504     bool scroll = (cx != m_lastContentsX || cy != m_lastContentsY);
505 
506     if (scroll) {
507 
508         // ??? All this scroll optimization saves about 7% cpu on my
509         //     machine when auto-scrolling the BWV1048 example.  Doesn't
510         //     seem worth the extra code.
511 
512         if (refreshRect.isValid()) {
513 
514             // If we've scrolled and there was an existing refresh
515             // rect, we can't be sure whether the refresh rect
516             // predated or postdated the internal update of scroll
517             // location.  Cut our losses and refresh everything.
518 
519             refreshRect = viewportContentsRect;
520 
521         } else {
522 
523             // No existing refresh rect: we only need to handle the
524             // scroll.
525 
526             // Horizontal scroll distance
527             int dx = m_lastContentsX - cx;
528 
529             // If we're scrolling horizontally
530             if (dx != 0) {
531 
532                 // If we're scrolling less than the entire viewport
533                 if (abs(dx) < w) {
534 
535                     // Scroll the segments layer sideways
536                     m_segmentsLayer.scroll(dx, 0, m_segmentsLayer.rect());
537 
538                     // Add the part that was exposed to the refreshRect
539                     if (dx < 0) {
540                         refreshRect |= QRect(cx + w + dx, cy, -dx, h);
541                     } else {
542                         refreshRect |= QRect(cx, cy, dx, h);
543                     }
544 
545                 } else {  // We've scrolled more than the entire viewport
546 
547                     // Refresh everything
548                     refreshRect = viewportContentsRect;
549                 }
550             }
551 
552             // Vertical scroll distance
553             int dy = m_lastContentsY - cy;
554 
555             // If we're scrolling vertically and the sideways scroll didn't
556             // result in a need to refresh everything,
557             if (dy != 0  &&  refreshRect != viewportContentsRect) {
558 
559                 // If we're scrolling less than the entire viewport
560                 if (abs(dy) < h) {
561 
562                     // Scroll the segments layer vertically
563                     m_segmentsLayer.scroll(0, dy, m_segmentsLayer.rect());
564 
565                     // Add the part that was exposed to the refreshRect
566                     if (dy < 0) {
567                         refreshRect |= QRect(cx, cy + h + dy, w, -dy);
568                     } else {
569                         refreshRect |= QRect(cx, cy, w, dy);
570                     }
571 
572                 } else {  // We've scrolled more than the entire viewport
573 
574                     // Refresh everything
575                     refreshRect = viewportContentsRect;
576                 }
577             }
578         }
579     }
580 
581     m_lastContentsX = cx;
582     m_lastContentsY = cy;
583 
584     // If we need to redraw the segments layer, do so.
585     if (refreshRect.isValid()) {
586         // Refresh the segments layer
587         drawSegments(refreshRect);
588         m_segmentsRefresh = QRect();
589     }
590 }
591 
drawSegments(const QRect & clipRect)592 void CompositionView::drawSegments(const QRect &clipRect)
593 {
594     Profiler profiler("CompositionView::drawSegments(clipRect)");
595 
596     QPainter segmentsLayerPainter(&m_segmentsLayer);
597     // Switch to contents coords.
598     segmentsLayerPainter.translate(-contentsX(), -contentsY());
599 
600     // *** Draw the background
601 
602     if (!m_backgroundPixmap.isNull()) {
603         QPoint offset(
604                 clipRect.x() % m_backgroundPixmap.height(),
605                 clipRect.y() % m_backgroundPixmap.width());
606         segmentsLayerPainter.drawTiledPixmap(
607                 clipRect, m_backgroundPixmap, offset);
608     } else {
609         segmentsLayerPainter.eraseRect(clipRect);
610     }
611 
612     // *** Draw the track dividers
613 
614     drawTrackDividers(&segmentsLayerPainter, clipRect);
615 
616     // *** Get Segment and Preview Rectangles
617 
618     // Assume we aren't going to show previews.
619     CompositionModelImpl::NotationPreviewRanges *notationPreview = nullptr;
620     CompositionModelImpl::AudioPreviews *audioPreview = nullptr;
621 
622     if (m_showPreviews) {
623         // Clear the previews.
624         // ??? Move this clearing into CompositionModelImpl::getSegmentRects()?
625         m_notationPreview.clear();
626         m_audioPreview.clear();
627 
628         // Indicate that we want previews.
629         notationPreview = &m_notationPreview;
630         audioPreview = &m_audioPreview;
631     }
632 
633     CompositionModelImpl::SegmentRects segmentRects;
634 
635     // Fetch segment rectangles and (optionally) previews
636     m_model->getSegmentRects(clipRect, &segmentRects, notationPreview, audioPreview);
637 
638     // *** Draw Segment Rectangles
639 
640     // For each segment rectangle, draw it
641     for (CompositionModelImpl::SegmentRects::const_iterator i = segmentRects.begin();
642          i != segmentRects.end(); ++i) {
643 
644         drawCompRect(&segmentsLayerPainter, clipRect, *i);
645     }
646 
647     drawIntersections(&segmentsLayerPainter, clipRect, segmentRects);
648 
649     // *** Draw Segment Previews
650 
651     if (m_showPreviews) {
652         // We'll be modifying the transform.  save()/restore() to be safe.
653         segmentsLayerPainter.save();
654 
655         // Audio Previews
656 
657         drawAudioPreviews(&segmentsLayerPainter, clipRect);
658 
659         // Notation Previews
660 
661         QColor defaultColor = CompositionColourCache::getInstance()->SegmentInternalPreview;
662 
663         // For each segment's preview range
664         for (CompositionModelImpl::NotationPreviewRanges::const_iterator notationPreviewIter =
665                  m_notationPreview.begin();
666              notationPreviewIter != m_notationPreview.end();
667              ++notationPreviewIter) {
668 
669             const CompositionModelImpl::NotationPreviewRange &notationPreviewRange =
670                     *notationPreviewIter;
671 
672             QColor color = notationPreviewRange.color.isValid() ?
673                            notationPreviewRange.color : defaultColor;
674 
675             // translate() calls are cumulative, so we need to be able to get
676             // back to where we were.  Note that resetTransform() would be
677             // too extreme as it would reverse the contents translation that
678             // is present in segmentsLayerPainter at this point in time.
679             segmentsLayerPainter.save();
680             // Adjust the coordinate system to account for any segment
681             // move offset and the vertical position of the segment.
682             segmentsLayerPainter.translate(
683                     notationPreviewRange.moveXOffset,
684                     notationPreviewRange.segmentTop);
685 
686             // For each event rectangle, draw it.
687             for (CompositionModelImpl::NotationPreview::const_iterator i =
688                      notationPreviewRange.begin;
689                  i != notationPreviewRange.end;
690                  ++i) {
691 
692                 QRect eventRect = *i;
693                 // Make the rect thicker vertically to match the old
694                 // appearance.  Without this, the rect is thin, which gives
695                 // slightly more information.
696                 // Also make the rect longer to close the gaps between the
697                 // events.  This is in keeping with the old appearance.
698                 eventRect.adjust(0,0,1,1);
699 
700                 // Per the Qt docs, fillRect() should be faster than
701                 // drawRect().  In practice, a small improvement was noted.
702                 segmentsLayerPainter.fillRect(eventRect, color);
703             }
704             // Restore the transformation.
705             segmentsLayerPainter.restore();
706         }
707 
708         segmentsLayerPainter.restore();
709     }
710 
711     // *** Draw Segment Labels
712 
713     if (m_showSegmentLabels) {
714         // For each segment rect, draw the label
715         for (CompositionModelImpl::SegmentRects::const_iterator i = segmentRects.begin();
716              i != segmentRects.end(); ++i) {
717 
718             drawCompRectLabel(&segmentsLayerPainter, *i);
719         }
720     }
721 }
722 
drawArtifacts()723 void CompositionView::drawArtifacts()
724 {
725     Profiler profiler("CompositionView::drawArtifacts()");
726 
727     // The entire viewport in contents coords.
728     QRect viewportContentsRect(
729             contentsX(), contentsY(),
730             viewport()->rect().width(), viewport()->rect().height());
731 
732     QPainter viewportPainter(viewport());
733     // Switch to contents coords.
734     viewportPainter.translate(-contentsX(), -contentsY());
735 
736     //
737     // Playback Pointer
738     //
739     viewportPainter.setPen(m_pointerPen);
740     viewportPainter.drawLine(m_pointerPos, 0, m_pointerPos, contentsHeight() - 1);
741 
742     //
743     // New Segment (SegmentPencil)
744     //
745     if (m_newSegmentRect.isValid()  &&
746         m_newSegmentRect.intersects(viewportContentsRect)) {
747 
748         viewportPainter.setPen(CompositionColourCache::getInstance()->SegmentBorder);
749         viewportPainter.setBrush(m_newSegmentColor);
750         drawRect(&viewportPainter, viewportContentsRect, m_newSegmentRect);
751     }
752 
753     //
754     // Tool guides (crosshairs)
755     //
756     if (m_drawGuides) {
757         viewportPainter.setPen(m_guideColor);
758         // Vertical Guide
759         viewportPainter.drawLine(m_guideX, 0, m_guideX, contentsHeight() - 1);
760         // Horizontal Guide
761         viewportPainter.drawLine(0, m_guideY, contentsWidth() - 1, m_guideY);
762     }
763 
764     //
765     // Selection Rect (rubber band)
766     //
767     if (m_drawSelectionRect) {
768         viewportPainter.save();
769 
770         viewportPainter.setPen(CompositionColourCache::getInstance()->SegmentBorder);
771         viewportPainter.setBrush(Qt::NoBrush);
772         viewportPainter.drawRect(m_selectionRect);
773 
774         viewportPainter.restore();
775     }
776 
777     //
778     // Floating Text
779     //
780     if (m_drawTextFloat)
781         drawTextFloat(&viewportPainter);
782 
783     //
784     // Split line
785     //
786     if (m_splitLinePos.x() > 0) {
787         viewportPainter.setPen(m_guideColor);
788         viewportPainter.drawLine(m_splitLinePos.x(), m_splitLinePos.y(),
789                     m_splitLinePos.x(), m_splitLinePos.y() + m_model->grid().getYSnap());
790     }
791 }
792 
drawTrackDividers(QPainter * segmentsLayerPainter,const QRect & clipRect)793 void CompositionView::drawTrackDividers(
794         QPainter *segmentsLayerPainter, const QRect &clipRect)
795 {
796     // Fetch track Y coordinates within the clip rectangle.  We expand the
797     // clip rectangle slightly because we are drawing a rather wide track
798     // divider, so we need enough divider coords to do the drawing even
799     // though the center of the divider might be slightly outside of the
800     // viewport.
801     CompositionModelImpl::YCoordVector trackYCoords =
802             m_model->getTrackYCoords(clipRect.adjusted(0,-1,0,+1));
803 
804     // Nothing to do?  Bail.
805     if (trackYCoords.empty())
806         return;
807 
808     const int left = clipRect.left();
809     const int right = clipRect.right();
810 
811     segmentsLayerPainter->save();
812 
813     // For each track Y coordinate
814     for (CompositionModelImpl::YCoordVector::const_iterator yi =
815              trackYCoords.begin();
816          yi != trackYCoords.end();
817          ++yi) {
818 
819         int y = *yi - 2;
820         segmentsLayerPainter->setPen(m_trackDividerColor);
821         segmentsLayerPainter->drawLine(left, y, right, y);
822 
823         ++y;
824         segmentsLayerPainter->setPen(m_trackDividerColor.lighter());
825         segmentsLayerPainter->drawLine(left, y, right, y);
826 
827         ++y;
828         segmentsLayerPainter->setPen(m_trackDividerColor.lighter());
829         segmentsLayerPainter->drawLine(left, y, right, y);
830 
831         ++y;
832         segmentsLayerPainter->setPen(m_trackDividerColor);
833         segmentsLayerPainter->drawLine(left, y, right, y);
834     }
835 
836     segmentsLayerPainter->restore();
837 }
838 
drawImage(QPainter * painter,QPoint dest,const CompositionModelImpl::QImageVector & tileVector,QRect source)839 void CompositionView::drawImage(
840         QPainter *painter,
841         QPoint dest, const CompositionModelImpl::QImageVector &tileVector,
842         QRect source)
843 {
844     // ??? This is an awful lot of complexity to accommodate the tiling
845     //     of the audio previews.  Why are they tiled?  Can they be
846     //     untiled so that all of this would reduce to a single drawImage()?
847 
848     // No tiles?  Bail.
849     if (tileVector.empty())
850         return;
851 
852     int tileWidth = AudioPreviewPainter::tileWidth();
853 
854     int firstTile = source.left() / tileWidth;
855     int firstTileStartX = source.left() % tileWidth;
856     int lastTile = source.right() / tileWidth;
857     int lastTileStopX = source.right() % tileWidth;
858 
859     if (firstTile < 0  ||  lastTile < 0)
860         return;
861 
862     // Most likely, the source rect needs normalizing.
863     if (lastTile < firstTile)
864         return;
865 
866     // If we are starting beyond the available tiles, bail.
867     if (firstTile >= (int)tileVector.size())
868         return;
869 
870     // If we are ending beyond the available tiles
871     if (lastTile >= (int)tileVector.size()) {
872         // Stop at the last.
873         lastTile = (int)tileVector.size() - 1;
874         lastTileStopX = tileWidth - 1;
875     }
876 
877     // Special case: Drawing from a single tile.
878     if (firstTile == lastTile) {
879         QRect tileSource = source;  // get top, bottom, and width
880         tileSource.setLeft(source.left() - tileWidth * firstTile);
881         painter->drawImage(dest, tileVector[firstTile], tileSource);
882         return;
883     }
884 
885     // *** First Tile
886 
887     QRect firstTileSource = source;  // get the top and bottom
888     firstTileSource.setLeft(firstTileStartX);
889     firstTileSource.setRight(tileWidth - 1);
890     painter->drawImage(dest, tileVector[firstTile], firstTileSource);
891     dest.setX(dest.x() + firstTileSource.width());
892 
893     // *** Middle Tile(s)
894 
895     int firstMiddleTile = firstTile + 1;
896     int lastMiddleTile = lastTile - 1;
897 
898     // An entire tile
899     QRect tileRect(source.x(), source.y(), tileWidth, source.height());
900 
901     // for each middle tile
902     for (int tile = firstMiddleTile; tile <= lastMiddleTile; ++tile) {
903         // draw the middle tile entirely
904         painter->drawImage(dest, tileVector[tile], tileRect);
905         dest.setX(dest.x() + tileRect.width());
906     }
907 
908     // *** Last Tile
909 
910     QRect lastTileSource = source;  // get the top and bottom
911     lastTileSource.setLeft(0);
912     lastTileSource.setRight(lastTileStopX);
913     painter->drawImage(dest, tileVector[lastTile], lastTileSource);
914 }
915 
drawAudioPreviews(QPainter * segmentsLayerPainter,const QRect & clipRect)916 void CompositionView::drawAudioPreviews(
917         QPainter *segmentsLayerPainter, const QRect &clipRect)
918 {
919     Profiler profiler("CompositionView::drawAudioPreviews");
920 
921     // for each audio preview
922     for (CompositionModelImpl::AudioPreviews::const_iterator audioPreviewIter = m_audioPreview.begin();
923          audioPreviewIter != m_audioPreview.end();
924          ++audioPreviewIter) {
925 
926         const CompositionModelImpl::AudioPreview &audioPreviewDrawDataItem = *audioPreviewIter;
927 
928         // If this one isn't in the clip rect, try the next.
929         if (!audioPreviewDrawDataItem.rect.intersects(clipRect))
930             continue;
931 
932         QPoint destPoint = audioPreviewDrawDataItem.rect.topLeft();
933         QRect sourceRect = audioPreviewDrawDataItem.rect;
934         // Translate contents coords to preview coords.
935         sourceRect.moveTo(0,0);
936 
937         // If the beginning is being resized to the right, clip the preview
938         if (audioPreviewDrawDataItem.resizeOffset > 0)
939             sourceRect.setLeft(audioPreviewDrawDataItem.resizeOffset);
940 
941         // draw the preview
942         drawImage(segmentsLayerPainter,
943                   destPoint,
944                   audioPreviewDrawDataItem.image,
945                   sourceRect);
946     }
947 }
948 
drawCompRect(QPainter * painter,const QRect & clipRect,const SegmentRect & rect,int intersectLvl)949 void CompositionView::drawCompRect(
950         QPainter *painter,
951         const QRect &clipRect,
952         const SegmentRect &rect,
953         int intersectLvl)
954 {
955     // Non repeating case, just draw the segment rect.
956     if (!rect.isRepeating()) {
957         painter->save();
958 
959         painter->setBrush(rect.brush);
960         painter->setPen(rect.pen);
961         drawRect(painter, clipRect, rect.rect, rect.selected, intersectLvl);
962 
963         painter->restore();
964 
965         return;
966     }
967 
968     // Repeating case.
969 
970     painter->save();
971 
972     // *** Base Segment
973 
974     QRect baseRect = rect.rect;
975     baseRect.setWidth(rect.baseWidth);
976 
977     painter->setPen(rect.pen);
978     painter->setBrush(rect.brush);
979     drawRect(painter, clipRect, baseRect, rect.selected, intersectLvl);
980 
981     // *** Repeat Area
982 
983     // ??? COPY.
984     SegmentRect::RepeatMarks repeatMarksX = rect.repeatMarks;
985     QRect repeatRect = rect.rect;
986     repeatRect.setLeft(repeatMarksX[0]);
987 
988     // The repeat area is lighter.
989     QBrush repeatBrush(rect.brush.color().lighter(150));
990 
991     painter->setBrush(repeatBrush);
992     drawRect(painter, clipRect, repeatRect, rect.selected, intersectLvl);
993 
994     // *** Repeat Marks
995 
996     painter->setPen(CompositionColourCache::getInstance()->RepeatSegmentBorder);
997 
998     // For each repeat mark, draw it.
999     for (size_t i = 0; i < repeatMarksX.size(); ++i) {
1000         int x = repeatMarksX[i];
1001         painter->drawLine(x, rect.rect.top(), x, rect.rect.bottom());
1002     }
1003 
1004     painter->restore();
1005 }
1006 
drawCompRectLabel(QPainter * painter,const SegmentRect & rect)1007 void CompositionView::drawCompRectLabel(
1008         QPainter *painter, const SegmentRect &rect)
1009 {
1010     // No label?  Bail.
1011     if (rect.label.isEmpty())
1012         return;
1013 
1014     painter->save();
1015 
1016     // Pick a font that will fit nicely within a segment rect.
1017     QFont font;
1018     font.setPixelSize(rect.rect.height() / 2.2);
1019     font.setWeight(QFont::Bold);
1020     painter->setFont(font);
1021 
1022     QRect labelRect = rect.rect;
1023     // Add a one character left margin.  Add a one pixel top margin to
1024     // make the text look a little more centered vertically.
1025     labelRect.adjust(painter->fontMetrics().boundingRect('x').width(), 1, 0, 0);
1026 
1027     QColor backgroundColor = rect.brush.color();
1028 
1029     // *** Draw the halo
1030 
1031     painter->setPen(backgroundColor);
1032 
1033     // For each halo offset, draw the text
1034     for (unsigned i = 0; i < m_haloOffsets.size(); ++i) {
1035         painter->drawText(labelRect.translated(m_haloOffsets[i]),
1036                           Qt::AlignLeft | Qt::AlignVCenter,
1037                           rect.label);
1038     }
1039 
1040     // *** Draw the text
1041 
1042     const bool lightBackground = (qGray(backgroundColor.rgb()) >= 127);
1043     // Based on the background, pick a contrasting pen.
1044     painter->setPen(lightBackground ? Qt::black : Qt::white);
1045     painter->drawText(labelRect,
1046                       Qt::AlignLeft | Qt::AlignVCenter, rect.label);
1047 
1048     painter->restore();
1049 }
1050 
drawRect(QPainter * p,const QRect & clipRect,const QRect & r,bool isSelected,int intersectLvl)1051 void CompositionView::drawRect(QPainter *p, const QRect &clipRect,
1052         const QRect &r, bool isSelected, int intersectLvl)
1053 {
1054     // If the rect isn't in the clip rect, bail.
1055     if (!r.intersects(clipRect))
1056         return;
1057 
1058     p->save();
1059 
1060     // Since we do partial updates when scrolling, make sure we don't
1061     // obliterate the previews.
1062     p->setClipRect(clipRect);
1063 
1064     // For a selected segment, go with a darker fill.
1065     if (isSelected) {
1066         QColor fillColor = p->brush().color().darker(200);
1067         p->setBrush(QBrush(fillColor));
1068     }
1069 
1070     // For intersecting segments, go with a darker fill.
1071     if (intersectLvl > 0) {
1072         QColor fillColor = p->brush().color().darker(intersectLvl * 105);
1073         p->setBrush(QBrush(fillColor));
1074     }
1075 
1076     QRect rect = r;
1077     // Shrink height by 1 to accommodate the dividers.
1078     // Shrink width by 1 so that adjacent segment borders don't overlap.
1079     // ??? Why isn't the SegmentRect already adjusted like this?
1080     rect.adjust(0, 0, -1, -1);
1081 
1082     p->drawRect(rect);
1083 
1084     p->restore();
1085 }
1086 
1087 // Functor to just compare the SegmentRect's QRect's.
1088 class CompareSegmentRects
1089 {
1090 public:
CompareSegmentRects(const SegmentRect & sr)1091     CompareSegmentRects(const SegmentRect &sr) : r(sr.rect) { }
operator ()(const SegmentRect & sr)1092     bool operator()(const SegmentRect &sr)
1093             { return (sr.rect == r); }
1094 private:
1095     QRect r;
1096 };
1097 
drawIntersections(QPainter * painter,const QRect & clipRect,const CompositionModelImpl::SegmentRects & rects)1098 void CompositionView::drawIntersections(
1099         QPainter *painter, const QRect &clipRect,
1100         const CompositionModelImpl::SegmentRects &rects)
1101 {
1102     // Intersections are most noticeable when recording over existing
1103     // segments.  They also play a part when moving a segment over top
1104     // of existing segments.
1105 
1106     // If there aren't enough rects for there to be an intersection, bail.
1107     if (rects.size() <= 1)
1108         return;
1109 
1110     CompositionModelImpl::SegmentRects intersections;
1111 
1112     // For each rect
1113     for (CompositionModelImpl::SegmentRects::const_iterator i = rects.begin();
1114          i != rects.end();
1115          ++i) {
1116 
1117         // For each rect after i
1118         for (CompositionModelImpl::SegmentRects::const_iterator j = i + 1;
1119              j != rects.end();
1120              ++j) {
1121 
1122             SegmentRect intersectRect = i->intersected(*j);
1123 
1124             // If no intersection, try the next rect.
1125             if (intersectRect.rect.isEmpty())
1126                 continue;
1127 
1128             // Check if we've already encountered this intersection.
1129             CompositionModelImpl::SegmentRects::iterator t =
1130                     std::find_if(intersections.begin(),
1131                               intersections.end(),
1132                               CompareSegmentRects(intersectRect));
1133 
1134             // If we've already seen this intersection, try the next rect.
1135             if (t != intersections.end())
1136                 continue;
1137 
1138             // Add it to the intersections vector.
1139             intersections.push_back(intersectRect);
1140         }
1141     }
1142 
1143     // For each intersection, draw the rectangle
1144     for (CompositionModelImpl::SegmentRects::iterator i =
1145              intersections.begin();
1146          i != intersections.end();
1147          ++i) {
1148 
1149         drawCompRect(painter, clipRect, *i, 1);
1150     }
1151 
1152 #if 0
1153 // This code is from when segments could overlap each other on a track.
1154 // Overlapping segments are no longer allowed.  See:
1155 //    CompositionModelImpl::setTrackHeights()
1156 //    Composition::getMaxContemporaneousSegmentsOnTrack()
1157     //
1158     // draw this level of intersections then compute and draw further ones
1159     //
1160     int level = 1;
1161 
1162     while (!intersections.empty()) {
1163 
1164         // For each intersection, draw the segment rectangle
1165         for (CompositionModelImpl::SegmentRects::iterator intersectionIter =
1166                  intersections.begin();
1167              intersectionIter != intersections.end();
1168              ++intersectionIter) {
1169 
1170             drawCompRect(painter, clipRect, *intersectionIter, level);
1171         }
1172 
1173         // Bail if more than 10 intersections.
1174         // Original comment: "put a limit on how many intersections we can
1175         //                    compute and draw - this grows exponentially"
1176         // That doesn't seem right.  Each time through, the intersections
1177         // will become fewer and fewer.
1178         if (intersections.size() > 10)
1179             break;
1180 
1181         ++level;
1182 
1183         CompositionModelImpl::SegmentRects intersections2;
1184 
1185         // For each intersection rect
1186         for (CompositionModelImpl::SegmentRects::iterator j =
1187                  intersections.begin();
1188              j != intersections.end();
1189              ++j) {
1190 
1191             const SegmentRect &testRect = *j;
1192 
1193             // For each rect after j
1194             for (CompositionModelImpl::SegmentRects::iterator i = j + 1;
1195                  i != intersections.end();
1196                  ++i) {
1197 
1198                 SegmentRect intersectRect = testRect.intersected(*i);
1199 
1200                 // If we have an intersection, and it isn't simply the
1201                 // "i" rect.
1202                 // ??? Why?  If the i rect is contained within the j
1203                 //     rect, we ignore it?
1204                 if (!intersectRect.isEmpty()  &&  intersectRect != *i) {
1205                     // Check to see if we've found this intersection already.
1206                     CompositionModelImpl::SegmentRects::iterator t =
1207                             std::find(intersections2.begin(),
1208                                       intersections2.end(),
1209                                       intersectRect);
1210 
1211                     // If we've not seen this intersection before
1212                     if (t == intersections2.end()) {
1213                         // Set the attributes.
1214                         // ??? What about selected?
1215                         intersectRect.setBrush(
1216                                 mixBrushes(testRect.getBrush(), i->getBrush()));
1217                     }
1218 
1219                     // ??? Add all intersections that we find, even if we've
1220                     //     already seen them?
1221                     intersections2.push_back(intersectRect);
1222                 }
1223             }
1224         }
1225 
1226         intersections = intersections2;
1227     }
1228 #endif
1229 }
1230 
drawTextFloat(QPainter * p)1231 void CompositionView::drawTextFloat(QPainter *p)
1232 {
1233     if (!m_model)
1234         return;
1235 
1236     // Find out how big of a rect we need for the text.
1237     QRect boundingRect = p->boundingRect(
1238             QRect(),  // we want the "required" rectangle
1239             0,        // we want the "required" rectangle
1240             m_textFloatText);
1241 
1242     // Add some margins to give the text room to breathe.
1243     boundingRect.adjust(-4,-2,+4,+2);
1244 
1245     QPoint pos(m_textFloatPos);
1246 
1247     // If the text float would appear above the top of the viewport
1248     if (pos.y() < contentsY()) {
1249         // Move it down
1250         pos.setY(pos.y() + m_model->grid().getYSnap() * 2 +
1251                  boundingRect.height());
1252     }
1253 
1254     boundingRect.moveTopLeft(pos);
1255 
1256     p->save();
1257 
1258     p->setPen(CompositionColourCache::getInstance()->RotaryFloatForeground);
1259     p->setBrush(CompositionColourCache::getInstance()->RotaryFloatBackground);
1260     p->drawRect(boundingRect);
1261     p->drawText(boundingRect, Qt::AlignCenter, m_textFloatText);
1262 
1263     p->restore();
1264 }
1265 
event(QEvent * e)1266 bool CompositionView::event(QEvent *e)
1267 {
1268     if (e->type() == AudioPeaksThread::AudioPeaksQueueEmpty) {
1269         // Audio previews have been generated, redraw the segments.
1270         segmentsNeedRefresh();
1271         viewport()->update();
1272 
1273         // No need to propagate to parent.
1274         e->accept();
1275         // Event was recognized.
1276         return true;
1277     }
1278 
1279     return RosegardenScrollView::event(e);
1280 }
1281 
1282 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
enterEvent(QEnterEvent *)1283 void CompositionView::enterEvent(QEnterEvent *)
1284 #else
1285 void CompositionView::enterEvent(QEvent *)
1286 #endif
1287 {
1288     // Ask RosegardenMainWindow to display the context help in the status bar.
1289     emit showContextHelp(m_toolContextHelp);
1290     m_contextHelpShown = true;
1291     // So we can get shift/ctrl/alt key presses.
1292     setFocus();
1293 }
1294 
leaveEvent(QEvent *)1295 void CompositionView::leaveEvent(QEvent *)
1296 {
1297     // Ask RosegardenMainWindow to clear the context help in the status bar.
1298     emit showContextHelp("");
1299     m_contextHelpShown = false;
1300     clearFocus();
1301 }
1302 
slotToolHelpChanged(const QString & text)1303 void CompositionView::slotToolHelpChanged(const QString &text)
1304 {
1305     // No change?  Bail.
1306     if (m_toolContextHelp == text)
1307         return;
1308 
1309     m_toolContextHelp = text;
1310 
1311     // If we're showing context help, ask RosegardenMainWindow to update
1312     // the context help in the status bar.
1313     if (m_contextHelpShown)
1314         emit showContextHelp(text);
1315 }
1316 
mousePressEvent(QMouseEvent * e)1317 void CompositionView::mousePressEvent(QMouseEvent *e)
1318 {
1319     // The left and middle buttons can be used for dragging out new
1320     // segments with SegmentSelector and SegmentPencil.  We want to
1321     // auto-scroll in those cases and others.
1322     // ??? Would it be better to push this down into the tools?
1323     if (e->button() == Qt::LeftButton  ||
1324         e->button() == Qt::MiddleButton)
1325         startAutoScroll();
1326 
1327     // Delegate to current tool
1328     if (m_currentTool)
1329         m_currentTool->mousePressEvent(e);
1330 }
1331 
mouseReleaseEvent(QMouseEvent * e)1332 void CompositionView::mouseReleaseEvent(QMouseEvent *e)
1333 {
1334     // In case there is no tool, and auto scroll is running.
1335     stopAutoScroll();
1336 
1337     if (m_currentTool)
1338         m_currentTool->mouseReleaseEvent(e);;
1339 }
1340 
mouseDoubleClickEvent(QMouseEvent * e)1341 void CompositionView::mouseDoubleClickEvent(QMouseEvent *e)
1342 {
1343     const QPoint contentsPos = viewportToContents(e->pos());
1344 
1345     ChangingSegmentPtr item = m_model->getSegmentAt(contentsPos);
1346 
1347     // If the user clicked on a space where there is no segment,
1348     // move the playback position pointer to that time.
1349     if (!item) {
1350         const RulerScale *ruler = grid().getRulerScale();
1351         if (ruler)
1352             emit setPointerPosition(ruler->getTimeForX(contentsPos.x()));
1353 
1354         return;
1355     }
1356 
1357     // Ask RosegardenMainViewWidget to launch the default editor for
1358     // the segment under the mouse pointer.
1359 
1360     if (item->isRepeating()) {
1361         const timeT time = item->getRepeatTimeAt(grid(), contentsPos);
1362 
1363         if (time > 0)
1364             emit editRepeat(item->getSegment(), time);
1365         else
1366             emit editSegment(item->getSegment());
1367 
1368     } else {
1369 
1370         emit editSegment(item->getSegment());
1371     }
1372 }
1373 
mouseMoveEvent(QMouseEvent * e)1374 void CompositionView::mouseMoveEvent(QMouseEvent *e)
1375 {
1376     if (!m_currentTool)
1377         return;
1378 
1379     // Delegate to the current tool.
1380     int followMode = m_currentTool->mouseMoveEvent(e);
1381 
1382     // ??? Can we push the rest of this down into the tools?
1383 
1384     setFollowMode(followMode);
1385 }
1386 
keyPressEvent(QKeyEvent * e)1387 void CompositionView::keyPressEvent(QKeyEvent *e)
1388 {
1389     // Let the baseclass have first dibs.
1390     RosegardenScrollView::keyPressEvent(e);
1391 
1392     if (!m_currentTool)
1393         return;
1394 
1395     // Delegate to the current tool.
1396     m_currentTool->keyPressEvent(e);
1397 }
1398 
keyReleaseEvent(QKeyEvent * e)1399 void CompositionView::keyReleaseEvent(QKeyEvent *e)
1400 {
1401     // Let the baseclass have first dibs.
1402     RosegardenScrollView::keyReleaseEvent(e);
1403 
1404     if (!m_currentTool)
1405         return;
1406 
1407     // Delegate to the current tool.
1408     m_currentTool->keyPressEvent(e);
1409 }
1410 
drawPointer(int pos)1411 void CompositionView::drawPointer(int pos)
1412 {
1413     // If we've not moved, bail.
1414     if (m_pointerPos == pos)
1415         return;
1416 
1417     Profiler profiler("CompositionView::drawPointer()");
1418 
1419     const int oldPos = m_pointerPos;
1420     m_pointerPos = pos;
1421 
1422     m_model->pointerPosChanged(pos);
1423 
1424     int deltaPos = abs(m_pointerPos - oldPos);
1425 
1426     // If the pointer has only moved slightly
1427     if (deltaPos <= m_pointerPen.width() * 2) {
1428 
1429         // Use one update rect instead of two.
1430 
1431         updateArtifacts(
1432                 QRect(std::min(m_pointerPos, oldPos) - m_pointerPen.width()/2, 0,
1433                       deltaPos + m_pointerPen.width(), contentsHeight()));
1434     } else {
1435 
1436         // Update the new pointer position.
1437         updateArtifacts(
1438                 QRect(m_pointerPos - m_pointerPen.width()/2, 0,
1439                       m_pointerPen.width(), contentsHeight()));
1440 
1441         // Update the old pointer position.
1442         updateArtifacts(
1443                 QRect(oldPos - m_pointerPen.width()/2, 0,
1444                       m_pointerPen.width(), contentsHeight()));
1445     }
1446 }
1447 
drawGuides(int x,int y)1448 void CompositionView::drawGuides(int x, int y)
1449 {
1450     m_drawGuides = true;
1451     m_guideX = x;
1452     m_guideY = y;
1453 
1454     slotUpdateArtifacts();
1455 }
1456 
hideGuides()1457 void CompositionView::hideGuides()
1458 {
1459     m_drawGuides = false;
1460 
1461     slotUpdateArtifacts();
1462 }
1463 
drawNewSegment(const QRect & r)1464 void CompositionView::drawNewSegment(const QRect &r)
1465 {
1466     QRect previousRect = m_newSegmentRect;
1467     m_newSegmentRect = r;
1468 
1469     slotAllNeedRefresh(m_newSegmentRect | previousRect);
1470 }
1471 
drawTextFloat(int x,int y,const QString & text)1472 void CompositionView::drawTextFloat(int x, int y, const QString &text)
1473 {
1474     m_textFloatPos.setX(x);
1475     m_textFloatPos.setY(y);
1476     m_textFloatText = text;
1477     m_drawTextFloat = true;
1478 
1479     slotUpdateArtifacts();
1480 }
1481 
slotControlChange(Instrument * instrument,int cc)1482 void CompositionView::slotControlChange(Instrument *instrument, int cc)
1483 {
1484     // If an audio instrument's volume or pan is changed, we need to redraw
1485     // the previews since the audio previews show the effects of volume and
1486     // pan.
1487 
1488     // This approach is a bit heavy-handed.  Even if the relevant audio
1489     // segment isn't visible, we still force an update.  This is simple.
1490     // Making it smarter probably isn't worth the time or the code.
1491 
1492     if (instrument->getType() != Instrument::Audio)
1493         return;
1494     if (cc != MIDI_CONTROLLER_VOLUME  &&  cc != MIDI_CONTROLLER_PAN)
1495         return;
1496 
1497     // Signal that the audio previews need to be deleted on the next timer.
1498     m_deleteAudioPreviewsNeeded = true;
1499 
1500     // The entire viewport in contents coords.
1501     // ??? This is copied all over.  Factor into a getViewportContentsRect().
1502     QRect viewportContentsRect(
1503             contentsX(), contentsY(),
1504             viewport()->rect().width(), viewport()->rect().height());
1505 
1506     // Signal that a refresh is needed on the next timer.
1507     slotAllNeedRefresh(viewportContentsRect);
1508 }
1509 
1510 
1511 }
1512