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 ¬ationPreviewRange =
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