1 /*
2  * PianoView.cpp - implementation of piano-widget used in instrument-track-window
3  *             for testing + according model class
4  *
5  * Copyright (c) 2004-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
6  *
7  * This file is part of LMMS - https://lmms.io
8  *
9  * This program is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU General Public
11  * License as published by the Free Software Foundation; either
12  * version 2 of the License, or (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17  * General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public
20  * License along with this program (see COPYING); if not, write to the
21  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
22  * Boston, MA 02110-1301 USA.
23  *
24  */
25 
26 /** \file piano.cpp
27  *  \brief A piano keyboard to play notes on in the instrument plugin window.
28  */
29 
30 /*
31  * \mainpage Instrument plugin keyboard display classes
32  *
33  * \section introduction Introduction
34  *
35  * \todo fill this out
36  * \todo write isWhite inline function and replace throughout
37  */
38 
39 #include <cmath>
40 
41 #include <QCursor>
42 #include <QKeyEvent>
43 #include <QPainter>
44 #include <QVBoxLayout>
45 
46 #include "PianoView.h"
47 #include "Piano.h"
48 #include "CaptionMenu.h"
49 #include "embed.h"
50 #include "gui_templates.h"
51 #include "InstrumentTrack.h"
52 #include "Knob.h"
53 #include "StringPairDrag.h"
54 #include "MainWindow.h"
55 
56 
57 /*! The scale of C Major - white keys only.
58  */
59 Keys WhiteKeys[] =
60 {
61 	Key_C, Key_D, Key_E, Key_F, Key_G, Key_A, Key_H
62 } ;
63 
64 
65 QPixmap * PianoView::s_whiteKeyPm = NULL;           /*!< A white key released */
66 QPixmap * PianoView::s_blackKeyPm = NULL;           /*!< A black key released */
67 QPixmap * PianoView::s_whiteKeyPressedPm = NULL;    /*!< A white key pressed */
68 QPixmap * PianoView::s_blackKeyPressedPm = NULL;    /*!< A black key pressed */
69 
70 
71 const int PIANO_BASE = 11;          /*!< The height of the root note display */
72 const int PW_WHITE_KEY_WIDTH = 10;  /*!< The width of a white key */
73 const int PW_BLACK_KEY_WIDTH = 8;   /*!< The width of a black key */
74 const int PW_WHITE_KEY_HEIGHT = 57; /*!< The height of a white key */
75 const int PW_BLACK_KEY_HEIGHT = 38; /*!< The height of a black key */
76 const int LABEL_TEXT_SIZE = 7;      /*!< The height of the key label text */
77 
78 
79 
80 
81 /*! \brief Create a new keyboard display view
82  *
83  *  \param _parent the parent instrument plugin window
84  *  \todo are the descriptions of the m_startkey and m_lastkey properties correct?
85  */
PianoView(QWidget * _parent)86 PianoView::PianoView( QWidget * _parent ) :
87 	QWidget( _parent ),             /*!< Our parent */
88 	ModelView( NULL, this ),        /*!< Our view Model */
89 	m_piano( NULL ),                /*!< Our piano Model */
90 	m_startKey( Key_C + Octave_3*KeysPerOctave ), /*!< The first key displayed? */
91 	m_lastKey( -1 )                 /*!< The last key displayed? */
92 {
93 	if( s_whiteKeyPm == NULL )
94 	{
95 		s_whiteKeyPm = new QPixmap( embed::getIconPixmap( "white_key" ) );
96 	}
97 	if( s_blackKeyPm == NULL )
98 	{
99 		s_blackKeyPm = new QPixmap( embed::getIconPixmap( "black_key" ) );
100 	}
101 	if( s_whiteKeyPressedPm == NULL )
102 	{
103 		s_whiteKeyPressedPm = new QPixmap( embed::getIconPixmap( "white_key_pressed" ) );
104 	}
105 	if ( s_blackKeyPressedPm == NULL )
106 	{
107 		s_blackKeyPressedPm = new QPixmap( embed::getIconPixmap( "black_key_pressed" ) );
108 	}
109 
110 	setAttribute( Qt::WA_OpaquePaintEvent, true );
111 	setFocusPolicy( Qt::StrongFocus );
112 	setMaximumWidth( WhiteKeysPerOctave * NumOctaves * PW_WHITE_KEY_WIDTH );
113 
114 	// create scrollbar at the bottom
115 	m_pianoScroll = new QScrollBar( Qt::Horizontal, this );
116 	m_pianoScroll->setSingleStep( 1 );
117 	m_pianoScroll->setPageStep( 20 );
118 	m_pianoScroll->setValue( Octave_3 * WhiteKeysPerOctave );
119 
120 	// and connect it to this widget
121 	connect( m_pianoScroll, SIGNAL( valueChanged( int ) ),
122 			this, SLOT( pianoScrolled( int ) ) );
123 
124 	// create a layout for ourselves
125 	QVBoxLayout * layout = new QVBoxLayout( this );
126 	layout->setSpacing( 0 );
127 	layout->setMargin( 0 );
128 	layout->addSpacing( PIANO_BASE+PW_WHITE_KEY_HEIGHT );
129 	layout->addWidget( m_pianoScroll );
130 
131 }
132 
133 
134 
135 
136 /*! \brief Destroy this piano display view
137  *
138  */
~PianoView()139 PianoView::~PianoView()
140 {
141 }
142 
143 
144 
145 
146 /*! \brief Map a keyboard key being pressed to a note in our keyboard view
147  *
148  *  \param _k The keyboard scan code of the key being pressed.
149  *  \todo check the scan codes for ',' = c, 'L' = c#, '.' = d, ':' = d#,
150  *     '/' = d, '[' = f', '=' = f'#, ']' = g' - Paul's additions
151  */
getKeyFromKeyEvent(QKeyEvent * _ke)152 int PianoView::getKeyFromKeyEvent( QKeyEvent * _ke )
153 {
154 #ifdef LMMS_BUILD_APPLE
155 	const int k = _ke->nativeVirtualKey();
156 #else
157 	const int k = _ke->nativeScanCode();
158 #endif
159 
160 #ifdef LMMS_BUILD_WIN32
161 	switch( k )
162 	{
163 		case 44: return 0; // Z  = C
164 		case 31: return 1; // S  = C#
165 		case 45: return 2; // X  = D
166 		case 32: return 3; // D  = D#
167 		case 46: return 4; // C  = E
168 		case 47: return 5; // V  = F
169 		case 34: return 6; // G  = F#
170 		case 48: return 7; // B  = G
171 		case 35: return 8; // H  = G#
172 		case 49: return 9; // N  = A
173 		case 36: return 10; // J = A#
174 		case 50: return 11; // M = B
175 		case 51: return 12; // , = c
176 		case 38: return 13; // L = c#
177 		case 52: return 14; // . = d
178 		case 39: return 15; // ; = d#
179 		//case 86: return 16; // / = e
180 		case 53: return 16; // / = e
181 		case 16: return 12; // Q = c
182 		case 3: return 13; // 2 = c#
183 		case 17: return 14; // W = d
184 		case 4: return 15; // 3 = d#
185 		case 18: return 16; // E = e
186 		case 19: return 17; // R = f
187 		case 6: return 18; // 5 = f#
188 		case 20: return 19; // T = g
189 		case 7: return 20; // 6 = g#
190 		case 21: return 21; // Y = a
191 		case 8: return 22; // 7 = a#
192 		case 22: return 23; // U = b
193 		case 23: return 24; // I = c'
194 		case 10: return 25; // 9 = c'#
195 		case 24: return 26; // O = d'
196 		case 11: return 27; // 0 = d'#
197 		case 25: return 28; // P = e'
198 		case 26: return 29; // [
199 		case 13: return 30; // =
200 		case 27: return 31; // ]
201 	}
202 #endif
203 #if defined(LMMS_BUILD_LINUX) || defined(LMMS_BUILD_OPENBSD) || defined(LMMS_BUILD_FREEBSD)
204 	switch( k )
205 	{
206 		case 52: return 0; // Z  = C
207 		case 39: return 1; // S  = C#
208 		case 53: return 2; // X  = D
209 		case 40: return 3; // D  = D#
210 		case 54: return 4; // C  = E
211 		case 55: return 5; // V  = F
212 		case 42: return 6; // G  = F#
213 		case 56: return 7; // B  = G
214 		case 43: return 8; // H  = G#
215 		case 57: return 9; // N  = A
216 		case 44: return 10; // J = A#
217 		case 58: return 11; // M = B
218 		case 59: return 12; // , = c
219 		case 46: return 13; // L = c#
220 		case 60: return 14; // . = d
221 		case 47: return 15; // ; = d#
222 		case 61: return 16; // / = e
223 		case 24: return 12; // Q = c
224 		case 11: return 13; // 2 = c#
225 		case 25: return 14; // W = d
226 		case 12: return 15; // 3 = d#
227 		case 26: return 16; // E = e
228 		case 27: return 17; // R = f
229 		case 14: return 18; // 5 = f#
230 		case 28: return 19; // T = g
231 		case 15: return 20; // 6 = g#
232 		case 29: return 21; // Y = a
233 		case 16: return 22; // 7 = a#
234 		case 30: return 23; // U = b
235 		case 31: return 24; // I = c'
236 		case 18: return 25; // 9 = c'#
237 		case 32: return 26; // O = d'
238 		case 19: return 27; // 0 = d'#
239 		case 33: return 28; // P = e'
240 		case 34: return 29; // [
241 		case 21: return 30; // =
242 		case 35: return 31; // ]
243 	}
244 #endif
245 #ifdef LMMS_BUILD_APPLE
246 	switch( k )
247 	{
248 		case 6: return 0; // Z  = C
249 		case 1: return 1; // S  = C#
250 		case 7: return 2; // X  = D
251 		case 2: return 3; // D  = D#
252 		case 8: return 4; // C  = E
253 		case 9: return 5; // V  = F
254 		case 5: return 6; // G  = F#
255 		case 11: return 7; // B  = G
256 		case 4: return 8; // H  = G#
257 		case 45: return 9; // N  = A
258 		case 38: return 10; // J = A#
259 		case 46: return 11; // M = B
260 		case 43: return 12; // , = c
261 		case 37: return 13; // L = c#
262 		case 47: return 14; // . = d
263 		case 41: return 15; // ; = d#
264 		case 44: return 16; // / = e
265 		case 12: return 12; // Q = c
266 		case 19: return 13; // 2 = c#
267 		case 13: return 14; // W = d
268 		case 20: return 15; // 3 = d#
269 		case 14: return 16; // E = e
270 		case 15: return 17; // R = f
271 		case 23: return 18; // 5 = f#
272 		case 17: return 19; // T = g
273 		case 22: return 20; // 6 = g#
274 		case 16: return 21; // Y = a
275 		case 26: return 22; // 7 = a#
276 		case 32: return 23; // U = b
277 		case 34: return 24; // I = c'
278 		case 25: return 25; // 9 = c'#
279 		case 31: return 26; // O = d'
280 		case 29: return 27; // 0 = d'#
281 		case 35: return 28; // P = e'
282 	}
283 #endif
284 
285 	return -100;
286 }
287 
288 
289 
290 
291 /*! \brief Register a change to this piano display view
292  *
293  */
modelChanged()294 void PianoView::modelChanged()
295 {
296 	m_piano = castModel<Piano>();
297 	if( m_piano != NULL )
298 	{
299 		connect( m_piano->instrumentTrack()->baseNoteModel(), SIGNAL( dataChanged() ),
300 					this, SLOT( update() ) );
301 	}
302 
303 }
304 
305 
306 
307 
308 // gets the key from the given mouse-position
309 /*! \brief Get the key from the mouse position in the piano display
310  *
311  *  First we determine it roughly by the position of the point given in
312  *  white key widths from our start.  We then add in any black keys that
313  *  might have been skipped over (they take a key number, but no 'white
314  *  key' space).  We then add in our starting key number.
315  *
316  *  We then determine whether it was a black key that was pressed by
317  *  checking whether it was within the vertical range of black keys.
318  *  Black keys sit exactly between white keys on this keyboard, so
319  *  we then shift the note down or up if we were in the left or right
320  *  half of the white note.  We only do this, of course, if the white
321  *  note has a black key on that side, so to speak.
322  *
323  *  This function returns const because there is a linear mapping from
324  *  the point given to the key returned that never changes.
325  *
326  *  \param _p The point that the mouse was pressed.
327  */
getKeyFromMouse(const QPoint & _p) const328 int PianoView::getKeyFromMouse( const QPoint & _p ) const
329 {
330 	int offset = _p.x() % PW_WHITE_KEY_WIDTH;
331 	if( offset < 0 ) offset += PW_WHITE_KEY_WIDTH;
332 	int key_num = ( _p.x() - offset) / PW_WHITE_KEY_WIDTH;
333 
334 	for( int i = 0; i <= key_num; ++i )
335 	{
336 		if ( Piano::isBlackKey( m_startKey+i ) )
337 		{
338 			++key_num;
339 		}
340 	}
341 	for( int i = 0; i >= key_num; --i )
342 	{
343 		if ( Piano::isBlackKey( m_startKey+i ) )
344 		{
345 			--key_num;
346 		}
347 	}
348 
349 	key_num += m_startKey;
350 
351 	// is it a black key?
352 	if( _p.y() < PIANO_BASE + PW_BLACK_KEY_HEIGHT )
353 	{
354 		// then do extra checking whether the mouse-cursor is over
355 		// a black key
356 		if( key_num > 0 && Piano::isBlackKey( key_num-1 ) &&
357 			offset <= ( PW_WHITE_KEY_WIDTH / 2 ) -
358 					( PW_BLACK_KEY_WIDTH / 2 ) )
359 		{
360 			--key_num;
361 		}
362 		if( key_num < NumKeys - 1 && Piano::isBlackKey( key_num+1 ) &&
363 			offset >= ( PW_WHITE_KEY_WIDTH -
364 					PW_BLACK_KEY_WIDTH / 2 ) )
365 		{
366 			++key_num;
367 		}
368 	}
369 
370 	// some range-checking-stuff
371 	return tLimit( key_num, 0, NumKeys - 1 );
372 }
373 
374 
375 
376 
377 // handler for scrolling-event
378 /*! \brief Handle the scrolling on the piano display view
379  *
380  *  We need to update our start key position based on the new position.
381  *
382  *  \param _new_pos the new key position.
383  */
pianoScrolled(int _new_pos)384 void PianoView::pianoScrolled( int _new_pos )
385 {
386 	m_startKey = WhiteKeys[_new_pos % WhiteKeysPerOctave]+
387 			( _new_pos / WhiteKeysPerOctave ) * KeysPerOctave;
388 
389 	update();
390 }
391 
392 
393 
394 
395 /*! \brief Handle a context menu selection on the piano display view
396  *
397  *  \param _me the ContextMenuEvent to handle.
398  *  \todo Is this right, or does this create the context menu?
399  */
contextMenuEvent(QContextMenuEvent * _me)400 void PianoView::contextMenuEvent( QContextMenuEvent * _me )
401 {
402 	if( _me->pos().y() > PIANO_BASE || m_piano == NULL )
403 	{
404 		QWidget::contextMenuEvent( _me );
405 		return;
406 	}
407 
408 	CaptionMenu contextMenu( tr( "Base note" ) );
409 	AutomatableModelView amv( m_piano->instrumentTrack()->baseNoteModel(), &contextMenu );
410 	amv.addDefaultActions( &contextMenu );
411 	contextMenu.exec( QCursor::pos() );
412 }
413 
414 
415 
416 
417 // handler for mouse-click-event
418 /*! \brief Handle a mouse click on this piano display view
419  *
420  *  We first determine the key number using the getKeyFromMouse() method.
421  *
422  *  If we're below the 'root key selection' area,
423  *  we set the volume of the note to be proportional to the vertical
424  *  position on the keyboard - lower down the key is louder, within the
425  *  boundaries of the (white or black) key pressed.  We then tell the
426  *  instrument to play that note, scaling for MIDI max loudness = 127.
427  *
428  *  If we're in the 'root key selection' area, of course, we set the
429  *  root key to be that key.
430  *
431  *  We finally update ourselves to show the key press
432  *
433  *  \param _me the mouse click to handle.
434  */
mousePressEvent(QMouseEvent * _me)435 void PianoView::mousePressEvent( QMouseEvent * _me )
436 {
437 	if( _me->button() == Qt::LeftButton && m_piano != NULL )
438 	{
439 		// get pressed key
440 		int key_num = getKeyFromMouse( _me->pos() );
441 		if( _me->pos().y() > PIANO_BASE )
442 		{
443 			int y_diff = _me->pos().y() - PIANO_BASE;
444 			int velocity = (int)( ( float ) y_diff /
445 				( Piano::isWhiteKey( key_num )  ?
446 				PW_WHITE_KEY_HEIGHT : PW_BLACK_KEY_HEIGHT ) *
447 						(float) m_piano->instrumentTrack()->midiPort()->baseVelocity() );
448 			if( y_diff < 0 )
449 			{
450 				velocity = 0;
451 			}
452 			else if( y_diff >
453 				( Piano::isWhiteKey( key_num ) ?
454 				PW_WHITE_KEY_HEIGHT : PW_BLACK_KEY_HEIGHT ) )
455 			{
456 				velocity = m_piano->instrumentTrack()->midiPort()->baseVelocity();
457 			}
458 			// set note on
459 			m_piano->midiEventProcessor()->processInEvent( MidiEvent( MidiNoteOn, -1, key_num, velocity ) );
460 			m_piano->setKeyState( key_num, true );
461 			m_lastKey = key_num;
462 
463 			emit keyPressed( key_num );
464 		}
465 		else
466 		{
467 			if( _me->modifiers() & Qt::ControlModifier )
468 			{
469 				new StringPairDrag( "automatable_model",
470 					QString::number( m_piano->instrumentTrack()->baseNoteModel()->id() ),
471 					QPixmap(), this );
472 				_me->accept();
473 			}
474 			else
475 			{
476 				m_piano->instrumentTrack()->baseNoteModel()->setInitValue( (float) key_num );
477 
478 				emit baseNoteChanged();
479 			}
480 		}
481 
482 		// and let the user see that he pressed a key... :)
483 		update();
484 	}
485 }
486 
487 
488 
489 
490 // handler for mouse-release-event
491 /*! \brief Handle a mouse release event on the piano display view
492  *
493  *  If a key was pressed by the in the mousePressEvent() function, we
494  *  turn the note off.
495  *
496  *  \param _me the mousePressEvent to handle.
497  */
mouseReleaseEvent(QMouseEvent *)498 void PianoView::mouseReleaseEvent( QMouseEvent * )
499 {
500 	if( m_lastKey != -1 )
501 	{
502 		if( m_piano != NULL )
503 		{
504 			m_piano->midiEventProcessor()->processInEvent( MidiEvent( MidiNoteOff, -1, m_lastKey, 0 ) );
505 			m_piano->setKeyState( m_lastKey, false );
506 		}
507 
508 		// and let the user see that he released a key... :)
509 		update();
510 
511 		m_lastKey = -1;
512 	}
513 }
514 
515 
516 
517 
518 // handler for mouse-move-event
519 /*! \brief Handle a mouse move event on the piano display view
520  *
521  *  This handles the user dragging the mouse across the keys.  It uses
522  *  code from mousePressEvent() and mouseReleaseEvent(), also correcting
523  *  for if the mouse movement has stayed within one key and if the mouse
524  *  has moved outside the vertical area of the keyboard (which is still
525  *  allowed but won't make the volume go up to 11).
526  *
527  *  \param _me the ContextMenuEvent to handle.
528  *  \todo Paul Wayper thinks that this code should be refactored to
529  *  reduce or remove the duplication between this, the mousePressEvent()
530  *  and mouseReleaseEvent() methods.
531  */
mouseMoveEvent(QMouseEvent * _me)532 void PianoView::mouseMoveEvent( QMouseEvent * _me )
533 {
534 	if( m_piano == NULL )
535 	{
536 		return;
537 	}
538 
539 	int key_num = getKeyFromMouse( _me->pos() );
540 	int y_diff = _me->pos().y() - PIANO_BASE;
541 	int velocity = (int)( (float) y_diff /
542 		( Piano::isWhiteKey( key_num ) ?
543 			PW_WHITE_KEY_HEIGHT : PW_BLACK_KEY_HEIGHT ) *
544 						(float) m_piano->instrumentTrack()->midiPort()->baseVelocity() );
545 	// maybe the user moved the mouse-cursor above or under the
546 	// piano-widget while holding left button so check that and
547 	// correct volume if necessary
548 	if( y_diff < 0 )
549 	{
550 		velocity = 0;
551 	}
552 	else if( y_diff >
553 		( Piano::isWhiteKey( key_num ) ?
554 				PW_WHITE_KEY_HEIGHT : PW_BLACK_KEY_HEIGHT ) )
555 	{
556 		velocity = m_piano->instrumentTrack()->midiPort()->baseVelocity();
557 	}
558 
559 	// is the calculated key different from current key? (could be the
560 	// user just moved the cursor one pixel left but on the same key)
561 	if( key_num != m_lastKey )
562 	{
563 		if( m_lastKey != -1 )
564 		{
565 			m_piano->midiEventProcessor()->processInEvent( MidiEvent( MidiNoteOff, -1, m_lastKey, 0 ) );
566 			m_piano->setKeyState( m_lastKey, false );
567 			m_lastKey = -1;
568 		}
569 		if( _me->buttons() & Qt::LeftButton )
570 		{
571 			if( _me->pos().y() > PIANO_BASE )
572 			{
573 				m_piano->midiEventProcessor()->processInEvent( MidiEvent( MidiNoteOn, -1, key_num, velocity ) );
574 				m_piano->setKeyState( key_num, true );
575 				m_lastKey = key_num;
576 			}
577 			else
578 			{
579 				m_piano->instrumentTrack()->baseNoteModel()->setInitValue( (float) key_num );
580 			}
581 		}
582 		// and let the user see that he pressed a key... :)
583 		update();
584 	}
585 	else if( m_piano->isKeyPressed( key_num ) )
586 	{
587 		m_piano->midiEventProcessor()->processInEvent( MidiEvent( MidiKeyPressure, -1, key_num, velocity ) );
588 	}
589 
590 }
591 
592 
593 
594 
595 /*! \brief Handle a key press event on the piano display view
596  *
597  *  We determine our key number from the getKeyFromKeyEvent() method,
598  *  and pass the event on to the piano's handleKeyPress() method if
599  *  auto-repeat is off.
600  *
601  *  \param _ke the KeyEvent to handle.
602  */
keyPressEvent(QKeyEvent * _ke)603 void PianoView::keyPressEvent( QKeyEvent * _ke )
604 {
605 	const int key_num = getKeyFromKeyEvent( _ke ) +
606 				( DefaultOctave - 1 ) * KeysPerOctave;
607 
608 	if( _ke->isAutoRepeat() == false && key_num > -1 )
609 	{
610 		if( m_piano != NULL )
611 		{
612 			m_piano->handleKeyPress( key_num );
613 			_ke->accept();
614 			update();
615 		}
616 	}
617 	else
618 	{
619 		_ke->ignore();
620 	}
621 }
622 
623 
624 
625 
626 /*! \brief Handle a key release event on the piano display view
627  *
628  *  The same logic as the keyPressEvent() method.
629  *
630  *  \param _ke the KeyEvent to handle.
631  */
keyReleaseEvent(QKeyEvent * _ke)632 void PianoView::keyReleaseEvent( QKeyEvent * _ke )
633 {
634 	const int key_num = getKeyFromKeyEvent( _ke ) +
635 				( DefaultOctave - 1 ) * KeysPerOctave;
636 	if( _ke->isAutoRepeat() == false && key_num > -1 )
637 	{
638 		if( m_piano != NULL )
639 		{
640 			m_piano->handleKeyRelease( key_num );
641 			_ke->accept();
642 			update();
643 		}
644 	}
645 	else
646 	{
647 		_ke->ignore();
648 	}
649 }
650 
651 
652 
653 
654 /*! \brief Handle the focus leaving the piano display view
655  *
656  *  Turn off all notes if we lose focus.
657  *
658  *  \todo Is there supposed to be a parameter given here?
659  */
focusOutEvent(QFocusEvent *)660 void PianoView::focusOutEvent( QFocusEvent * )
661 {
662 	if( m_piano == NULL )
663 	{
664 		return;
665 	}
666 
667 	// focus just switched to another control inside the instrument track
668 	// window we live in?
669 	if( parentWidget()->parentWidget()->focusWidget() != this &&
670 		parentWidget()->parentWidget()->focusWidget() != NULL &&
671 		!(parentWidget()->parentWidget()->
672 				focusWidget()->inherits( "QLineEdit" ) ||
673 		parentWidget()->parentWidget()->
674 				focusWidget()->inherits( "QPlainTextEdit" ) ))
675 	{
676 		// then reclaim keyboard focus!
677 		setFocus();
678 		return;
679 	}
680 
681 	// if we loose focus, we HAVE to note off all running notes because
682 	// we don't receive key-release-events anymore and so the notes would
683 	// hang otherwise
684 	for( int i = 0; i < NumKeys; ++i )
685 	{
686 		m_piano->midiEventProcessor()->processInEvent( MidiEvent( MidiNoteOff, -1, i, 0 ) );
687 		m_piano->setKeyState( i, false );
688 	}
689 	update();
690 }
691 
692 
693 
694 
695 /*! \brief update scrollbar range after resize
696  *
697  *  After resizing we need to adjust range of scrollbar for not allowing
698  *  to scroll too far to the right.
699  *
700  *  \param _event resize-event object (unused)
701  */
resizeEvent(QResizeEvent * _event)702 void PianoView::resizeEvent( QResizeEvent * _event )
703 {
704 	QWidget::resizeEvent( _event );
705 	m_pianoScroll->setRange( 0, WhiteKeysPerOctave * NumOctaves -
706 					(int) ceil( (float) width() /
707 							PW_WHITE_KEY_WIDTH ) );
708 }
709 
710 
711 
712 
713 /*! \brief Convert a key number to an X coordinate in the piano display view
714  *
715  *  We can immediately discard the trivial case of when the key number is
716  *  less than our starting key.  We then iterate through the keys from the
717  *  start key to this key, adding the width of each key as we go.  For
718  *  black keys, and the first white key if there is no black key between
719  *  two white keys, we add half a white key width; for that second white
720  *  key, we add a whole width.  That takes us to the boundary of a white
721  *  key - subtract half a width to get to the middle.
722  *
723  *  \param _key_num the keyboard key to translate
724  *  \todo is this description of what the method does correct?
725  *  \todo replace the final subtract with initialising x to width/2.
726  */
getKeyX(int _key_num) const727 int PianoView::getKeyX( int _key_num ) const
728 {
729 	int k = m_startKey;
730 	if( _key_num < m_startKey )
731 	{
732 		return ( _key_num - k ) * PW_WHITE_KEY_WIDTH / 2;
733 	}
734 
735 	int x = 0;
736 	int white_cnt = 0;
737 
738 	while( k <= _key_num )
739 	{
740 		if( Piano::isWhiteKey( k ) )
741 		{
742 			++white_cnt;
743 			if( white_cnt > 1 )
744 			{
745 				x += PW_WHITE_KEY_WIDTH;
746 			}
747 			else
748 			{
749 				x += PW_WHITE_KEY_WIDTH/2;
750 			}
751 		}
752 		else
753 		{
754 			white_cnt = 0;
755 			x += PW_WHITE_KEY_WIDTH/2;
756 		}
757 		++k;
758 	}
759 
760 	x -= PW_WHITE_KEY_WIDTH / 2;
761 
762 	return x;
763 
764 }
765 
766 
767 
768 
769 /*! \brief Paint the piano display view in response to an event
770  *
771  *  This method draws the piano and the 'root note' base.  It draws
772  *  the base first, then all the white keys, then all the black keys.
773  *
774  *  \todo Is there supposed to be a parameter given here?
775  */
paintEvent(QPaintEvent *)776 void PianoView::paintEvent( QPaintEvent * )
777 {
778 	QPainter p( this );
779 
780 	// set smaller font for printing number of every octave
781 	p.setFont( pointSize<LABEL_TEXT_SIZE>( p.font() ) );
782 
783 
784 	// draw bar above the keyboard (there will be the labels
785 	// for all C's)
786 	p.fillRect( QRect( 0, 1, width(), PIANO_BASE-2 ), p.background() );
787 
788 	// draw the line above the keyboard
789 	p.setPen( Qt::black );
790 	p.drawLine( 0, 0, width(), 0 );
791 	p.drawLine( 0, PIANO_BASE-1, width(), PIANO_BASE-1 );
792 
793 	p.setPen( Qt::white );
794 
795 	const int base_key = ( m_piano != NULL ) ?
796 		m_piano->instrumentTrack()->baseNoteModel()->value() : 0;
797 
798 	QColor baseKeyColor = QApplication::palette().color( QPalette::Active,
799 							QPalette::BrightText );
800 	if( Piano::isWhiteKey( base_key ) )
801 	{
802 		p.fillRect( QRect( getKeyX( base_key ), 1, PW_WHITE_KEY_WIDTH-1,
803 							PIANO_BASE-2 ), baseKeyColor );
804 	}
805 	else
806 	{
807 		p.fillRect( QRect( getKeyX( base_key ) + 1, 1,
808 				PW_BLACK_KEY_WIDTH - 1, PIANO_BASE - 2 ), baseKeyColor);
809 	}
810 
811 
812 	int cur_key = m_startKey;
813 
814 	// draw all white keys...
815 	for( int x = 0; x < width(); )
816 	{
817 		while( Piano::isBlackKey( cur_key ) )
818 		{
819 			++cur_key;
820 		}
821 
822 		// draw pressed or not pressed key, depending on state of
823 		// current key
824 		if( m_piano && m_piano->isKeyPressed( cur_key ) )
825 		{
826 			p.drawPixmap( x, PIANO_BASE, *s_whiteKeyPressedPm );
827 		}
828 		else
829 		{
830 			p.drawPixmap( x, PIANO_BASE, *s_whiteKeyPm );
831 		}
832 
833 		x += PW_WHITE_KEY_WIDTH;
834 
835 		if( (Keys) (cur_key%KeysPerOctave) == Key_C )
836 		{
837 			// label key of note C with "C" and number of current
838 			// octave
839 			p.drawText( x - PW_WHITE_KEY_WIDTH, LABEL_TEXT_SIZE + 2,
840 					QString( "C" ) + QString::number(
841 					cur_key / KeysPerOctave, 10 ) );
842 		}
843 		++cur_key;
844 	}
845 
846 
847 	// reset all values, because now we're going to draw all black keys
848 	cur_key = m_startKey;
849 	int white_cnt = 0;
850 
851 	int startKey = m_startKey;
852 	if( startKey > 0 && Piano::isBlackKey( (Keys)(--startKey) ) )
853 	{
854 		if( m_piano && m_piano->isKeyPressed( startKey ) )
855 		{
856 			p.drawPixmap( 0 - PW_WHITE_KEY_WIDTH / 2, PIANO_BASE, *s_blackKeyPressedPm );
857 		}
858 		else
859 		{
860 			p.drawPixmap( 0 - PW_WHITE_KEY_WIDTH / 2, PIANO_BASE, *s_blackKeyPm );
861 		}
862 	}
863 
864 	// now draw all black keys...
865 	for( int x = 0; x < width(); )
866 	{
867 		if( Piano::isBlackKey( cur_key ) )
868 		{
869 			// draw pressed or not pressed key, depending on
870 			// state of current key
871 			if( m_piano && m_piano->isKeyPressed( cur_key ) )
872 			{
873 				p.drawPixmap( x + PW_WHITE_KEY_WIDTH / 2, PIANO_BASE, *s_blackKeyPressedPm );
874 			}
875 			else
876 			{
877 				p.drawPixmap( x + PW_WHITE_KEY_WIDTH / 2, PIANO_BASE, *s_blackKeyPm );
878 			}
879 			x += PW_WHITE_KEY_WIDTH;
880 			white_cnt = 0;
881 		}
882 		else
883 		{
884 			// simple workaround for increasing x if there were two
885 			// white keys (e.g. between E and F)
886 			++white_cnt;
887 			if( white_cnt > 1 )
888 			{
889 				x += PW_WHITE_KEY_WIDTH;
890 			}
891 		}
892 		++cur_key;
893 	}
894 }
895 
896 
897 
898 
899 
900 
901