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 "[MatrixWidget]"
19
20 #include "MatrixWidget.h"
21
22 #include "MatrixScene.h"
23 #include "MatrixToolBox.h"
24 #include "MatrixTool.h"
25 #include "MatrixSelector.h"
26 #include "MatrixPainter.h"
27 #include "MatrixEraser.h"
28 #include "MatrixMover.h"
29 #include "MatrixResizer.h"
30 #include "MatrixVelocity.h"
31 #include "MatrixMouseEvent.h"
32 #include "MatrixViewSegment.h"
33 #include "PianoKeyboard.h"
34
35 #include "document/RosegardenDocument.h"
36
37 #include "gui/application/RosegardenMainWindow.h"
38
39 #include "gui/widgets/Panner.h"
40 #include "gui/widgets/Panned.h"
41 #include "gui/widgets/Thumbwheel.h"
42
43 #include "gui/rulers/PitchRuler.h"
44 #include "gui/rulers/PercussionPitchRuler.h"
45
46 #include "gui/rulers/ControlRulerWidget.h"
47 #include "gui/rulers/StandardRuler.h"
48 #include "gui/rulers/TempoRuler.h"
49 #include "gui/rulers/ChordNameRuler.h"
50 #include "gui/rulers/LoopRuler.h"
51
52 #include "gui/general/ThornStyle.h"
53
54 #include "gui/studio/StudioControl.h"
55
56 #include "misc/Debug.h"
57
58 #include "base/Composition.h"
59 #include "base/Instrument.h"
60 //#include "base/MidiProgram.h"
61 #include "base/RulerScale.h"
62 #include "base/PropertyName.h"
63 #include "base/BaseProperties.h"
64 #include "base/Controllable.h"
65 #include "base/Studio.h"
66 //#include "base/InstrumentStaticSignals.h"
67 #include "base/Device.h"
68 #include "base/MidiDevice.h"
69 #include "base/SoftSynthDevice.h"
70 #include "base/ColourMap.h"
71
72 #include <QApplication>
73 #include <QColor>
74 #include <QGraphicsView>
75 #include <QGridLayout>
76 #include <QLabel>
77 #include <QScrollBar>
78 #include <QTimer>
79 #include <QGraphicsScene>
80 #include <QGraphicsProxyWidget>
81 #include <QPushButton>
82 #include <QRegularExpression>
83
84 namespace Rosegarden
85 {
86
87
88 // Widgets vertical positions inside the main QGridLayout (m_layout).
89 enum {
90 CHORDNAMERULER_ROW,
91 TEMPORULER_ROW,
92 TOPRULER_ROW,
93 PANNED_ROW, // Matrix
94 BOTTOMRULER_ROW,
95 CONTROLS_ROW,
96 HSCROLLBAR_ROW,
97 SEGMENTLABEL_ROW,
98 PANNER_ROW // Navigation area
99 };
100
101 // Widgets horizontal positions inside the main QGridLayout (m_layout).
102 enum {
103 HEADER_COL, // Piano Ruler
104 MAIN_COL,
105 };
106
MatrixWidget(bool drumMode)107 MatrixWidget::MatrixWidget(bool drumMode) :
108 m_document(nullptr),
109 m_scene(nullptr),
110 m_view(nullptr),
111 m_playTracking(true),
112 m_hZoomFactor(1.0),
113 m_vZoomFactor(1.0),
114 m_instrument(nullptr),
115 m_localMapping(nullptr),
116 m_pitchRuler(nullptr),
117 m_pianoScene(nullptr),
118 m_pianoView(nullptr),
119 m_onlyKeyMapping(false),
120 m_drumMode(drumMode),
121 m_firstNote(0),
122 m_lastNote(0),
123 m_highlightVisible(true),
124 m_toolBox(nullptr),
125 m_currentTool(nullptr),
126 m_currentVelocity(100),
127 m_lastZoomWasHV(true),
128 m_lastH(0),
129 m_lastV(0),
130 m_chordNameRuler(nullptr),
131 m_tempoRuler(nullptr),
132 m_topStandardRuler(nullptr),
133 m_bottomStandardRuler(nullptr),
134 m_referenceScale(nullptr)
135 {
136 //RG_DEBUG << "ctor";
137
138 m_layout = new QGridLayout;
139 setLayout(m_layout);
140
141 // Remove thick black lines between rulers and matrix
142 m_layout->setSpacing(0);
143
144 // Remove black margins around the matrix
145 m_layout->setContentsMargins(0, 0, 0, 0);
146
147 m_view = new Panned;
148 m_view->setBackgroundBrush(Qt::white);
149 m_view->setWheelZoomPan(true);
150 m_layout->addWidget(m_view, PANNED_ROW, MAIN_COL, 1, 1);
151
152 m_pianoView = new Panned;
153 m_pianoView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
154 m_pianoView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
155 m_layout->addWidget(m_pianoView, PANNED_ROW, HEADER_COL, 1, 1);
156
157 m_controlsWidget = new ControlRulerWidget;
158 m_layout->addWidget(m_controlsWidget, CONTROLS_ROW, MAIN_COL, 1, 1);
159
160 m_segmentLabel = new QLabel("Segment Label");
161 m_segmentLabel->setAlignment(Qt::AlignHCenter);
162 m_segmentLabel->setAutoFillBackground(true);
163 m_layout->addWidget(m_segmentLabel, SEGMENTLABEL_ROW, MAIN_COL, 1, 1);
164
165 // the panner along with zoom controls in one strip at one grid location
166 QWidget *panner = new QWidget;
167 QHBoxLayout *pannerLayout = new QHBoxLayout;
168 pannerLayout->setContentsMargins(0, 0, 0, 0);
169 pannerLayout->setSpacing(0);
170 panner->setLayout(pannerLayout);
171
172 // the segment changer roller
173 m_changerWidget = new QWidget;
174 m_changerWidget->setAutoFillBackground(true);
175 QVBoxLayout *changerWidgetLayout = new QVBoxLayout;
176 m_changerWidget->setLayout(changerWidgetLayout);
177
178 bool useRed = true;
179 m_segmentChanger = new Thumbwheel(Qt::Vertical, useRed);
180 m_segmentChanger->setToolTip(tr("<qt>Rotate wheel to change the active segment</qt>"));
181 m_segmentChanger->setFixedWidth(18);
182 m_segmentChanger->setMinimumValue(-120);
183 m_segmentChanger->setMaximumValue(120);
184 m_segmentChanger->setDefaultValue(0);
185 m_segmentChanger->setShowScale(true);
186 m_segmentChanger->setValue(60);
187 m_segmentChanger->setSpeed(0.05);
188 m_lastSegmentChangerValue = m_segmentChanger->getValue();
189 connect(m_segmentChanger, &Thumbwheel::valueChanged, this,
190 &MatrixWidget::slotSegmentChangerMoved);
191 changerWidgetLayout->addWidget(m_segmentChanger);
192
193 pannerLayout->addWidget(m_changerWidget);
194
195 // the panner
196 m_panner = new Panner;
197 m_panner->setMaximumHeight(60);
198 m_panner->setBackgroundBrush(Qt::white);
199 m_panner->setOptimizationFlag(QGraphicsView::DontAdjustForAntialiasing, true);
200
201 pannerLayout->addWidget(m_panner);
202
203 // row, col, row span, col span
204 QFrame *controls = new QFrame;
205
206 QGridLayout *controlsLayout = new QGridLayout;
207 controlsLayout->setSpacing(0);
208 controlsLayout->setContentsMargins(0, 0, 0, 0);
209 controls->setLayout(controlsLayout);
210
211 m_HVzoom = new Thumbwheel(Qt::Vertical);
212 m_HVzoom->setFixedSize(QSize(40, 40));
213 m_HVzoom->setToolTip(tr("Zoom"));
214
215 // +/- 20 clicks seems to be the reasonable limit
216 m_HVzoom->setMinimumValue(-20);
217 m_HVzoom->setMaximumValue(20);
218 m_HVzoom->setDefaultValue(0);
219 m_HVzoom->setBright(true);
220 m_HVzoom->setShowScale(true);
221 m_lastHVzoomValue = m_HVzoom->getValue();
222 controlsLayout->addWidget(m_HVzoom, 0, 0, Qt::AlignCenter);
223
224 connect(m_HVzoom, &Thumbwheel::valueChanged, this,
225 &MatrixWidget::slotPrimaryThumbwheelMoved);
226
227 m_Hzoom = new Thumbwheel(Qt::Horizontal);
228 m_Hzoom->setFixedSize(QSize(50, 16));
229 m_Hzoom->setToolTip(tr("Horizontal Zoom"));
230
231 m_Hzoom->setMinimumValue(-25);
232 m_Hzoom->setMaximumValue(60);
233 m_Hzoom->setDefaultValue(0);
234 m_Hzoom->setBright(false);
235 controlsLayout->addWidget(m_Hzoom, 1, 0);
236 connect(m_Hzoom, &Thumbwheel::valueChanged, this,
237 &MatrixWidget::slotHorizontalThumbwheelMoved);
238
239 m_Vzoom = new Thumbwheel(Qt::Vertical);
240 m_Vzoom->setFixedSize(QSize(16, 50));
241 m_Vzoom->setToolTip(tr("Vertical Zoom"));
242 m_Vzoom->setMinimumValue(-25);
243 m_Vzoom->setMaximumValue(60);
244 m_Vzoom->setDefaultValue(0);
245 m_Vzoom->setBright(false);
246 controlsLayout->addWidget(m_Vzoom, 0, 1, Qt::AlignRight);
247
248 connect(m_Vzoom, &Thumbwheel::valueChanged, this,
249 &MatrixWidget::slotVerticalThumbwheelMoved);
250
251 // a blank QPushButton forced square looks better than the tool button did
252 m_reset = new QPushButton;
253 m_reset->setFixedSize(QSize(10, 10));
254 m_reset->setToolTip(tr("Reset Zoom"));
255 controlsLayout->addWidget(m_reset, 1, 1, Qt::AlignCenter);
256
257 connect(m_reset, &QAbstractButton::clicked, this,
258 &MatrixWidget::slotResetZoomClicked);
259
260 pannerLayout->addWidget(controls);
261
262 m_layout->addWidget(panner, PANNER_ROW, HEADER_COL, 1, 2);
263
264 // Rulers being not defined still, they can't be added to m_layout.
265 // This will be done in setSegments().
266
267 // Move the scroll bar from m_view to MatrixWidget
268 m_view->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
269 m_layout->addWidget(m_view->horizontalScrollBar(),
270 HSCROLLBAR_ROW, MAIN_COL, 1, 1);
271
272 // Hide or show the horizontal scroll bar when needed
273 connect(m_view->horizontalScrollBar(), &QAbstractSlider::rangeChanged,
274 this, &MatrixWidget::slotHScrollBarRangeChanged);
275
276 connect(m_view, &Panned::viewportChanged,
277 m_panner, &Panner::slotSetPannedRect);
278
279 connect(m_view, &Panned::viewportChanged,
280 m_pianoView, &Panned::slotSetViewport);
281
282 connect(m_view, &Panned::viewportChanged,
283 m_controlsWidget, &ControlRulerWidget::slotSetPannedRect);
284
285 connect(m_view, &Panned::zoomIn,
286 this, &MatrixWidget::slotZoomIn);
287 connect(m_view, &Panned::zoomOut,
288 this, &MatrixWidget::slotZoomOut);
289
290 connect(m_panner, &Panner::pannedRectChanged,
291 m_view, &Panned::slotSetViewport);
292
293 connect(m_panner, &Panner::pannedRectChanged,
294 m_pianoView, &Panned::slotSetViewport);
295
296 connect(m_panner, &Panner::pannedRectChanged,
297 m_controlsWidget, &ControlRulerWidget::slotSetPannedRect);
298
299 connect(m_view, &Panned::pannedContentsScrolled,
300 this, &MatrixWidget::slotScrollRulers);
301
302 connect(m_panner, &Panner::zoomIn,
303 this, &MatrixWidget::slotSyncPannerZoomIn);
304
305 connect(m_panner, &Panner::zoomOut,
306 this, &MatrixWidget::slotSyncPannerZoomOut);
307
308 connect(m_pianoView, &Panned::wheelEventReceived,
309 m_view, &Panned::slotEmulateWheelEvent);
310
311 // Connect ControlRulerWidget for Auto-Scroll.
312 connect(m_controlsWidget, &ControlRulerWidget::mousePress,
313 this, &MatrixWidget::slotCRWMousePress);
314 connect(m_controlsWidget, &ControlRulerWidget::mouseMove,
315 this, &MatrixWidget::slotCRWMouseMove);
316 connect(m_controlsWidget, &ControlRulerWidget::mouseRelease,
317 this, &MatrixWidget::slotCRWMouseRelease);
318
319 m_toolBox = new MatrixToolBox(this);
320
321 // Relay context help from matrix tools
322 connect(m_toolBox, &BaseToolBox::showContextHelp,
323 this, &MatrixWidget::showContextHelp);
324
325 // Relay context help from matrix rulers
326 connect(m_controlsWidget, &ControlRulerWidget::showContextHelp,
327 this, &MatrixWidget::showContextHelp);
328
329 MatrixMover *matrixMoverTool = dynamic_cast <MatrixMover *> (m_toolBox->getTool(MatrixMover::ToolName()));
330 if (matrixMoverTool) {
331 connect(matrixMoverTool, &MatrixMover::hoveredOverNoteChanged,
332 m_controlsWidget, &ControlRulerWidget::slotHoveredOverNoteChanged);
333 }
334
335 // MatrixVelocity *matrixVelocityTool = dynamic_cast <MatrixVelocity *> (m_toolBox->getTool(MatrixVelocity::ToolName()));
336 // if (matrixVelocityTool) {
337 // connect(matrixVelocityTool, SIGNAL(hoveredOverNoteChanged()),
338 // m_controlsWidget, SLOT(slotHoveredOverNoteChanged()));
339 // }
340
341 connect(this, &MatrixWidget::toolChanged,
342 m_controlsWidget, &ControlRulerWidget::slotSetTool);
343
344 // Make sure MatrixScene always gets mouse move events even when the
345 // button isn't pressed. This way the keys on the piano keyboard
346 // to the left are always highlighted to show which note we are on.
347 m_view->setMouseTracking(true);
348
349 connect(RosegardenMainWindow::self()->getDocument(),
350 &RosegardenDocument::documentModified,
351 this, &MatrixWidget::slotDocumentModified);
352
353 // Set up AutoScroller.
354 m_autoScroller.connectScrollArea(m_view);
355 }
356
~MatrixWidget()357 MatrixWidget::~MatrixWidget()
358 {
359 // ??? QSharedPointer would be nice, but it opens up a can of worms
360 // since this is passed to reuse code that is used across the system.
361 // Panned and Panner in this case. Probably worth doing. Just a
362 // bit more work than usual.
363 delete m_scene;
364 // ??? See above.
365 delete m_pianoScene;
366 }
367
368 void
setSegments(RosegardenDocument * document,std::vector<Segment * > segments)369 MatrixWidget::setSegments(RosegardenDocument *document,
370 std::vector<Segment *> segments)
371 {
372 if (m_document) {
373 disconnect(m_document, &RosegardenDocument::pointerPositionChanged,
374 this, &MatrixWidget::slotPointerPositionChanged);
375 }
376
377 m_document = document;
378
379 Composition &comp = document->getComposition();
380
381 Track *track;
382 Instrument *instr;
383
384 // Look at segments to see if we need piano keyboard or key mapping ruler
385 // (cf comment in MatrixScene::setSegments())
386 m_onlyKeyMapping = true;
387 std::vector<Segment *>::iterator si;
388 for (si=segments.begin(); si!=segments.end(); ++si) {
389 track = comp.getTrackById((*si)->getTrack());
390 instr = document->getStudio().getInstrumentById(track->getInstrument());
391 if (instr) {
392 if (!instr->getKeyMapping()) {
393 m_onlyKeyMapping = false;
394 }
395 }
396 }
397 // Note : m_onlyKeyMapping, whose value is defined above,
398 // must be set before calling m_scene->setSegments()
399
400 delete m_scene;
401 m_scene = new MatrixScene();
402 m_scene->setMatrixWidget(this);
403 m_scene->setSegments(document, segments);
404
405 m_referenceScale = m_scene->getReferenceScale();
406
407 connect(m_scene, &MatrixScene::mousePressed,
408 this, &MatrixWidget::slotDispatchMousePress);
409
410 connect(m_scene, &MatrixScene::mouseMoved,
411 this, &MatrixWidget::slotDispatchMouseMove);
412
413 connect(m_scene, &MatrixScene::mouseReleased,
414 this, &MatrixWidget::slotDispatchMouseRelease);
415
416 connect(m_scene, &MatrixScene::mouseDoubleClicked,
417 this, &MatrixWidget::slotDispatchMouseDoubleClick);
418
419 connect(m_scene, &MatrixScene::segmentDeleted,
420 this, &MatrixWidget::segmentDeleted);
421
422 connect(m_scene, &MatrixScene::sceneDeleted,
423 this, &MatrixWidget::sceneDeleted);
424
425 m_view->setScene(m_scene);
426
427 m_toolBox->setScene(m_scene);
428
429 m_panner->setScene(m_scene);
430
431 connect(m_view, &Panned::mouseLeaves,
432 this, &MatrixWidget::slotMouseLeavesView);
433
434 generatePitchRuler();
435
436 m_controlsWidget->setViewSegment(
437 dynamic_cast<ViewSegment *>(m_scene->getCurrentViewSegment()));
438 m_controlsWidget->setRulerScale(m_referenceScale);
439
440 // For some reason this doesn't work in the constructor - not looked in detail
441 // ( ^^^ it's because m_scene is only set after construction --cc)
442 connect(m_scene, &MatrixScene::currentViewSegmentChanged,
443 m_controlsWidget, &ControlRulerWidget::slotSetCurrentViewSegment);
444
445 connect(m_scene, &MatrixScene::selectionChanged,
446 m_controlsWidget, &ControlRulerWidget::slotSelectionChanged);
447
448 connect(m_controlsWidget, &ControlRulerWidget::childRulerSelectionChanged,
449 m_scene, &MatrixScene::slotRulerSelectionChanged);
450
451 m_controlsWidget->launchMatrixRulers(segments);
452
453 connect(m_scene, SIGNAL(selectionChanged()),
454 this, SIGNAL(selectionChanged()));
455
456 m_topStandardRuler = new StandardRuler(document,
457 m_referenceScale,
458 false);
459
460 m_bottomStandardRuler = new StandardRuler(document,
461 m_referenceScale,
462 true);
463
464 m_tempoRuler = new TempoRuler(m_referenceScale,
465 document,
466 24, // height
467 true, // small
468 ThornStyle::isEnabled());
469
470 m_chordNameRuler = new ChordNameRuler(m_referenceScale,
471 document,
472 segments,
473 24); // height
474
475 m_layout->addWidget(m_topStandardRuler, TOPRULER_ROW, MAIN_COL, 1, 1);
476 m_layout->addWidget(m_bottomStandardRuler, BOTTOMRULER_ROW, MAIN_COL, 1, 1);
477 m_layout->addWidget(m_tempoRuler, TEMPORULER_ROW, MAIN_COL, 1, 1);
478 m_layout->addWidget(m_chordNameRuler, CHORDNAMERULER_ROW, MAIN_COL, 1, 1);
479
480 m_topStandardRuler->setSnapGrid(m_scene->getSnapGrid());
481 m_bottomStandardRuler->setSnapGrid(m_scene->getSnapGrid());
482
483 m_topStandardRuler->connectRulerToDocPointer(document);
484 m_bottomStandardRuler->connectRulerToDocPointer(document);
485
486 connect(m_topStandardRuler, &StandardRuler::dragPointerToPosition,
487 this, &MatrixWidget::slotStandardRulerDrag);
488 connect(m_bottomStandardRuler, &StandardRuler::dragPointerToPosition,
489 this, &MatrixWidget::slotStandardRulerDrag);
490
491 connect(m_topStandardRuler->getLoopRuler(), &LoopRuler::startMouseMove,
492 this, &MatrixWidget::slotSRStartMouseMove);
493 connect(m_topStandardRuler->getLoopRuler(), &LoopRuler::stopMouseMove,
494 this, &MatrixWidget::slotSRStopMouseMove);
495 connect(m_bottomStandardRuler->getLoopRuler(), &LoopRuler::startMouseMove,
496 this, &MatrixWidget::slotSRStartMouseMove);
497 connect(m_bottomStandardRuler->getLoopRuler(), &LoopRuler::stopMouseMove,
498 this, &MatrixWidget::slotSRStopMouseMove);
499
500 connect(m_tempoRuler, &TempoRuler::mousePress,
501 this, &MatrixWidget::slotTRMousePress);
502 connect(m_tempoRuler, &TempoRuler::mouseRelease,
503 this, &MatrixWidget::slotTRMouseRelease);
504
505 connect(m_document, &RosegardenDocument::pointerPositionChanged,
506 this, &MatrixWidget::slotPointerPositionChanged);
507
508 m_chordNameRuler->setReady();
509
510 updateSegmentChangerBackground();
511
512 // If there is only one Segment, hide the widgets that are only needed
513 // for multiple Segments.
514 if (segments.size() == 1) {
515 m_segmentLabel->hide();
516 m_changerWidget->hide();
517 }
518
519 // Go with zoom factors from the first Segment.
520 setHorizontalZoomFactor(segments[0]->matrixHZoomFactor);
521 setVerticalZoomFactor(segments[0]->matrixVZoomFactor);
522
523 }
524
525 void
generatePitchRuler()526 MatrixWidget::generatePitchRuler()
527 {
528 delete m_pianoScene; // Delete the old m_pitchRuler if any
529 m_localMapping.reset();
530 bool isPercussion = false;
531
532 Composition &comp = m_document->getComposition();
533 const MidiKeyMapping *mapping = nullptr;
534 Track *track = comp.getTrackById(m_scene->getCurrentSegment()->getTrack());
535 m_instrument = m_document->getStudio().
536 getInstrumentById(track->getInstrument());
537 if (m_instrument) {
538
539 // Make instrument tell us if it gets destroyed.
540 connect(m_instrument, &QObject::destroyed,
541 this, &MatrixWidget::slotInstrumentGone);
542
543 mapping = m_instrument->getKeyMapping();
544 if (mapping) {
545 //RG_DEBUG << "generatePitchRuler(): Instrument has key mapping: " << mapping->getName();
546 m_localMapping.reset(new MidiKeyMapping(*mapping));
547 m_localMapping->extend();
548 isPercussion = true;
549 } else {
550 //RG_DEBUG << "generatePitchRuler(): Instrument has no key mapping";
551 isPercussion = false;
552 }
553 }
554 if (mapping && !m_localMapping->getMap().empty()) {
555 m_pitchRuler = new PercussionPitchRuler(nullptr, m_localMapping,
556 m_scene->getYResolution());
557 } else {
558 if (m_onlyKeyMapping) {
559 // In such a case, a matrix resolution of 11 is used.
560 // (See comments in MatrixScene::setSegments().)
561 // As the piano keyboard works only with a resolution of 7, an
562 // empty key mapping will be used in place of the keyboard.
563 m_localMapping.reset(new MidiKeyMapping());
564 m_localMapping->getMap()[0] = ""; // extent() doesn't work?
565 m_localMapping->getMap()[127] = "";
566 m_pitchRuler = new PercussionPitchRuler(nullptr, m_localMapping,
567 m_scene->getYResolution());
568 } else {
569 m_pitchRuler = new PianoKeyboard(nullptr);
570 }
571 }
572
573 m_pitchRuler->setFixedSize(m_pitchRuler->sizeHint());
574 m_pianoView->setFixedWidth(m_pitchRuler->sizeHint().width());
575
576 m_pianoScene = new QGraphicsScene();
577 QGraphicsProxyWidget *pianoKbd = m_pianoScene->addWidget(m_pitchRuler);
578 m_pianoView->setScene(m_pianoScene);
579 m_pianoView->centerOn(pianoKbd);
580
581 QObject::connect(m_pitchRuler, &PitchRuler::hoveredOverKeyChanged,
582 this, &MatrixWidget::slotHoveredOverKeyChanged);
583
584 QObject::connect(m_pitchRuler, &PitchRuler::keyPressed,
585 this, &MatrixWidget::slotKeyPressed);
586
587 QObject::connect(m_pitchRuler, &PitchRuler::keySelected,
588 this, &MatrixWidget::slotKeySelected);
589
590 // Don't send the "note off" midi message to a percussion instrument
591 // when clicking on the pitch ruler
592 if (!isPercussion || !m_drumMode) {
593 connect(m_pitchRuler, &PitchRuler::keyReleased,
594 this, &MatrixWidget::slotKeyReleased);
595 }
596
597 // If piano scene and matrix scene don't have the same height
598 // one may shift from the other when scrolling vertically
599 QRectF viewRect = m_scene->sceneRect();
600 QRectF pianoRect = m_pianoScene->sceneRect();
601 pianoRect.setHeight(viewRect.height() + 18);
602 m_pianoScene->setSceneRect(pianoRect);
603 //@@@ The 18 pixels have been added empirically in line above to
604 // avoid any offset between matrix and pitchruler at the end of
605 // vertical scroll. I have no idea from where they come from.
606
607
608 // Apply current zoom to the new pitch ruler
609 if (m_lastZoomWasHV) {
610 // Set the view's matrix.
611 // ??? Why? Why only in this case? Why not in the other case?
612 // Why isn't this handled elsewhere? Is it?
613 QTransform m;
614 m.scale(m_hZoomFactor, m_vZoomFactor);
615 m_view->setTransform(m);
616 // Only vertical zoom factor is applied to pitch ruler
617 QTransform m2;
618 m2.scale(1, m_vZoomFactor);
619 m_pianoView->setTransform(m2);
620 m_pianoView->setFixedWidth(m_pitchRuler->sizeHint().width());
621 } else {
622 // Only vertical zoom factor is applied to pitch ruler
623 QTransform m;
624 m.scale(1.0, m_vZoomFactor);
625 m_pianoView->setTransform(m);
626 m_pianoView->setFixedWidth(m_pitchRuler->sizeHint().width());
627 }
628
629 // Move vertically the pianoView scene to fit the matrix scene.
630 QRect mr = m_view->rect();
631 QRect pr = m_pianoView->rect();
632 QRectF smr = m_view->mapToScene(mr).boundingRect();
633 QRectF spr = m_pianoView->mapToScene(pr).boundingRect();
634 m_pianoView->centerOn(spr.center().x(), smr.center().y());
635
636 m_pianoView->update();
637 }
638
639 bool
segmentsContainNotes() const640 MatrixWidget::segmentsContainNotes() const
641 {
642 if (!m_scene)
643 return false;
644
645 return m_scene->segmentsContainNotes();
646 }
647
648 void
setHorizontalZoomFactor(double factor)649 MatrixWidget::setHorizontalZoomFactor(double factor)
650 {
651 // NOTE: scaling the keyboard up and down works well for the primary zoom
652 // because it maintains the same aspect ratio for each step. I tried a few
653 // different ways to deal with this before deciding that since
654 // independent-axis zoom is a separate and mutually exclusive subsystem,
655 // about the only sensible thing we can do is keep the keyboard scaled at
656 // 1.0 horizontally, and only scale it vertically. Git'r done.
657
658 m_hZoomFactor = factor;
659 if (m_referenceScale)
660 m_referenceScale->setXZoomFactor(m_hZoomFactor);
661 m_view->resetTransform();
662 m_view->scale(m_hZoomFactor, m_vZoomFactor);
663 // Only vertical zoom factor is applied to pitch ruler
664 QTransform m;
665 m.scale(1.0, m_vZoomFactor);
666 m_pianoView->setTransform(m);
667 m_pianoView->setFixedWidth(m_pitchRuler->sizeHint().width());
668 slotScrollRulers();
669
670 // Store in Segment(s) for next time.
671 if (m_scene)
672 m_scene->setHorizontalZoomFactor(factor);
673 }
674
675 void
setVerticalZoomFactor(double factor)676 MatrixWidget::setVerticalZoomFactor(double factor)
677 {
678 m_vZoomFactor = factor;
679 if (m_referenceScale)
680 m_referenceScale->setYZoomFactor(m_vZoomFactor);
681 m_view->resetTransform();
682 m_view->scale(m_hZoomFactor, m_vZoomFactor);
683 // Only vertical zoom factor is applied to pitch ruler
684 QTransform m;
685 m.scale(1.0, m_vZoomFactor);
686 m_pianoView->setTransform(m);
687 m_pianoView->setFixedWidth(m_pitchRuler->sizeHint().width());
688
689 // Store in Segment(s) for next time.
690 if (m_scene)
691 m_scene->setVerticalZoomFactor(factor);
692 }
693
694 void
zoomInFromPanner()695 MatrixWidget::zoomInFromPanner()
696 {
697 m_hZoomFactor /= 1.1;
698 m_vZoomFactor /= 1.1;
699 if (m_referenceScale)
700 m_referenceScale->setXZoomFactor(m_hZoomFactor);
701 QTransform m;
702 m.scale(m_hZoomFactor, m_vZoomFactor);
703 m_view->setTransform(m);
704 // Only vertical zoom factor is applied to pitch ruler
705 QTransform m2;
706 m2.scale(1, m_vZoomFactor);
707 m_pianoView->setTransform(m2);
708 m_pianoView->setFixedWidth(m_pitchRuler->sizeHint().width());
709 slotScrollRulers();
710
711 // Store in Segment(s) for next time.
712 if (m_scene) {
713 m_scene->setHorizontalZoomFactor(m_hZoomFactor);
714 m_scene->setVerticalZoomFactor(m_vZoomFactor);
715 }
716 }
717
718 void
zoomOutFromPanner()719 MatrixWidget::zoomOutFromPanner()
720 {
721 m_hZoomFactor *= 1.1;
722 m_vZoomFactor *= 1.1;
723 if (m_referenceScale)
724 m_referenceScale->setXZoomFactor(m_hZoomFactor);
725 QTransform m;
726 m.scale(m_hZoomFactor, m_vZoomFactor);
727 m_view->setTransform(m);
728 // Only vertical zoom factor is applied to pitch ruler
729 QTransform m2;
730 m2.scale(1, m_vZoomFactor);
731 m_pianoView->setTransform(m2);
732 m_pianoView->setFixedWidth(m_pitchRuler->sizeHint().width());
733 slotScrollRulers();
734
735 // Store in Segment(s) for next time.
736 if (m_scene) {
737 m_scene->setHorizontalZoomFactor(m_hZoomFactor);
738 m_scene->setVerticalZoomFactor(m_vZoomFactor);
739 }
740 }
741
742 void
slotScrollRulers()743 MatrixWidget::slotScrollRulers()
744 {
745 // Get time of the window left
746 QPointF topLeft = m_view->mapToScene(0, 0);
747
748 // Apply zoom correction
749 int x = topLeft.x() * m_hZoomFactor;
750
751 // Scroll rulers accordingly
752 // ( -2 : to fix a small offset between view and rulers)
753 m_topStandardRuler->slotScrollHoriz(x - 2);
754 m_bottomStandardRuler->slotScrollHoriz(x - 2);
755 m_tempoRuler->slotScrollHoriz(x - 2);
756 m_chordNameRuler->slotScrollHoriz(x - 2);
757 }
758
759 EventSelection *
getSelection() const760 MatrixWidget::getSelection() const
761 {
762 if (!m_scene)
763 return nullptr;
764
765 return m_scene->getSelection();
766 }
767
768 void
setSelection(EventSelection * s,bool preview)769 MatrixWidget::setSelection(EventSelection *s, bool preview)
770 {
771 if (!m_scene)
772 return;
773
774 m_scene->setSelection(s, preview);
775 }
776
777 const SnapGrid *
getSnapGrid() const778 MatrixWidget::getSnapGrid() const
779 {
780 if (!m_scene)
781 return nullptr;
782
783 return m_scene->getSnapGrid();
784 }
785
786 void
setSnap(timeT t)787 MatrixWidget::setSnap(timeT t)
788 {
789 if (!m_scene)
790 return;
791
792 m_scene->setSnap(t);
793 }
794
795 void
selectAll()796 MatrixWidget::selectAll()
797 {
798 if (!m_scene)
799 return;
800
801 m_scene->selectAll();
802 }
803
804 void
clearSelection()805 MatrixWidget::clearSelection()
806 {
807 // Actually we don't clear the selection immediately: if we're
808 // using some tool other than the select tool, then the first
809 // press switches us back to the select tool.
810
811 // ??? Why? Plus there's a bug. The toolbar button does not
812 // become pressed for the select tool. Instead it still
813 // shows the old tool. Recommend changing this to do what
814 // the user has asked. Just call setSelection(). I've
815 // tested it and it works fine.
816
817 MatrixSelector *selector = dynamic_cast<MatrixSelector *>(m_currentTool);
818
819 if (!selector)
820 setSelectAndEditTool();
821 else
822 setSelection(nullptr, false);
823 }
824
825 Segment *
getCurrentSegment()826 MatrixWidget::getCurrentSegment()
827 {
828 if (!m_scene)
829 return nullptr;
830
831 return m_scene->getCurrentSegment();
832 }
833
834 void
previousSegment()835 MatrixWidget::previousSegment()
836 {
837 if (!m_scene)
838 return;
839
840 Segment *s = m_scene->getPriorSegment();
841 if (s)
842 m_scene->setCurrentSegment(s);
843 updatePointer(m_document->getComposition().getPosition());
844 updateSegmentChangerBackground();
845 }
846
847 void
nextSegment()848 MatrixWidget::nextSegment()
849 {
850 if (!m_scene)
851 return;
852
853 Segment *s = m_scene->getNextSegment();
854 if (s)
855 m_scene->setCurrentSegment(s);
856 updatePointer(m_document->getComposition().getPosition());
857 updateSegmentChangerBackground();
858 }
859
860 Device *
getCurrentDevice()861 MatrixWidget::getCurrentDevice()
862 {
863 Segment *segment = getCurrentSegment();
864 if (!segment)
865 return nullptr;
866
867 Studio &studio = m_document->getStudio();
868 Instrument *instrument =
869 studio.getInstrumentById
870 (segment->getComposition()->getTrackById(segment->getTrack())->
871 getInstrument());
872 if (!instrument)
873 return nullptr;
874
875 return instrument->getDevice();
876 }
877
878 void
slotDispatchMousePress(const MatrixMouseEvent * e)879 MatrixWidget::slotDispatchMousePress(const MatrixMouseEvent *e)
880 {
881 if (!m_currentTool)
882 return;
883
884 // Check for left and right *first*
885 if ((e->buttons & Qt::LeftButton) && (e->buttons & Qt::RightButton)) {
886 m_currentTool->handleMidButtonPress(e);
887 } else if (e->buttons & Qt::LeftButton) {
888 m_currentTool->handleLeftButtonPress(e);
889 } else if (e->buttons & Qt::MiddleButton) {
890 m_currentTool->handleMidButtonPress(e);
891 } else if (e->buttons & Qt::RightButton) {
892 m_currentTool->handleRightButtonPress(e);
893 }
894
895 m_autoScroller.start();
896 }
897
898 void
slotDispatchMouseMove(const MatrixMouseEvent * e)899 MatrixWidget::slotDispatchMouseMove(const MatrixMouseEvent *e)
900 {
901 if (m_highlightVisible)
902 m_pitchRuler->showHighlight(e->pitch);
903
904 // Needed to remove black trailers left by highlight at high zoom levels.
905 m_pianoView->update();
906
907 if (!m_currentTool)
908 return;
909
910 FollowMode followMode = m_currentTool->handleMouseMove(e);
911
912 m_autoScroller.setFollowMode(followMode);
913 }
914
915 void
slotDispatchMouseRelease(const MatrixMouseEvent * e)916 MatrixWidget::slotDispatchMouseRelease(const MatrixMouseEvent *e)
917 {
918 m_autoScroller.stop();
919
920 if (!m_currentTool)
921 return;
922
923 m_currentTool->handleMouseRelease(e);
924 }
925
926 void
slotDispatchMouseDoubleClick(const MatrixMouseEvent * e)927 MatrixWidget::slotDispatchMouseDoubleClick(const MatrixMouseEvent *e)
928 {
929 if (!m_currentTool)
930 return;
931
932 m_currentTool->handleMouseDoubleClick(e);
933 }
934
935 void
showHighlight(bool visible)936 MatrixWidget::showHighlight(bool visible)
937 {
938 m_highlightVisible = visible;
939
940 if (!visible)
941 m_pitchRuler->hideHighlight();
942 }
943
944 void
setCanvasCursor(QCursor c)945 MatrixWidget::setCanvasCursor(QCursor c)
946 {
947 if (m_view)
948 m_view->viewport()->setCursor(c);
949 }
950
951 void
setTool(QString name)952 MatrixWidget::setTool(QString name)
953 {
954 MatrixTool *tool = dynamic_cast<MatrixTool *>(m_toolBox->getTool(name));
955 if (!tool)
956 return;
957
958 if (m_currentTool)
959 m_currentTool->stow();
960
961 m_currentTool = tool;
962 m_currentTool->ready();
963 emit toolChanged(name);
964 }
965
966 void
setDrawTool()967 MatrixWidget::setDrawTool()
968 {
969 setTool(MatrixPainter::ToolName());
970 }
971
972 void
setEraseTool()973 MatrixWidget::setEraseTool()
974 {
975 setTool(MatrixEraser::ToolName());
976 }
977
978 void
setSelectAndEditTool()979 MatrixWidget::setSelectAndEditTool()
980 {
981 setTool(MatrixSelector::ToolName());
982
983 MatrixSelector *selector = dynamic_cast<MatrixSelector *>(m_currentTool);
984 if (selector) {
985 //RG_DEBUG << "setSelectAndEditTool(): selector successfully set";
986
987 // ??? This will pile up connections each time we select the arrow
988 // tool. Need to use Qt::AutoConnection | Qt::UniqueConnection.
989 // This will cause connect() to fail. Not sure how. Might
990 // need to check for errors.
991 connect(selector, &MatrixSelector::editTriggerSegment,
992 this, &MatrixWidget::editTriggerSegment);
993 }
994 }
995
996 void
setMoveTool()997 MatrixWidget::setMoveTool()
998 {
999 setTool(MatrixMover::ToolName());
1000 }
1001
1002 void
setResizeTool()1003 MatrixWidget::setResizeTool()
1004 {
1005 setTool(MatrixResizer::ToolName());
1006 }
1007
1008 void
setVelocityTool()1009 MatrixWidget::setVelocityTool()
1010 {
1011 setTool(MatrixVelocity::ToolName());
1012 }
1013
1014 void
setScrollToFollowPlayback(bool tracking)1015 MatrixWidget::setScrollToFollowPlayback(bool tracking)
1016 {
1017 m_playTracking = tracking;
1018
1019 if (m_playTracking)
1020 m_view->ensurePositionPointerInView(true);
1021 }
1022
1023 void
showVelocityRuler()1024 MatrixWidget::showVelocityRuler()
1025 {
1026 m_controlsWidget->togglePropertyRuler(BaseProperties::VELOCITY);
1027 }
1028
1029 void
showPitchBendRuler()1030 MatrixWidget::showPitchBendRuler()
1031 {
1032 m_controlsWidget->togglePitchBendRuler();
1033 }
1034
1035 void
addControlRuler(QAction * action)1036 MatrixWidget::addControlRuler(QAction *action)
1037 {
1038 QString name = action->text();
1039
1040 // FIX #1543: If name happens to come to us with an & included somewhere,
1041 // strip the & so the string will match the one we are comparing later on.
1042 //
1043 name.replace(QRegularExpression("&"), "");
1044
1045 //RG_DEBUG << "addControlRuler()";
1046 //RG_DEBUG << " my name is " << name;
1047
1048 // we just cheaply paste the code from MatrixView that created the menu to
1049 // figure out what its indices must point to (and thinking about this whole
1050 // thing, I bet it's all buggy as hell in a multi-track view where the
1051 // active segment can change, and the segment's track's device could be
1052 // completely different from whatever was first used to create the menu...
1053 // there will probably be refresh problems and crashes and general bugginess
1054 // 20% of the time, but a solution that works 80% of the time is worth
1055 // shipping, I just read on some blog, and damn the torpedoes)
1056 Controllable *c =
1057 dynamic_cast<MidiDevice *>(getCurrentDevice());
1058 if (!c) {
1059 c = dynamic_cast<SoftSynthDevice *>(getCurrentDevice());
1060 if (!c)
1061 return ;
1062 }
1063
1064 const ControlList &list = c->getControlParameters();
1065
1066 QString itemStr;
1067 // int i = 0;
1068
1069 for (ControlList::const_iterator it = list.begin();
1070 it != list.end();
1071 ++it) {
1072
1073 // Pitch Bend is treated separately now, and there's no point in adding
1074 // "unsupported" controllers to the menu, so skip everything else
1075 if (it->getType() != Controller::EventType)
1076 continue;
1077
1078 const QString hexValue =
1079 QString::asprintf("(0x%x)", it->getControllerNumber());
1080
1081 // strings extracted from data files must be QObject::tr()
1082 QString itemStr = QObject::tr("%1 Controller %2 %3")
1083 .arg(QObject::tr(it->getName().c_str()))
1084 .arg(it->getControllerNumber())
1085 .arg(hexValue);
1086
1087 if (name != itemStr)
1088 continue;
1089
1090 //RG_DEBUG << "addControlRuler(): name: " << name << " should match itemStr: " << itemStr;
1091
1092 m_controlsWidget->addControlRuler(*it);
1093
1094 // if (i == menuIndex) m_controlsWidget->slotAddControlRuler(*p);
1095 // else i++;
1096 }
1097 }
1098
1099 void
slotHScrollBarRangeChanged(int min,int max)1100 MatrixWidget::slotHScrollBarRangeChanged(int min, int max)
1101 {
1102 if (max > min)
1103 m_view->horizontalScrollBar()->show();
1104 else
1105 m_view->horizontalScrollBar()->hide();
1106 }
1107
1108 void
updatePointer(timeT t)1109 MatrixWidget::updatePointer(timeT t)
1110 {
1111 if (!m_scene)
1112 return;
1113
1114 // Convert to scene X coords.
1115 double pointerX = m_scene->getRulerScale()->getXForTime(t);
1116
1117 // Compute the limits of the scene.
1118 double sceneXMin = m_scene->sceneRect().left();
1119 double sceneXMax = m_scene->sceneRect().right();
1120
1121 // If the pointer has gone outside the limits
1122 if (pointerX < sceneXMin || sceneXMax < pointerX) {
1123 // Never move the pointer outside the scene (else the scene will grow)
1124 m_view->hidePositionPointer();
1125 m_panner->slotHidePositionPointer();
1126 } else {
1127 m_view->showPositionPointer(pointerX);
1128 m_panner->slotShowPositionPointer(pointerX);
1129 }
1130 }
1131
1132 void
slotPointerPositionChanged(timeT t)1133 MatrixWidget::slotPointerPositionChanged(timeT t)
1134 {
1135 // ??? We don't really need "t". We could just use
1136 // m_document->getComposition().getPosition().
1137
1138 updatePointer(t);
1139
1140 // Auto-scroll
1141
1142 if (m_playTracking)
1143 m_view->ensurePositionPointerInView(true); // page
1144 }
1145
1146 void
slotStandardRulerDrag(timeT t)1147 MatrixWidget::slotStandardRulerDrag(timeT t)
1148 {
1149 updatePointer(t);
1150 }
1151
1152 void
slotSRStartMouseMove()1153 MatrixWidget::slotSRStartMouseMove()
1154 {
1155 m_autoScroller.setFollowMode(FOLLOW_HORIZONTAL);
1156 m_autoScroller.start();
1157 }
1158
1159 void
slotSRStopMouseMove()1160 MatrixWidget::slotSRStopMouseMove()
1161 {
1162 m_autoScroller.stop();
1163 }
1164
1165 void
slotCRWMousePress()1166 MatrixWidget::slotCRWMousePress()
1167 {
1168 m_autoScroller.start();
1169 }
1170
1171 void
slotCRWMouseMove(FollowMode followMode)1172 MatrixWidget::slotCRWMouseMove(FollowMode followMode)
1173 {
1174 m_autoScroller.setFollowMode(followMode);
1175 }
1176
1177 void
slotCRWMouseRelease()1178 MatrixWidget::slotCRWMouseRelease()
1179 {
1180 m_autoScroller.stop();
1181 }
1182
1183 void
slotTRMousePress()1184 MatrixWidget::slotTRMousePress()
1185 {
1186 m_autoScroller.setFollowMode(FOLLOW_HORIZONTAL);
1187 m_autoScroller.start();
1188 }
1189
1190 void
slotTRMouseRelease()1191 MatrixWidget::slotTRMouseRelease()
1192 {
1193 m_autoScroller.stop();
1194 }
1195
1196 void
setTempoRulerVisible(bool visible)1197 MatrixWidget::setTempoRulerVisible(bool visible)
1198 {
1199 if (visible)
1200 m_tempoRuler->show();
1201 else
1202 m_tempoRuler->hide();
1203 }
1204
1205 void
setChordNameRulerVisible(bool visible)1206 MatrixWidget::setChordNameRulerVisible(bool visible)
1207 {
1208 if (visible)
1209 m_chordNameRuler->show();
1210 else
1211 m_chordNameRuler->hide();
1212 }
1213
1214 void
showEvent(QShowEvent * event)1215 MatrixWidget::showEvent(QShowEvent * event)
1216 {
1217 QWidget::showEvent(event);
1218 slotScrollRulers();
1219 }
1220
1221 void
slotHorizontalThumbwheelMoved(int v)1222 MatrixWidget::slotHorizontalThumbwheelMoved(int v)
1223 {
1224 // limits sanity check
1225 if (v < -25)
1226 v = -25;
1227 if (v > 60)
1228 v = 60;
1229 if (m_lastH < -25)
1230 m_lastH = -25;
1231 if (m_lastH > 60)
1232 m_lastH = 60;
1233
1234 int steps = v - m_lastH;
1235 if (steps < 0)
1236 steps *= -1;
1237
1238 bool zoomingIn = (v > m_lastH);
1239 double newZoom = m_hZoomFactor;
1240
1241 for (int i = 0; i < steps; ++i) {
1242 if (zoomingIn)
1243 newZoom *= 1.1;
1244 else
1245 newZoom /= 1.1;
1246 }
1247
1248 //RG_DEBUG << "slotHorizontalThumbwheelMoved(): v is: " << v << " h zoom factor was: " << m_lastH << " now: " << newZoom << " zooming " << (zoomingIn ? "IN" : "OUT");
1249
1250 setHorizontalZoomFactor(newZoom);
1251 m_lastH = v;
1252 m_lastZoomWasHV = false;
1253 }
1254
1255 void
slotVerticalThumbwheelMoved(int v)1256 MatrixWidget::slotVerticalThumbwheelMoved(int v)
1257 {
1258 // limits sanity check
1259 if (v < -25)
1260 v = -25;
1261 if (v > 60)
1262 v = 60;
1263 if (m_lastV < -25)
1264 m_lastV = -25;
1265 if (m_lastV > 60)
1266 m_lastV = 60;
1267
1268 int steps = v - m_lastV;
1269 if (steps < 0)
1270 steps *= -1;
1271
1272 bool zoomingIn = (v > m_lastV);
1273 double newZoom = m_vZoomFactor;
1274
1275 for (int i = 0; i < steps; ++i) {
1276 if (zoomingIn)
1277 newZoom *= 1.1;
1278 else
1279 newZoom /= 1.1;
1280 }
1281
1282 //RG_DEBUG << "slotVerticalThumbwheelMoved(): v is: " << v << " z zoom factor was: " << m_lastV << " now: " << newZoom << " zooming " << (zoomingIn ? "IN" : "OUT");
1283
1284 setVerticalZoomFactor(newZoom);
1285 m_lastV = v;
1286 m_lastZoomWasHV = false;
1287 }
1288
1289 void
slotPrimaryThumbwheelMoved(int v)1290 MatrixWidget::slotPrimaryThumbwheelMoved(int v)
1291 {
1292 // little bit of kludge work to deal with value manipulations that are
1293 // outside of the constraints imposed by the primary zoom wheel itself
1294 if (v < -20)
1295 v = -20;
1296 if (v > 20)
1297 v = 20;
1298 if (m_lastHVzoomValue < -20)
1299 m_lastHVzoomValue = -20;
1300 if (m_lastHVzoomValue > 20)
1301 m_lastHVzoomValue = 20;
1302
1303 // When dragging the wheel up and down instead of mouse wheeling it, it
1304 // steps according to its speed. I don't see a sure way (and after all
1305 // there are no docs!) to make sure dragging results in a smooth 1:1
1306 // relationship when compared with mouse wheeling, and we are just hijacking
1307 // zoomInFromPanner() here, so we will look at the number of steps
1308 // between the old value and the last one, and call the slot that many times
1309 // in order to enforce the 1:1 relationship.
1310 int steps = v - m_lastHVzoomValue;
1311 if (steps < 0)
1312 steps *= -1;
1313
1314 for (int i = 0; i < steps; ++i) {
1315 if (v < m_lastHVzoomValue)
1316 zoomInFromPanner();
1317 else if (v > m_lastHVzoomValue)
1318 zoomOutFromPanner();
1319 }
1320
1321 m_lastHVzoomValue = v;
1322 m_lastZoomWasHV = true;
1323 }
1324
1325 void
slotResetZoomClicked()1326 MatrixWidget::slotResetZoomClicked()
1327 {
1328 //RG_DEBUG << "slotResetZoomClicked()";
1329
1330 m_hZoomFactor = 1.0;
1331 m_vZoomFactor = 1.0;
1332 if (m_referenceScale) {
1333 m_referenceScale->setXZoomFactor(m_hZoomFactor);
1334 m_referenceScale->setYZoomFactor(m_vZoomFactor);
1335 }
1336 m_view->resetTransform();
1337 QTransform m;
1338 m.scale(m_hZoomFactor, m_vZoomFactor);
1339 m_view->setTransform(m);
1340 m_view->scale(m_hZoomFactor, m_vZoomFactor);
1341 // Only vertical zoom factor is applied to pitch ruler
1342 QTransform m2;
1343 m2.scale(1, m_vZoomFactor);
1344 m_pianoView->setTransform(m2);
1345 m_pianoView->setFixedWidth(m_pitchRuler->sizeHint().width());
1346 slotScrollRulers();
1347
1348 // scale factor 1.0 = 100% zoom
1349 m_Hzoom->setValue(1);
1350 m_Vzoom->setValue(1);
1351 m_HVzoom->setValue(0);
1352 m_lastHVzoomValue = 0;
1353 m_lastH = 0;
1354 m_lastV = 0;
1355
1356 // Store in Segment(s) for next time.
1357 if (m_scene) {
1358 m_scene->setHorizontalZoomFactor(m_hZoomFactor);
1359 m_scene->setVerticalZoomFactor(m_vZoomFactor);
1360 }
1361 }
1362
1363 void
slotSyncPannerZoomIn()1364 MatrixWidget::slotSyncPannerZoomIn()
1365 {
1366 int v = m_lastHVzoomValue - 1;
1367
1368 m_HVzoom->setValue(v);
1369 slotPrimaryThumbwheelMoved(v);
1370 }
1371
1372 void
slotSyncPannerZoomOut()1373 MatrixWidget::slotSyncPannerZoomOut()
1374 {
1375 int v = m_lastHVzoomValue + 1;
1376
1377 m_HVzoom->setValue(v);
1378 slotPrimaryThumbwheelMoved(v);
1379 }
1380
1381 void
slotSegmentChangerMoved(int v)1382 MatrixWidget::slotSegmentChangerMoved(int v)
1383 {
1384 // see comments in slotPrimaryThumbWheelMoved() for an explanation of that
1385 // mechanism, which is repurposed and simplified here
1386
1387 if (v < -120)
1388 v = -120;
1389 if (v > 120)
1390 v = 120;
1391 if (m_lastSegmentChangerValue < -120)
1392 m_lastSegmentChangerValue = -120;
1393 if (m_lastSegmentChangerValue > 120)
1394 m_lastSegmentChangerValue = 120;
1395
1396 int steps = v - m_lastSegmentChangerValue;
1397 if (steps < 0) steps *= -1;
1398
1399 for (int i = 0; i < steps; ++i) {
1400 if (v < m_lastSegmentChangerValue)
1401 nextSegment();
1402 else if (v > m_lastSegmentChangerValue)
1403 previousSegment();
1404 }
1405
1406 m_lastSegmentChangerValue = v;
1407 updateSegmentChangerBackground();
1408
1409 // If we are switching between a pitched instrument segment and a pecussion
1410 // segment or betwween two percussion segments with different percussion
1411 // sets, the pitch ruler may need to be regenerated.
1412 // TODO : test if regeneration is really needed before doing it
1413 generatePitchRuler();
1414 }
1415
1416 void
updateSegmentChangerBackground()1417 MatrixWidget::updateSegmentChangerBackground()
1418 {
1419 const Composition &composition = m_document->getComposition();
1420 const Segment *segment = m_scene->getCurrentSegment();
1421
1422 // set the changer widget background to the now current segment's
1423 // background, and reset the tooltip style to compensate
1424 QColor segmentColor = composition.getSegmentColourMap().
1425 getColour(segment->getColourIndex());
1426
1427 QPalette palette = m_changerWidget->palette();
1428 palette.setColor(QPalette::Window, segmentColor);
1429 m_changerWidget->setPalette(palette);
1430
1431 const Track *track = composition.getTrackById(segment->getTrack());
1432 if (!track)
1433 return;
1434
1435 QString trackLabel = QString::fromStdString(track->getLabel());
1436 if (trackLabel == "")
1437 trackLabel = tr("<untitled>");
1438
1439 const QString segmentText = tr("Track %1 (%2) | %3").
1440 arg(track->getPosition() + 1).
1441 arg(trackLabel).
1442 arg(QString::fromStdString(segment->getLabel()));
1443
1444 m_segmentLabel->setText(segmentText);
1445
1446 // Segment label colors
1447 palette = m_segmentLabel->palette();
1448 // Background
1449 palette.setColor(QPalette::Window, segmentColor);
1450 // Foreground/Text
1451 palette.setColor(QPalette::WindowText, segment->getPreviewColour());
1452 m_segmentLabel->setPalette(palette);
1453 }
1454
1455 void
slotHoveredOverKeyChanged(unsigned int y)1456 MatrixWidget::slotHoveredOverKeyChanged(unsigned int y)
1457 {
1458 //RG_DEBUG << "slotHoveredOverKeyChanged(" << y << ")";
1459
1460 int evPitch = m_scene->calculatePitchFromY(y);
1461 m_pitchRuler->showHighlight(evPitch);
1462 m_pianoView->update(); // Needed to remove black trailers left by
1463 // highlight at high zoom levels
1464 }
1465
1466 void
slotMouseLeavesView()1467 MatrixWidget::slotMouseLeavesView()
1468 {
1469 // The mouse has left the view, so the highlight in the pitch ruler
1470 // has to be un-highlighted.
1471 m_pitchRuler->hideHighlight();
1472
1473 // At high zoom levels, black trailers are left behind. This makes
1474 // sure they are removed. (Retest this.)
1475 m_pianoView->update();
1476 }
1477
1478
slotKeyPressed(unsigned int y,bool repeating)1479 void MatrixWidget::slotKeyPressed(unsigned int y, bool repeating)
1480 {
1481 //RG_DEBUG << "slotKeyPressed(" << y << ")";
1482
1483 slotHoveredOverKeyChanged(y);
1484 int evPitch = m_scene->calculatePitchFromY(y);
1485
1486 // If we are part of a run up the keyboard, don't send redundant
1487 // note ons. Otherwise we will send a note on for every tiny movement
1488 // of the mouse.
1489 if (m_lastNote == evPitch && repeating)
1490 return;
1491
1492 // Save value
1493 m_lastNote = evPitch;
1494 if (!repeating)
1495 m_firstNote = evPitch;
1496
1497 Composition &comp = m_document->getComposition();
1498 Studio &studio = m_document->getStudio();
1499
1500 MatrixViewSegment *current = m_scene->getCurrentViewSegment();
1501 Track *track = comp.getTrackById(current->getSegment().getTrack());
1502 if (!track)
1503 return;
1504
1505 Instrument *ins = studio.getInstrumentById(track->getInstrument());
1506
1507 StudioControl::playPreviewNote(
1508 ins, // instrument
1509 evPitch + current->getSegment().getTranspose(), // pitch
1510 MidiMaxValue, // velocity
1511 RealTime(-1, 0), // duration: Note-on, no note-off.
1512 false); // oneshot
1513 }
1514
slotKeySelected(unsigned int y,bool repeating)1515 void MatrixWidget::slotKeySelected(unsigned int y, bool repeating)
1516 {
1517 //RG_DEBUG << "slotKeySelected(" << y << ")";
1518
1519 // This is called when shift is held down and a key is pressed on the
1520 // pitch ruler. This will select all notes with this pitch in the
1521 // segment.
1522
1523 slotHoveredOverKeyChanged(y);
1524
1525 // getCanvasView()->scrollVertSmallSteps(y);
1526
1527 int evPitch = m_scene->calculatePitchFromY(y);
1528
1529 // If we are part of a run up the keyboard, don't send redundant
1530 // note ons. Otherwise we will send a note on for every tiny movement
1531 // of the mouse.
1532 if (m_lastNote == evPitch && repeating)
1533 return;
1534
1535 // Save value
1536 m_lastNote = evPitch;
1537 if (!repeating)
1538 m_firstNote = evPitch;
1539
1540 MatrixViewSegment *current = m_scene->getCurrentViewSegment();
1541
1542 EventSelection *eventSelection = new EventSelection(current->getSegment());
1543
1544 // For each Event in the current Segment...
1545 for (Segment::iterator i = current->getSegment().begin();
1546 current->getSegment().isBeforeEndMarker(i);
1547 ++i) {
1548
1549 // If this is a Note event with a pitch...
1550 if ((*i)->isa(Note::EventType) &&
1551 (*i)->has(BaseProperties::PITCH)) {
1552
1553 MidiByte pitch = (*i)->get<Int>(BaseProperties::PITCH);
1554
1555 // If this note is between the first note selected and
1556 // where we are now, select it.
1557 if (pitch >= std::min((int)m_firstNote, evPitch) &&
1558 pitch <= std::max((int)m_firstNote, evPitch)) {
1559 eventSelection->addEvent(*i);
1560 }
1561 }
1562 }
1563
1564 if (getSelection()) {
1565 // allow addFromSelection to deal with eliminating duplicates
1566 eventSelection->addFromSelection(getSelection());
1567 }
1568
1569 setSelection(eventSelection, false);
1570
1571 // now play the note as well
1572 Composition &comp = m_document->getComposition();
1573 Studio &studio = m_document->getStudio();
1574
1575 Track *track = comp.getTrackById(current->getSegment().getTrack());
1576 if (!track)
1577 return;
1578
1579 Instrument *ins = studio.getInstrumentById(track->getInstrument());
1580
1581 StudioControl::playPreviewNote(
1582 ins, // instrument
1583 evPitch + current->getSegment().getTranspose(), // pitch
1584 MidiMaxValue, // velocity
1585 RealTime(-1, 0), // duration: Note-on, no note-off.
1586 false); // oneshot
1587 }
1588
slotKeyReleased(unsigned int y,bool repeating)1589 void MatrixWidget::slotKeyReleased(unsigned int y, bool repeating)
1590 {
1591 //RG_DEBUG << "slotKeyReleased(" << y << ")";
1592
1593 int evPitch = m_scene->calculatePitchFromY(y);
1594
1595 // If we are part of a run up the keyboard, don't send a
1596 // note off for every tiny movement of the mouse.
1597 if (m_lastNote == evPitch && repeating)
1598 return;
1599
1600 // send note off (note on at zero velocity)
1601
1602 Composition &comp = m_document->getComposition();
1603 Studio &studio = m_document->getStudio();
1604
1605 MatrixViewSegment *current = m_scene->getCurrentViewSegment();
1606 Track *track = comp.getTrackById(current->getSegment().getTrack());
1607 if (!track)
1608 return;
1609
1610 Instrument *ins = studio.getInstrumentById(track->getInstrument());
1611
1612 StudioControl::playPreviewNote(
1613 ins, // instrument
1614 evPitch + current->getSegment().getTranspose(), // pitch
1615 0, // velocity: 0 => note-off
1616 RealTime(0, 1), // duration: Need non-zero to get through.
1617 false); // oneshot
1618 }
1619
1620 void
showInitialPointer()1621 MatrixWidget::showInitialPointer()
1622 {
1623 if (!m_scene)
1624 return;
1625
1626 updatePointer(m_document->getComposition().getPosition());
1627
1628 // QGraphicsView usually defaults to the center. We want to
1629 // start at the left center.
1630 // Note: This worked fine at the end of setSegments() as well.
1631 // This feels like a better place. Especially since we
1632 // might want to scroll to where the pointer is.
1633 // ??? Probably better to scroll left/right so that the beginning
1634 // of the selected Segment is at the left edge. That's a bit
1635 // tricky, but if we don't do that, the user might end up someplace
1636 // unexpected when editing multiple segments.
1637 m_view->centerOn(QPointF(0, m_view->sceneRect().center().y()));
1638 }
1639
1640 void
slotInstrumentGone()1641 MatrixWidget::slotInstrumentGone()
1642 {
1643 m_instrument = nullptr;
1644 }
1645
1646 void
slotPlayPreviewNote(Segment * segment,int pitch)1647 MatrixWidget::slotPlayPreviewNote(Segment *segment, int pitch)
1648 {
1649 m_scene->playNote(*segment, pitch, 100);
1650 }
1651
1652 void
slotDocumentModified(bool)1653 MatrixWidget::slotDocumentModified(bool)
1654 {
1655 // This covers the test case when the user toggles the "Percussion"
1656 // checkbox on the MIPP. Also covers the test case where the key
1657 // map changes due to a change in percussion program.
1658
1659 // ??? Eventually, this should do a full refresh of the
1660 // entire MatrixWidget and should cover all test cases where
1661 // the UI must update. Unfortunately, the design of MatrixWidget
1662 // does not include an updateWidgets(). We'll probably have to
1663 // redesign and rewrite MatrixWidget before this becomes possible.
1664
1665 generatePitchRuler();
1666 }
1667
1668 void
slotZoomIn()1669 MatrixWidget::slotZoomIn()
1670 {
1671 int v = m_lastH - 1;
1672
1673 m_Hzoom->setValue(v);
1674 slotHorizontalThumbwheelMoved(v);
1675 }
1676
1677 void
slotZoomOut()1678 MatrixWidget::slotZoomOut()
1679 {
1680 int v = m_lastH + 1;
1681
1682 m_Hzoom->setValue(v);
1683 slotHorizontalThumbwheelMoved(v);
1684 }
1685
1686
1687 }
1688
1689