1 /*
2  * PianoRoll.cpp - implementation of piano roll which is used for actual
3  *                  writing of melodies
4  *
5  * Copyright (c) 2004-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
6  * Copyright (c) 2008 Andrew Kelley <superjoe30/at/gmail/dot/com>
7  *
8  * This file is part of LMMS - https://lmms.io
9  *
10  * This program is free software; you can redistribute it and/or
11  * modify it under the terms of the GNU General Public
12  * License as published by the Free Software Foundation; either
13  * version 2 of the License, or (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18  * General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public
21  * License along with this program (see COPYING); if not, write to the
22  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
23  * Boston, MA 02110-1301 USA.
24  *
25  */
26 
27 #include <QApplication>
28 #include <QClipboard>
29 #include <QKeyEvent>
30 #include <QLabel>
31 #include <QLayout>
32 #include <QMdiArea>
33 #include <QPainter>
34 #include <QPointer>
35 #include <QScrollBar>
36 #include <QStyleOption>
37 #include <QSignalMapper>
38 
39 #ifndef __USE_XOPEN
40 #define __USE_XOPEN
41 #endif
42 
43 #include <math.h>
44 
45 #include "AutomationEditor.h"
46 #include "ActionGroup.h"
47 #include "ConfigManager.h"
48 #include "PianoRoll.h"
49 #include "BBTrackContainer.h"
50 #include "Clipboard.h"
51 #include "ComboBox.h"
52 #include "debug.h"
53 #include "DetuningHelper.h"
54 #include "embed.h"
55 #include "GuiApplication.h"
56 #include "gui_templates.h"
57 #include "InstrumentTrack.h"
58 #include "MainWindow.h"
59 #include "Pattern.h"
60 #include "SongEditor.h"
61 #include "TextFloat.h"
62 #include "TimeLineWidget.h"
63 
64 
65 #if QT_VERSION < 0x040800
66 #define MiddleButton MidButton
67 #endif
68 
69 
70 typedef AutomationPattern::timeMap timeMap;
71 
72 
73 // some constants...
74 const int INITIAL_PIANOROLL_WIDTH = 860;
75 const int INITIAL_PIANOROLL_HEIGHT = 480;
76 
77 const int SCROLLBAR_SIZE = 12;
78 const int PIANO_X = 0;
79 
80 const int WHITE_KEY_WIDTH = 64;
81 const int BLACK_KEY_WIDTH = 41;
82 const int WHITE_KEY_SMALL_HEIGHT = 18;
83 const int WHITE_KEY_BIG_HEIGHT = 24;
84 const int BLACK_KEY_HEIGHT = 16;
85 const int C_KEY_LABEL_X = WHITE_KEY_WIDTH - 19;
86 const int KEY_LINE_HEIGHT = 12;
87 const int OCTAVE_HEIGHT = KEY_LINE_HEIGHT * KeysPerOctave;	// = 12 * 12;
88 
89 const int NOTE_EDIT_RESIZE_BAR = 6;
90 const int NOTE_EDIT_MIN_HEIGHT = 50;
91 const int KEY_AREA_MIN_HEIGHT = 100;
92 const int PR_BOTTOM_MARGIN = SCROLLBAR_SIZE;
93 const int PR_TOP_MARGIN = 16;
94 const int PR_RIGHT_MARGIN = SCROLLBAR_SIZE;
95 
96 
97 // width of area used for resizing (the grip at the end of a note)
98 const int RESIZE_AREA_WIDTH = 9;
99 
100 // width of line for setting volume/panning of note
101 const int NOTE_EDIT_LINE_WIDTH = 3;
102 
103 // key where to start
104 const int INITIAL_START_KEY = Key_C + Octave_4 * KeysPerOctave;
105 
106 // number of each note to provide in quantization and note lengths
107 const int NUM_EVEN_LENGTHS = 6;
108 const int NUM_TRIPLET_LENGTHS = 5;
109 
110 
111 
112 QPixmap * PianoRoll::s_whiteKeySmallPm = NULL;
113 QPixmap * PianoRoll::s_whiteKeySmallPressedPm = NULL;
114 QPixmap * PianoRoll::s_whiteKeyBigPm = NULL;
115 QPixmap * PianoRoll::s_whiteKeyBigPressedPm = NULL;
116 QPixmap * PianoRoll::s_blackKeyPm = NULL;
117 QPixmap * PianoRoll::s_blackKeyPressedPm = NULL;
118 QPixmap * PianoRoll::s_toolDraw = NULL;
119 QPixmap * PianoRoll::s_toolErase = NULL;
120 QPixmap * PianoRoll::s_toolSelect = NULL;
121 QPixmap * PianoRoll::s_toolMove = NULL;
122 QPixmap * PianoRoll::s_toolOpen = NULL;
123 
124 TextFloat * PianoRoll::s_textFloat = NULL;
125 
126 static QString s_noteStrings[12] = { "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"};
127 
getNoteString(int key)128 static QString getNoteString( int key )
129 {
130 	return s_noteStrings[key % 12] + QString::number( static_cast<int>( key / KeysPerOctave ) );
131 }
132 
133 
134 
135 // used for drawing of piano
136 PianoRoll::PianoRollKeyTypes PianoRoll::prKeyOrder[] =
137 {
138 	PR_WHITE_KEY_SMALL, PR_BLACK_KEY, PR_WHITE_KEY_BIG, PR_BLACK_KEY,
139 	PR_WHITE_KEY_SMALL, PR_WHITE_KEY_SMALL, PR_BLACK_KEY, PR_WHITE_KEY_BIG,
140 	PR_BLACK_KEY, PR_WHITE_KEY_BIG, PR_BLACK_KEY, PR_WHITE_KEY_SMALL
141 } ;
142 
143 
144 const int DEFAULT_PR_PPT = KEY_LINE_HEIGHT * DefaultStepsPerTact;
145 
146 const QVector<double> PianoRoll::m_zoomLevels =
147 		{ 0.125f, 0.25f, 0.5f, 1.0f, 2.0f, 4.0f, 8.0f };
148 
149 
PianoRoll()150 PianoRoll::PianoRoll() :
151 	m_nemStr( QVector<QString>() ),
152 	m_noteEditMenu( NULL ),
153 	m_semiToneMarkerMenu( NULL ),
154 	m_zoomingModel(),
155 	m_quantizeModel(),
156 	m_noteLenModel(),
157 	m_scaleModel(),
158 	m_chordModel(),
159 	m_pattern( NULL ),
160 	m_currentPosition(),
161 	m_recording( false ),
162 	m_currentNote( NULL ),
163 	m_action( ActionNone ),
164 	m_noteEditMode( NoteEditVolume ),
165 	m_moveBoundaryLeft( 0 ),
166 	m_moveBoundaryTop( 0 ),
167 	m_moveBoundaryRight( 0 ),
168 	m_moveBoundaryBottom( 0 ),
169 	m_mouseDownKey( 0 ),
170 	m_mouseDownTick( 0 ),
171 	m_lastMouseX( 0 ),
172 	m_lastMouseY( 0 ),
173 	m_oldNotesEditHeight( 100 ),
174 	m_notesEditHeight( 100 ),
175 	m_ppt( DEFAULT_PR_PPT ),
176 	m_lenOfNewNotes( MidiTime( 0, DefaultTicksPerTact/4 ) ),
177 	m_lastNoteVolume( DefaultVolume ),
178 	m_lastNotePanning( DefaultPanning ),
179 	m_startKey( INITIAL_START_KEY ),
180 	m_lastKey( 0 ),
181 	m_editMode( ModeDraw ),
182 	m_ctrlMode( ModeDraw ),
183 	m_mouseDownRight( false ),
184 	m_scrollBack( false ),
185 	m_barLineColor( 0, 0, 0 ),
186 	m_beatLineColor( 0, 0, 0 ),
187 	m_lineColor( 0, 0, 0 ),
188 	m_noteModeColor( 0, 0, 0 ),
189 	m_noteColor( 0, 0, 0 ),
190 	m_barColor( 0, 0, 0 ),
191 	m_selectedNoteColor( 0, 0, 0 ),
192 	m_textColor( 0, 0, 0 ),
193 	m_textColorLight( 0, 0, 0 ),
194 	m_textShadow( 0, 0, 0 ),
195 	m_markedSemitoneColor( 0, 0, 0 ),
196 	m_noteOpacity( 255 ),
197 	m_noteBorders( true ),
198 	m_backgroundShade( 0, 0, 0 )
199 {
200 	// gui names of edit modes
201 	m_nemStr.push_back( tr( "Note Velocity" ) );
202 	m_nemStr.push_back( tr( "Note Panning" ) );
203 
204 	QSignalMapper * signalMapper = new QSignalMapper( this );
205 	m_noteEditMenu = new QMenu( this );
206 	m_noteEditMenu->clear();
207 	for( int i = 0; i < m_nemStr.size(); ++i )
208 	{
209 		QAction * act = new QAction( m_nemStr.at(i), this );
210 		connect( act, SIGNAL(triggered()), signalMapper, SLOT(map()) );
211 		signalMapper->setMapping( act, i );
212 		m_noteEditMenu->addAction( act );
213 	}
214 	connect( signalMapper, SIGNAL(mapped(int)),
215 			this, SLOT(changeNoteEditMode(int)) );
216 
217 	signalMapper = new QSignalMapper( this );
218 	m_semiToneMarkerMenu = new QMenu( this );
219 
220 	QAction* markSemitoneAction = new QAction( tr("Mark/unmark current semitone"), this );
221 	QAction* markAllOctaveSemitonesAction = new QAction( tr("Mark/unmark all corresponding octave semitones"), this );
222 	QAction* markScaleAction = new QAction( tr("Mark current scale"), this );
223 	QAction* markChordAction = new QAction( tr("Mark current chord"), this );
224 	QAction* unmarkAllAction = new QAction( tr("Unmark all"), this );
225 	QAction* copyAllNotesAction = new QAction( tr("Select all notes on this key"), this);
226 
227 	connect( markSemitoneAction, SIGNAL(triggered()), signalMapper, SLOT(map()) );
228 	connect( markAllOctaveSemitonesAction, SIGNAL(triggered()), signalMapper, SLOT(map()) );
229 	connect( markScaleAction, SIGNAL(triggered()), signalMapper, SLOT(map()) );
230 	connect( markChordAction, SIGNAL(triggered()), signalMapper, SLOT(map()) );
231 	connect( unmarkAllAction, SIGNAL(triggered()), signalMapper, SLOT(map()) );
232 	connect( copyAllNotesAction, SIGNAL(triggered()), signalMapper, SLOT(map()) );
233 
234 	signalMapper->setMapping( markSemitoneAction, static_cast<int>( stmaMarkCurrentSemiTone ) );
235 	signalMapper->setMapping( markAllOctaveSemitonesAction, static_cast<int>( stmaMarkAllOctaveSemiTones ) );
236 	signalMapper->setMapping( markScaleAction, static_cast<int>( stmaMarkCurrentScale ) );
237 	signalMapper->setMapping( markChordAction, static_cast<int>( stmaMarkCurrentChord ) );
238 	signalMapper->setMapping( unmarkAllAction, static_cast<int>( stmaUnmarkAll ) );
239 	signalMapper->setMapping( copyAllNotesAction, static_cast<int>( stmaCopyAllNotesOnKey ) );
240 
241 	markScaleAction->setEnabled( false );
242 	markChordAction->setEnabled( false );
243 
244 	connect( this, SIGNAL(semiToneMarkerMenuScaleSetEnabled(bool)), markScaleAction, SLOT(setEnabled(bool)) );
245 	connect( this, SIGNAL(semiToneMarkerMenuChordSetEnabled(bool)), markChordAction, SLOT(setEnabled(bool)) );
246 
247 	connect( signalMapper, SIGNAL(mapped(int)), this, SLOT(markSemiTone(int)) );
248 
249 	m_semiToneMarkerMenu->addAction( markSemitoneAction );
250 	m_semiToneMarkerMenu->addAction( markAllOctaveSemitonesAction );
251 	m_semiToneMarkerMenu->addAction( markScaleAction );
252 	m_semiToneMarkerMenu->addAction( markChordAction );
253 	m_semiToneMarkerMenu->addAction( unmarkAllAction );
254 	m_semiToneMarkerMenu->addAction( copyAllNotesAction );
255 
256 	// init pixmaps
257 	if( s_whiteKeySmallPm == NULL )
258 	{
259 		s_whiteKeySmallPm = new QPixmap( embed::getIconPixmap(
260 						"pr_white_key_small" ) );
261 	}
262 	if( s_whiteKeySmallPressedPm == NULL )
263 	{
264 		s_whiteKeySmallPressedPm = new QPixmap( embed::getIconPixmap(
265 						"pr_white_key_small_pressed" ) );
266 	}
267 	if( s_whiteKeyBigPm == NULL )
268 	{
269 		s_whiteKeyBigPm = new QPixmap( embed::getIconPixmap(
270 							"pr_white_key_big" ) );
271 	}
272 	if( s_whiteKeyBigPressedPm == NULL )
273 	{
274 		s_whiteKeyBigPressedPm = new QPixmap( embed::getIconPixmap(
275 							"pr_white_key_big_pressed" ) );
276 	}
277 	if( s_blackKeyPm == NULL )
278 	{
279 		s_blackKeyPm = new QPixmap( embed::getIconPixmap(
280 							"pr_black_key" ) );
281 	}
282 	if( s_blackKeyPressedPm == NULL )
283 	{
284 		s_blackKeyPressedPm = new QPixmap( embed::getIconPixmap(
285 							"pr_black_key_pressed" ) );
286 	}
287 	if( s_toolDraw == NULL )
288 	{
289 		s_toolDraw = new QPixmap( embed::getIconPixmap( "edit_draw" ) );
290 	}
291 	if( s_toolErase == NULL )
292 	{
293 		s_toolErase= new QPixmap( embed::getIconPixmap( "edit_erase" ) );
294 	}
295 	if( s_toolSelect == NULL )
296 	{
297 		s_toolSelect = new QPixmap( embed::getIconPixmap( "edit_select" ) );
298 	}
299 	if( s_toolMove == NULL )
300 	{
301 		s_toolMove = new QPixmap( embed::getIconPixmap( "edit_move" ) );
302 	}
303 	if( s_toolOpen == NULL )
304 	{
305 		s_toolOpen = new QPixmap( embed::getIconPixmap( "automation" ) );
306 	}
307 
308 	// init text-float
309 	if( s_textFloat == NULL )
310 	{
311 		s_textFloat = new TextFloat;
312 	}
313 
314 	setAttribute( Qt::WA_OpaquePaintEvent, true );
315 
316 	// add time-line
317 	m_timeLine = new TimeLineWidget( WHITE_KEY_WIDTH, 0, m_ppt,
318 					Engine::getSong()->getPlayPos(
319 						Song::Mode_PlayPattern ),
320 						m_currentPosition, this );
321 	connect( this, SIGNAL( positionChanged( const MidiTime & ) ),
322 		m_timeLine, SLOT( updatePosition( const MidiTime & ) ) );
323 	connect( m_timeLine, SIGNAL( positionChanged( const MidiTime & ) ),
324 			this, SLOT( updatePosition( const MidiTime & ) ) );
325 
326 	// update timeline when in record-accompany mode
327 	connect( Engine::getSong()->getPlayPos( Song::Mode_PlaySong ).m_timeLine,
328 				SIGNAL( positionChanged( const MidiTime & ) ),
329 			this,
330 			SLOT( updatePositionAccompany( const MidiTime & ) ) );
331 	// TODO
332 /*	connect( engine::getSong()->getPlayPos( Song::Mode_PlayBB ).m_timeLine,
333 				SIGNAL( positionChanged( const MidiTime & ) ),
334 			this,
335 			SLOT( updatePositionAccompany( const MidiTime & ) ) );*/
336 
337 	removeSelection();
338 
339 	// init scrollbars
340 	m_leftRightScroll = new QScrollBar( Qt::Horizontal, this );
341 	m_leftRightScroll->setSingleStep( 1 );
342 	connect( m_leftRightScroll, SIGNAL( valueChanged( int ) ), this,
343 						SLOT( horScrolled( int ) ) );
344 
345 	m_topBottomScroll = new QScrollBar( Qt::Vertical, this );
346 	m_topBottomScroll->setSingleStep( 1 );
347 	m_topBottomScroll->setPageStep( 20 );
348 	connect( m_topBottomScroll, SIGNAL( valueChanged( int ) ), this,
349 						SLOT( verScrolled( int ) ) );
350 
351 	// setup zooming-stuff
352 	for( float const & zoomLevel : m_zoomLevels )
353 	{
354 		m_zoomingModel.addItem( QString( "%1\%" ).arg( zoomLevel * 100 ) );
355 	}
356 	m_zoomingModel.setValue( m_zoomingModel.findText( "100%" ) );
357 	connect( &m_zoomingModel, SIGNAL( dataChanged() ),
358 					this, SLOT( zoomingChanged() ) );
359 
360 	// Set up quantization model
361 	m_quantizeModel.addItem( tr( "Note lock" ) );
362 	for( int i = 0; i <= NUM_EVEN_LENGTHS; ++i )
363 	{
364 		m_quantizeModel.addItem( "1/" + QString::number( 1 << i ) );
365 	}
366 	for( int i = 0; i < NUM_TRIPLET_LENGTHS; ++i )
367 	{
368 		m_quantizeModel.addItem( "1/" + QString::number( (1 << i) * 3 ) );
369 	}
370 	m_quantizeModel.addItem( "1/192" );
371 	m_quantizeModel.setValue( m_quantizeModel.findText( "1/16" ) );
372 
373 	connect( &m_quantizeModel, SIGNAL( dataChanged() ),
374 					this, SLOT( quantizeChanged() ) );
375 
376 	// Set up note length model
377 	m_noteLenModel.addItem( tr( "Last note" ),
378 					new PixmapLoader( "edit_draw" ) );
379 	const QString pixmaps[] = { "whole", "half", "quarter", "eighth",
380 						"sixteenth", "thirtysecond", "triplethalf",
381 						"tripletquarter", "tripleteighth",
382 						"tripletsixteenth", "tripletthirtysecond" } ;
383 
384 	for( int i = 0; i < NUM_EVEN_LENGTHS; ++i )
385 	{
386 		PixmapLoader *loader = new PixmapLoader( "note_" + pixmaps[i] );
387 		m_noteLenModel.addItem( "1/" + QString::number( 1 << i ), loader );
388 	}
389 	for( int i = 0; i < NUM_TRIPLET_LENGTHS; ++i )
390 	{
391 		PixmapLoader *loader = new PixmapLoader( "note_" + pixmaps[i+NUM_EVEN_LENGTHS] );
392 		m_noteLenModel.addItem( "1/" + QString::number( (1 << i) * 3 ), loader );
393 	}
394 	m_noteLenModel.setValue( 0 );
395 
396 	// Note length change can cause a redraw if Q is set to lock
397 	connect( &m_noteLenModel, SIGNAL( dataChanged() ),
398 					this, SLOT( quantizeChanged() ) );
399 
400 	// Set up scale model
401 	const InstrumentFunctionNoteStacking::ChordTable& chord_table =
402 			InstrumentFunctionNoteStacking::ChordTable::getInstance();
403 
404 	m_scaleModel.addItem( tr("No scale") );
405 	for( const InstrumentFunctionNoteStacking::Chord& chord : chord_table )
406 	{
407 		if( chord.isScale() )
408 		{
409 			m_scaleModel.addItem( chord.getName() );
410 		}
411 	}
412 
413 	m_scaleModel.setValue( 0 );
414 	// change can update m_semiToneMarkerMenu
415 	connect( &m_scaleModel, SIGNAL( dataChanged() ),
416 						this, SLOT( updateSemiToneMarkerMenu() ) );
417 
418 	// Set up chord model
419 	m_chordModel.addItem( tr("No chord") );
420 	for( const InstrumentFunctionNoteStacking::Chord& chord : chord_table )
421 	{
422 		if( ! chord.isScale() )
423 		{
424 			m_chordModel.addItem( chord.getName() );
425 		}
426 	}
427 
428 	m_chordModel.setValue( 0 );
429 
430 	// change can update m_semiToneMarkerMenu
431 	connect( &m_chordModel, SIGNAL( dataChanged() ),
432 					this, SLOT( updateSemiToneMarkerMenu() ) );
433 
434 	setFocusPolicy( Qt::StrongFocus );
435 	setFocus();
436 	setMouseTracking( true );
437 
438 	connect( &m_scaleModel, SIGNAL( dataChanged() ),
439 					this, SLOT( updateSemiToneMarkerMenu() ) );
440 
441 	connect( Engine::getSong(), SIGNAL( timeSignatureChanged( int, int ) ),
442 						this, SLOT( update() ) );
443 
444 	//connection for selecion from timeline
445 	connect( m_timeLine, SIGNAL( regionSelectedFromPixels( int, int ) ),
446 			this, SLOT( selectRegionFromPixels( int, int ) ) );
447 }
448 
449 
450 
reset()451 void PianoRoll::reset()
452 {
453 	m_lastNoteVolume = DefaultVolume;
454 	m_lastNotePanning = DefaultPanning;
455 }
456 
showTextFloat(const QString & text,const QPoint & pos,int timeout)457 void PianoRoll::showTextFloat(const QString &text, const QPoint &pos, int timeout)
458 {
459 	s_textFloat->setText( text );
460 	// show the float, offset slightly so as to not obscure anything
461 	s_textFloat->moveGlobal( this, pos + QPoint(4, 16) );
462 	if (timeout == -1)
463 	{
464 		s_textFloat->show();
465 	}
466 	else
467 	{
468 		s_textFloat->setVisibilityTimeOut( timeout );
469 	}
470 }
471 
472 
showVolTextFloat(volume_t vol,const QPoint & pos,int timeout)473 void PianoRoll::showVolTextFloat(volume_t vol, const QPoint &pos, int timeout)
474 {
475 	//! \todo display velocity for MIDI-based instruments
476 	// possibly dBFS values too? not sure if it makes sense for note volumes...
477 	showTextFloat( tr("Velocity: %1%").arg( vol ), pos, timeout );
478 }
479 
480 
showPanTextFloat(panning_t pan,const QPoint & pos,int timeout)481 void PianoRoll::showPanTextFloat(panning_t pan, const QPoint &pos, int timeout)
482 {
483 	QString text;
484 	if( pan < 0 )
485 	{
486 		text = tr("Panning: %1% left").arg( qAbs( pan ) );
487 	}
488 	else if( pan > 0 )
489 	{
490 		text = tr("Panning: %1% right").arg( qAbs( pan ) );
491 	}
492 	else
493 	{
494 		text = tr("Panning: center");
495 	}
496 	showTextFloat( text, pos, timeout );
497 }
498 
499 
500 
changeNoteEditMode(int i)501 void PianoRoll::changeNoteEditMode( int i )
502 {
503 	m_noteEditMode = (NoteEditMode) i;
504 	repaint();
505 }
506 
507 
markSemiTone(int i)508 void PianoRoll::markSemiTone( int i )
509 {
510 	const int key = m_pianoKeySelected;
511 	const InstrumentFunctionNoteStacking::Chord * chord = nullptr;
512 
513 	switch( static_cast<SemiToneMarkerAction>( i ) )
514 	{
515 		case stmaUnmarkAll:
516 			m_markedSemiTones.clear();
517 			break;
518 		case stmaMarkCurrentSemiTone:
519 		{
520 			QList<int>::iterator it = qFind( m_markedSemiTones.begin(), m_markedSemiTones.end(), key );
521 			if( it != m_markedSemiTones.end() )
522 			{
523 				m_markedSemiTones.erase( it );
524 			}
525 			else
526 			{
527 				m_markedSemiTones.push_back( key );
528 			}
529 			break;
530 		}
531 		case stmaMarkAllOctaveSemiTones:
532 		{
533 			QList<int> aok = getAllOctavesForKey(key);
534 
535 			if ( m_markedSemiTones.contains(key) )
536 			{
537 				// lets erase all of the ones that match this by octave
538 				QList<int>::iterator i;
539 				for (int ix = 0; ix < aok.size(); ++ix)
540 				{
541 					i = qFind(m_markedSemiTones.begin(), m_markedSemiTones.end(), aok.at(ix));
542 					if (i != m_markedSemiTones.end())
543 					{
544 						m_markedSemiTones.erase(i);
545 					}
546 				}
547 			}
548 			else
549 			{
550 				// we should add all of the ones that match this by octave
551 				m_markedSemiTones.append(aok);
552 			}
553 
554 			break;
555 		}
556 		case stmaMarkCurrentScale:
557 			chord = & InstrumentFunctionNoteStacking::ChordTable::getInstance()
558 					.getScaleByName( m_scaleModel.currentText() );
559 		case stmaMarkCurrentChord:
560 		{
561 			if( ! chord )
562 			{
563 				chord = & InstrumentFunctionNoteStacking::ChordTable::getInstance()
564 						.getChordByName( m_chordModel.currentText() );
565 			}
566 
567 			if( chord->isEmpty() )
568 			{
569 				break;
570 			}
571 			else if( chord->isScale() )
572 			{
573 				m_markedSemiTones.clear();
574 			}
575 
576 			const int first = chord->isScale() ? 0 : key;
577 			const int last = chord->isScale() ? NumKeys : key + chord->last();
578 			const int cap = ( chord->isScale() || chord->last() == 0 ) ? KeysPerOctave : chord->last();
579 
580 			for( int i = first; i <= last; i++ )
581 			{
582 			  if( chord->hasSemiTone( ( i + cap - ( key % cap ) ) % cap ) )
583 				{
584 					m_markedSemiTones.push_back( i );
585 				}
586 			}
587 			break;
588 		}
589 		case stmaCopyAllNotesOnKey:
590 		{
591 			selectNotesOnKey();
592 			break;
593 		}
594 		default:
595 			;
596 	}
597 
598 	qSort( m_markedSemiTones.begin(), m_markedSemiTones.end(), qGreater<int>() );
599 	QList<int>::iterator new_end = std::unique( m_markedSemiTones.begin(), m_markedSemiTones.end() );
600 	m_markedSemiTones.erase( new_end, m_markedSemiTones.end() );
601 }
602 
603 
~PianoRoll()604 PianoRoll::~PianoRoll()
605 {
606 }
607 
608 
setCurrentPattern(Pattern * newPattern)609 void PianoRoll::setCurrentPattern( Pattern* newPattern )
610 {
611 	if( hasValidPattern() )
612 	{
613 		m_pattern->instrumentTrack()->disconnect( this );
614 	}
615 
616 	// force the song-editor to stop playing if it played pattern before
617 	if( Engine::getSong()->isPlaying() &&
618 		Engine::getSong()->playMode() == Song::Mode_PlayPattern )
619 	{
620 		Engine::getSong()->playPattern( NULL );
621 	}
622 
623 	// set new data
624 	m_pattern = newPattern;
625 	m_currentPosition = 0;
626 	m_currentNote = NULL;
627 	m_startKey = INITIAL_START_KEY;
628 
629 	if( ! hasValidPattern() )
630 	{
631 		//resizeEvent( NULL );
632 
633 		update();
634 		emit currentPatternChanged();
635 		return;
636 	}
637 
638 	m_leftRightScroll->setValue( 0 );
639 
640 	// determine the central key so that we can scroll to it
641 	int central_key = 0;
642 	int total_notes = 0;
643 	for( const Note *note : m_pattern->notes() )
644 	{
645 		if( note->length() > 0 )
646 		{
647 			central_key += note->key();
648 			++total_notes;
649 		}
650 	}
651 
652 	if( total_notes > 0 )
653 	{
654 		central_key = central_key / total_notes -
655 				( KeysPerOctave * NumOctaves - m_totalKeysToScroll ) / 2;
656 		m_startKey = tLimit( central_key, 0, NumOctaves * KeysPerOctave );
657 	}
658 
659 	// resizeEvent() does the rest for us (scrolling, range-checking
660 	// of start-notes and so on...)
661 	resizeEvent( NULL );
662 
663 	// make sure to always get informed about the pattern being destroyed
664 	connect( m_pattern, SIGNAL( destroyedPattern( Pattern* ) ), this, SLOT( hidePattern( Pattern* ) ) );
665 
666 	connect( m_pattern->instrumentTrack(), SIGNAL( midiNoteOn( const Note& ) ), this, SLOT( startRecordNote( const Note& ) ) );
667 	connect( m_pattern->instrumentTrack(), SIGNAL( midiNoteOff( const Note& ) ), this, SLOT( finishRecordNote( const Note& ) ) );
668 	connect( m_pattern->instrumentTrack()->pianoModel(), SIGNAL( dataChanged() ), this, SLOT( update() ) );
669 
670 	update();
671 	emit currentPatternChanged();
672 }
673 
674 
675 
hidePattern(Pattern * pattern)676 void PianoRoll::hidePattern( Pattern* pattern )
677 {
678 	if( m_pattern == pattern )
679 	{
680 		setCurrentPattern( NULL );
681 	}
682 }
683 
selectRegionFromPixels(int xStart,int xEnd)684 void PianoRoll::selectRegionFromPixels( int xStart, int xEnd )
685 {
686 
687 	xStart -= WHITE_KEY_WIDTH;
688 	xEnd  -= WHITE_KEY_WIDTH;
689 
690 	// select an area of notes
691 	int pos_ticks = xStart * MidiTime::ticksPerTact() / m_ppt +
692 					m_currentPosition;
693 	int key_num = 0;
694 	m_selectStartTick = pos_ticks;
695 	m_selectedTick = 0;
696 	m_selectStartKey = key_num;
697 	m_selectedKeys = 1;
698 	// change size of selection
699 
700 	// get tick in which the cursor is posated
701 	pos_ticks = xEnd  * MidiTime::ticksPerTact() / m_ppt +
702 					m_currentPosition;
703 	key_num = 120;
704 
705 	m_selectedTick = pos_ticks - m_selectStartTick;
706 	if( (int) m_selectStartTick + m_selectedTick < 0 )
707 	{
708 		m_selectedTick = -static_cast<int>(
709 					m_selectStartTick );
710 	}
711 	m_selectedKeys = key_num - m_selectStartKey;
712 	if( key_num <= m_selectStartKey )
713 	{
714 		--m_selectedKeys;
715 	}
716 
717 	computeSelectedNotes( false );
718 }
719 
720 
721 
722 
723 
724 
725 /** \brief qproperty access implementation */
726 
barLineColor() const727 QColor PianoRoll::barLineColor() const
728 { return m_barLineColor; }
729 
setBarLineColor(const QColor & c)730 void PianoRoll::setBarLineColor( const QColor & c )
731 { m_barLineColor = c; }
732 
beatLineColor() const733 QColor PianoRoll::beatLineColor() const
734 { return m_beatLineColor; }
735 
setBeatLineColor(const QColor & c)736 void PianoRoll::setBeatLineColor( const QColor & c )
737 { m_beatLineColor = c; }
738 
lineColor() const739 QColor PianoRoll::lineColor() const
740 { return m_lineColor; }
741 
setLineColor(const QColor & c)742 void PianoRoll::setLineColor( const QColor & c )
743 { m_lineColor = c; }
744 
noteModeColor() const745 QColor PianoRoll::noteModeColor() const
746 { return m_noteModeColor; }
747 
setNoteModeColor(const QColor & c)748 void PianoRoll::setNoteModeColor( const QColor & c )
749 { m_noteModeColor = c; }
750 
noteColor() const751 QColor PianoRoll::noteColor() const
752 { return m_noteColor; }
753 
setNoteColor(const QColor & c)754 void PianoRoll::setNoteColor( const QColor & c )
755 { m_noteColor = c; }
756 
barColor() const757 QColor PianoRoll::barColor() const
758 { return m_barColor; }
759 
setBarColor(const QColor & c)760 void PianoRoll::setBarColor( const QColor & c )
761 { m_barColor = c; }
762 
selectedNoteColor() const763 QColor PianoRoll::selectedNoteColor() const
764 { return m_selectedNoteColor; }
765 
setSelectedNoteColor(const QColor & c)766 void PianoRoll::setSelectedNoteColor( const QColor & c )
767 { m_selectedNoteColor = c; }
768 
textColor() const769 QColor PianoRoll::textColor() const
770 { return m_textColor; }
771 
setTextColor(const QColor & c)772 void PianoRoll::setTextColor( const QColor & c )
773 { m_textColor = c; }
774 
textColorLight() const775 QColor PianoRoll::textColorLight() const
776 { return m_textColorLight; }
777 
setTextColorLight(const QColor & c)778 void PianoRoll::setTextColorLight( const QColor & c )
779 { m_textColorLight = c; }
780 
textShadow() const781 QColor PianoRoll::textShadow() const
782 { return m_textShadow; }
783 
setTextShadow(const QColor & c)784 void PianoRoll::setTextShadow( const QColor & c )
785 { m_textShadow = c; }
786 
markedSemitoneColor() const787 QColor PianoRoll::markedSemitoneColor() const
788 { return m_markedSemitoneColor; }
789 
setMarkedSemitoneColor(const QColor & c)790 void PianoRoll::setMarkedSemitoneColor( const QColor & c )
791 { m_markedSemitoneColor = c; }
792 
noteOpacity() const793 int PianoRoll::noteOpacity() const
794 { return m_noteOpacity; }
795 
setNoteOpacity(const int i)796 void PianoRoll::setNoteOpacity( const int i )
797 { m_noteOpacity = i; }
798 
noteBorders() const799 bool PianoRoll::noteBorders() const
800 { return m_noteBorders; }
801 
setNoteBorders(const bool b)802 void PianoRoll::setNoteBorders( const bool b )
803 { m_noteBorders = b; }
804 
backgroundShade() const805 QColor PianoRoll::backgroundShade() const
806 { return m_backgroundShade; }
807 
setBackgroundShade(const QColor & c)808 void PianoRoll::setBackgroundShade( const QColor & c )
809 { m_backgroundShade = c; }
810 
811 
812 
813 
814 
drawNoteRect(QPainter & p,int x,int y,int width,const Note * n,const QColor & noteCol,const QColor & selCol,const int noteOpc,const bool borders)815 void PianoRoll::drawNoteRect( QPainter & p, int x, int y,
816 				int width, const Note * n, const QColor & noteCol,
817 				const QColor & selCol, const int noteOpc, const bool borders )
818 {
819 	++x;
820 	++y;
821 	width -= 2;
822 
823 	if( width <= 0 )
824 	{
825 		width = 2;
826 	}
827 
828 	int volVal = qMin( 255, 100 + (int) ( ( (float)( n->getVolume() - MinVolume ) ) /
829 			( (float)( MaxVolume - MinVolume ) ) * 155.0f) );
830 	float rightPercent = qMin<float>( 1.0f,
831 			( (float)( n->getPanning() - PanningLeft ) ) /
832 			( (float)( PanningRight - PanningLeft ) ) * 2.0f );
833 
834 	float leftPercent = qMin<float>( 1.0f,
835 			( (float)( PanningRight - n->getPanning() ) ) /
836 			( (float)( PanningRight - PanningLeft ) ) * 2.0f );
837 
838 	QColor col = QColor( noteCol );
839 	QPen pen;
840 
841 	if( n->selected() )
842 	{
843 		col = QColor( selCol );
844 	}
845 
846 	const int borderWidth = borders ? 1 : 0;
847 
848 	const int noteHeight = KEY_LINE_HEIGHT - 1 - borderWidth;
849 	int noteWidth = width + 1 - borderWidth;
850 
851 	// adjust note to make it a bit faded if it has a lower volume
852 	// in stereo using gradients
853 	QColor lcol = QColor::fromHsv( col.hue(), col.saturation(),
854 						volVal * leftPercent, noteOpc );
855 	QColor rcol = QColor::fromHsv( col.hue(), col.saturation(),
856 						volVal * rightPercent, noteOpc );
857 
858 	QLinearGradient gradient( x, y, x, y + noteHeight );
859 	gradient.setColorAt( 0, rcol );
860 	gradient.setColorAt( 1, lcol );
861 	p.setBrush( gradient );
862 
863 	if ( borders )
864 	{
865 		p.setPen( col );
866 	}
867 	else
868 	{
869 		p.setPen( Qt::NoPen );
870 	}
871 
872 	p.drawRect( x, y, noteWidth, noteHeight );
873 
874 	// draw the note endmark, to hint the user to resize
875 	p.setBrush( col );
876 	if( width > 2 )
877 	{
878 		const int endmarkWidth = 3 - borderWidth;
879 		p.drawRect( x + noteWidth - endmarkWidth, y, endmarkWidth, noteHeight );
880 	}
881 }
882 
883 
884 
885 
drawDetuningInfo(QPainter & _p,const Note * _n,int _x,int _y) const886 void PianoRoll::drawDetuningInfo( QPainter & _p, const Note * _n, int _x,
887 								int _y ) const
888 {
889 	int middle_y = _y + KEY_LINE_HEIGHT / 2;
890 	_p.setPen( noteColor() );
891 	_p.setClipRect(WHITE_KEY_WIDTH, PR_TOP_MARGIN,
892 		width() - WHITE_KEY_WIDTH,
893 		keyAreaBottom() - PR_TOP_MARGIN);
894 
895 	int old_x = 0;
896 	int old_y = 0;
897 
898 	timeMap & map = _n->detuning()->automationPattern()->getTimeMap();
899 	for( timeMap::ConstIterator it = map.begin(); it != map.end(); ++it )
900 	{
901 		int pos_ticks = it.key();
902 		int pos_x = _x + pos_ticks * m_ppt / MidiTime::ticksPerTact();
903 
904 		const float level = it.value();
905 
906 		int pos_y = middle_y - level * KEY_LINE_HEIGHT;
907 
908 		if( old_x != 0 && old_y != 0 )
909 		{
910 			switch( _n->detuning()->automationPattern()->progressionType() )
911 			{
912 			case AutomationPattern::DiscreteProgression:
913 				_p.drawLine( old_x, old_y, pos_x, old_y );
914 				_p.drawLine( pos_x, old_y, pos_x, pos_y );
915 				break;
916 			case AutomationPattern::CubicHermiteProgression: /* TODO */
917 			case AutomationPattern::LinearProgression:
918 				_p.drawLine( old_x, old_y, pos_x, pos_y );
919 				break;
920 			}
921 		}
922 
923 		_p.drawLine( pos_x - 1, pos_y, pos_x + 1, pos_y );
924 		_p.drawLine( pos_x, pos_y - 1, pos_x, pos_y + 1 );
925 
926 		old_x = pos_x;
927 		old_y = pos_y;
928 	}
929 }
930 
931 
932 
933 
removeSelection()934 void PianoRoll::removeSelection()
935 {
936 	m_selectStartTick = 0;
937 	m_selectedTick = 0;
938 	m_selectStartKey = 0;
939 	m_selectedKeys = 0;
940 }
941 
942 
943 
944 
clearSelectedNotes()945 void PianoRoll::clearSelectedNotes()
946 {
947 	if( m_pattern != NULL )
948 	{
949 		for( Note *note : m_pattern->notes() )
950 		{
951 			note->setSelected( false );
952 		}
953 	}
954 }
955 
956 
957 
958 
shiftSemiTone(int amount)959 void PianoRoll::shiftSemiTone( int amount ) // shift notes by amount semitones
960 {
961 	if (!hasValidPattern()) {return;}
962 
963 	m_pattern->addJournalCheckPoint();
964 	bool useAllNotes = ! isSelection();
965 	for( Note *note : m_pattern->notes() )
966 	{
967 		// if none are selected, move all notes, otherwise
968 		// only move selected notes
969 		if( useAllNotes || note->selected() )
970 		{
971 			note->setKey( note->key() + amount );
972 		}
973 	}
974 
975 	m_pattern->rearrangeAllNotes();
976 	m_pattern->dataChanged();
977 
978 	// we modified the song
979 	update();
980 	gui->songEditor()->update();
981 }
982 
983 
984 
985 
shiftPos(int amount)986 void PianoRoll::shiftPos( int amount ) //shift notes pos by amount
987 {
988 	if (!hasValidPattern()) {return;}
989 
990 	m_pattern->addJournalCheckPoint();
991 	bool useAllNotes = ! isSelection();
992 
993 	bool first = true;
994 	for( Note *note : m_pattern->notes() )
995 	{
996 		// if none are selected, move all notes, otherwise
997 		// only move selected notes
998 		if( note->selected() || (useAllNotes && note->length() > 0) )
999 		{
1000 			// don't let notes go to out of bounds
1001 			if( first )
1002 			{
1003 				m_moveBoundaryLeft = note->pos();
1004 				if( m_moveBoundaryLeft + amount < 0 )
1005 				{
1006 					amount += 0 - (amount + m_moveBoundaryLeft);
1007 				}
1008 				first = false;
1009 			}
1010 			note->setPos( note->pos() + amount );
1011 		}
1012 	}
1013 
1014 	m_pattern->rearrangeAllNotes();
1015 	m_pattern->updateLength();
1016 	m_pattern->dataChanged();
1017 
1018 	// we modified the song
1019 	update();
1020 	gui->songEditor()->update();
1021 }
1022 
1023 
1024 
1025 
isSelection() const1026 bool PianoRoll::isSelection() const // are any notes selected?
1027 {
1028 	for( const Note *note : m_pattern->notes() )
1029 	{
1030 		if( note->selected() )
1031 		{
1032 			return true;
1033 		}
1034 	}
1035 
1036 	return false;
1037 }
1038 
1039 
1040 
selectionCount() const1041 int PianoRoll::selectionCount() const // how many notes are selected?
1042 {
1043 	int sum = 0;
1044 
1045 	for( const Note *note : m_pattern->notes() )
1046 	{
1047 		if( note->selected() )
1048 		{
1049 			++sum;
1050 		}
1051 	}
1052 
1053 	return sum;
1054 }
1055 
1056 
1057 
keyPressEvent(QKeyEvent * ke)1058 void PianoRoll::keyPressEvent(QKeyEvent* ke )
1059 {
1060 	if( hasValidPattern() && ke->modifiers() == Qt::NoModifier )
1061 	{
1062 		const int key_num = PianoView::getKeyFromKeyEvent( ke ) + ( DefaultOctave - 1 ) * KeysPerOctave;
1063 
1064 		if(! ke->isAutoRepeat() && key_num > -1)
1065 		{
1066 			m_pattern->instrumentTrack()->pianoModel()->handleKeyPress( key_num );
1067 			ke->accept();
1068 		}
1069 	}
1070 
1071 	switch( ke->key() )
1072 	{
1073 		case Qt::Key_Up:
1074 		case Qt::Key_Down:
1075 			{
1076 				int direction = (ke->key() == Qt::Key_Up ? +1 : -1);
1077 				if( ( ke->modifiers() & Qt::ControlModifier ) && m_action == ActionNone )
1078 				{
1079 					// shift selection up an octave
1080 					// if nothing selected, shift _everything_
1081 					if (hasValidPattern())
1082 					{
1083 						shiftSemiTone( 12 * direction );
1084 					}
1085 				}
1086 				else if((ke->modifiers() & Qt::ShiftModifier) && m_action == ActionNone)
1087 				{
1088 					// Move selected notes up by one semitone
1089 					if (hasValidPattern())
1090 					{
1091 						shiftSemiTone( 1 * direction );
1092 					}
1093 				}
1094 				else
1095 				{
1096 					// scroll
1097 					m_topBottomScroll->setValue( m_topBottomScroll->value() -
1098 						cm_scrollAmtVert * direction );
1099 
1100 					// if they are moving notes around or resizing,
1101 					// recalculate the note/resize position
1102 					if( m_action == ActionMoveNote ||
1103 							m_action == ActionResizeNote )
1104 					{
1105 						dragNotes( m_lastMouseX, m_lastMouseY,
1106 									ke->modifiers() & Qt::AltModifier,
1107 									ke->modifiers() & Qt::ShiftModifier,
1108 									ke->modifiers() & Qt::ControlModifier );
1109 					}
1110 				}
1111 				ke->accept();
1112 				break;
1113 			}
1114 
1115 		case Qt::Key_Right:
1116 		case Qt::Key_Left:
1117 			{
1118 				int direction = (ke->key() == Qt::Key_Right ? +1 : -1);
1119 				if( ke->modifiers() & Qt::ControlModifier && m_action == ActionNone )
1120 				{
1121 					// Move selected notes by one bar to the left
1122 					if (hasValidPattern())
1123 					{
1124 						shiftPos( direction * MidiTime::ticksPerTact() );
1125 					}
1126 				}
1127 				else if( ke->modifiers() & Qt::ShiftModifier && m_action == ActionNone)
1128 				{
1129 					// move notes
1130 					if (hasValidPattern())
1131 					{
1132 						bool quantized = ! ( ke->modifiers() & Qt::AltModifier );
1133 						int amt = quantized ? quantization() : 1;
1134 						shiftPos( direction * amt );
1135 					}
1136 				}
1137 				else if( ke->modifiers() & Qt::AltModifier)
1138 				{
1139 					// switch to editing a pattern adjacent to this one in the song editor
1140 					if (hasValidPattern())
1141 					{
1142 						Pattern * p = direction > 0 ? m_pattern->nextPattern()
1143 										: m_pattern->previousPattern();
1144 						if(p != NULL)
1145 						{
1146 							setCurrentPattern(p);
1147 						}
1148 					}
1149 				}
1150 				else
1151 				{
1152 					// scroll
1153 					m_leftRightScroll->setValue( m_leftRightScroll->value() +
1154 						direction * cm_scrollAmtHoriz );
1155 
1156 					// if they are moving notes around or resizing,
1157 					// recalculate the note/resize position
1158 					if( m_action == ActionMoveNote ||
1159 							m_action == ActionResizeNote )
1160 					{
1161 						dragNotes( m_lastMouseX, m_lastMouseY,
1162 									ke->modifiers() & Qt::AltModifier,
1163 									ke->modifiers() & Qt::ShiftModifier,
1164 									ke->modifiers() & Qt::ControlModifier );
1165 					}
1166 
1167 				}
1168 				ke->accept();
1169 				break;
1170 			}
1171 
1172 		case Qt::Key_A:
1173 			if( ke->modifiers() & Qt::ControlModifier )
1174 			{
1175 				ke->accept();
1176 				if (ke->modifiers() & Qt::ShiftModifier)
1177 				{
1178 					// Ctrl + Shift + A = deselect all notes
1179 					clearSelectedNotes();
1180 				}
1181 				else
1182 				{
1183 					// Ctrl + A = select all notes
1184 					selectAll();
1185 				}
1186 				update();
1187 			}
1188 			break;
1189 
1190 		case Qt::Key_Escape:
1191 			// Same as Ctrl + Shift + A
1192 			clearSelectedNotes();
1193 			break;
1194 
1195 		case Qt::Key_Backspace:
1196 		case Qt::Key_Delete:
1197 			deleteSelectedNotes();
1198 			ke->accept();
1199 			break;
1200 
1201 		case Qt::Key_Home:
1202 			m_timeLine->pos().setTicks( 0 );
1203 			m_timeLine->updatePosition();
1204 			ke->accept();
1205 			break;
1206 
1207 		case Qt::Key_0:
1208 		case Qt::Key_1:
1209 		case Qt::Key_2:
1210 		case Qt::Key_3:
1211 		case Qt::Key_4:
1212 		case Qt::Key_5:
1213 		case Qt::Key_6:
1214 		case Qt::Key_7:
1215 		case Qt::Key_8:
1216 		case Qt::Key_9:
1217 		{
1218 			int len = 1 + ke->key() - Qt::Key_0;
1219 			if( len == 10 )
1220 			{
1221 				len = 0;
1222 			}
1223 			if( ke->modifiers() & ( Qt::ControlModifier | Qt::KeypadModifier ) )
1224 			{
1225 				m_noteLenModel.setValue( len );
1226 				ke->accept();
1227 			}
1228 			else if( ke->modifiers() & Qt::AltModifier )
1229 			{
1230 				m_quantizeModel.setValue( len );
1231 				ke->accept();
1232 			}
1233 			break;
1234 		}
1235 
1236 		case Qt::Key_Control:
1237 			// Enter selection mode if:
1238 			// -> this window is active
1239 			// -> shift is not pressed
1240 			// (<S-C-drag> is shortcut for sticky note resize)
1241 			if ( !( ke->modifiers() & Qt::ShiftModifier ) && isActiveWindow() )
1242 			{
1243 				m_ctrlMode = m_editMode;
1244 				m_editMode = ModeSelect;
1245 				QApplication::changeOverrideCursor( Qt::ArrowCursor );
1246 				ke->accept();
1247 			}
1248 			break;
1249 		default:
1250 			break;
1251 	}
1252 
1253 	update();
1254 }
1255 
1256 
1257 
1258 
keyReleaseEvent(QKeyEvent * ke)1259 void PianoRoll::keyReleaseEvent(QKeyEvent* ke )
1260 {
1261 	if( hasValidPattern() && ke->modifiers() == Qt::NoModifier )
1262 	{
1263 		const int key_num = PianoView::getKeyFromKeyEvent( ke ) + ( DefaultOctave - 1 ) * KeysPerOctave;
1264 
1265 		if( ! ke->isAutoRepeat() && key_num > -1 )
1266 		{
1267 			m_pattern->instrumentTrack()->pianoModel()->handleKeyRelease( key_num );
1268 			ke->accept();
1269 		}
1270 	}
1271 
1272 	switch( ke->key() )
1273 	{
1274 		case Qt::Key_Control:
1275 			computeSelectedNotes( ke->modifiers() & Qt::ShiftModifier);
1276 			m_editMode = m_ctrlMode;
1277 			update();
1278 			break;
1279 
1280 		// update after undo/redo
1281 		case Qt::Key_Z:
1282 		case Qt::Key_R:
1283 			if( hasValidPattern() && ke->modifiers() == Qt::ControlModifier )
1284 			{
1285 				update();
1286 			}
1287 			break;
1288 	}
1289 
1290 	update();
1291 }
1292 
1293 
1294 
1295 
leaveEvent(QEvent * e)1296 void PianoRoll::leaveEvent(QEvent * e )
1297 {
1298 	while( QApplication::overrideCursor() != NULL )
1299 	{
1300 		QApplication::restoreOverrideCursor();
1301 	}
1302 
1303 	QWidget::leaveEvent( e );
1304 	s_textFloat->hide();
1305 	update(); // cleaning inner mouse-related graphics
1306 }
1307 
1308 
1309 
1310 
noteEditTop() const1311 int PianoRoll::noteEditTop() const
1312 {
1313 	return height() - PR_BOTTOM_MARGIN -
1314 		m_notesEditHeight + NOTE_EDIT_RESIZE_BAR;
1315 }
1316 
1317 
1318 
1319 
noteEditBottom() const1320 int PianoRoll::noteEditBottom() const
1321 {
1322 	return height() - PR_BOTTOM_MARGIN;
1323 }
1324 
1325 
1326 
1327 
noteEditRight() const1328 int PianoRoll::noteEditRight() const
1329 {
1330 	return width() - PR_RIGHT_MARGIN;
1331 }
1332 
1333 
1334 
1335 
noteEditLeft() const1336 int PianoRoll::noteEditLeft() const
1337 {
1338 	return WHITE_KEY_WIDTH;
1339 }
1340 
1341 
1342 
1343 
keyAreaTop() const1344 int PianoRoll::keyAreaTop() const
1345 {
1346 	return PR_TOP_MARGIN;
1347 }
1348 
1349 
1350 
1351 
keyAreaBottom() const1352 int PianoRoll::keyAreaBottom() const
1353 {
1354 	return height() - PR_BOTTOM_MARGIN - m_notesEditHeight;
1355 }
1356 
1357 
1358 
1359 
mousePressEvent(QMouseEvent * me)1360 void PianoRoll::mousePressEvent(QMouseEvent * me )
1361 {
1362 	m_startedWithShift = me->modifiers() & Qt::ShiftModifier;
1363 
1364 	if( ! hasValidPattern() )
1365 	{
1366 		return;
1367 	}
1368 
1369 	if( m_editMode == ModeEditDetuning && noteUnderMouse() )
1370 	{
1371 		static QPointer<AutomationPattern> detuningPattern = nullptr;
1372 		if (detuningPattern.data() != nullptr)
1373 		{
1374 			detuningPattern->disconnect(this);
1375 		}
1376 		Note* n = noteUnderMouse();
1377 		if (n->detuning() == nullptr)
1378 		{
1379 			n->createDetuning();
1380 		}
1381 		detuningPattern = n->detuning()->automationPattern();
1382 		connect(detuningPattern.data(), SIGNAL(dataChanged()), this, SLOT(update()));
1383 		gui->automationEditor()->open(detuningPattern);
1384 		return;
1385 	}
1386 
1387 	// if holding control, go to selection mode unless shift is also pressed
1388 	if( me->modifiers() & Qt::ControlModifier && m_editMode != ModeSelect )
1389 	{
1390 		m_ctrlMode = m_editMode;
1391 		m_editMode = ModeSelect;
1392 		QApplication::changeOverrideCursor( QCursor( Qt::ArrowCursor ) );
1393 		update();
1394 	}
1395 
1396 	// keep track of the point where the user clicked down
1397 	if( me->button() == Qt::LeftButton )
1398 	{
1399 		m_moveStartX = me->x();
1400 		m_moveStartY = me->y();
1401 	}
1402 
1403 	if( me->y() > keyAreaBottom() && me->y() < noteEditTop() )
1404 	{
1405 		// resizing the note edit area
1406 		m_action = ActionResizeNoteEditArea;
1407 		m_oldNotesEditHeight = m_notesEditHeight;
1408 		return;
1409 	}
1410 
1411 	if( me->y() > PR_TOP_MARGIN )
1412 	{
1413 		bool edit_note = ( me->y() > noteEditTop() );
1414 
1415 		int key_num = getKey( me->y() );
1416 
1417 		int x = me->x();
1418 
1419 
1420 		if( x > WHITE_KEY_WIDTH )
1421 		{
1422 			// set, move or resize note
1423 
1424 			x -= WHITE_KEY_WIDTH;
1425 
1426 			// get tick in which the user clicked
1427 			int pos_ticks = x * MidiTime::ticksPerTact() / m_ppt +
1428 							m_currentPosition;
1429 
1430 
1431 			// get note-vector of current pattern
1432 			const NoteVector & notes = m_pattern->notes();
1433 
1434 			// will be our iterator in the following loop
1435 			NoteVector::ConstIterator it = notes.begin()+notes.size()-1;
1436 
1437 			// loop through whole note-vector...
1438 			for( int i = 0; i < notes.size(); ++i )
1439 			{
1440 				Note *note = *it;
1441 				MidiTime len = note->length();
1442 				if( len < 0 )
1443 				{
1444 					len = 4;
1445 				}
1446 				// and check whether the user clicked on an
1447 				// existing note or an edit-line
1448 				if( pos_ticks >= note->pos() &&
1449 						len > 0 &&
1450 					(
1451 					( ! edit_note &&
1452 					pos_ticks <= note->pos() + len &&
1453 					note->key() == key_num )
1454 					||
1455 					( edit_note &&
1456 					pos_ticks <= note->pos() +
1457 						NOTE_EDIT_LINE_WIDTH * MidiTime::ticksPerTact() / m_ppt )
1458 					)
1459 					)
1460 				{
1461 					break;
1462 				}
1463 				--it;
1464 			}
1465 
1466 			// first check whether the user clicked in note-edit-
1467 			// area
1468 			if( edit_note )
1469 			{
1470 				m_pattern->addJournalCheckPoint();
1471 				// scribble note edit changes
1472 				mouseMoveEvent( me );
1473 				return;
1474 			}
1475 			// left button??
1476 			else if( me->button() == Qt::LeftButton &&
1477 							m_editMode == ModeDraw )
1478 			{
1479 				// whether this action creates new note(s) or not
1480 				bool is_new_note = false;
1481 
1482 				Note * created_new_note = NULL;
1483 				// did it reach end of vector because
1484 				// there's no note??
1485 				if( it == notes.begin()-1 )
1486 				{
1487 					is_new_note = true;
1488 					m_pattern->addJournalCheckPoint();
1489 
1490 					// then set new note
1491 
1492 					// clear selection and select this new note
1493 					clearSelectedNotes();
1494 
1495 					// +32 to quanitize the note correctly when placing notes with
1496 					// the mouse.  We do this here instead of in note.quantized
1497 					// because live notes should still be quantized at the half.
1498 					MidiTime note_pos( pos_ticks - ( quantization() / 2 ) );
1499 					MidiTime note_len( newNoteLen() );
1500 
1501 					Note new_note( note_len, note_pos, key_num );
1502 					new_note.setSelected( true );
1503 					new_note.setPanning( m_lastNotePanning );
1504 					new_note.setVolume( m_lastNoteVolume );
1505 					created_new_note = m_pattern->addNote( new_note );
1506 
1507 					const InstrumentFunctionNoteStacking::Chord & chord = InstrumentFunctionNoteStacking::ChordTable::getInstance()
1508 						.getChordByName( m_chordModel.currentText() );
1509 
1510 					if( ! chord.isEmpty() )
1511 					{
1512 						// if a chord is selected, create following notes in chord
1513 						// or arpeggio mode
1514 						const bool arpeggio = me->modifiers() & Qt::ShiftModifier;
1515 						for( int i = 1; i < chord.size(); i++ )
1516 						{
1517 							if( arpeggio )
1518 							{
1519 								note_pos += note_len;
1520 							}
1521 							Note new_note( note_len, note_pos, key_num + chord[i] );
1522 							new_note.setSelected( true );
1523 							new_note.setPanning( m_lastNotePanning );
1524 							new_note.setVolume( m_lastNoteVolume );
1525 							m_pattern->addNote( new_note );
1526 						}
1527 					}
1528 
1529 					// reset it so that it can be used for
1530 					// ops (move, resize) after this
1531 					// code-block
1532 					it = notes.begin();
1533 					while( it != notes.end() && *it != created_new_note )
1534 					{
1535 						++it;
1536 					}
1537 				}
1538 
1539 				Note *current_note = *it;
1540 				m_currentNote = current_note;
1541 				m_lastNotePanning = current_note->getPanning();
1542 				m_lastNoteVolume = current_note->getVolume();
1543 				m_lenOfNewNotes = current_note->length();
1544 
1545 				// remember which key and tick we started with
1546 				m_mouseDownKey = m_startKey;
1547 				m_mouseDownTick = m_currentPosition;
1548 
1549 				bool first = true;
1550 				for( it = notes.begin(); it != notes.end(); ++it )
1551 				{
1552 					Note *note = *it;
1553 
1554 					// remember note starting positions
1555 					note->setOldKey( note->key() );
1556 					note->setOldPos( note->pos() );
1557 					note->setOldLength( note->length() );
1558 
1559 					if( note->selected() )
1560 					{
1561 
1562 						// figure out the bounding box of all the selected notes
1563 						if( first )
1564 						{
1565 							m_moveBoundaryLeft = note->pos().getTicks();
1566 							m_moveBoundaryRight = note->endPos();
1567 							m_moveBoundaryBottom = note->key();
1568 							m_moveBoundaryTop = note->key();
1569 
1570 							first = false;
1571 						}
1572 						else
1573 						{
1574 							m_moveBoundaryLeft = qMin(
1575 												note->pos().getTicks(),
1576 												(tick_t) m_moveBoundaryLeft );
1577 							m_moveBoundaryRight = qMax( (int) note->endPos(),
1578 													m_moveBoundaryRight );
1579 							m_moveBoundaryBottom = qMin( note->key(),
1580 											   m_moveBoundaryBottom );
1581 							m_moveBoundaryTop = qMax( note->key(),
1582 														m_moveBoundaryTop );
1583 						}
1584 					}
1585 				}
1586 
1587 				// if clicked on an unselected note, remove selection
1588 				// and select that new note
1589 				if( ! m_currentNote->selected() )
1590 				{
1591 					clearSelectedNotes();
1592 					m_currentNote->setSelected( true );
1593 					m_moveBoundaryLeft = m_currentNote->pos().getTicks();
1594 					m_moveBoundaryRight = m_currentNote->endPos();
1595 					m_moveBoundaryBottom = m_currentNote->key();
1596 					m_moveBoundaryTop = m_currentNote->key();
1597 				}
1598 
1599 
1600 				// clicked at the "tail" of the note?
1601 				if( pos_ticks * m_ppt / MidiTime::ticksPerTact() >
1602 						m_currentNote->endPos() * m_ppt / MidiTime::ticksPerTact() - RESIZE_AREA_WIDTH
1603 					&& m_currentNote->length() > 0 )
1604 				{
1605 					m_pattern->addJournalCheckPoint();
1606 					// then resize the note
1607 					m_action = ActionResizeNote;
1608 
1609 					for (Note *note : getSelectedNotes())
1610 					{
1611 					    if (note->oldLength() <= 0) { note->setOldLength(4); }
1612 					}
1613 
1614 					// set resize-cursor
1615 					QCursor c( Qt::SizeHorCursor );
1616 					QApplication::setOverrideCursor( c );
1617 				}
1618 				else
1619 				{
1620 					if( ! created_new_note )
1621 					{
1622 						m_pattern->addJournalCheckPoint();
1623 					}
1624 
1625 					// otherwise move it
1626 					m_action = ActionMoveNote;
1627 
1628 					// set move-cursor
1629 					QCursor c( Qt::SizeAllCursor );
1630 					QApplication::setOverrideCursor( c );
1631 
1632 					// if they're holding shift, copy all selected notes
1633 					if( ! is_new_note && me->modifiers() & Qt::ShiftModifier )
1634 					{
1635 						// vector to hold new notes until we're through the loop
1636 						QVector<Note> newNotes;
1637 						for( Note* const& note : notes )
1638 						{
1639 							if( note->selected() )
1640 							{
1641 								// copy this note
1642 								Note noteCopy( *note );
1643 								newNotes.push_back( noteCopy );
1644 							}
1645 							++it;
1646 						}
1647 
1648 						if( newNotes.size() != 0 )
1649 						{
1650 							//put notes from vector into piano roll
1651 							for( int i = 0; i < newNotes.size(); ++i)
1652 							{
1653 								Note * newNote = m_pattern->addNote( newNotes[i], false );
1654 								newNote->setSelected( false );
1655 							}
1656 
1657 							// added new notes, so must update engine, song, etc
1658 							Engine::getSong()->setModified();
1659 							update();
1660 							gui->songEditor()->update();
1661 						}
1662 					}
1663 
1664 					// play the note
1665 					testPlayNote( m_currentNote );
1666 				}
1667 
1668 				Engine::getSong()->setModified();
1669 			}
1670 			else if( ( me->buttons() == Qt::RightButton &&
1671 							m_editMode == ModeDraw ) ||
1672 					m_editMode == ModeErase )
1673 			{
1674 				// erase single note
1675 				m_mouseDownRight = true;
1676 				if( it != notes.begin()-1 )
1677 				{
1678 					m_pattern->addJournalCheckPoint();
1679 					m_pattern->removeNote( *it );
1680 					Engine::getSong()->setModified();
1681 				}
1682 			}
1683 			else if( me->button() == Qt::LeftButton &&
1684 							m_editMode == ModeSelect )
1685 			{
1686 				// select an area of notes
1687 
1688 				m_selectStartTick = pos_ticks;
1689 				m_selectedTick = 0;
1690 				m_selectStartKey = key_num;
1691 				m_selectedKeys = 1;
1692 				m_action = ActionSelectNotes;
1693 
1694 				// call mousemove to fix glitch where selection
1695 				// appears in wrong spot on mousedown
1696 				mouseMoveEvent( me );
1697 			}
1698 
1699 			update();
1700 		}
1701 		else if( me->y() < keyAreaBottom() )
1702 		{
1703 			// reference to last key needed for both
1704 			// right click (used for copy all keys on note)
1705 			// and for playing the key when left-clicked
1706 			m_lastKey = key_num;
1707 
1708 			// clicked on keyboard on the left
1709 			if( me->buttons() == Qt::RightButton )
1710 			{
1711 				// right click, tone marker contextual menu
1712 				m_pianoKeySelected = getKey( me->y() );
1713 				m_semiToneMarkerMenu->popup( mapToGlobal( QPoint( me->x(), me->y() ) ) );
1714 			}
1715 			else
1716 			{
1717 				// left click - play the note
1718 				int v = ( (float) x ) / ( (float) WHITE_KEY_WIDTH ) * MidiDefaultVelocity;
1719 				m_pattern->instrumentTrack()->pianoModel()->handleKeyPress( key_num, v );
1720 			}
1721 		}
1722 		else
1723 		{
1724 			if( me->buttons() == Qt::LeftButton )
1725 			{
1726 				// clicked in the box below the keys to the left of note edit area
1727 				m_noteEditMode = (NoteEditMode)(((int)m_noteEditMode)+1);
1728 				if( m_noteEditMode == NoteEditCount )
1729 				{
1730 					m_noteEditMode = (NoteEditMode) 0;
1731 				}
1732 				repaint();
1733 			}
1734 			else if( me->buttons() == Qt::RightButton )
1735 			{
1736 				// pop menu asking which one they want to edit
1737 				m_noteEditMenu->popup( mapToGlobal( QPoint( me->x(), me->y() ) ) );
1738 			}
1739 		}
1740 	}
1741 }
1742 
1743 
1744 
1745 
mouseDoubleClickEvent(QMouseEvent * me)1746 void PianoRoll::mouseDoubleClickEvent(QMouseEvent * me )
1747 {
1748 	if( ! hasValidPattern() )
1749 	{
1750 		return;
1751 	}
1752 
1753 	// if they clicked in the note edit area, enter value for the volume bar
1754 	if( me->x() > noteEditLeft() && me->x() < noteEditRight()
1755 		&& me->y() > noteEditTop() && me->y() < noteEditBottom() )
1756 	{
1757 		// get values for going through notes
1758 		int pixel_range = 4;
1759 		int x = me->x() - WHITE_KEY_WIDTH;
1760 		const int ticks_start = ( x-pixel_range/2 ) *
1761 					MidiTime::ticksPerTact() / m_ppt + m_currentPosition;
1762 		const int ticks_end = ( x+pixel_range/2 ) *
1763 					MidiTime::ticksPerTact() / m_ppt + m_currentPosition;
1764 		const int ticks_middle = x * MidiTime::ticksPerTact() / m_ppt + m_currentPosition;
1765 
1766 		// go through notes to figure out which one we want to change
1767 		bool altPressed = me->modifiers() & Qt::AltModifier;
1768 		NoteVector nv;
1769 		for ( Note * i : m_pattern->notes() )
1770 		{
1771 			if( i->withinRange( ticks_start, ticks_end ) || ( i->selected() && !altPressed ) )
1772 			{
1773 				nv += i;
1774 			}
1775 		}
1776 		// make sure we're on a note
1777 		if( nv.size() > 0 )
1778 		{
1779 			const Note * closest = NULL;
1780 			int closest_dist = 9999999;
1781 			// if we caught multiple notes and we're not editing a
1782 			// selection, find the closest...
1783 			if( nv.size() > 1 && !isSelection() )
1784 			{
1785 				for ( const Note * i : nv )
1786 				{
1787 					const int dist = qAbs( i->pos().getTicks() - ticks_middle );
1788 					if( dist < closest_dist ) { closest = i; closest_dist = dist; }
1789 				}
1790 				// ... then remove all notes from the vector that aren't on the same exact time
1791 				NoteVector::Iterator it = nv.begin();
1792 				while( it != nv.end() )
1793 				{
1794 					const Note *note = *it;
1795 					if( note->pos().getTicks() != closest->pos().getTicks() )
1796 					{
1797 						it = nv.erase( it );
1798 					}
1799 					else
1800 					{
1801 						it++;
1802 					}
1803 				}
1804 			}
1805 			enterValue( &nv );
1806 		}
1807 	}
1808 	else
1809 	{
1810 		QWidget::mouseDoubleClickEvent(me);
1811 	}
1812 }
1813 
1814 
1815 
1816 
testPlayNote(Note * n)1817 void PianoRoll::testPlayNote( Note * n )
1818 {
1819 	m_lastKey = n->key();
1820 
1821 	if( ! n->isPlaying() && ! m_recording )
1822 	{
1823 		n->setIsPlaying( true );
1824 
1825 		const int baseVelocity = m_pattern->instrumentTrack()->midiPort()->baseVelocity();
1826 
1827 		m_pattern->instrumentTrack()->pianoModel()->handleKeyPress( n->key(), n->midiVelocity( baseVelocity ) );
1828 
1829 		MidiEvent event( MidiMetaEvent, -1, n->key(), panningToMidi( n->getPanning() ) );
1830 
1831 		event.setMetaEvent( MidiNotePanning );
1832 
1833 		m_pattern->instrumentTrack()->processInEvent( event, 0 );
1834 	}
1835 }
1836 
1837 
1838 
1839 
pauseTestNotes(bool pause)1840 void PianoRoll::pauseTestNotes( bool pause )
1841 {
1842 	for (Note *note : m_pattern->notes())
1843 	{
1844 		if( note->isPlaying() )
1845 		{
1846 			if( pause )
1847 			{
1848 				// stop note
1849 				m_pattern->instrumentTrack()->pianoModel()->handleKeyRelease( note->key() );
1850 			}
1851 			else
1852 			{
1853 				// start note
1854 				note->setIsPlaying( false );
1855 				testPlayNote( note );
1856 			}
1857 		}
1858 	}
1859 }
1860 
1861 
1862 
1863 
testPlayKey(int key,int velocity,int pan)1864 void PianoRoll::testPlayKey( int key, int velocity, int pan )
1865 {
1866 	// turn off old key
1867 	m_pattern->instrumentTrack()->pianoModel()->handleKeyRelease( m_lastKey );
1868 
1869 	// remember which one we're playing
1870 	m_lastKey = key;
1871 
1872 	// play new key
1873 	m_pattern->instrumentTrack()->pianoModel()->handleKeyPress( key, velocity );
1874 }
1875 
1876 
1877 
1878 
computeSelectedNotes(bool shift)1879 void PianoRoll::computeSelectedNotes(bool shift)
1880 {
1881 	if( m_selectStartTick == 0 &&
1882 		m_selectedTick == 0 &&
1883 		m_selectStartKey == 0 &&
1884 		m_selectedKeys == 0 )
1885 	{
1886 		// don't bother, there's no selection
1887 		return;
1888 	}
1889 
1890 	// setup selection-vars
1891 	int sel_pos_start = m_selectStartTick;
1892 	int sel_pos_end = m_selectStartTick+m_selectedTick;
1893 	if( sel_pos_start > sel_pos_end )
1894 	{
1895 		qSwap<int>( sel_pos_start, sel_pos_end );
1896 	}
1897 
1898 	int sel_key_start = m_selectStartKey - m_startKey + 1;
1899 	int sel_key_end = sel_key_start + m_selectedKeys;
1900 	if( sel_key_start > sel_key_end )
1901 	{
1902 		qSwap<int>( sel_key_start, sel_key_end );
1903 	}
1904 
1905 	//int y_base = noteEditTop() - 1;
1906 	if( hasValidPattern() )
1907 	{
1908 		for( Note *note : m_pattern->notes() )
1909 		{
1910 			// make a new selection unless they're holding shift
1911 			if( ! shift )
1912 			{
1913 				note->setSelected( false );
1914 			}
1915 
1916 			int len_ticks = note->length();
1917 
1918 			if( len_ticks == 0 )
1919 			{
1920 				continue;
1921 			}
1922 			else if( len_ticks < 0 )
1923 			{
1924 				len_ticks = 4;
1925 			}
1926 
1927 			const int key = note->key() - m_startKey + 1;
1928 
1929 			int pos_ticks = note->pos();
1930 
1931 			// if the selection even barely overlaps the note
1932 			if( key > sel_key_start &&
1933 				key <= sel_key_end &&
1934 				pos_ticks + len_ticks > sel_pos_start &&
1935 				pos_ticks < sel_pos_end )
1936 			{
1937 				// remove from selection when holding shift
1938 				bool selected = shift && note->selected();
1939 				note->setSelected( ! selected);
1940 			}
1941 		}
1942 	}
1943 
1944 	removeSelection();
1945 	update();
1946 }
1947 
1948 
1949 
1950 
mouseReleaseEvent(QMouseEvent * me)1951 void PianoRoll::mouseReleaseEvent( QMouseEvent * me )
1952 {
1953 	bool mustRepaint = false;
1954 
1955 	s_textFloat->hide();
1956 
1957 	if( me->button() & Qt::LeftButton )
1958 	{
1959 		mustRepaint = true;
1960 
1961 		if( m_action == ActionSelectNotes && m_editMode == ModeSelect )
1962 		{
1963 			// select the notes within the selection rectangle and
1964 			// then destroy the selection rectangle
1965 			computeSelectedNotes(
1966 					me->modifiers() & Qt::ShiftModifier );
1967 		}
1968 		else if( m_action == ActionMoveNote )
1969 		{
1970 			// we moved one or more notes so they have to be
1971 			// moved properly according to new starting-
1972 			// time in the note-array of pattern
1973 			m_pattern->rearrangeAllNotes();
1974 
1975 		}
1976 
1977 		if( m_action == ActionMoveNote || m_action == ActionResizeNote )
1978 		{
1979 			// if we only moved one note, deselect it so we can
1980 			// edit the notes in the note edit area
1981 			if( selectionCount() == 1 )
1982 			{
1983 				clearSelectedNotes();
1984 			}
1985 		}
1986 	}
1987 
1988 	if( me->button() & Qt::RightButton )
1989 	{
1990 		m_mouseDownRight = false;
1991 		mustRepaint = true;
1992 	}
1993 
1994 	if( hasValidPattern() )
1995 	{
1996 		// turn off all notes that are playing
1997 		for ( Note *note : m_pattern->notes() )
1998 		{
1999 			if( note->isPlaying() )
2000 			{
2001 				m_pattern->instrumentTrack()->pianoModel()->
2002 						handleKeyRelease( note->key() );
2003 				note->setIsPlaying( false );
2004 			}
2005 		}
2006 
2007 		// stop playing keys that we let go of
2008 		m_pattern->instrumentTrack()->pianoModel()->
2009 						handleKeyRelease( m_lastKey );
2010 	}
2011 
2012 	m_currentNote = NULL;
2013 	m_action = ActionNone;
2014 
2015 	if( m_editMode == ModeDraw )
2016 	{
2017 		QApplication::restoreOverrideCursor();
2018 	}
2019 
2020 	if( mustRepaint )
2021 	{
2022 		repaint();
2023 	}
2024 }
2025 
2026 
2027 
2028 
mouseMoveEvent(QMouseEvent * me)2029 void PianoRoll::mouseMoveEvent( QMouseEvent * me )
2030 {
2031 	if( ! hasValidPattern() )
2032 	{
2033 		update();
2034 		return;
2035 	}
2036 
2037 	if( m_action == ActionNone && me->buttons() == 0 )
2038 	{
2039 		if( me->y() > keyAreaBottom() && me->y() < noteEditTop() )
2040 		{
2041 			QApplication::setOverrideCursor(
2042 					QCursor( Qt::SizeVerCursor ) );
2043 			return;
2044 		}
2045 	}
2046 	else if( m_action == ActionResizeNoteEditArea )
2047 	{
2048 		// change m_notesEditHeight and then repaint
2049 		m_notesEditHeight = tLimit<int>(
2050 					m_oldNotesEditHeight - ( me->y() - m_moveStartY ),
2051 					NOTE_EDIT_MIN_HEIGHT,
2052 					height() - PR_TOP_MARGIN - NOTE_EDIT_RESIZE_BAR -
2053 									PR_BOTTOM_MARGIN - KEY_AREA_MIN_HEIGHT );
2054 		repaint();
2055 		return;
2056 	}
2057 
2058 	if( me->y() > PR_TOP_MARGIN || m_action != ActionNone )
2059 	{
2060 		bool edit_note = ( me->y() > noteEditTop() )
2061 						&& m_action != ActionSelectNotes;
2062 
2063 
2064 		int key_num = getKey( me->y() );
2065 		int x = me->x();
2066 
2067 		// see if they clicked on the keyboard on the left
2068 		if( x < WHITE_KEY_WIDTH && m_action == ActionNone
2069 		    && ! edit_note && key_num != m_lastKey
2070 			&& me->buttons() & Qt::LeftButton )
2071 		{
2072 			// clicked on a key, play the note
2073 			testPlayKey( key_num, ( (float) x ) / ( (float) WHITE_KEY_WIDTH ) * MidiDefaultVelocity, 0 );
2074 			update();
2075 			return;
2076 		}
2077 
2078 		x -= WHITE_KEY_WIDTH;
2079 
2080 		if( me->buttons() & Qt::LeftButton
2081 			&& m_editMode == ModeDraw
2082 			&& (m_action == ActionMoveNote || m_action == ActionResizeNote ) )
2083 		{
2084 			// handle moving notes and resizing them
2085 			bool replay_note = key_num != m_lastKey
2086 							&& m_action == ActionMoveNote;
2087 
2088 			if( replay_note || ( m_action == ActionMoveNote && ( me->modifiers() & Qt::ShiftModifier ) && ! m_startedWithShift ) )
2089 			{
2090 				pauseTestNotes();
2091 			}
2092 
2093 			dragNotes( me->x(), me->y(),
2094 				me->modifiers() & Qt::AltModifier,
2095 				me->modifiers() & Qt::ShiftModifier,
2096 				me->modifiers() & Qt::ControlModifier );
2097 
2098 			if( replay_note && m_action == ActionMoveNote && ! ( ( me->modifiers() & Qt::ShiftModifier ) && ! m_startedWithShift ) )
2099 			{
2100 				pauseTestNotes( false );
2101 			}
2102 		}
2103 		else if( m_editMode != ModeErase &&
2104 			( edit_note || m_action == ActionChangeNoteProperty ) &&
2105 				( me->buttons() & Qt::LeftButton || me->buttons() & Qt::MiddleButton
2106 				|| ( me->buttons() & Qt::RightButton && me->modifiers() & Qt::ShiftModifier ) ) )
2107 		{
2108 			// editing note properties
2109 
2110 			// Change notes within a certain pixel range of where
2111 			// the mouse cursor is
2112 			int pixel_range = 14;
2113 
2114 			// convert to ticks so that we can check which notes
2115 			// are in the range
2116 			int ticks_start = ( x-pixel_range/2 ) *
2117 					MidiTime::ticksPerTact() / m_ppt + m_currentPosition;
2118 			int ticks_end = ( x+pixel_range/2 ) *
2119 					MidiTime::ticksPerTact() / m_ppt + m_currentPosition;
2120 
2121 			// get note-vector of current pattern
2122 			const NoteVector & notes = m_pattern->notes();
2123 
2124 			// determine what volume/panning to set note to
2125 			// if middle-click, set to defaults
2126 			volume_t vol = DefaultVolume;
2127 			panning_t pan = DefaultPanning;
2128 
2129 			if( me->buttons() & Qt::LeftButton )
2130 			{
2131 				vol = tLimit<int>( MinVolume +
2132 								( ( (float)noteEditBottom() ) - ( (float)me->y() ) ) /
2133 								( (float)( noteEditBottom() - noteEditTop() ) ) *
2134 								( MaxVolume - MinVolume ),
2135 											MinVolume, MaxVolume );
2136 				pan = tLimit<int>( PanningLeft +
2137 								( (float)( noteEditBottom() - me->y() ) ) /
2138 								( (float)( noteEditBottom() - noteEditTop() ) ) *
2139 								( (float)( PanningRight - PanningLeft ) ),
2140 										  PanningLeft, PanningRight);
2141 			}
2142 
2143 			if( m_noteEditMode == NoteEditVolume )
2144 			{
2145 				m_lastNoteVolume = vol;
2146 				showVolTextFloat( vol, me->pos() );
2147 			}
2148 			else if( m_noteEditMode == NoteEditPanning )
2149 			{
2150 				m_lastNotePanning = pan;
2151 				showPanTextFloat( pan, me->pos() );
2152 			}
2153 
2154 			// When alt is pressed we only edit the note under the cursor
2155 			bool altPressed = me->modifiers() & Qt::AltModifier;
2156 			// We iterate from last note in pattern to the first,
2157 			// chronologically
2158 			NoteVector::ConstIterator it = notes.begin()+notes.size()-1;
2159 			for( int i = 0; i < notes.size(); ++i )
2160 			{
2161 				Note* n = *it;
2162 
2163 				bool isUnderPosition = n->withinRange( ticks_start, ticks_end );
2164 				// Play note under the cursor
2165 				if ( isUnderPosition ) { testPlayNote( n ); }
2166 				// If note is:
2167 				// Under the cursor, when there is no selection
2168 				// Selected, and alt is not pressed
2169 				// Under the cursor, selected, and alt is pressed
2170 				if ( ( isUnderPosition && !isSelection() ) ||
2171 					  ( n->selected() && !altPressed ) ||
2172 					  ( isUnderPosition && n->selected() && altPressed )
2173 					)
2174 				{
2175 					if( m_noteEditMode == NoteEditVolume )
2176 					{
2177 						n->setVolume( vol );
2178 
2179 						const int baseVelocity = m_pattern->instrumentTrack()->midiPort()->baseVelocity();
2180 
2181 						m_pattern->instrumentTrack()->processInEvent( MidiEvent( MidiKeyPressure, -1, n->key(), n->midiVelocity( baseVelocity ) ) );
2182 					}
2183 					else if( m_noteEditMode == NoteEditPanning )
2184 					{
2185 						n->setPanning( pan );
2186 						MidiEvent evt( MidiMetaEvent, -1, n->key(), panningToMidi( pan ) );
2187 						evt.setMetaEvent( MidiNotePanning );
2188 						m_pattern->instrumentTrack()->processInEvent( evt );
2189 					}
2190 				}
2191 				else if( n->isPlaying() && !isSelection() )
2192 				{
2193 					// mouse not over this note, stop playing it.
2194 					m_pattern->instrumentTrack()->pianoModel()->handleKeyRelease( n->key() );
2195 
2196 					n->setIsPlaying( false );
2197 				}
2198 
2199 
2200 				--it;
2201 			}
2202 
2203 			// Emit pattern has changed
2204 			m_pattern->dataChanged();
2205 		}
2206 
2207 		else if( me->buttons() == Qt::NoButton && m_editMode == ModeDraw )
2208 		{
2209 			// set move- or resize-cursor
2210 
2211 			// get tick in which the cursor is posated
2212 			int pos_ticks = ( x * MidiTime::ticksPerTact() ) /
2213 						m_ppt + m_currentPosition;
2214 
2215 			// get note-vector of current pattern
2216 			const NoteVector & notes = m_pattern->notes();
2217 
2218 			// will be our iterator in the following loop
2219 			NoteVector::ConstIterator it = notes.begin()+notes.size()-1;
2220 
2221 			// loop through whole note-vector...
2222 			for( int i = 0; i < notes.size(); ++i )
2223 			{
2224 				Note *note = *it;
2225 				// and check whether the cursor is over an
2226 				// existing note
2227 				if( pos_ticks >= note->pos() &&
2228 			    		pos_ticks <= note->pos() +
2229 							note->length() &&
2230 					note->key() == key_num &&
2231 					note->length() > 0 )
2232 				{
2233 					break;
2234 				}
2235 				--it;
2236 			}
2237 
2238 			// did it reach end of vector because there's
2239 			// no note??
2240 			if( it != notes.begin()-1 )
2241 			{
2242 				Note *note = *it;
2243 				// x coordinate of the right edge of the note
2244 				int noteRightX = ( note->pos() + note->length() -
2245 					m_currentPosition) * m_ppt/MidiTime::ticksPerTact();
2246 				// cursor at the "tail" of the note?
2247 				bool atTail = note->length() > 0 && x > noteRightX -
2248 							RESIZE_AREA_WIDTH;
2249 				Qt::CursorShape cursorShape = atTail ? Qt::SizeHorCursor :
2250 				                                       Qt::SizeAllCursor;
2251 				if( QApplication::overrideCursor() )
2252 				{
2253 					if( QApplication::overrideCursor()->shape() != cursorShape )
2254 					{
2255 						while( QApplication::overrideCursor() != NULL )
2256 						{
2257 							QApplication::restoreOverrideCursor();
2258 						}
2259 						QApplication::setOverrideCursor(QCursor(cursorShape));
2260 					}
2261 				}
2262 				else
2263 				{
2264 					QApplication::setOverrideCursor(QCursor(cursorShape));
2265 				}
2266 			}
2267 			else
2268 			{
2269 				// the cursor is over no note, so restore cursor
2270 				while( QApplication::overrideCursor() != NULL )
2271 				{
2272 					QApplication::restoreOverrideCursor();
2273 				}
2274 			}
2275 		}
2276 		else if( me->buttons() & Qt::LeftButton &&
2277 						m_editMode == ModeSelect &&
2278 						m_action == ActionSelectNotes )
2279 		{
2280 
2281 			// change size of selection
2282 
2283 			// get tick in which the cursor is posated
2284 			int pos_ticks = x * MidiTime::ticksPerTact() / m_ppt +
2285 							m_currentPosition;
2286 
2287 			m_selectedTick = pos_ticks - m_selectStartTick;
2288 			if( (int) m_selectStartTick + m_selectedTick < 0 )
2289 			{
2290 				m_selectedTick = -static_cast<int>(
2291 							m_selectStartTick );
2292 			}
2293 			m_selectedKeys = key_num - m_selectStartKey;
2294 			if( key_num <= m_selectStartKey )
2295 			{
2296 				--m_selectedKeys;
2297 			}
2298 		}
2299 		else if( ( m_editMode == ModeDraw && me->buttons() & Qt::RightButton )
2300 				|| ( m_editMode == ModeErase && me->buttons() ) )
2301 		{
2302 			// holding down right-click to delete notes or holding down
2303 			// any key if in erase mode
2304 
2305 			// get tick in which the user clicked
2306 			int pos_ticks = x * MidiTime::ticksPerTact() / m_ppt +
2307 							m_currentPosition;
2308 
2309 
2310 			// get note-vector of current pattern
2311 			const NoteVector & notes = m_pattern->notes();
2312 
2313 			// will be our iterator in the following loop
2314 			NoteVector::ConstIterator it = notes.begin();
2315 
2316 			// loop through whole note-vector...
2317 			while( it != notes.end() )
2318 			{
2319 				Note *note = *it;
2320 				MidiTime len = note->length();
2321 				if( len < 0 )
2322 				{
2323 					len = 4;
2324 				}
2325 				// and check whether the user clicked on an
2326 				// existing note or an edit-line
2327 				if( pos_ticks >= note->pos() &&
2328 						len > 0 &&
2329 					(
2330 					( ! edit_note &&
2331 					pos_ticks <= note->pos() + len &&
2332 					note->key() == key_num )
2333 					||
2334 					( edit_note &&
2335 					pos_ticks <= note->pos() +
2336 							NOTE_EDIT_LINE_WIDTH *
2337 						MidiTime::ticksPerTact() /
2338 								m_ppt )
2339 					)
2340 					)
2341 				{
2342 					// delete this note
2343 					m_pattern->removeNote( note );
2344 					Engine::getSong()->setModified();
2345 				}
2346 				else
2347 				{
2348 					++it;
2349 				}
2350 			}
2351 		}
2352 	}
2353 	else
2354 	{
2355 		if( me->buttons() & Qt::LeftButton &&
2356 					m_editMode == ModeSelect &&
2357 					m_action == ActionSelectNotes )
2358 		{
2359 
2360 			int x = me->x() - WHITE_KEY_WIDTH;
2361 			if( x < 0 && m_currentPosition > 0 )
2362 			{
2363 				x = 0;
2364 				QCursor::setPos( mapToGlobal( QPoint(
2365 							WHITE_KEY_WIDTH,
2366 							me->y() ) ) );
2367 				if( m_currentPosition >= 4 )
2368 				{
2369 					m_leftRightScroll->setValue(
2370 							m_currentPosition - 4 );
2371 				}
2372 				else
2373 				{
2374 					m_leftRightScroll->setValue( 0 );
2375 				}
2376 			}
2377 			else if( x > width() - WHITE_KEY_WIDTH )
2378 			{
2379 				x = width() - WHITE_KEY_WIDTH;
2380 				QCursor::setPos( mapToGlobal( QPoint( width(),
2381 							me->y() ) ) );
2382 				m_leftRightScroll->setValue( m_currentPosition +
2383 									4 );
2384 			}
2385 
2386 			// get tick in which the cursor is posated
2387 			int pos_ticks = x * MidiTime::ticksPerTact()/ m_ppt +
2388 							m_currentPosition;
2389 
2390 			m_selectedTick = pos_ticks -
2391 							m_selectStartTick;
2392 			if( (int) m_selectStartTick + m_selectedTick <
2393 									0 )
2394 			{
2395 				m_selectedTick = -static_cast<int>(
2396 							m_selectStartTick );
2397 			}
2398 
2399 
2400 			int key_num = getKey( me->y() );
2401 			int visible_keys = ( height() - PR_TOP_MARGIN -
2402 						PR_BOTTOM_MARGIN -
2403 						m_notesEditHeight ) /
2404 							KEY_LINE_HEIGHT + 2;
2405 			const int s_key = m_startKey - 1;
2406 
2407 			if( key_num <= s_key )
2408 			{
2409 				QCursor::setPos( mapToGlobal( QPoint( me->x(),
2410 							keyAreaBottom() ) ) );
2411 				m_topBottomScroll->setValue(
2412 					m_topBottomScroll->value() + 1 );
2413 				key_num = s_key;
2414 			}
2415 			else if( key_num >= s_key + visible_keys )
2416 			{
2417 				QCursor::setPos( mapToGlobal( QPoint( me->x(),
2418 							PR_TOP_MARGIN ) ) );
2419 				m_topBottomScroll->setValue(
2420 					m_topBottomScroll->value() - 1 );
2421 				key_num = s_key + visible_keys;
2422 			}
2423 
2424 			m_selectedKeys = key_num - m_selectStartKey;
2425 			if( key_num <= m_selectStartKey )
2426 			{
2427 				--m_selectedKeys;
2428 			}
2429 		}
2430 		QApplication::restoreOverrideCursor();
2431 	}
2432 
2433 	m_lastMouseX = me->x();
2434 	m_lastMouseY = me->y();
2435 
2436 	update();
2437 }
2438 
2439 
2440 
2441 
dragNotes(int x,int y,bool alt,bool shift,bool ctrl)2442 void PianoRoll::dragNotes( int x, int y, bool alt, bool shift, bool ctrl )
2443 {
2444 	// dragging one or more notes around
2445 
2446 	// convert pixels to ticks and keys
2447 	int off_x = x - m_moveStartX;
2448 	int off_ticks = off_x * MidiTime::ticksPerTact() / m_ppt;
2449 	int off_key = getKey( y ) - getKey( m_moveStartY );
2450 
2451 	// handle scroll changes while dragging
2452 	off_ticks -= m_mouseDownTick - m_currentPosition;
2453 	off_key -= m_mouseDownKey - m_startKey;
2454 
2455 
2456 	// if they're not holding alt, quantize the offset
2457 	if( ! alt )
2458 	{
2459 		off_ticks = floor( off_ticks / quantization() )
2460 						* quantization();
2461 	}
2462 
2463 	// make sure notes won't go outside boundary conditions
2464 	if( m_action == ActionMoveNote && ! ( shift && ! m_startedWithShift ) )
2465 	{
2466 		if( m_moveBoundaryLeft + off_ticks < 0 )
2467 		{
2468 			off_ticks -= (off_ticks + m_moveBoundaryLeft);
2469 		}
2470 		if( m_moveBoundaryTop + off_key > NumKeys )
2471 		{
2472 			off_key -= NumKeys - (m_moveBoundaryTop + off_key);
2473 		}
2474 		if( m_moveBoundaryBottom + off_key < 0 )
2475 		{
2476 			off_key -= (m_moveBoundaryBottom + off_key);
2477 		}
2478 	}
2479 
2480 
2481 	// get note-vector of current pattern
2482 	const NoteVector & notes = m_pattern->notes();
2483 
2484 	if (m_action == ActionMoveNote)
2485 	{
2486 		for (Note *note : notes)
2487 		{
2488 			if( note->selected() )
2489 			{
2490 				if( shift && ! m_startedWithShift )
2491 				{
2492 					// quick resize, toggled by holding shift after starting a note move, but not before
2493 					int ticks_new = note->oldLength().getTicks() + off_ticks;
2494 					if( ticks_new <= 0 )
2495 					{
2496 						ticks_new = 1;
2497 					}
2498 					note->setLength( MidiTime( ticks_new ) );
2499 					m_lenOfNewNotes = note->length();
2500 				}
2501 				else
2502 				{
2503 					// moving note
2504 					int pos_ticks = note->oldPos().getTicks() + off_ticks;
2505 					int key_num = note->oldKey() + off_key;
2506 
2507 					// ticks can't be negative
2508 					pos_ticks = qMax(0, pos_ticks);
2509 					// upper/lower bound checks on key_num
2510 					key_num = qMax(0, key_num);
2511 					key_num = qMin(key_num, NumKeys);
2512 
2513 					note->setPos( MidiTime( pos_ticks ) );
2514 					note->setKey( key_num );
2515 				}
2516 			}
2517 		}
2518 	}
2519 	else if (m_action == ActionResizeNote)
2520 	{
2521 		// When resizing notes:
2522 		// If shift is not pressed, resize the selected notes but do not rearrange them
2523 		// If shift is pressed we resize and rearrange only the selected notes
2524 		// If shift + ctrl then we also rearrange all posterior notes (sticky)
2525 		// If shift is pressed but only one note is selected, apply sticky
2526 
2527 		if (shift)
2528 		{
2529 			// Algorithm:
2530 			// Relative to the starting point of the left-most selected note,
2531 			//   all selected note start-points and *endpoints* (not length) should be scaled by a calculated factor.
2532 			// This factor is such that the endpoint of the note whose handle is being dragged should lie under the cursor.
2533 			// first, determine the start-point of the left-most selected note:
2534 			int stretchStartTick = -1;
2535 			for (const Note *note : notes)
2536 			{
2537 				if (note->selected() && (stretchStartTick < 0 || note->oldPos().getTicks() < stretchStartTick))
2538 				{
2539 					stretchStartTick = note->oldPos().getTicks();
2540 				}
2541 			}
2542 			// determine the ending tick of the right-most selected note
2543 			const Note *posteriorNote = nullptr;
2544 			for (const Note *note : notes)
2545 			{
2546 				if (note->selected() && (posteriorNote == nullptr ||
2547 					note->oldPos().getTicks() + note->oldLength().getTicks() >
2548 					posteriorNote->oldPos().getTicks() + posteriorNote->oldLength().getTicks()))
2549 				{
2550 					posteriorNote = note;
2551 				}
2552 			}
2553 			int posteriorEndTick = posteriorNote->pos().getTicks() + posteriorNote->length().getTicks();
2554 			// end-point of the note whose handle is being dragged:
2555 			int stretchEndTick = m_currentNote->oldPos().getTicks() + m_currentNote->oldLength().getTicks();
2556 			// Calculate factor by which to scale the start-point and end-point of all selected notes
2557 			float scaleFactor = (float)(stretchEndTick - stretchStartTick + off_ticks) / qMax(1, stretchEndTick - stretchStartTick);
2558 			scaleFactor = qMax(0.0f, scaleFactor);
2559 
2560 			// process all selected notes & determine how much the endpoint of the right-most note was shifted
2561 			int posteriorDeltaThisFrame = 0;
2562 			for (Note *note : notes)
2563 			{
2564 				if(note->selected())
2565 				{
2566 					// scale relative start and end positions by scaleFactor
2567 					int newStart = stretchStartTick + scaleFactor *
2568 						(note->oldPos().getTicks() - stretchStartTick);
2569 					int newEnd = stretchStartTick + scaleFactor *
2570 						(note->oldPos().getTicks()+note->oldLength().getTicks() - stretchStartTick);
2571 					// if  not holding alt, quantize the offsets
2572 					if(!alt)
2573 					{
2574 						// quantize start time
2575 						int oldStart = note->oldPos().getTicks();
2576 						int startDiff = newStart - oldStart;
2577 						startDiff = floor(startDiff / quantization()) * quantization();
2578 						newStart = oldStart + startDiff;
2579 						// quantize end time
2580 						int oldEnd = oldStart + note->oldLength().getTicks();
2581 						int endDiff = newEnd - oldEnd;
2582 						endDiff = floor(endDiff / quantization()) * quantization();
2583 						newEnd = oldEnd + endDiff;
2584 					}
2585 					int newLength = qMax(1, newEnd-newStart);
2586 					if (note == posteriorNote)
2587 					{
2588 						posteriorDeltaThisFrame = (newStart+newLength) -
2589 							(note->pos().getTicks() + note->length().getTicks());
2590 					}
2591 					note->setLength( MidiTime(newLength) );
2592 					note->setPos( MidiTime(newStart) );
2593 
2594 					m_lenOfNewNotes = note->length();
2595 				}
2596 			}
2597 			if (ctrl || selectionCount() == 1)
2598 			{
2599 				// if holding ctrl or only one note is selected, reposition posterior notes
2600 				for (Note *note : notes)
2601 				{
2602 					if (!note->selected() && note->pos().getTicks() >= posteriorEndTick)
2603 					{
2604 						int newStart = note->pos().getTicks() + posteriorDeltaThisFrame;
2605 						note->setPos( MidiTime(newStart) );
2606 					}
2607 				}
2608 			}
2609 		}
2610 		else
2611 		{
2612 			// shift is not pressed; stretch length of selected notes but not their position
2613 			for (Note *note : notes)
2614 			{
2615 				if (note->selected())
2616 				{
2617 					int newLength = note->oldLength() + off_ticks;
2618 					newLength = qMax(1, newLength);
2619 					note->setLength( MidiTime(newLength) );
2620 
2621 					m_lenOfNewNotes = note->length();
2622 				}
2623 			}
2624 		}
2625 	}
2626 
2627 	m_pattern->updateLength();
2628 	m_pattern->dataChanged();
2629 	Engine::getSong()->setModified();
2630 }
2631 
xCoordOfTick(int tick)2632 int PianoRoll::xCoordOfTick(int tick )
2633 {
2634 	return WHITE_KEY_WIDTH + ( ( tick - m_currentPosition )
2635 		* m_ppt / MidiTime::ticksPerTact() );
2636 }
2637 
paintEvent(QPaintEvent * pe)2638 void PianoRoll::paintEvent(QPaintEvent * pe )
2639 {
2640 	bool drawNoteNames = ConfigManager::inst()->value( "ui", "printnotelabels").toInt();
2641 
2642 	QStyleOption opt;
2643 	opt.initFrom( this );
2644 	QPainter p( this );
2645 	style()->drawPrimitive( QStyle::PE_Widget, &opt, &p, this );
2646 
2647 	QBrush bgColor = p.background();
2648 
2649 	// fill with bg color
2650 	p.fillRect( 0, 0, width(), height(), bgColor );
2651 
2652 	// set font-size to 8
2653 	p.setFont( pointSize<8>( p.font() ) );
2654 
2655 	// y_offset is used to align the piano-keys on the key-lines
2656 	int y_offset = 0;
2657 
2658 	// calculate y_offset according to first key
2659 	switch( prKeyOrder[m_startKey % KeysPerOctave] )
2660 	{
2661 		case PR_BLACK_KEY: y_offset = KEY_LINE_HEIGHT / 4; break;
2662 		case PR_WHITE_KEY_BIG: y_offset = KEY_LINE_HEIGHT / 2; break;
2663 		case PR_WHITE_KEY_SMALL:
2664 			if( prKeyOrder[( ( m_startKey + 1 ) %
2665 					KeysPerOctave)] != PR_BLACK_KEY )
2666 			{
2667 				y_offset = KEY_LINE_HEIGHT / 2;
2668 			}
2669 			break;
2670 	}
2671 	// start drawing at the bottom
2672 	int key_line_y = qMin(keyAreaBottom() - 1, KEY_LINE_HEIGHT * NumKeys);
2673 	// we need to set m_notesEditHeight here because it needs to fill in the
2674 	// rest of the window if key_line_y is bound to KEY_LINE_HEIGHT * NumKeys
2675 	if (key_line_y == KEY_LINE_HEIGHT * NumKeys) {
2676 		m_notesEditHeight = height() - (PR_TOP_MARGIN + KEY_LINE_HEIGHT * NumKeys);
2677 	}
2678 	// used for aligning black-keys later
2679 	int first_white_key_height = WHITE_KEY_SMALL_HEIGHT;
2680 	// key-counter - only needed for finding out whether the processed
2681 	// key is the first one
2682 	int keys_processed = 0;
2683 
2684 	int key = m_startKey;
2685 
2686 	// draw all white keys...
2687 	for( int y = key_line_y + 1 + y_offset; y > PR_TOP_MARGIN;
2688 			key_line_y -= KEY_LINE_HEIGHT, ++keys_processed )
2689 	{
2690 		// check for white key that is only half visible on the
2691 		// bottom of piano-roll
2692 		if( keys_processed == 0 &&
2693 			prKeyOrder[m_startKey % KeysPerOctave] ==
2694 								PR_BLACK_KEY )
2695 		{
2696 			// draw it!
2697 			p.drawPixmap( PIANO_X, y - WHITE_KEY_SMALL_HEIGHT,
2698 							*s_whiteKeySmallPm );
2699 			// update y-pos
2700 			y -= WHITE_KEY_SMALL_HEIGHT / 2;
2701 			// move first black key down (we didn't draw whole
2702 			// white key so black key needs to be lifted down)
2703 			// (default for first_white_key_height =
2704 			// WHITE_KEY_SMALL_HEIGHT, so WHITE_KEY_SMALL_HEIGHT/2
2705 			// is smaller)
2706 			first_white_key_height = WHITE_KEY_SMALL_HEIGHT / 2;
2707 		}
2708 		// check whether to draw a big or a small white key
2709 		if( prKeyOrder[key % KeysPerOctave] == PR_WHITE_KEY_SMALL )
2710 		{
2711 			// draw a small one while checking if it is pressed or not
2712 			if( hasValidPattern() && m_pattern->instrumentTrack()->pianoModel()->isKeyPressed( key ) )
2713 			{
2714 				p.drawPixmap( PIANO_X, y - WHITE_KEY_SMALL_HEIGHT, *s_whiteKeySmallPressedPm );
2715 			}
2716 			else
2717 			{
2718 				p.drawPixmap( PIANO_X, y - WHITE_KEY_SMALL_HEIGHT, *s_whiteKeySmallPm );
2719 			}
2720 			// update y-pos
2721 			y -= WHITE_KEY_SMALL_HEIGHT;
2722 
2723 		}
2724 		else if( prKeyOrder[key % KeysPerOctave] == PR_WHITE_KEY_BIG )
2725 		{
2726 			// draw a big one while checking if it is pressed or not
2727 			if( hasValidPattern() && m_pattern->instrumentTrack()->pianoModel()->isKeyPressed( key ) )
2728 			{
2729 				p.drawPixmap( PIANO_X, y - WHITE_KEY_BIG_HEIGHT, *s_whiteKeyBigPressedPm );
2730 			}
2731 			else
2732 			{
2733 				p.drawPixmap( PIANO_X, y-WHITE_KEY_BIG_HEIGHT, *s_whiteKeyBigPm );
2734 			}
2735 			// if a big white key has been the first key,
2736 			// black keys needs to be lifted up
2737 			if( keys_processed == 0 )
2738 			{
2739 				first_white_key_height = WHITE_KEY_BIG_HEIGHT;
2740 			}
2741 			// update y-pos
2742 			y -= WHITE_KEY_BIG_HEIGHT;
2743 		}
2744 
2745 		// Compute the corrections for the note names
2746 		int yCorrectionForNoteLabels = 0;
2747 
2748 		int keyCode = key % KeysPerOctave;
2749 		switch( keyCode )
2750 		{
2751 		case 0:
2752 		case 5:
2753 			yCorrectionForNoteLabels = -4;
2754 			break;
2755 		case 2:
2756 		case 7:
2757 		case 9:
2758 			yCorrectionForNoteLabels = -2;
2759 			break;
2760 		case 4:
2761 		case 11:
2762 			yCorrectionForNoteLabels = 2;
2763 			break;
2764 		}
2765 
2766 		if( Piano::isWhiteKey( key ) )
2767 		{
2768 			// Draw note names if activated in the preferences, C notes are always drawn
2769 			if ( key % 12 == 0 || drawNoteNames )
2770 			{
2771 				QString noteString = getNoteString( key );
2772 
2773 				QPoint textStart( WHITE_KEY_WIDTH - 18, key_line_y );
2774 				textStart += QPoint( 0, yCorrectionForNoteLabels );
2775 
2776 				p.setPen( textShadow() );
2777 				p.drawText( textStart + QPoint( 1, 1 ), noteString );
2778 				// The C key is painted darker than the other ones
2779 				if ( key % 12 == 0 )
2780 				{
2781 					p.setPen( textColor() );
2782 				}
2783 				else
2784 				{
2785 					p.setPen( textColorLight() );
2786 				}
2787 				p.drawText( textStart, noteString );
2788 			}
2789 		}
2790 		++key;
2791 	}
2792 
2793 	// reset all values, because now we're going to draw all black keys
2794 	key = m_startKey;
2795 	keys_processed = 0;
2796 	int white_cnt = 0;
2797 	key_line_y = qMin(keyAreaBottom(), KEY_LINE_HEIGHT * NumKeys);
2798 
2799 	// and go!
2800 	for( int y = key_line_y + y_offset;
2801 					y > PR_TOP_MARGIN; ++keys_processed )
2802 	{
2803 		// check for black key that is only half visible on the bottom
2804 		// of piano-roll
2805 		if( keys_processed == 0
2806 		    // current key may not be a black one
2807 		    && prKeyOrder[key % KeysPerOctave] != PR_BLACK_KEY
2808 		    // but the previous one must be black (we must check this
2809 		    // because there might be two white keys (E-F)
2810 		    && prKeyOrder[( key - 1 ) % KeysPerOctave] ==
2811 								PR_BLACK_KEY )
2812 		{
2813 			// draw the black key!
2814 			p.drawPixmap( PIANO_X, y - BLACK_KEY_HEIGHT / 2,
2815 								*s_blackKeyPm );
2816 			// is the one after the start-note a black key??
2817 			if( prKeyOrder[( key + 1 ) % KeysPerOctave] !=
2818 								PR_BLACK_KEY )
2819 			{
2820 				// no, then move it up!
2821 				y -= KEY_LINE_HEIGHT / 2;
2822 			}
2823 		}
2824 		// current key black?
2825 		if( prKeyOrder[key % KeysPerOctave] == PR_BLACK_KEY)
2826 		{
2827 			// then draw it (calculation of y very complicated,
2828 			// but that's the only working solution, sorry...)
2829 			// check if the key is pressed or not
2830 			if( hasValidPattern() && m_pattern->instrumentTrack()->pianoModel()->isKeyPressed( key ) )
2831 			{
2832 				p.drawPixmap( PIANO_X, y - ( first_white_key_height -
2833 						WHITE_KEY_SMALL_HEIGHT ) -
2834 						WHITE_KEY_SMALL_HEIGHT/2 - 1 -
2835 						BLACK_KEY_HEIGHT, *s_blackKeyPressedPm );
2836 			}
2837 		    else
2838 			{
2839 				p.drawPixmap( PIANO_X, y - ( first_white_key_height -
2840 						WHITE_KEY_SMALL_HEIGHT ) -
2841 						WHITE_KEY_SMALL_HEIGHT/2 - 1 -
2842 						BLACK_KEY_HEIGHT, *s_blackKeyPm );
2843 			}
2844 			// update y-pos
2845 			y -= WHITE_KEY_BIG_HEIGHT;
2846 			// reset white-counter
2847 			white_cnt = 0;
2848 		}
2849 		else
2850 		{
2851 			// simple workaround for increasing x if there were
2852 			// two white keys (e.g. between E and F)
2853 			++white_cnt;
2854 			if( white_cnt > 1 )
2855 			{
2856 				y -= WHITE_KEY_BIG_HEIGHT/2;
2857 			}
2858 		}
2859 
2860 		++key;
2861 	}
2862 
2863 
2864 	// erase the area below the piano, because there might be keys that
2865 	// should be only half-visible
2866 	p.fillRect( QRect( 0, key_line_y,
2867 			WHITE_KEY_WIDTH, noteEditBottom() - key_line_y ), bgColor );
2868 
2869 	// display note editing info
2870 	QFont f = p.font();
2871 	f.setBold( false );
2872 	p.setFont( pointSize<10>( f ) );
2873 	p.setPen( noteModeColor() );
2874 	p.drawText( QRect( 0, key_line_y,
2875 					  WHITE_KEY_WIDTH, noteEditBottom() - key_line_y ),
2876 			   Qt::AlignCenter | Qt::TextWordWrap,
2877 			   m_nemStr.at( m_noteEditMode ) + ":" );
2878 
2879 	// set clipping area, because we are not allowed to paint over
2880 	// keyboard...
2881 	p.setClipRect( WHITE_KEY_WIDTH, PR_TOP_MARGIN,
2882 				width() - WHITE_KEY_WIDTH,
2883 				height() - PR_TOP_MARGIN - PR_BOTTOM_MARGIN );
2884 
2885 	// draw the grid
2886 	if( hasValidPattern() )
2887 	{
2888 		int q, x, tick;
2889 
2890 		if( m_zoomingModel.value() > 3 )
2891 		{
2892 			// If we're over 100% zoom, we allow all quantization level grids
2893 			q = quantization();
2894 		}
2895 		else if( quantization() % 3 != 0 )
2896 		{
2897 			// If we're under 100% zoom, we allow quantization grid up to 1/24 for triplets
2898 			// to ensure a dense doesn't fill out the background
2899 			q = quantization() < 8 ? 8 : quantization();
2900 		}
2901 		else {
2902 			// If we're under 100% zoom, we allow quantization grid up to 1/32 for normal notes
2903 			q = quantization() < 6 ? 6 : quantization();
2904 		}
2905 
2906 		// First we draw the vertical quantization lines
2907 		for( tick = m_currentPosition - m_currentPosition % q, x = xCoordOfTick( tick );
2908 			x <= width(); tick += q, x = xCoordOfTick( tick ) )
2909 		{
2910 			p.setPen( lineColor() );
2911 			p.drawLine( x, PR_TOP_MARGIN, x, height() - PR_BOTTOM_MARGIN );
2912 		}
2913 
2914 		// Draw horizontal lines
2915 		key = m_startKey;
2916 		for( int y = key_line_y - 1; y > PR_TOP_MARGIN;
2917 				y -= KEY_LINE_HEIGHT )
2918 		{
2919 			if( static_cast<Keys>( key % KeysPerOctave ) == Key_C )
2920 			{
2921 				// C note gets accented
2922 				p.setPen( beatLineColor() );
2923 			}
2924 			else
2925 			{
2926 				p.setPen( lineColor() );
2927 			}
2928 			p.drawLine( WHITE_KEY_WIDTH, y, width(), y );
2929 			++key;
2930 		}
2931 
2932 
2933 		// Draw alternating shades on bars
2934 		float timeSignature = static_cast<float>( Engine::getSong()->getTimeSigModel().getNumerator() )
2935 				/ static_cast<float>( Engine::getSong()->getTimeSigModel().getDenominator() );
2936 		float zoomFactor = m_zoomLevels[m_zoomingModel.value()];
2937 		//the bars which disappears at the left side by scrolling
2938 		int leftBars = m_currentPosition * zoomFactor / MidiTime::ticksPerTact();
2939 
2940 		//iterates the visible bars and draw the shading on uneven bars
2941 		for( int x = WHITE_KEY_WIDTH, barCount = leftBars; x < width() + m_currentPosition * zoomFactor / timeSignature; x += m_ppt, ++barCount )
2942 		{
2943 			if( ( barCount + leftBars )  % 2 != 0 )
2944 			{
2945 				p.fillRect( x - m_currentPosition * zoomFactor / timeSignature, PR_TOP_MARGIN, m_ppt,
2946 					height() - ( PR_BOTTOM_MARGIN + PR_TOP_MARGIN ), backgroundShade() );
2947 			}
2948 		}
2949 
2950 		// Draw the vertical beat lines
2951 		int ticksPerBeat = DefaultTicksPerTact /
2952 			Engine::getSong()->getTimeSigModel().getDenominator();
2953 
2954 		for( tick = m_currentPosition - m_currentPosition % ticksPerBeat,
2955 			x = xCoordOfTick( tick ); x <= width();
2956 			tick += ticksPerBeat, x = xCoordOfTick( tick ) )
2957 		{
2958 			p.setPen( beatLineColor() );
2959 			p.drawLine( x, PR_TOP_MARGIN, x, height() - PR_BOTTOM_MARGIN );
2960 		}
2961 
2962 		// Draw the vertical bar lines
2963 		for( tick = m_currentPosition - m_currentPosition % MidiTime::ticksPerTact(),
2964 			x = xCoordOfTick( tick ); x <= width();
2965 			tick += MidiTime::ticksPerTact(), x = xCoordOfTick( tick ) )
2966 		{
2967 			p.setPen( barLineColor() );
2968 			p.drawLine( x, PR_TOP_MARGIN, x, height() - PR_BOTTOM_MARGIN );
2969 		}
2970 
2971 		// draw marked semitones after the grid
2972 		for( int i = 0; i < m_markedSemiTones.size(); i++ )
2973 		{
2974 			const int key_num = m_markedSemiTones.at( i );
2975 			const int y = key_line_y + 5
2976 				- KEY_LINE_HEIGHT * ( key_num - m_startKey + 1 );
2977 
2978 			if( y > key_line_y )
2979 			{
2980 				break;
2981 			}
2982 
2983 			p.fillRect( WHITE_KEY_WIDTH + 1, y - KEY_LINE_HEIGHT / 2, width() - 10, KEY_LINE_HEIGHT + 1,
2984 				    markedSemitoneColor() );
2985 		}
2986 	}
2987 
2988 	// following code draws all notes in visible area
2989 	// and the note editing stuff (volume, panning, etc)
2990 
2991 	// setup selection-vars
2992 	int sel_pos_start = m_selectStartTick;
2993 	int sel_pos_end = m_selectStartTick+m_selectedTick;
2994 	if( sel_pos_start > sel_pos_end )
2995 	{
2996 		qSwap<int>( sel_pos_start, sel_pos_end );
2997 	}
2998 
2999 	int sel_key_start = m_selectStartKey - m_startKey + 1;
3000 	int sel_key_end = sel_key_start + m_selectedKeys;
3001 	if( sel_key_start > sel_key_end )
3002 	{
3003 		qSwap<int>( sel_key_start, sel_key_end );
3004 	}
3005 
3006 	int y_base = key_line_y - 1;
3007 	if( hasValidPattern() )
3008 	{
3009 		p.setClipRect( WHITE_KEY_WIDTH, PR_TOP_MARGIN,
3010 				width() - WHITE_KEY_WIDTH,
3011 				height() - PR_TOP_MARGIN );
3012 
3013 		const int visible_keys = ( key_line_y-keyAreaTop() ) /
3014 							KEY_LINE_HEIGHT + 2;
3015 
3016 		QPolygonF editHandles;
3017 
3018 		for( const Note *note : m_pattern->notes() )
3019 		{
3020 			int len_ticks = note->length();
3021 
3022 			if( len_ticks == 0 )
3023 			{
3024 				continue;
3025 			}
3026 			else if( len_ticks < 0 )
3027 			{
3028 				len_ticks = 4;
3029 			}
3030 
3031 			const int key = note->key() - m_startKey + 1;
3032 
3033 			int pos_ticks = note->pos();
3034 
3035 			int note_width = len_ticks * m_ppt / MidiTime::ticksPerTact();
3036 			const int x = ( pos_ticks - m_currentPosition ) *
3037 					m_ppt / MidiTime::ticksPerTact();
3038 			// skip this note if not in visible area at all
3039 			if( !( x + note_width >= 0 && x <= width() - WHITE_KEY_WIDTH ) )
3040 			{
3041 				continue;
3042 			}
3043 
3044 			// is the note in visible area?
3045 			if( key > 0 && key <= visible_keys )
3046 			{
3047 
3048 				// we've done and checked all, let's draw the
3049 				// note
3050 				drawNoteRect( p, x + WHITE_KEY_WIDTH,
3051 						y_base - key * KEY_LINE_HEIGHT,
3052 								note_width, note, noteColor(), selectedNoteColor(),
3053 							 	noteOpacity(), noteBorders() );
3054 			}
3055 
3056 			// draw note editing stuff
3057 			int editHandleTop = 0;
3058 			if( m_noteEditMode == NoteEditVolume )
3059 			{
3060 				QColor color = barColor().lighter( 30 + ( note->getVolume() * 90 / MaxVolume ) );
3061 				if( note->selected() )
3062 				{
3063 					color = selectedNoteColor();
3064 				}
3065 				p.setPen( QPen( color, NOTE_EDIT_LINE_WIDTH ) );
3066 
3067 				editHandleTop = noteEditBottom() -
3068 					( (float)( note->getVolume() - MinVolume ) ) /
3069 					( (float)( MaxVolume - MinVolume ) ) *
3070 					( (float)( noteEditBottom() - noteEditTop() ) );
3071 
3072 				p.drawLine( QLineF ( noteEditLeft() + x + 0.5, editHandleTop + 0.5,
3073 							noteEditLeft() + x + 0.5, noteEditBottom() + 0.5 ) );
3074 
3075 			}
3076 			else if( m_noteEditMode == NoteEditPanning )
3077 			{
3078 				QColor color = noteColor();
3079 				if( note->selected() )
3080 				{
3081 					color = selectedNoteColor();
3082 				}
3083 
3084 				p.setPen( QPen( color, NOTE_EDIT_LINE_WIDTH ) );
3085 
3086 				editHandleTop = noteEditBottom() -
3087 					( (float)( note->getPanning() - PanningLeft ) ) /
3088 					( (float)( (PanningRight - PanningLeft ) ) ) *
3089 					( (float)( noteEditBottom() - noteEditTop() ) );
3090 
3091 				p.drawLine( QLine( noteEditLeft() + x, noteEditTop() +
3092 						( (float)( noteEditBottom() - noteEditTop() ) ) / 2.0f,
3093 						    noteEditLeft() + x , editHandleTop ) );
3094 			}
3095 			editHandles << QPoint ( x + noteEditLeft(),
3096 						editHandleTop );
3097 
3098 			if( note->hasDetuningInfo() )
3099 			{
3100 				drawDetuningInfo( p, note,
3101 					x + WHITE_KEY_WIDTH,
3102 					y_base - key * KEY_LINE_HEIGHT );
3103 				p.setClipRect(WHITE_KEY_WIDTH, PR_TOP_MARGIN,
3104 					width() - WHITE_KEY_WIDTH,
3105 					height() - PR_TOP_MARGIN);
3106 			}
3107 		}
3108 
3109 
3110 		p.setPen( QPen( noteColor(), NOTE_EDIT_LINE_WIDTH + 2 ) );
3111 		p.drawPoints( editHandles );
3112 
3113 	}
3114 	else
3115 	{
3116 		QFont f = p.font();
3117 		f.setBold( true );
3118 		p.setFont( pointSize<14>( f ) );
3119 		p.setPen( QApplication::palette().color( QPalette::Active,
3120 							QPalette::BrightText ) );
3121 		p.drawText( WHITE_KEY_WIDTH + 20, PR_TOP_MARGIN + 40,
3122 				tr( "Please open a pattern by double-clicking "
3123 								"on it!" ) );
3124 	}
3125 
3126 	p.setClipRect( WHITE_KEY_WIDTH, PR_TOP_MARGIN, width() -
3127 				WHITE_KEY_WIDTH, height() - PR_TOP_MARGIN -
3128 					m_notesEditHeight - PR_BOTTOM_MARGIN );
3129 
3130 	// now draw selection-frame
3131 	int x = ( ( sel_pos_start - m_currentPosition ) * m_ppt ) /
3132 						MidiTime::ticksPerTact();
3133 	int w = ( ( ( sel_pos_end - m_currentPosition ) * m_ppt ) /
3134 						MidiTime::ticksPerTact() ) - x;
3135 	int y = (int) y_base - sel_key_start * KEY_LINE_HEIGHT;
3136 	int h = (int) y_base - sel_key_end * KEY_LINE_HEIGHT - y;
3137 	p.setPen( selectedNoteColor() );
3138 	p.setBrush( Qt::NoBrush );
3139 	p.drawRect( x + WHITE_KEY_WIDTH, y, w, h );
3140 
3141 	// TODO: Get this out of paint event
3142 	int l = ( hasValidPattern() )? (int) m_pattern->length() : 0;
3143 
3144 	// reset scroll-range
3145 	if( m_leftRightScroll->maximum() != l )
3146 	{
3147 		m_leftRightScroll->setRange( 0, l );
3148 		m_leftRightScroll->setPageStep( l );
3149 	}
3150 
3151 	// set line colors
3152 	QColor editAreaCol = QColor( lineColor() );
3153 	QColor currentKeyCol = QColor( beatLineColor() );
3154 
3155 	editAreaCol.setAlpha( 64 );
3156 	currentKeyCol.setAlpha( 64 );
3157 
3158 	// horizontal line for the key under the cursor
3159 	if( hasValidPattern() )
3160 	{
3161 		int key_num = getKey( mapFromGlobal( QCursor::pos() ).y() );
3162 		p.fillRect( 10, key_line_y + 3 - KEY_LINE_HEIGHT *
3163 					( key_num - m_startKey + 1 ), width() - 10, KEY_LINE_HEIGHT - 7, currentKeyCol );
3164 	}
3165 
3166 	// bar to resize note edit area
3167 	p.setClipRect( 0, 0, width(), height() );
3168 	p.fillRect( QRect( 0, key_line_y,
3169 					width()-PR_RIGHT_MARGIN, NOTE_EDIT_RESIZE_BAR ), editAreaCol );
3170 
3171 	const QPixmap * cursor = NULL;
3172 	// draw current edit-mode-icon below the cursor
3173 	switch( m_editMode )
3174 	{
3175 		case ModeDraw:
3176 			if( m_mouseDownRight )
3177 			{
3178 				cursor = s_toolErase;
3179 			}
3180 			else if( m_action == ActionMoveNote )
3181 			{
3182 				cursor = s_toolMove;
3183 			}
3184 			else
3185 			{
3186 				cursor = s_toolDraw;
3187 			}
3188 			break;
3189 		case ModeErase: cursor = s_toolErase; break;
3190 		case ModeSelect: cursor = s_toolSelect; break;
3191 		case ModeEditDetuning: cursor = s_toolOpen; break;
3192 	}
3193 	QPoint mousePosition = mapFromGlobal( QCursor::pos() );
3194 	if( cursor != NULL && mousePosition.y() > keyAreaTop() && mousePosition.x() > noteEditLeft())
3195 	{
3196 		p.drawPixmap( mousePosition + QPoint( 8, 8 ), *cursor );
3197 	}
3198 }
3199 
3200 
3201 
3202 
3203 // responsible for moving/resizing scrollbars after window-resizing
resizeEvent(QResizeEvent * re)3204 void PianoRoll::resizeEvent(QResizeEvent * re)
3205 {
3206 	m_leftRightScroll->setGeometry( WHITE_KEY_WIDTH,
3207 								      height() -
3208 								SCROLLBAR_SIZE,
3209 					width()-WHITE_KEY_WIDTH,
3210 							SCROLLBAR_SIZE );
3211 	m_topBottomScroll->setGeometry( width() - SCROLLBAR_SIZE, PR_TOP_MARGIN,
3212 						SCROLLBAR_SIZE,
3213 						height() - PR_TOP_MARGIN -
3214 						SCROLLBAR_SIZE );
3215 
3216 	int total_pixels = OCTAVE_HEIGHT * NumOctaves - ( height() -
3217 					PR_TOP_MARGIN - PR_BOTTOM_MARGIN -
3218 							m_notesEditHeight );
3219 	m_totalKeysToScroll = total_pixels * KeysPerOctave / OCTAVE_HEIGHT;
3220 
3221 	m_topBottomScroll->setRange( 0, m_totalKeysToScroll );
3222 
3223 	if( m_startKey > m_totalKeysToScroll )
3224 	{
3225 		m_startKey = m_totalKeysToScroll;
3226 	}
3227 	m_topBottomScroll->setValue( m_totalKeysToScroll - m_startKey );
3228 
3229 	Engine::getSong()->getPlayPos( Song::Mode_PlayPattern
3230 					).m_timeLine->setFixedWidth( width() );
3231 
3232 	update();
3233 }
3234 
3235 
3236 
3237 
wheelEvent(QWheelEvent * we)3238 void PianoRoll::wheelEvent(QWheelEvent * we )
3239 {
3240 	we->accept();
3241 	// handle wheel events for note edit area - for editing note vol/pan with mousewheel
3242 	if( we->x() > noteEditLeft() && we->x() < noteEditRight()
3243 	&& we->y() > noteEditTop() && we->y() < noteEditBottom() )
3244 	{
3245 		if (!hasValidPattern()) {return;}
3246 		// get values for going through notes
3247 		int pixel_range = 8;
3248 		int x = we->x() - WHITE_KEY_WIDTH;
3249 		int ticks_start = ( x - pixel_range / 2 ) *
3250 					MidiTime::ticksPerTact() / m_ppt + m_currentPosition;
3251 		int ticks_end = ( x + pixel_range / 2 ) *
3252 					MidiTime::ticksPerTact() / m_ppt + m_currentPosition;
3253 
3254 		// When alt is pressed we only edit the note under the cursor
3255 		bool altPressed = we->modifiers() & Qt::AltModifier;
3256 		// go through notes to figure out which one we want to change
3257 		NoteVector nv;
3258 		for ( Note * i : m_pattern->notes() )
3259 		{
3260 			if( i->withinRange( ticks_start, ticks_end ) || ( i->selected() && !altPressed ) )
3261 			{
3262 				nv += i;
3263 			}
3264 		}
3265 		if( nv.size() > 0 )
3266 		{
3267 			const int step = we->delta() > 0 ? 1.0 : -1.0;
3268 			if( m_noteEditMode == NoteEditVolume )
3269 			{
3270 				for ( Note * n : nv )
3271 				{
3272 					volume_t vol = tLimit<int>( n->getVolume() + step, MinVolume, MaxVolume );
3273 					n->setVolume( vol );
3274 				}
3275 				bool allVolumesEqual = std::all_of( nv.begin(), nv.end(),
3276 					[nv](const Note *note)
3277 					{
3278 						return note->getVolume() == nv[0]->getVolume();
3279 					});
3280 				if ( allVolumesEqual )
3281 				{
3282 					// show the volume hover-text only if all notes have the
3283 					// same volume
3284 					showVolTextFloat( nv[0]->getVolume(), we->pos(), 1000 );
3285 				}
3286 			}
3287 			else if( m_noteEditMode == NoteEditPanning )
3288 			{
3289 				for ( Note * n : nv )
3290 				{
3291 					panning_t pan = tLimit<int>( n->getPanning() + step, PanningLeft, PanningRight );
3292 					n->setPanning( pan );
3293 				}
3294 				bool allPansEqual = std::all_of( nv.begin(), nv.end(),
3295 					[nv](const Note *note)
3296 					{
3297 						return note->getPanning() == nv[0]->getPanning();
3298 					});
3299 				if ( allPansEqual )
3300 				{
3301 					// show the pan hover-text only if all notes have the same
3302 					// panning
3303 					showPanTextFloat( nv[0]->getPanning(), we->pos(), 1000 );
3304 				}
3305 			}
3306 			update();
3307 		}
3308 	}
3309 
3310 	// not in note edit area, so handle scrolling/zooming and quantization change
3311 	else
3312 	if( we->modifiers() & Qt::ControlModifier && we->modifiers() & Qt::AltModifier )
3313 	{
3314 		int q = m_quantizeModel.value();
3315 		if( we->delta() > 0 )
3316 		{
3317 			q--;
3318 		}
3319 		else if( we->delta() < 0 )
3320 		{
3321 			q++;
3322 		}
3323 		q = qBound( 0, q, m_quantizeModel.size() - 1 );
3324 		m_quantizeModel.setValue( q );
3325 	}
3326 	else if( we->modifiers() & Qt::ControlModifier && we->modifiers() & Qt::ShiftModifier )
3327 	{
3328 		int l = m_noteLenModel.value();
3329 		if( we->delta() > 0 )
3330 		{
3331 			l--;
3332 		}
3333 		else if( we->delta() < 0 )
3334 		{
3335 			l++;
3336 		}
3337 		l = qBound( 0, l, m_noteLenModel.size() - 1 );
3338 		m_noteLenModel.setValue( l );
3339 	}
3340 	else if( we->modifiers() & Qt::ControlModifier )
3341 	{
3342 		int z = m_zoomingModel.value();
3343 		if( we->delta() > 0 )
3344 		{
3345 			z++;
3346 		}
3347 		else if( we->delta() < 0 )
3348 		{
3349 			z--;
3350 		}
3351 		z = qBound( 0, z, m_zoomingModel.size() - 1 );
3352 		// update combobox with zooming-factor
3353 		m_zoomingModel.setValue( z );
3354 	}
3355 	else if( we->modifiers() & Qt::ShiftModifier
3356 			 || we->orientation() == Qt::Horizontal )
3357 	{
3358 		m_leftRightScroll->setValue( m_leftRightScroll->value() -
3359 							we->delta() * 2 / 15 );
3360 	}
3361 	else
3362 	{
3363 		m_topBottomScroll->setValue( m_topBottomScroll->value() -
3364 							we->delta() / 30 );
3365 	}
3366 }
3367 
3368 
3369 
3370 
focusOutEvent(QFocusEvent *)3371 void PianoRoll::focusOutEvent( QFocusEvent * )
3372 {
3373 	if( hasValidPattern() )
3374 	{
3375 		for( int i = 0; i < NumKeys; ++i )
3376 		{
3377 			m_pattern->instrumentTrack()->pianoModel()->midiEventProcessor()->processInEvent( MidiEvent( MidiNoteOff, -1, i, 0 ) );
3378 			m_pattern->instrumentTrack()->pianoModel()->setKeyState( i, false );
3379 		}
3380 	}
3381 	m_editMode = m_ctrlMode;
3382 	update();
3383 }
3384 
3385 
3386 
3387 
getKey(int y) const3388 int PianoRoll::getKey(int y ) const
3389 {
3390 	int key_line_y = keyAreaBottom() - 1;
3391 	// pressed key on piano
3392 	int key_num = ( key_line_y - y ) / KEY_LINE_HEIGHT;
3393 	key_num += m_startKey;
3394 
3395 	// some range-checking-stuff
3396 	if( key_num < 0 )
3397 	{
3398 		key_num = 0;
3399 	}
3400 
3401 	if( key_num >= KeysPerOctave * NumOctaves )
3402 	{
3403 		key_num = KeysPerOctave * NumOctaves - 1;
3404 	}
3405 
3406 	return key_num;
3407 }
3408 
getAllOctavesForKey(int keyToMirror) const3409 QList<int> PianoRoll::getAllOctavesForKey( int keyToMirror ) const
3410 {
3411 	QList<int> keys;
3412 
3413 	for (int i=keyToMirror % KeysPerOctave; i < NumKeys; i += KeysPerOctave)
3414 	{
3415 		keys.append(i);
3416 	}
3417 
3418 	return keys;
3419 }
3420 
desiredPlayModeForAccompany() const3421 Song::PlayModes PianoRoll::desiredPlayModeForAccompany() const
3422 {
3423 	if( m_pattern->getTrack()->trackContainer() ==
3424 					Engine::getBBTrackContainer() )
3425 	{
3426 		return Song::Mode_PlayBB;
3427 	}
3428 	return Song::Mode_PlaySong;
3429 }
3430 
3431 
3432 
3433 
play()3434 void PianoRoll::play()
3435 {
3436 	if( ! hasValidPattern() )
3437 	{
3438 		return;
3439 	}
3440 
3441 	if( Engine::getSong()->playMode() != Song::Mode_PlayPattern )
3442 	{
3443 		Engine::getSong()->playPattern( m_pattern );
3444 	}
3445 	else
3446 	{
3447 		Engine::getSong()->togglePause();
3448 	}
3449 }
3450 
3451 
3452 
3453 
record()3454 void PianoRoll::record()
3455 {
3456 	if( Engine::getSong()->isPlaying() )
3457 	{
3458 		stop();
3459 	}
3460 	if( m_recording || ! hasValidPattern() )
3461 	{
3462 		return;
3463 	}
3464 
3465 	m_pattern->addJournalCheckPoint();
3466 	m_recording = true;
3467 
3468 	Engine::getSong()->playPattern( m_pattern, false );
3469 }
3470 
3471 
3472 
3473 
recordAccompany()3474 void PianoRoll::recordAccompany()
3475 {
3476 	if( Engine::getSong()->isPlaying() )
3477 	{
3478 		stop();
3479 	}
3480 	if( m_recording || ! hasValidPattern() )
3481 	{
3482 		return;
3483 	}
3484 
3485 	m_pattern->addJournalCheckPoint();
3486 	m_recording = true;
3487 
3488 	if( m_pattern->getTrack()->trackContainer() == Engine::getSong() )
3489 	{
3490 		Engine::getSong()->playSong();
3491 	}
3492 	else
3493 	{
3494 		Engine::getSong()->playBB();
3495 	}
3496 }
3497 
3498 
3499 
3500 
3501 
stop()3502 void PianoRoll::stop()
3503 {
3504 	Engine::getSong()->stop();
3505 	m_recording = false;
3506 	m_scrollBack = ( m_timeLine->autoScroll() == TimeLineWidget::AutoScrollEnabled );
3507 }
3508 
3509 
3510 
3511 
startRecordNote(const Note & n)3512 void PianoRoll::startRecordNote(const Note & n )
3513 {
3514 	if( m_recording && hasValidPattern() &&
3515 			Engine::getSong()->isPlaying() &&
3516 			(Engine::getSong()->playMode() == desiredPlayModeForAccompany() ||
3517 			 Engine::getSong()->playMode() == Song::Mode_PlayPattern ))
3518 	{
3519 		MidiTime sub;
3520 		if( Engine::getSong()->playMode() == Song::Mode_PlaySong )
3521 		{
3522 			sub = m_pattern->startPosition();
3523 		}
3524 		Note n1( 1, Engine::getSong()->getPlayPos(
3525 					Engine::getSong()->playMode() ) - sub,
3526 				n.key(), n.getVolume(), n.getPanning() );
3527 		if( n1.pos() >= 0 )
3528 		{
3529 			m_recordingNotes << n1;
3530 		}
3531 	}
3532 }
3533 
3534 
3535 
3536 
finishRecordNote(const Note & n)3537 void PianoRoll::finishRecordNote(const Note & n )
3538 {
3539 	if( m_recording && hasValidPattern() &&
3540 		Engine::getSong()->isPlaying() &&
3541 			( Engine::getSong()->playMode() ==
3542 					desiredPlayModeForAccompany() ||
3543 				Engine::getSong()->playMode() ==
3544 					Song::Mode_PlayPattern ) )
3545 	{
3546 		for( QList<Note>::Iterator it = m_recordingNotes.begin();
3547 					it != m_recordingNotes.end(); ++it )
3548 		{
3549 			if( it->key() == n.key() )
3550 			{
3551 				Note n1( n.length(), it->pos(),
3552 						it->key(), it->getVolume(),
3553 						it->getPanning() );
3554 				n1.quantizeLength( quantization() );
3555 
3556 				//Get selected chord
3557 				const InstrumentFunctionNoteStacking::Chord & chord = InstrumentFunctionNoteStacking::ChordTable::getInstance()
3558 					.getChordByName( m_chordModel.currentText() );
3559 
3560 				if( !chord.isEmpty() )
3561 				{
3562 					for( int i = 1; i < chord.size(); i++ )
3563 					{
3564 						Note new_note( n.length(), it->pos(), it->key() + chord[i] );
3565 						new_note.setPanning( it->getPanning() );
3566 						new_note.setVolume( it->getVolume() );
3567 						m_pattern->addNote( new_note );
3568 					}
3569 				}
3570 
3571 				m_pattern->addNote( n1 );
3572 				update();
3573 				m_recordingNotes.erase( it );
3574 				break;
3575 			}
3576 		}
3577 	}
3578 }
3579 
3580 
3581 
3582 
horScrolled(int new_pos)3583 void PianoRoll::horScrolled(int new_pos )
3584 {
3585 	m_currentPosition = new_pos;
3586 	emit positionChanged( m_currentPosition );
3587 	update();
3588 }
3589 
3590 
3591 
3592 
verScrolled(int new_pos)3593 void PianoRoll::verScrolled( int new_pos )
3594 {
3595 	// revert value
3596 	m_startKey = m_totalKeysToScroll - new_pos;
3597 
3598 	update();
3599 }
3600 
3601 
3602 
3603 
setEditMode(int mode)3604 void PianoRoll::setEditMode(int mode)
3605 {
3606 	m_ctrlMode = m_editMode = (EditModes) mode;
3607 }
3608 
3609 
3610 
3611 
selectAll()3612 void PianoRoll::selectAll()
3613 {
3614 	if( ! hasValidPattern() )
3615 	{
3616 		return;
3617 	}
3618 
3619 	// if first_time = true, we HAVE to set the vars for select
3620 	bool first_time = true;
3621 
3622 	for( const Note *note : m_pattern->notes() )
3623 	{
3624 		int len_ticks = static_cast<int>( note->length() ) > 0 ?
3625 				static_cast<int>( note->length() ) : 1;
3626 
3627 		const int key = note->key();
3628 
3629 		int pos_ticks = note->pos();
3630 		if( key <= m_selectStartKey || first_time )
3631 		{
3632 			// if we move start-key down, we have to add
3633 			// the difference between old and new start-key
3634 			// to m_selectedKeys, otherwise the selection
3635 			// is just moved down...
3636 			m_selectedKeys += m_selectStartKey
3637 							- ( key - 1 );
3638 			m_selectStartKey = key - 1;
3639 		}
3640 		if( key >= m_selectedKeys + m_selectStartKey ||
3641 							first_time )
3642 		{
3643 			m_selectedKeys = key - m_selectStartKey;
3644 		}
3645 		if( pos_ticks < m_selectStartTick ||
3646 							first_time )
3647 		{
3648 			m_selectStartTick = pos_ticks;
3649 		}
3650 		if( pos_ticks + len_ticks >
3651 			m_selectStartTick + m_selectedTick ||
3652 							first_time )
3653 		{
3654 			m_selectedTick = pos_ticks +
3655 						len_ticks -
3656 						m_selectStartTick;
3657 		}
3658 		first_time = false;
3659 	}
3660 }
3661 
3662 
3663 
3664 
3665 // returns vector with pointers to all selected notes
getSelectedNotes()3666 NoteVector PianoRoll::getSelectedNotes()
3667 {
3668 	NoteVector selectedNotes;
3669 
3670 	if (hasValidPattern())
3671 	{
3672 		for( Note *note : m_pattern->notes() )
3673 		{
3674 			if( note->selected() )
3675 			{
3676 				selectedNotes.push_back( note );
3677 			}
3678 		}
3679 	}
3680 	return selectedNotes;
3681 }
3682 
3683 // selects all notess associated with m_lastKey
selectNotesOnKey()3684 void PianoRoll::selectNotesOnKey()
3685 {
3686 	if (hasValidPattern()) {
3687 		for (Note * note : m_pattern->notes()) {
3688 			if (note->key() == m_lastKey) {
3689 				note->setSelected(true);
3690 			}
3691 		}
3692 	}
3693 }
3694 
enterValue(NoteVector * nv)3695 void PianoRoll::enterValue( NoteVector* nv )
3696 {
3697 
3698 	if( m_noteEditMode == NoteEditVolume )
3699 	{
3700 		bool ok;
3701 		int new_val;
3702 		new_val = QInputDialog::getInt(	this, "Piano roll: note velocity",
3703 					tr( "Please enter a new value between %1 and %2:" ).
3704 						arg( MinVolume ).arg( MaxVolume ),
3705 					(*nv)[0]->getVolume(),
3706 					MinVolume, MaxVolume, 1, &ok );
3707 
3708 		if( ok )
3709 		{
3710 			for ( Note * n : *nv )
3711 			{
3712 				n->setVolume( new_val );
3713 			}
3714 			m_lastNoteVolume = new_val;
3715 		}
3716 	}
3717 	else if( m_noteEditMode == NoteEditPanning )
3718 	{
3719 		bool ok;
3720 		int new_val;
3721 		new_val = QInputDialog::getInt(	this, "Piano roll: note panning",
3722 					tr( "Please enter a new value between %1 and %2:" ).
3723 							arg( PanningLeft ).arg( PanningRight ),
3724 						(*nv)[0]->getPanning(),
3725 						PanningLeft, PanningRight, 1, &ok );
3726 
3727 		if( ok )
3728 		{
3729 			for ( Note * n : *nv )
3730 			{
3731 				n->setPanning( new_val );
3732 			}
3733 			m_lastNotePanning = new_val;
3734 		}
3735 
3736 	}
3737 }
3738 
3739 
copyToClipboard(const NoteVector & notes) const3740 void PianoRoll::copyToClipboard( const NoteVector & notes ) const
3741 {
3742 	DataFile dataFile( DataFile::ClipboardData );
3743 	QDomElement note_list = dataFile.createElement( "note-list" );
3744 	dataFile.content().appendChild( note_list );
3745 
3746 	MidiTime start_pos( notes.front()->pos().getTact(), 0 );
3747 	for( const Note *note : notes )
3748 	{
3749 		Note clip_note( *note );
3750 		clip_note.setPos( clip_note.pos( start_pos ) );
3751 		clip_note.saveState( dataFile, note_list );
3752 	}
3753 
3754 	QMimeData * clip_content = new QMimeData;
3755 	clip_content->setData( Clipboard::mimeType(), dataFile.toString().toUtf8() );
3756 	QApplication::clipboard()->setMimeData( clip_content,
3757 							QClipboard::Clipboard );
3758 }
3759 
3760 
3761 
3762 
copySelectedNotes()3763 void PianoRoll::copySelectedNotes()
3764 {
3765 	NoteVector selected_notes = getSelectedNotes();
3766 
3767 	if( ! selected_notes.empty() )
3768 	{
3769 		copyToClipboard( selected_notes );
3770 	}
3771 }
3772 
3773 
3774 
3775 
cutSelectedNotes()3776 void PianoRoll::cutSelectedNotes()
3777 {
3778 	if( ! hasValidPattern() )
3779 	{
3780 		return;
3781 	}
3782 
3783 	NoteVector selected_notes = getSelectedNotes();
3784 
3785 	if( ! selected_notes.empty() )
3786 	{
3787 		copyToClipboard( selected_notes );
3788 
3789 		Engine::getSong()->setModified();
3790 
3791 		for( Note *note : selected_notes )
3792 		{
3793 			// note (the memory of it) is also deleted by
3794 			// pattern::removeNote(...) so we don't have to do that
3795 			m_pattern->removeNote( note );
3796 		}
3797 	}
3798 
3799 	update();
3800 	gui->songEditor()->update();
3801 }
3802 
3803 
3804 
3805 
pasteNotes()3806 void PianoRoll::pasteNotes()
3807 {
3808 	if( ! hasValidPattern() )
3809 	{
3810 		return;
3811 	}
3812 
3813 	QString value = QApplication::clipboard()
3814 				->mimeData( QClipboard::Clipboard )
3815 						->data( Clipboard::mimeType() );
3816 
3817 	if( ! value.isEmpty() )
3818 	{
3819 		DataFile dataFile( value.toUtf8() );
3820 
3821 		QDomNodeList list = dataFile.elementsByTagName( Note::classNodeName() );
3822 
3823 		// remove selection and select the newly pasted notes
3824 		clearSelectedNotes();
3825 
3826 		if( ! list.isEmpty() )
3827 		{
3828 			m_pattern->addJournalCheckPoint();
3829 		}
3830 
3831 		for( int i = 0; ! list.item( i ).isNull(); ++i )
3832 		{
3833 			// create the note
3834 			Note cur_note;
3835 			cur_note.restoreState( list.item( i ).toElement() );
3836 			cur_note.setPos( cur_note.pos() + Note::quantized( m_timeLine->pos(), quantization() ) );
3837 
3838 			// select it
3839 			cur_note.setSelected( true );
3840 
3841 			// add to pattern
3842 			m_pattern->addNote( cur_note, false );
3843 		}
3844 
3845 		// we only have to do the following lines if we pasted at
3846 		// least one note...
3847 		Engine::getSong()->setModified();
3848 		update();
3849 		gui->songEditor()->update();
3850 	}
3851 }
3852 
3853 
3854 
3855 
deleteSelectedNotes()3856 void PianoRoll::deleteSelectedNotes()
3857 {
3858 	if( ! hasValidPattern() )
3859 	{
3860 		return;
3861 	}
3862 
3863 	bool update_after_delete = false;
3864 
3865 	m_pattern->addJournalCheckPoint();
3866 
3867 	// get note-vector of current pattern
3868 	const NoteVector & notes = m_pattern->notes();
3869 
3870 	// will be our iterator in the following loop
3871 	NoteVector::ConstIterator it = notes.begin();
3872 	while( it != notes.end() )
3873 	{
3874 		Note *note = *it;
3875 		if( note->selected() )
3876 		{
3877 			// delete this note
3878 			m_pattern->removeNote( note );
3879 			update_after_delete = true;
3880 
3881 			// start over, make sure we get all the notes
3882 			it = notes.begin();
3883 		}
3884 		else
3885 		{
3886 			++it;
3887 		}
3888 	}
3889 
3890 	if( update_after_delete )
3891 	{
3892 		Engine::getSong()->setModified();
3893 		update();
3894 		gui->songEditor()->update();
3895 	}
3896 
3897 }
3898 
3899 
3900 
3901 
autoScroll(const MidiTime & t)3902 void PianoRoll::autoScroll( const MidiTime & t )
3903 {
3904 	const int w = width() - WHITE_KEY_WIDTH;
3905 	if( t > m_currentPosition + w * MidiTime::ticksPerTact() / m_ppt )
3906 	{
3907 		m_leftRightScroll->setValue( t.getTact() * MidiTime::ticksPerTact() );
3908 	}
3909 	else if( t < m_currentPosition )
3910 	{
3911 		MidiTime t2 = qMax( t - w * MidiTime::ticksPerTact() *
3912 					MidiTime::ticksPerTact() / m_ppt, (tick_t) 0 );
3913 		m_leftRightScroll->setValue( t2.getTact() * MidiTime::ticksPerTact() );
3914 	}
3915 	m_scrollBack = false;
3916 }
3917 
3918 
3919 
3920 
updatePosition(const MidiTime & t)3921 void PianoRoll::updatePosition( const MidiTime & t )
3922 {
3923 	if( ( Engine::getSong()->isPlaying()
3924 			&& Engine::getSong()->playMode() == Song::Mode_PlayPattern
3925 			&& m_timeLine->autoScroll() == TimeLineWidget::AutoScrollEnabled
3926 		) || m_scrollBack )
3927 	{
3928 		autoScroll( t );
3929 	}
3930 }
3931 
3932 
3933 
3934 
updatePositionAccompany(const MidiTime & t)3935 void PianoRoll::updatePositionAccompany( const MidiTime & t )
3936 {
3937 	Song * s = Engine::getSong();
3938 
3939 	if( m_recording && hasValidPattern() &&
3940 					s->playMode() != Song::Mode_PlayPattern )
3941 	{
3942 		MidiTime pos = t;
3943 		if( s->playMode() != Song::Mode_PlayBB )
3944 		{
3945 			pos -= m_pattern->startPosition();
3946 		}
3947 		if( (int) pos > 0 )
3948 		{
3949 			s->getPlayPos( Song::Mode_PlayPattern ).setTicks( pos );
3950 			autoScroll( pos );
3951 		}
3952 	}
3953 }
3954 
3955 
3956 
3957 
zoomingChanged()3958 void PianoRoll::zoomingChanged()
3959 {
3960 	m_ppt = m_zoomLevels[m_zoomingModel.value()] * DEFAULT_PR_PPT;
3961 
3962 	assert( m_ppt > 0 );
3963 
3964 	m_timeLine->setPixelsPerTact( m_ppt );
3965 	update();
3966 }
3967 
3968 
3969 
3970 
quantizeChanged()3971 void PianoRoll::quantizeChanged()
3972 {
3973 	update();
3974 }
3975 
3976 
3977 
3978 
quantization() const3979 int PianoRoll::quantization() const
3980 {
3981 	if( m_quantizeModel.value() == 0 )
3982 	{
3983 		if( m_noteLenModel.value() > 0 )
3984 		{
3985 			return newNoteLen();
3986 		}
3987 		else
3988 		{
3989 			return DefaultTicksPerTact / 16;
3990 		}
3991 	}
3992 
3993 	QString text = m_quantizeModel.currentText();
3994 	return DefaultTicksPerTact / text.right( text.length() - 2 ).toInt();
3995 }
3996 
3997 
quantizeNotes()3998 void PianoRoll::quantizeNotes()
3999 {
4000 	if( ! hasValidPattern() )
4001 	{
4002 		return;
4003 	}
4004 
4005 	m_pattern->addJournalCheckPoint();
4006 
4007 	NoteVector notes = getSelectedNotes();
4008 
4009 	if( notes.empty() )
4010 	{
4011 		for( Note* n : m_pattern->notes() )
4012 		{
4013 			notes.push_back( n );
4014 		}
4015 	}
4016 
4017 	for( Note* n : notes )
4018 	{
4019 		if( n->length() == MidiTime( 0 ) )
4020 		{
4021 			continue;
4022 		}
4023 
4024 		Note copy(*n);
4025 		m_pattern->removeNote( n );
4026 		copy.quantizePos( quantization() );
4027 		m_pattern->addNote( copy );
4028 	}
4029 
4030 	update();
4031 	gui->songEditor()->update();
4032 	Engine::getSong()->setModified();
4033 }
4034 
4035 
4036 
4037 
updateSemiToneMarkerMenu()4038 void PianoRoll::updateSemiToneMarkerMenu()
4039 {
4040 	const InstrumentFunctionNoteStacking::ChordTable& chord_table =
4041 			InstrumentFunctionNoteStacking::ChordTable::getInstance();
4042 	const InstrumentFunctionNoteStacking::Chord& scale =
4043 			chord_table.getScaleByName( m_scaleModel.currentText() );
4044 	const InstrumentFunctionNoteStacking::Chord& chord =
4045 			chord_table.getChordByName( m_chordModel.currentText() );
4046 
4047 	emit semiToneMarkerMenuScaleSetEnabled( ! scale.isEmpty() );
4048 	emit semiToneMarkerMenuChordSetEnabled( ! chord.isEmpty() );
4049 }
4050 
4051 
4052 
4053 
newNoteLen() const4054 MidiTime PianoRoll::newNoteLen() const
4055 {
4056 	if( m_noteLenModel.value() == 0 )
4057 	{
4058 		return m_lenOfNewNotes;
4059 	}
4060 
4061 	QString text = m_noteLenModel.currentText();
4062 	return DefaultTicksPerTact / text.right( text.length() - 2 ).toInt();
4063 }
4064 
4065 
4066 
4067 
mouseOverNote()4068 bool PianoRoll::mouseOverNote()
4069 {
4070 	return hasValidPattern() && noteUnderMouse() != NULL;
4071 }
4072 
4073 
4074 
4075 
noteUnderMouse()4076 Note * PianoRoll::noteUnderMouse()
4077 {
4078 	QPoint pos = mapFromGlobal( QCursor::pos() );
4079 
4080 	if( pos.x() <= WHITE_KEY_WIDTH
4081 		|| pos.x() > width() - SCROLLBAR_SIZE
4082 		|| pos.y() < PR_TOP_MARGIN
4083 		|| pos.y() > keyAreaBottom() )
4084 	{
4085 		return NULL;
4086 	}
4087 
4088 	int key_num = getKey( pos.y() );
4089 	int pos_ticks = ( pos.x() - WHITE_KEY_WIDTH ) *
4090 			MidiTime::ticksPerTact() / m_ppt + m_currentPosition;
4091 
4092 	// loop through whole note-vector...
4093 	for( Note* const& note : m_pattern->notes() )
4094 	{
4095 		// and check whether the cursor is over an
4096 		// existing note
4097 		if( pos_ticks >= note->pos()
4098 				&& pos_ticks <= note->endPos()
4099 				&& note->key() == key_num
4100 				&& note->length() > 0 )
4101 		{
4102 			return note;
4103 		}
4104 	}
4105 
4106 	return NULL;
4107 }
4108 
4109 
4110 
4111 
PianoRollWindow()4112 PianoRollWindow::PianoRollWindow() :
4113 	Editor(true),
4114 	m_editor(new PianoRoll())
4115 {
4116 	setCentralWidget( m_editor );
4117 
4118 	m_playAction->setToolTip(tr( "Play/pause current pattern (Space)" ) );
4119 	m_recordAction->setToolTip(tr( "Record notes from MIDI-device/channel-piano" ) );
4120 	m_recordAccompanyAction->setToolTip( tr( "Record notes from MIDI-device/channel-piano while playing song or BB track" ) );
4121 	m_stopAction->setToolTip( tr( "Stop playing of current pattern (Space)" ) );
4122 
4123 	m_playAction->setWhatsThis(
4124 		tr( "Click here to play the current pattern. "
4125 			"This is useful while editing it. The pattern is "
4126 			"automatically looped when its end is reached." ) );
4127 	m_recordAction->setWhatsThis(
4128 		tr( "Click here to record notes from a MIDI-"
4129 			"device or the virtual test-piano of the according "
4130 			"channel-window to the current pattern. When recording "
4131 			"all notes you play will be written to this pattern "
4132 			"and you can play and edit them afterwards." ) );
4133 	m_recordAccompanyAction->setWhatsThis(
4134 		tr( "Click here to record notes from a MIDI-"
4135 			"device or the virtual test-piano of the according "
4136 			"channel-window to the current pattern. When recording "
4137 			"all notes you play will be written to this pattern "
4138 			"and you will hear the song or BB track in the background." ) );
4139 	m_stopAction->setWhatsThis(
4140 		tr( "Click here to stop playback of current pattern." ) );
4141 
4142 	DropToolBar *notesActionsToolBar = addDropToolBarToTop( tr( "Edit actions" ) );
4143 
4144 	// init edit-buttons at the top
4145 	ActionGroup* editModeGroup = new ActionGroup( this );
4146 	QAction* drawAction = editModeGroup->addAction( embed::getIconPixmap( "edit_draw" ), tr( "Draw mode (Shift+D)" ) );
4147 	QAction* eraseAction = editModeGroup->addAction( embed::getIconPixmap( "edit_erase" ), tr("Erase mode (Shift+E)" ) );
4148 	QAction* selectAction = editModeGroup->addAction( embed::getIconPixmap( "edit_select" ), tr( "Select mode (Shift+S)" ) );
4149 	QAction* pitchBendAction = editModeGroup->addAction( embed::getIconPixmap( "automation" ), tr("Pitch Bend mode (Shift+T)" ) );
4150 
4151 	drawAction->setChecked( true );
4152 
4153 	drawAction->setShortcut( Qt::SHIFT | Qt::Key_D );
4154 	eraseAction->setShortcut( Qt::SHIFT | Qt::Key_E );
4155 	selectAction->setShortcut( Qt::SHIFT | Qt::Key_S );
4156 	pitchBendAction->setShortcut( Qt::SHIFT | Qt::Key_T );
4157 
4158 	drawAction->setWhatsThis(
4159 		tr( "Click here and draw mode will be activated. In this "
4160 			"mode you can add, resize and move notes. This "
4161 			"is the default mode which is used most of the time. "
4162 			"You can also press 'Shift+D' on your keyboard to "
4163 			"activate this mode. In this mode, hold %1 to "
4164 			"temporarily go into select mode." ).arg(
4165 				#ifdef LMMS_BUILD_APPLE
4166 				"⌘" ) );
4167 				#else
4168 				"Ctrl" ) );
4169 				#endif
4170 	eraseAction->setWhatsThis(
4171 		tr( "Click here and erase mode will be activated. In this "
4172 			"mode you can erase notes. You can also press "
4173 			"'Shift+E' on your keyboard to activate this mode." ) );
4174 	selectAction->setWhatsThis(
4175 		tr( "Click here and select mode will be activated. "
4176 			"In this mode you can select notes. Alternatively, "
4177 			"you can hold %1 in draw mode to temporarily use "
4178 			"select mode." ).arg(
4179 				#ifdef LMMS_BUILD_APPLE
4180 				"⌘" ) );
4181 				#else
4182 				"Ctrl" ) );
4183 				#endif
4184 	pitchBendAction->setWhatsThis(
4185 		tr( "Click here and Pitch Bend mode will be activated. "
4186 			"In this mode you can click a note to open its "
4187 			"automation detuning. You can utilize this to slide "
4188 			"notes from one to another. You can also press "
4189 			"'Shift+T' on your keyboard to activate this mode." ) );
4190 
4191 	connect( editModeGroup, SIGNAL( triggered( int ) ), m_editor, SLOT( setEditMode( int ) ) );
4192 
4193 	QAction* quantizeAction = new QAction(embed::getIconPixmap( "quantize" ), tr( "Quantize" ), this );
4194 	connect( quantizeAction, SIGNAL( triggered() ), m_editor, SLOT( quantizeNotes() ) );
4195 
4196 	notesActionsToolBar->addAction( drawAction );
4197 	notesActionsToolBar->addAction( eraseAction );
4198 	notesActionsToolBar->addAction( selectAction );
4199 	notesActionsToolBar->addAction( pitchBendAction );
4200 	notesActionsToolBar->addSeparator();
4201 	notesActionsToolBar->addAction( quantizeAction );
4202 
4203 	// Copy + paste actions
4204 	DropToolBar *copyPasteActionsToolBar =  addDropToolBarToTop( tr( "Copy paste controls" ) );
4205 
4206 	QAction* cutAction = new QAction(embed::getIconPixmap( "edit_cut" ),
4207 							  tr( "Cut selected notes (%1+X)" ).arg(
4208 									#ifdef LMMS_BUILD_APPLE
4209 									"⌘" ), this );
4210 									#else
4211 									"Ctrl" ), this );
4212 									#endif
4213 
4214 	QAction* copyAction = new QAction(embed::getIconPixmap( "edit_copy" ),
4215 							   tr( "Copy selected notes (%1+C)" ).arg(
4216 	 								#ifdef LMMS_BUILD_APPLE
4217 	 								"⌘"), this);
4218 	 								#else
4219 									"Ctrl" ), this );
4220 	 								#endif
4221 
4222 	QAction* pasteAction = new QAction(embed::getIconPixmap( "edit_paste" ),
4223 					tr( "Paste notes from clipboard (%1+V)" ).arg(
4224 						#ifdef LMMS_BUILD_APPLE
4225 						"⌘" ), this );
4226 						#else
4227 						"Ctrl" ), this );
4228 						#endif
4229 
4230 	cutAction->setWhatsThis(
4231 		tr( "Click here and the selected notes will be cut into the "
4232 			"clipboard. You can paste them anywhere in any pattern "
4233 			"by clicking on the paste button." ) );
4234 	copyAction->setWhatsThis(
4235 		tr( "Click here and the selected notes will be copied into the "
4236 			"clipboard. You can paste them anywhere in any pattern "
4237 			"by clicking on the paste button." ) );
4238 	pasteAction->setWhatsThis(
4239 		tr( "Click here and the notes from the clipboard will be "
4240 			"pasted at the first visible measure." ) );
4241 
4242 	cutAction->setShortcut( Qt::CTRL | Qt::Key_X );
4243 	copyAction->setShortcut( Qt::CTRL | Qt::Key_C );
4244 	pasteAction->setShortcut( Qt::CTRL | Qt::Key_V );
4245 
4246 	connect( cutAction, SIGNAL( triggered() ), m_editor, SLOT( cutSelectedNotes() ) );
4247 	connect( copyAction, SIGNAL( triggered() ), m_editor, SLOT( copySelectedNotes() ) );
4248 	connect( pasteAction, SIGNAL( triggered() ), m_editor, SLOT( pasteNotes() ) );
4249 
4250 	copyPasteActionsToolBar->addAction( cutAction );
4251 	copyPasteActionsToolBar->addAction( copyAction );
4252 	copyPasteActionsToolBar->addAction( pasteAction );
4253 
4254 
4255 	DropToolBar *timeLineToolBar = addDropToolBarToTop( tr( "Timeline controls" ) );
4256 	m_editor->m_timeLine->addToolButtons( timeLineToolBar );
4257 
4258 
4259 	addToolBarBreak();
4260 
4261 
4262 	DropToolBar *zoomAndNotesToolBar = addDropToolBarToTop( tr( "Zoom and note controls" ) );
4263 
4264 	QLabel * zoom_lbl = new QLabel( m_toolBar );
4265 	zoom_lbl->setPixmap( embed::getIconPixmap( "zoom" ) );
4266 
4267 	m_zoomingComboBox = new ComboBox( m_toolBar );
4268 	m_zoomingComboBox->setModel( &m_editor->m_zoomingModel );
4269 	m_zoomingComboBox->setFixedSize( 64, 22 );
4270 
4271 	// setup quantize-stuff
4272 	QLabel * quantize_lbl = new QLabel( m_toolBar );
4273 	quantize_lbl->setPixmap( embed::getIconPixmap( "quantize" ) );
4274 
4275 	m_quantizeComboBox = new ComboBox( m_toolBar );
4276 	m_quantizeComboBox->setModel( &m_editor->m_quantizeModel );
4277 	m_quantizeComboBox->setFixedSize( 64, 22 );
4278 
4279 	// setup note-len-stuff
4280 	QLabel * note_len_lbl = new QLabel( m_toolBar );
4281 	note_len_lbl->setPixmap( embed::getIconPixmap( "note" ) );
4282 
4283 	m_noteLenComboBox = new ComboBox( m_toolBar );
4284 	m_noteLenComboBox->setModel( &m_editor->m_noteLenModel );
4285 	m_noteLenComboBox->setFixedSize( 105, 22 );
4286 
4287 	// setup scale-stuff
4288 	QLabel * scale_lbl = new QLabel( m_toolBar );
4289 	scale_lbl->setPixmap( embed::getIconPixmap( "scale" ) );
4290 
4291 	m_scaleComboBox = new ComboBox( m_toolBar );
4292 	m_scaleComboBox->setModel( &m_editor->m_scaleModel );
4293 	m_scaleComboBox->setFixedSize( 105, 22 );
4294 
4295 	// setup chord-stuff
4296 	QLabel * chord_lbl = new QLabel( m_toolBar );
4297 	chord_lbl->setPixmap( embed::getIconPixmap( "chord" ) );
4298 
4299 	m_chordComboBox = new ComboBox( m_toolBar );
4300 	m_chordComboBox->setModel( &m_editor->m_chordModel );
4301 	m_chordComboBox->setFixedSize( 105, 22 );
4302 
4303 
4304 	zoomAndNotesToolBar->addWidget( zoom_lbl );
4305 	zoomAndNotesToolBar->addWidget( m_zoomingComboBox );
4306 
4307 	zoomAndNotesToolBar->addSeparator();
4308 	zoomAndNotesToolBar->addWidget( quantize_lbl );
4309 	zoomAndNotesToolBar->addWidget( m_quantizeComboBox );
4310 
4311 	zoomAndNotesToolBar->addSeparator();
4312 	zoomAndNotesToolBar->addWidget( note_len_lbl );
4313 	zoomAndNotesToolBar->addWidget( m_noteLenComboBox );
4314 
4315 	zoomAndNotesToolBar->addSeparator();
4316 	zoomAndNotesToolBar->addWidget( scale_lbl );
4317 	zoomAndNotesToolBar->addWidget( m_scaleComboBox );
4318 
4319 	zoomAndNotesToolBar->addSeparator();
4320 	zoomAndNotesToolBar->addWidget( chord_lbl );
4321 	zoomAndNotesToolBar->addWidget( m_chordComboBox );
4322 
4323 	m_zoomingComboBox->setWhatsThis(
4324 				tr(
4325 					"This controls the magnification of an axis. "
4326 					"It can be helpful to choose magnification for a specific "
4327 					"task. For ordinary editing, the magnification should be "
4328 					"fitted to your smallest notes. "
4329 					) );
4330 
4331 	m_quantizeComboBox->setWhatsThis(
4332 				tr(
4333 					"The 'Q' stands for quantization, and controls the grid size "
4334 					"notes and control points snap to. "
4335 					"With smaller quantization values, you can draw shorter notes "
4336 					"in Piano Roll, and more exact control points in the "
4337 					"Automation Editor."
4338 
4339 					) );
4340 
4341 	m_noteLenComboBox->setWhatsThis(
4342 				tr(
4343 					"This lets you select the length of new notes. "
4344 					"'Last Note' means that LMMS will use the note length of "
4345 					"the note you last edited"
4346 					) );
4347 
4348 	m_scaleComboBox->setWhatsThis(
4349 				tr(
4350 					"The feature is directly connected to the context-menu "
4351 					"on the virtual keyboard, to the left in Piano Roll. "
4352 					"After you have chosen the scale you want "
4353 					"in this drop-down menu, "
4354 					"you can right click on a desired key in the virtual keyboard, "
4355 					"and then choose 'Mark current Scale'. "
4356 					"LMMS will highlight all notes that belongs to the chosen scale, "
4357 					"and in the key you have selected!"
4358 					) );
4359 
4360 	m_chordComboBox->setWhatsThis(
4361 				tr(
4362 					"Let you select a chord which LMMS then can draw or highlight."
4363 					"You can find the most common chords in this drop-down menu. "
4364 					"After you have selected a chord, click anywhere to place the chord, and right "
4365 					"click on the virtual keyboard to open context menu and highlight the chord. "
4366 					"To return to single note placement, you need to choose 'No chord' "
4367 					"in this drop-down menu."
4368 					) );
4369 
4370 	// setup our actual window
4371 	setFocusPolicy( Qt::StrongFocus );
4372 	setFocus();
4373 	setWindowIcon( embed::getIconPixmap( "piano" ) );
4374 	setCurrentPattern( NULL );
4375 
4376 	// Connections
4377 	connect( m_editor, SIGNAL( currentPatternChanged() ), this, SIGNAL( currentPatternChanged() ) );
4378 	connect( m_editor, SIGNAL( currentPatternChanged() ), this, SLOT( patternRenamed() ) );
4379 }
4380 
4381 
4382 
4383 
currentPattern() const4384 const Pattern* PianoRollWindow::currentPattern() const
4385 {
4386 	return m_editor->currentPattern();
4387 }
4388 
4389 
4390 
4391 
setCurrentPattern(Pattern * pattern)4392 void PianoRollWindow::setCurrentPattern( Pattern* pattern )
4393 {
4394 	m_editor->setCurrentPattern( pattern );
4395 
4396 	if ( pattern )
4397 	{
4398 		setWindowTitle( tr( "Piano-Roll - %1" ).arg( pattern->name() ) );
4399 		connect( pattern->instrumentTrack(), SIGNAL( nameChanged() ), this, SLOT( patternRenamed()) );
4400 		connect( pattern, SIGNAL( dataChanged() ), this, SLOT( patternRenamed() ) );
4401 	}
4402 	else
4403 	{
4404 		setWindowTitle( tr( "Piano-Roll - no pattern" ) );
4405 	}
4406 }
4407 
4408 
4409 
4410 
isRecording() const4411 bool PianoRollWindow::isRecording() const
4412 {
4413 	return m_editor->isRecording();
4414 }
4415 
4416 
4417 
4418 
quantization() const4419 int PianoRollWindow::quantization() const
4420 {
4421 	return m_editor->quantization();
4422 }
4423 
4424 
4425 
4426 
play()4427 void PianoRollWindow::play()
4428 {
4429 	m_editor->play();
4430 }
4431 
4432 
4433 
4434 
stop()4435 void PianoRollWindow::stop()
4436 {
4437 	m_editor->stop();
4438 }
4439 
4440 
4441 
4442 
record()4443 void PianoRollWindow::record()
4444 {
4445 	m_editor->record();
4446 }
4447 
4448 
4449 
4450 
recordAccompany()4451 void PianoRollWindow::recordAccompany()
4452 {
4453 	m_editor->recordAccompany();
4454 }
4455 
4456 
4457 
4458 
stopRecording()4459 void PianoRollWindow::stopRecording()
4460 {
4461 	m_editor->stopRecording();
4462 }
4463 
4464 
4465 
4466 
reset()4467 void PianoRollWindow::reset()
4468 {
4469 	m_editor->reset();
4470 }
4471 
4472 
4473 
4474 
saveSettings(QDomDocument & doc,QDomElement & de)4475 void PianoRollWindow::saveSettings( QDomDocument & doc, QDomElement & de )
4476 {
4477 	MainWindow::saveWidgetState( this, de );
4478 }
4479 
4480 
4481 
4482 
loadSettings(const QDomElement & de)4483 void PianoRollWindow::loadSettings( const QDomElement & de )
4484 {
4485 	MainWindow::restoreWidgetState( this, de );
4486 }
4487 
4488 
4489 
4490 
sizeHint() const4491 QSize PianoRollWindow::sizeHint() const
4492 {
4493 	return { INITIAL_PIANOROLL_WIDTH, INITIAL_PIANOROLL_HEIGHT };
4494 }
4495 
4496 
4497 
4498 
patternRenamed()4499 void PianoRollWindow::patternRenamed()
4500 {
4501 	if ( currentPattern() )
4502 	{
4503 		setWindowTitle( tr( "Piano-Roll - %1" ).arg( currentPattern()->name() ) );
4504 	}
4505 	else
4506 	{
4507 		setWindowTitle( tr( "Piano-Roll - no pattern" ) );
4508 	}
4509 }
4510 
4511 
4512 
4513 
focusInEvent(QFocusEvent * event)4514 void PianoRollWindow::focusInEvent( QFocusEvent * event )
4515 {
4516 	// when the window is given focus, also give focus to the actual piano roll
4517 	m_editor->setFocus( event->reason() );
4518 }
4519