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 "[MatrixScene]"
19 
20 #include "MatrixScene.h"
21 
22 #include "MatrixMouseEvent.h"
23 #include "MatrixViewSegment.h"
24 #include "MatrixWidget.h"
25 #include "MatrixElement.h"
26 
27 #include "gui/application/RosegardenMainWindow.h"
28 #include "document/RosegardenDocument.h"
29 #include "document/CommandHistory.h"
30 #include "misc/ConfigGroups.h"
31 
32 #include "misc/Debug.h"
33 #include "base/RulerScale.h"
34 #include "base/SnapGrid.h"
35 
36 #include "gui/general/GUIPalette.h"
37 #include "gui/widgets/Panned.h"
38 
39 #include "base/BaseProperties.h"
40 #include "base/NotationRules.h"
41 #include "gui/studio/StudioControl.h"
42 
43 #include <QGraphicsSceneMouseEvent>
44 #include <QGraphicsLineItem>
45 #include <QSettings>
46 #include <QPointF>
47 #include <QRectF>
48 
49 #include <algorithm>  // for std::sort
50 
51 //#define DEBUG_MOUSE
52 
53 namespace Rosegarden
54 {
55 
56 
57 using namespace BaseProperties;
58 
MatrixScene()59 MatrixScene::MatrixScene() :
60     m_widget(nullptr),
61     m_document(nullptr),
62     m_scale(nullptr),
63     m_referenceScale(nullptr),
64     m_snapGrid(nullptr),
65     m_resolution(8),
66     m_selection(nullptr),
67     m_currentSegmentIndex(0)
68 {
69     connect(CommandHistory::getInstance(), SIGNAL(commandExecuted()),
70             this, SLOT(slotCommandExecuted()));
71 }
72 
~MatrixScene()73 MatrixScene::~MatrixScene()
74 {
75     RG_DEBUG << "MatrixScene::~MatrixScene() - start";
76 
77     if (m_document) {
78         if (!isCompositionDeleted()) { // implemented in CompositionObserver
79             m_document->getComposition().removeObserver(this);
80         }
81     }
82     for (unsigned int i = 0; i < m_viewSegments.size(); ++i) {
83         delete m_viewSegments[i];
84     }
85     delete m_snapGrid;
86     delete m_referenceScale;
87     delete m_scale;
88     delete m_selection;
89 
90     RG_DEBUG << "MatrixScene::~MatrixScene() - end";
91 }
92 
93 namespace
94 {
95     // Functor for std::sort that compares the track positions of two Segments.
96     struct TrackPositionLess {
TrackPositionLessRosegarden::__anon8c58d50c0111::TrackPositionLess97         TrackPositionLess() :
98             m_composition(RosegardenMainWindow::self()->getDocument()->
99                               getComposition())
100         {
101         }
102 
operator ()Rosegarden::__anon8c58d50c0111::TrackPositionLess103         bool operator()(const Segment *lhs, const Segment *rhs)
104         {
105             // ??? Could also sort by Segment name and Segment start time.
106             const int lPos =
107                     m_composition.getTrackById(lhs->getTrack())->getPosition();
108             const int rPos =
109                     m_composition.getTrackById(rhs->getTrack())->getPosition();
110             return (lPos < rPos);
111         }
112 
113     private:
114         const Composition &m_composition;
115     };
116 }
117 
118 void
setSegments(RosegardenDocument * document,std::vector<Segment * > segments)119 MatrixScene::setSegments(RosegardenDocument *document,
120                          std::vector<Segment *> segments)
121 {
122     if (m_document && document != m_document) {
123         m_document->getComposition().removeObserver(this);
124     }
125 
126     m_document = document;
127     m_segments = segments;
128 
129     // Sort the Segments into TrackPosition order.  This makes the
130     // Segment changer wheel in the Matrix editor
131     // (MatrixWidget::m_segmentChanger) easier to understand.
132     std::sort(m_segments.begin(), m_segments.end(), TrackPositionLess());
133 
134     m_document->getComposition().addObserver(this);
135 
136     SegmentSelection selection;
137     selection.insert(segments.begin(), segments.end());
138 
139     delete m_snapGrid;
140     delete m_scale;
141     delete m_referenceScale;
142     m_scale = new SegmentsRulerScale(&m_document->getComposition(),
143                                      selection,
144                                      0,
145                                      Note(Note::Shortest).getDuration() / 2.0);
146 
147     m_referenceScale = new ZoomableRulerScale(m_scale);
148     m_snapGrid = new SnapGrid(m_referenceScale);
149 
150     for (unsigned int i = 0; i < m_viewSegments.size(); ++i) {
151         delete m_viewSegments[i];
152     }
153     m_viewSegments.clear();
154 
155     // We should show diamonds instead of bars whenever we are in
156     // "drum mode" (i.e. whenever we were invoked using the percussion
157     // matrix menu option instead of the normal matrix one).
158 
159     // The question of whether to show the key names instead of the
160     // piano keyboard is a separate one, handled in MatrixWidget, and
161     // it depends solely on whether a key mapping exists for the
162     // instrument (it is independent of whether this is a percussion
163     // matrix or not).
164 
165     // Nevertheless, if the key names are shown, we need a little more space
166     // between horizontal lines. That's why m_resolution depends from
167     // keyMapping.
168 
169     // But since several segments may be edited in the same matrix, we
170     // have to deal with simultaneous display of segments using piano keyboard
171     // and segments using key mapping.
172     // Key mapping may be displayed with piano keyboard resolution (even if
173     // space is a bit short for the text) but the opposite is not possible.
174     // So the only (easy) way I found is to use the resolution fitting with
175     // piano keyboard when at least one segment needs it.
176 
177     bool drumMode = false;
178     bool keyMapping = false;
179     if (m_widget) {
180         drumMode = m_widget->isDrumMode();
181         keyMapping = m_widget->hasOnlyKeyMapping();
182     }
183     m_resolution = 8;
184     if (keyMapping) m_resolution = 11;
185 
186     bool haveSetSnap = false;
187 
188     for (unsigned int i = 0; i < m_segments.size(); ++i) {
189 
190         int snapGridSize = m_segments[i]->getSnapGridSize();
191 
192         if (snapGridSize != -1) {
193             m_snapGrid->setSnapTime(snapGridSize);
194             haveSetSnap = true;
195         }
196 
197         MatrixViewSegment *vs = new MatrixViewSegment(this,
198                                                       m_segments[i],
199                                                       drumMode);
200         (void)vs->getViewElementList(); // make sure it has been created
201         m_viewSegments.push_back(vs);
202     }
203 
204     if (!haveSetSnap) {
205         QSettings settings;
206         settings.beginGroup(MatrixViewConfigGroup);
207         timeT snap = settings.value("Snap Grid Size",
208                                     (int)SnapGrid::SnapToBeat).toInt();
209         m_snapGrid->setSnapTime(snap);
210         settings.endGroup();
211         for (unsigned int i = 0; i < m_segments.size(); ++i) {
212             m_segments[i]->setSnapGridSize(snap);
213         }
214     }
215 
216     recreateLines();
217     updateCurrentSegment();
218 }
219 
220 Segment *
getCurrentSegment()221 MatrixScene::getCurrentSegment()
222 {
223     if (m_segments.empty()) return nullptr;
224     if (m_currentSegmentIndex >= int(m_segments.size())) {
225         m_currentSegmentIndex = int(m_segments.size()) - 1;
226     }
227     return m_segments[m_currentSegmentIndex];
228 }
229 
230 void
setCurrentSegment(Segment * s)231 MatrixScene::setCurrentSegment(Segment *s)
232 {
233     for (int i = 0; i < int(m_segments.size()); ++i) {
234         if (s == m_segments[i]) {
235             m_currentSegmentIndex = i;
236             recreatePitchHighlights();
237             updateCurrentSegment();
238             return;
239         }
240     }
241     RG_WARNING << "WARNING: MatrixScene::setCurrentSegment: unknown segment" << s;
242 }
243 
244 Segment *
getPriorSegment()245 MatrixScene::getPriorSegment()
246 {
247     if (m_currentSegmentIndex == 0) return nullptr;
248     return m_segments[m_currentSegmentIndex-1];
249 }
250 
251 Segment *
getNextSegment()252 MatrixScene::getNextSegment()
253 {
254     if ((unsigned int) m_currentSegmentIndex + 1 >= m_segments.size()) return nullptr;
255     return m_segments[m_currentSegmentIndex+1];
256 }
257 
258 MatrixViewSegment *
getCurrentViewSegment()259 MatrixScene::getCurrentViewSegment()
260 {
261     if (m_viewSegments.empty()) return nullptr;
262     return m_viewSegments[0];
263 }
264 
265 bool
segmentsContainNotes() const266 MatrixScene::segmentsContainNotes() const
267 {
268     for (unsigned int i = 0; i < m_segments.size(); ++i) {
269 
270         const Segment *segment = m_segments[i];
271 
272         for (Segment::const_iterator i = segment->begin();
273              segment->isBeforeEndMarker(i); ++i) {
274 
275             if (((*i)->getType() == Note::EventType)) {
276                 return true;
277             }
278         }
279     }
280 
281     return false;
282 }
283 
284 void
recreateLines()285 MatrixScene::recreateLines()
286 {
287     timeT start = 0, end = 0;
288 
289     // Determine total distance that requires lines (considering multiple segments
290     for (unsigned int i = 0; i < m_segments.size(); ++i) {
291         if (i == 0 || m_segments[i]->getClippedStartTime() < start) {
292             start = m_segments[i]->getClippedStartTime();
293         }
294         if (i == 0 || m_segments[i]->getEndMarkerTime() > end) {
295             end = m_segments[i]->getEndMarkerTime();
296         }
297     }
298 
299     // Pen Width?
300     double pw = 0;
301 
302     double startPos = m_scale->getXForTime(start);
303     double endPos = m_scale->getXForTime(end);
304 
305     // Draw horizontal lines
306     int i = 0;
307     while (i < 127) {
308          int y = (i + 1) * (m_resolution + 1);
309          QGraphicsLineItem *line;
310          if (i < (int)m_horizontals.size()) {
311              line = m_horizontals[i];
312          } else {
313              line = new QGraphicsLineItem;
314              line->setZValue(-9);
315              line->setPen(QPen(GUIPalette::getColour
316                                (GUIPalette::MatrixHorizontalLine), pw));
317              addItem(line);
318              m_horizontals.push_back(line);
319          }
320          line->setLine(startPos, y, endPos, y);
321          line->show();
322          ++i;
323      }
324 
325 
326      // Hide the other lines, if there are any.  Just a double check.
327      while (i < (int)m_horizontals.size()) {
328          m_horizontals[i]->hide();
329          ++i;
330     }
331 
332     setSceneRect(QRectF(startPos, 0, endPos - startPos, 128 * (m_resolution + 1)));
333 
334     Composition *c = &m_document->getComposition();
335 
336     int firstbar = c->getBarNumber(start), lastbar = c->getBarNumber(end);
337 
338     // Draw Vertical Lines
339     i = 0;
340     for (int bar = firstbar; bar <= lastbar; ++bar) {
341 
342         std::pair<timeT, timeT> range = c->getBarRange(bar);
343 
344         bool discard = false;  // was not initalied...hmmm...try false?
345         TimeSignature timeSig = c->getTimeSignatureInBar(bar, discard);
346 
347         double x0 = m_scale->getXForTime(range.first);
348         double x1 = m_scale->getXForTime(range.second);
349         double width = x1 - x0;
350 
351         double gridLines; // number of grid lines per bar may be fractional
352 
353         // If the snap time is zero we default to beat markers
354         //
355         if (m_snapGrid && m_snapGrid->getSnapTime(x0)) {
356             gridLines = double(timeSig.getBarDuration()) /
357                         double(m_snapGrid->getSnapTime(x0));
358         } else {
359             gridLines = timeSig.getBeatsPerBar();
360         }
361 
362         double dx = width / gridLines;
363         double x = x0;
364 
365         for (int index = 0; index < gridLines; ++index) {
366 
367             // Check to see if we are withing the first segments start time.
368             if (x < startPos) {
369                 x += dx;
370                 continue;
371             }
372 
373             // Exit if we have passed the end of last segment end time.
374             if (x > endPos) {
375                 break;
376             }
377 
378             QGraphicsLineItem *line;
379 
380             if (i < (int)m_verticals.size()) {
381                 line = m_verticals[i];
382             } else {
383                 line = new QGraphicsLineItem;
384                 addItem(line);
385                 m_verticals.push_back(line);
386             }
387 
388             if (index == 0) {
389               // index 0 is the bar line
390                 line->setPen(QPen(GUIPalette::getColour(GUIPalette::MatrixBarLine), pw));
391             } else {
392                 line->setPen(QPen(GUIPalette::getColour(GUIPalette::BeatLine), pw));
393             }
394 
395             line->setZValue(index > 0 ? -10 : -8);
396             line->setLine(x, 0, x, 128 * (m_resolution + 1));
397 
398             line->show();
399             x += dx;
400             ++i;
401         }
402     }
403 
404     // Hide the other lines. We are not using them right now.
405     while (i < (int)m_verticals.size()) {
406         m_verticals[i]->hide();
407         ++i;
408     }
409 
410     recreatePitchHighlights();
411 
412     // Force update so all vertical lines are drawn correctly
413     update();
414 }
415 
416 void
recreatePitchHighlights()417 MatrixScene::recreatePitchHighlights()
418 {
419     Segment *segment = getCurrentSegment();
420     if (!segment) return;
421 
422     timeT k0 = segment->getClippedStartTime();
423     timeT k1 = segment->getClippedStartTime();
424 
425     int i = 0;
426 
427     while (k0 < segment->getEndMarkerTime()) {
428 
429         Rosegarden::Key key = segment->getKeyAtTime(k0);
430 
431         // offset the highlights according to how far this key's tonic pitch is
432         // from C major (0)
433         int offset = key.getTonicPitch();
434 
435         // correct for segment transposition, moving representation the opposite
436         // of pitch to cancel it out (C (MIDI pitch 0) in Bb (-2) is concert
437         // Bb (10), so 0 -1 == 11 -1 == 10, we have to go +1 == 11 +1 == 0)
438         int correction = segment->getTranspose(); // eg. -2
439         correction *= -1;                         // eg.  2
440 
441         // key is Bb for Bb instrument, getTonicPitch() returned 10, correction
442         // is +2
443         offset -= correction;
444         offset += 12;
445         offset %= 12;
446 
447         if (!segment->getNextKeyTime(k0, k1)) k1 = segment->getEndMarkerTime();
448 
449         if (k0 == k1) {
450             RG_WARNING << "WARNING: MatrixScene::recreatePitchHighlights: k0 == k1 ==" << k0;
451             break;
452         }
453 
454         double x0 = m_scale->getXForTime(k0);
455         double x1 = m_scale->getXForTime(k1);
456 
457         // calculate the highlights relative to C major, plus offset
458         // (I think this enough to do the job.  It passes casual tests.)
459         const int hcount = 3;
460         int hsteps[hcount];
461         hsteps[0] = scale_Cmajor[0] + offset; // tonic
462         hsteps[2] = scale_Cmajor[4] + offset; // fifth
463 
464         if (key.isMinor()) {
465             hsteps[1] = scale_Cminor[2] + offset; // minor third
466         } else {
467             hsteps[1] = scale_Cmajor[2] + offset; // major third
468         }
469 
470         for (int j = 0; j < hcount; ++j) {
471 
472             int pitch = hsteps[j];
473             while (pitch < 128) {
474 
475                 QGraphicsRectItem *rect;
476 
477                 if (i < (int)m_highlights.size()) {
478                     rect = m_highlights[i];
479                 } else {
480                     rect = new QGraphicsRectItem;
481                     rect->setZValue(-11);
482                     rect->setPen(Qt::NoPen);
483                     addItem(rect);
484                     m_highlights.push_back(rect);
485                 }
486 
487                 if (j == 0) {
488                     rect->setBrush(GUIPalette::getColour
489                                    (GUIPalette::MatrixTonicHighlight));
490                 } else {
491                     rect->setBrush(GUIPalette::getColour
492                                    (GUIPalette::MatrixPitchHighlight));
493                 }
494 
495 //                rect->setRect(0.5, 0.5, x1 - x0, m_resolution + 1);
496                 rect->setRect(0, 0, x1 - x0, m_resolution + 1);
497                 rect->setPos(x0, (127 - pitch) * (m_resolution + 1));
498                 rect->show();
499 
500                 pitch += 12;
501 
502                 ++i;
503             }
504         }
505 
506         k0 = k1;
507     }
508     while (i < (int)m_highlights.size()) {
509         m_highlights[i]->hide();
510         ++i;
511     }
512 }
513 
514 void
setupMouseEvent(QGraphicsSceneMouseEvent * e,MatrixMouseEvent & mme) const515 MatrixScene::setupMouseEvent(QGraphicsSceneMouseEvent *e,
516                              MatrixMouseEvent &mme) const
517 {
518     double sx = e->scenePos().x();
519     int sy = lrint(e->scenePos().y());
520 
521     mme.viewpos = e->screenPos();
522 
523     mme.sceneX = sx;
524     mme.sceneY = sy;
525 
526     mme.modifiers = e->modifiers();
527     mme.buttons = e->buttons();
528 
529     mme.element = nullptr;
530 
531     QList<QGraphicsItem *> l = items(e->scenePos());
532 //    MATRIX_DEBUG << "Found " << l.size() << " items at " << e->scenePos();
533     for (int i = 0; i < l.size(); ++i) {
534         MatrixElement *element = MatrixElement::getMatrixElement(l[i]);
535         if (element) {
536             // items are in z-order from top, so this is most salient
537             mme.element = element;
538             break;
539         }
540     }
541 
542     mme.viewSegment = m_viewSegments[m_currentSegmentIndex];
543 
544     mme.time = m_scale->getTimeForX(sx);
545 
546     if (e->modifiers() & Qt::ShiftModifier) {
547         mme.snappedLeftTime = mme.time;
548         mme.snappedRightTime = mme.time;
549         mme.snapUnit = Note(Note::Shortest).getDuration();
550     } else {
551 //        mme.snappedLeftTime = m_snapGrid->snapX(sx, SnapGrid::SnapLeft);
552 //        mme.snappedRightTime = m_snapGrid->snapX(sx, SnapGrid::SnapRight);
553         mme.snappedLeftTime = m_snapGrid->snapTime(mme.time, SnapGrid::SnapLeft);
554         mme.snappedRightTime = m_snapGrid->snapTime(mme.time, SnapGrid::SnapRight);
555         mme.snapUnit = m_snapGrid->getSnapTime(sx);
556     }
557 
558     if (mme.viewSegment) {
559         timeT start = mme.viewSegment->getSegment().getClippedStartTime();
560         timeT end = mme.viewSegment->getSegment().getEndMarkerTime();
561         if (mme.snappedLeftTime < start) mme.snappedLeftTime = start;
562         if (mme.snappedLeftTime + mme.snapUnit > end) {
563             mme.snappedLeftTime = end - mme.snapUnit;
564         }
565         if (mme.snappedRightTime < start) mme.snappedRightTime = start;
566         if (mme.snappedRightTime > end) mme.snappedRightTime = end;
567     }
568 
569    mme.pitch = calculatePitchFromY(sy);
570 
571 #ifdef DEBUG_MOUSE
572     MATRIX_DEBUG << "MatrixScene::setupMouseEvent: sx = " << sx
573                  << ", sy = " << sy
574                  << ", modifiers = " << mme.modifiers
575                  << ", buttons = " << mme.buttons
576                  << ", element = " << mme.element
577                  << ", viewSegment = " << mme.viewSegment
578                  << ", time = " << mme.time
579                  << ", pitch = " << mme.pitch
580                  << endl;
581 #endif
582 }
583 
calculatePitchFromY(int y) const584 int MatrixScene::calculatePitchFromY(int y) const {
585     int pitch = 127 - (y / (m_resolution + 1));
586     if (pitch < 0) pitch = 0;
587     if (pitch > 127) pitch = 127;
588     return pitch;
589 }
590 
591 void
mousePressEvent(QGraphicsSceneMouseEvent * e)592 MatrixScene::mousePressEvent(QGraphicsSceneMouseEvent *e)
593 {
594     MatrixMouseEvent nme;
595     setupMouseEvent(e, nme);
596     emit mousePressed(&nme);
597 }
598 
599 void
mouseMoveEvent(QGraphicsSceneMouseEvent * e)600 MatrixScene::mouseMoveEvent(QGraphicsSceneMouseEvent *e)
601 {
602     MatrixMouseEvent nme;
603     setupMouseEvent(e, nme);
604     emit mouseMoved(&nme);
605 }
606 
607 void
mouseReleaseEvent(QGraphicsSceneMouseEvent * e)608 MatrixScene::mouseReleaseEvent(QGraphicsSceneMouseEvent *e)
609 {
610     MatrixMouseEvent nme;
611     setupMouseEvent(e, nme);
612     emit mouseReleased(&nme);
613 }
614 
615 void
mouseDoubleClickEvent(QGraphicsSceneMouseEvent * e)616 MatrixScene::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *e)
617 {
618     MatrixMouseEvent nme;
619     setupMouseEvent(e, nme);
620     emit mouseDoubleClicked(&nme);
621 }
622 
623 void
slotCommandExecuted()624 MatrixScene::slotCommandExecuted()
625 {
626     checkUpdate();
627 }
628 
629 void
checkUpdate()630 MatrixScene::checkUpdate()
631 {
632     bool updateSelectionElementStatus = false;
633 
634     for (unsigned int i = 0; i < m_viewSegments.size(); ++i) {
635 
636         SegmentRefreshStatus &rs = m_viewSegments[i]->getRefreshStatus();
637 
638         if (rs.needsRefresh()) {
639             m_viewSegments[i]->updateElements(rs.from(), rs.to());
640             if (!updateSelectionElementStatus && m_selection) {
641                 updateSelectionElementStatus =
642                     (m_viewSegments[i]->getSegment() == m_selection->getSegment());
643             }
644         }
645 
646         rs.setNeedsRefresh(false);
647     }
648 
649     if (updateSelectionElementStatus) {
650         setSelectionElementStatus(m_selection, true);
651     }
652 }
653 
654 void
segmentEndMarkerTimeChanged(const Segment *,bool)655 MatrixScene::segmentEndMarkerTimeChanged(const Segment *, bool)
656 {
657     MATRIX_DEBUG << "MatrixScene::segmentEndMarkerTimeChanged";
658     recreateLines();
659 }
660 
661 void
timeSignatureChanged(const Composition * c)662 MatrixScene::timeSignatureChanged(const Composition *c)
663 {
664     if (!m_document || !c || (c != &m_document->getComposition())) return;
665 }
666 
667 void
segmentRemoved(const Composition * c,Segment * s)668 MatrixScene::segmentRemoved(const Composition *c, Segment *s)
669 {
670     MATRIX_DEBUG << "MatrixScene::segmentRemoved(" << c << "," << s << ")";
671 
672     if (!m_document || !c || (c != &m_document->getComposition())) return;
673 
674     for (std::vector<MatrixViewSegment *>::iterator i = m_viewSegments.begin();
675          i != m_viewSegments.end(); ++i) {
676         if (s == &(*i)->getSegment()) {
677             emit segmentDeleted(s);
678             delete *i;
679             m_viewSegments.erase(i);
680             break;
681         }
682     }
683 
684     if (m_viewSegments.empty()) {
685         MATRIX_DEBUG << "(Scene is now empty)";
686         emit sceneDeleted();
687     }
688 }
689 
690 void
handleEventAdded(Event * e)691 MatrixScene::handleEventAdded(Event *e)
692 {
693     if (e->getType() == Rosegarden::Key::EventType) {
694         recreatePitchHighlights();
695     }
696 }
697 
698 void
handleEventRemoved(Event * e)699 MatrixScene::handleEventRemoved(Event *e)
700 {
701     if (m_selection && m_selection->contains(e)) m_selection->removeEvent(e);
702     if (e->getType() == Rosegarden::Key::EventType) {
703         recreatePitchHighlights();
704     }
705     update();
706     emit eventRemoved(e);
707 }
708 
709 void
setSelection(EventSelection * s,bool preview)710 MatrixScene::setSelection(EventSelection *s, bool preview)
711 {
712     if (!m_selection && !s) return;
713     if (m_selection == s) return;
714     if (m_selection && s && *m_selection == *s) {
715         // selections are identical, no need to update elements, but
716         // still need to replace the old selection to avoid a leak
717         // (can't just delete s in case caller still refers to it)
718         EventSelection *oldSelection = m_selection;
719         m_selection = s;
720         delete oldSelection;
721         return;
722     }
723 
724     EventSelection *oldSelection = m_selection;
725     m_selection = s;
726 
727     if (oldSelection) {
728         setSelectionElementStatus(oldSelection, false);
729     }
730 
731     if (m_selection) {
732         setSelectionElementStatus(m_selection, true);
733         emit QGraphicsScene::selectionChanged();
734     }
735 
736     if (preview) previewSelection(m_selection, oldSelection);
737     delete oldSelection;
738     emit QGraphicsScene::selectionChanged();
739 }
740 
741 void
slotRulerSelectionChanged(EventSelection * s)742 MatrixScene::slotRulerSelectionChanged(EventSelection *s)
743 {
744     RG_DEBUG << "MatrixScene: caught " << (s ? "useful" : "null" ) << " selection change from ruler";
745     if (m_selection) {
746         if (s) m_selection->addFromSelection(s);
747         setSelectionElementStatus(m_selection, true);
748     }
749 }
750 
751 void
setSingleSelectedEvent(MatrixViewSegment * vs,MatrixElement * e,bool preview)752 MatrixScene::setSingleSelectedEvent(MatrixViewSegment *vs,
753                                     MatrixElement *e,
754                                     bool preview)
755 {
756     if (!vs || !e) return;
757     EventSelection *s = new EventSelection(vs->getSegment());
758     s->addEvent(e->event());
759     setSelection(s, preview);
760 }
761 
762 void
setSingleSelectedEvent(Segment * seg,Event * e,bool preview)763 MatrixScene::setSingleSelectedEvent(Segment *seg,
764                                     Event *e,
765                                     bool preview)
766 {
767     if (!seg || !e) return;
768     EventSelection *s = new EventSelection(*seg);
769     s->addEvent(e);
770     setSelection(s, preview);
771 }
772 
773 void
selectAll()774 MatrixScene::selectAll()
775 {
776     Segment *segment = getCurrentSegment();
777     if (!segment) return;
778     Segment::iterator it = segment->begin();
779     EventSelection *selection = new EventSelection(*segment);
780 
781     for (; segment->isBeforeEndMarker(it); ++it) {
782         if ((*it)->isa(Note::EventType)) {
783             selection->addEvent(*it);
784         }
785     }
786 
787     setSelection(selection, false);
788 }
789 
790 void
setSelectionElementStatus(EventSelection * s,bool set)791 MatrixScene::setSelectionElementStatus(EventSelection *s, bool set)
792 {
793     if (!s) return;
794 
795     MatrixViewSegment *vs = nullptr;
796 
797     for (std::vector<MatrixViewSegment *>::iterator i = m_viewSegments.begin();
798          i != m_viewSegments.end(); ++i) {
799 
800         if (&(*i)->getSegment() == &s->getSegment()) {
801             vs = *i;
802             break;
803         }
804     }
805 
806     if (!vs) return;
807 
808     for (EventSelection::eventcontainer::iterator i = s->getSegmentEvents().begin();
809          i != s->getSegmentEvents().end(); ++i) {
810 
811         Event *e = *i;
812 
813         ViewElementList::iterator vsi = vs->findEvent(e);
814         if (vsi == vs->getViewElementList()->end()) continue;
815 
816         MatrixElement *el = static_cast<MatrixElement *>(*vsi);
817         el->setSelected(set);
818     }
819 }
820 
821 void
previewSelection(EventSelection * s,EventSelection * oldSelection)822 MatrixScene::previewSelection(EventSelection *s,
823                               EventSelection *oldSelection)
824 {
825     if (!s) return;
826     if (!m_document->isSoundEnabled()) return;
827 
828     for (EventSelection::eventcontainer::iterator i = s->getSegmentEvents().begin();
829          i != s->getSegmentEvents().end(); ++i) {
830 
831         Event *e = *i;
832         if (oldSelection && oldSelection->contains(e)) continue;
833 
834         long pitch;
835         if (e->get<Int>(BaseProperties::PITCH, pitch)) {
836             long velocity = -1;
837             (void)(e->get<Int>(BaseProperties::VELOCITY, velocity));
838             if (!(e->has(BaseProperties::TIED_BACKWARD) &&
839                   e->get<Bool>(BaseProperties::TIED_BACKWARD))) {
840                 playNote(s->getSegment(), pitch, velocity);
841             }
842         }
843     }
844 }
845 
846 void
updateCurrentSegment()847 MatrixScene::updateCurrentSegment()
848 {
849     MATRIX_DEBUG << "MatrixScene::updateCurrentSegment: current is " << m_currentSegmentIndex;
850     for (int i = 0; i < (int)m_viewSegments.size(); ++i) {
851         bool current = (i == m_currentSegmentIndex);
852         ViewElementList *vel = m_viewSegments[i]->getViewElementList();
853         for (ViewElementList::const_iterator j = vel->begin();
854              j != vel->end(); ++j) {
855             MatrixElement *mel = dynamic_cast<MatrixElement *>(*j);
856             if (!mel) continue;
857             mel->setCurrent(current);
858         }
859         if (current) emit currentViewSegmentChanged(m_viewSegments[i]);
860     }
861 
862     // changing the current segment may have overridden selection border colours
863     setSelectionElementStatus(m_selection, true);
864 }
865 
866 void
setSnap(timeT t)867 MatrixScene::setSnap(timeT t)
868 {
869     MATRIX_DEBUG << "MatrixScene::slotSetSnap: time is " << t;
870     m_snapGrid->setSnapTime(t);
871 
872     for (size_t i = 0; i < m_segments.size(); ++i) {
873         m_segments[i]->setSnapGridSize(t);
874     }
875 
876     QSettings settings;
877     settings.beginGroup(MatrixViewConfigGroup);
878     settings.setValue("Snap Grid Size", (int)t);
879     settings.endGroup();
880 
881     recreateLines();
882 }
883 
884 bool
constrainToSegmentArea(QPointF & scenePos)885 MatrixScene::constrainToSegmentArea(QPointF &scenePos)
886 {
887     bool ok = true;
888 
889     int pitch = 127 - (lrint(scenePos.y()) / (m_resolution + 1));
890     if (pitch < 0) {
891         ok = false;
892         scenePos.setY(127 * (m_resolution + 1));
893     } else if (pitch > 127) {
894         ok = false;
895         scenePos.setY(0);
896     }
897 
898     timeT t = m_scale->getTimeForX(scenePos.x());
899     timeT start = 0, end = 0;
900     for (size_t i = 0; i < m_segments.size(); ++i) {
901         timeT t0 = m_segments[i]->getClippedStartTime();
902         timeT t1 = m_segments[i]->getEndMarkerTime();
903         if (i == 0 || t0 < start) start = t0;
904         if (i == 0 || t1 > end) end = t1;
905     }
906     if (t < start) {
907         ok = false;
908         scenePos.setX(m_scale->getXForTime(start));
909     } else if (t > end) {
910         ok = false;
911         scenePos.setX(m_scale->getXForTime(end));
912     }
913 
914     return ok;
915 }
916 
917 void
playNote(Segment & segment,int pitch,int velocity)918 MatrixScene::playNote(Segment &segment, int pitch, int velocity)
919 {
920 //    std::cout << "Scene is playing a note of pitch: " << pitch
921 //              << " + " <<  segment.getTranspose();
922     if (!m_document) return;
923 
924     Instrument *instrument = m_document->getStudio().getInstrumentFor(&segment);
925 
926     StudioControl::playPreviewNote(instrument,
927                                    pitch + segment.getTranspose(),
928                                    velocity,
929                                    RealTime(0, 250000000));
930 }
931 
932 void
setHorizontalZoomFactor(double factor)933 MatrixScene::setHorizontalZoomFactor(double factor)
934 {
935     for (Segment *segment : m_segments) {
936         segment->matrixHZoomFactor = factor;
937     }
938 }
939 
940 void
setVerticalZoomFactor(double factor)941 MatrixScene::setVerticalZoomFactor(double factor)
942 {
943     for (Segment *segment : m_segments) {
944         segment->matrixVZoomFactor = factor;
945     }
946 }
947 
948 
949 }
950