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