1 /*
2  * Pattern.cpp - implementation of class pattern which holds notes
3  *
4  * Copyright (c) 2004-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
5  * Copyright (c) 2005-2007 Danny McRae <khjklujn/at/yahoo.com>
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 #include "Pattern.h"
26 
27 #include <QTimer>
28 #include <QMenu>
29 #include <QMouseEvent>
30 #include <QPainter>
31 #include <QPushButton>
32 
33 #include "InstrumentTrack.h"
34 #include "gui_templates.h"
35 #include "embed.h"
36 #include "GuiApplication.h"
37 #include "PianoRoll.h"
38 #include "RenameDialog.h"
39 #include "SampleBuffer.h"
40 #include "AudioSampleRecorder.h"
41 #include "BBTrackContainer.h"
42 #include "StringPairDrag.h"
43 #include "MainWindow.h"
44 
45 
46 QPixmap * PatternView::s_stepBtnOn0 = NULL;
47 QPixmap * PatternView::s_stepBtnOn200 = NULL;
48 QPixmap * PatternView::s_stepBtnOff = NULL;
49 QPixmap * PatternView::s_stepBtnOffLight = NULL;
50 
51 
52 
Pattern(InstrumentTrack * _instrument_track)53 Pattern::Pattern( InstrumentTrack * _instrument_track ) :
54 	TrackContentObject( _instrument_track ),
55 	m_instrumentTrack( _instrument_track ),
56 	m_patternType( BeatPattern ),
57 	m_steps( MidiTime::stepsPerTact() )
58 {
59 	setName( _instrument_track->name() );
60 	if( _instrument_track->trackContainer()
61 					== Engine::getBBTrackContainer() )
62 	{
63 		resizeToFirstTrack();
64 	}
65 	init();
66 	setAutoResize( true );
67 }
68 
69 
70 
71 
Pattern(const Pattern & other)72 Pattern::Pattern( const Pattern& other ) :
73 	TrackContentObject( other.m_instrumentTrack ),
74 	m_instrumentTrack( other.m_instrumentTrack ),
75 	m_patternType( other.m_patternType ),
76 	m_steps( other.m_steps )
77 {
78 	for( NoteVector::ConstIterator it = other.m_notes.begin(); it != other.m_notes.end(); ++it )
79 	{
80 		m_notes.push_back( new Note( **it ) );
81 	}
82 
83 	init();
84 	switch( getTrack()->trackContainer()->type() )
85 	{
86 		case TrackContainer::BBContainer:
87 			setAutoResize( true );
88 			break;
89 
90 		case TrackContainer::SongContainer:
91 			// move down
92 		default:
93 			setAutoResize( false );
94 			break;
95 	}
96 }
97 
98 
~Pattern()99 Pattern::~Pattern()
100 {
101 	emit destroyedPattern( this );
102 
103 	for( NoteVector::Iterator it = m_notes.begin();
104 						it != m_notes.end(); ++it )
105 	{
106 		delete *it;
107 	}
108 
109 	m_notes.clear();
110 }
111 
112 
113 
114 
resizeToFirstTrack()115 void Pattern::resizeToFirstTrack()
116 {
117 	// Resize this track to be the same as existing tracks in the BB
118 	const TrackContainer::TrackList & tracks =
119 		m_instrumentTrack->trackContainer()->tracks();
120 	for(unsigned int trackID = 0; trackID < tracks.size(); ++trackID)
121 	{
122 		if(tracks.at(trackID)->type() == Track::InstrumentTrack)
123 		{
124 			if(tracks.at(trackID) != m_instrumentTrack)
125 			{
126 				unsigned int currentTCO = m_instrumentTrack->
127 					getTCOs().indexOf(this);
128 				m_steps = static_cast<Pattern *>
129 					(tracks.at(trackID)->getTCO(currentTCO))
130 					->m_steps;
131 			}
132 			break;
133 		}
134 	}
135 }
136 
137 
138 
139 
init()140 void Pattern::init()
141 {
142 	connect( Engine::getSong(), SIGNAL( timeSignatureChanged( int, int ) ),
143 				this, SLOT( changeTimeSignature() ) );
144 	saveJournallingState( false );
145 
146 	updateLength();
147 	restoreJournallingState();
148 }
149 
150 
151 
152 
updateLength()153 void Pattern::updateLength()
154 {
155 	if( m_patternType == BeatPattern )
156 	{
157 		changeLength( beatPatternLength() );
158 		updateBBTrack();
159 		return;
160 	}
161 
162 	tick_t max_length = MidiTime::ticksPerTact();
163 
164 	for( NoteVector::ConstIterator it = m_notes.begin();
165 						it != m_notes.end(); ++it )
166 	{
167 		if( ( *it )->length() > 0 )
168 		{
169 			max_length = qMax<tick_t>( max_length,
170 							( *it )->endPos() );
171 		}
172 	}
173 	changeLength( MidiTime( max_length ).nextFullTact() *
174 						MidiTime::ticksPerTact() );
175 	updateBBTrack();
176 }
177 
178 
179 
180 
beatPatternLength() const181 MidiTime Pattern::beatPatternLength() const
182 {
183 	tick_t max_length = MidiTime::ticksPerTact();
184 
185 	for( NoteVector::ConstIterator it = m_notes.begin();
186 						it != m_notes.end(); ++it )
187 	{
188 		if( ( *it )->length() < 0 )
189 		{
190 			max_length = qMax<tick_t>( max_length,
191 				( *it )->pos() + 1 );
192 		}
193 	}
194 
195 	if( m_steps != MidiTime::stepsPerTact() )
196 	{
197 		max_length = m_steps * MidiTime::ticksPerTact() /
198 						MidiTime::stepsPerTact();
199 	}
200 
201 	return MidiTime( max_length ).nextFullTact() * MidiTime::ticksPerTact();
202 }
203 
204 
205 
206 
addNote(const Note & _new_note,const bool _quant_pos)207 Note * Pattern::addNote( const Note & _new_note, const bool _quant_pos )
208 {
209 	Note * new_note = new Note( _new_note );
210 	if( _quant_pos && gui->pianoRoll() )
211 	{
212 		new_note->quantizePos( gui->pianoRoll()->quantization() );
213 	}
214 
215 	instrumentTrack()->lock();
216 	m_notes.insert(std::upper_bound(m_notes.begin(), m_notes.end(), new_note, Note::lessThan), new_note);
217 	instrumentTrack()->unlock();
218 
219 	checkType();
220 	updateLength();
221 
222 	emit dataChanged();
223 
224 	return new_note;
225 }
226 
227 
228 
229 
removeNote(Note * _note_to_del)230 void Pattern::removeNote( Note * _note_to_del )
231 {
232 	instrumentTrack()->lock();
233 	NoteVector::Iterator it = m_notes.begin();
234 	while( it != m_notes.end() )
235 	{
236 		if( *it == _note_to_del )
237 		{
238 			delete *it;
239 			m_notes.erase( it );
240 			break;
241 		}
242 		++it;
243 	}
244 	instrumentTrack()->unlock();
245 
246 	checkType();
247 	updateLength();
248 
249 	emit dataChanged();
250 }
251 
252 
253 // returns a pointer to the note at specified step, or NULL if note doesn't exist
254 
noteAtStep(int _step)255 Note * Pattern::noteAtStep( int _step )
256 {
257 	for( NoteVector::Iterator it = m_notes.begin(); it != m_notes.end();
258 									++it )
259 	{
260 		if( ( *it )->pos() == MidiTime::stepPosition( _step )
261 						&& ( *it )->length() < 0 )
262 		{
263 			return *it;
264 		}
265 	}
266 	return NULL;
267 }
268 
269 
270 
rearrangeAllNotes()271 void Pattern::rearrangeAllNotes()
272 {
273 	// sort notes by start time
274 	std::sort(m_notes.begin(), m_notes.end(), Note::lessThan);
275 }
276 
277 
278 
clearNotes()279 void Pattern::clearNotes()
280 {
281 	instrumentTrack()->lock();
282 	for( NoteVector::Iterator it = m_notes.begin(); it != m_notes.end();
283 									++it )
284 	{
285 		delete *it;
286 	}
287 	m_notes.clear();
288 	instrumentTrack()->unlock();
289 
290 	checkType();
291 	emit dataChanged();
292 }
293 
294 
295 
296 
addStepNote(int step)297 Note * Pattern::addStepNote( int step )
298 {
299 	return addNote( Note( MidiTime( -DefaultTicksPerTact ),
300 				MidiTime::stepPosition( step ) ), false );
301 }
302 
303 
304 
305 
setStep(int step,bool enabled)306 void Pattern::setStep( int step, bool enabled )
307 {
308 	if( enabled )
309 	{
310 		if ( !noteAtStep( step ) )
311 		{
312 			addStepNote( step );
313 		}
314 		return;
315 	}
316 
317 	while( Note * note = noteAtStep( step ) )
318 	{
319 		removeNote( note );
320 	}
321 }
322 
323 
324 
325 
setType(PatternTypes _new_pattern_type)326 void Pattern::setType( PatternTypes _new_pattern_type )
327 {
328 	if( _new_pattern_type == BeatPattern ||
329 				_new_pattern_type == MelodyPattern )
330 	{
331 		m_patternType = _new_pattern_type;
332 	}
333 }
334 
335 
336 
337 
checkType()338 void Pattern::checkType()
339 {
340 	NoteVector::Iterator it = m_notes.begin();
341 	while( it != m_notes.end() )
342 	{
343 		if( ( *it )->length() > 0 )
344 		{
345 			setType( MelodyPattern );
346 			return;
347 		}
348 		++it;
349 	}
350 	setType( BeatPattern );
351 }
352 
353 
354 
355 
saveSettings(QDomDocument & _doc,QDomElement & _this)356 void Pattern::saveSettings( QDomDocument & _doc, QDomElement & _this )
357 {
358 	_this.setAttribute( "type", m_patternType );
359 	_this.setAttribute( "name", name() );
360 	// as the target of copied/dragged pattern is always an existing
361 	// pattern, we must not store actual position, instead we store -1
362 	// which tells loadSettings() not to mess around with position
363 	if( _this.parentNode().nodeName() == "clipboard" ||
364 			_this.parentNode().nodeName() == "dnddata" )
365 	{
366 		_this.setAttribute( "pos", -1 );
367 	}
368 	else
369 	{
370 		_this.setAttribute( "pos", startPosition() );
371 	}
372 	_this.setAttribute( "muted", isMuted() );
373 	_this.setAttribute( "steps", m_steps );
374 
375 	// now save settings of all notes
376 	for( NoteVector::Iterator it = m_notes.begin();
377 						it != m_notes.end(); ++it )
378 	{
379 		( *it )->saveState( _doc, _this );
380 	}
381 }
382 
383 
384 
385 
loadSettings(const QDomElement & _this)386 void Pattern::loadSettings( const QDomElement & _this )
387 {
388 	m_patternType = static_cast<PatternTypes>( _this.attribute( "type"
389 								).toInt() );
390 	setName( _this.attribute( "name" ) );
391 	if( _this.attribute( "pos" ).toInt() >= 0 )
392 	{
393 		movePosition( _this.attribute( "pos" ).toInt() );
394 	}
395 	if( _this.attribute( "muted" ).toInt() != isMuted() )
396 	{
397 		toggleMute();
398 	}
399 
400 	clearNotes();
401 
402 	QDomNode node = _this.firstChild();
403 	while( !node.isNull() )
404 	{
405 		if( node.isElement() &&
406 			!node.toElement().attribute( "metadata" ).toInt() )
407 		{
408 			Note * n = new Note;
409 			n->restoreState( node.toElement() );
410 			m_notes.push_back( n );
411 		}
412 		node = node.nextSibling();
413         }
414 
415 	m_steps = _this.attribute( "steps" ).toInt();
416 	if( m_steps == 0 )
417 	{
418 		m_steps = MidiTime::stepsPerTact();
419 	}
420 
421 	checkType();
422 	updateLength();
423 
424 	emit dataChanged();
425 }
426 
427 
428 
429 
previousPattern() const430 Pattern *  Pattern::previousPattern() const
431 {
432 	return adjacentPatternByOffset(-1);
433 }
434 
435 
436 
437 
nextPattern() const438 Pattern *  Pattern::nextPattern() const
439 {
440 	return adjacentPatternByOffset(1);
441 }
442 
443 
444 
445 
adjacentPatternByOffset(int offset) const446 Pattern * Pattern::adjacentPatternByOffset(int offset) const
447 {
448 	QVector<TrackContentObject *> tcos = m_instrumentTrack->getTCOs();
449 	int tcoNum = m_instrumentTrack->getTCONum(this);
450 	return dynamic_cast<Pattern*>(tcos.value(tcoNum + offset, NULL));
451 }
452 
453 
454 
455 
clear()456 void Pattern::clear()
457 {
458 	addJournalCheckPoint();
459 	clearNotes();
460 }
461 
462 
463 
464 
addSteps()465 void Pattern::addSteps()
466 {
467 	m_steps += MidiTime::stepsPerTact();
468 	updateLength();
469 	emit dataChanged();
470 }
471 
cloneSteps()472 void Pattern::cloneSteps()
473 {
474 	int oldLength = m_steps;
475 	m_steps *= 2; // cloning doubles the track
476 	for(int i = 0; i < oldLength; ++i )
477 	{
478 		Note *toCopy = noteAtStep( i );
479 		if( toCopy )
480 		{
481 			setStep( oldLength + i, true );
482 			Note *newNote = noteAtStep( oldLength + i );
483 			newNote->setKey( toCopy->key() );
484 			newNote->setLength( toCopy->length() );
485 			newNote->setPanning( toCopy->getPanning() );
486 			newNote->setVolume( toCopy->getVolume() );
487 		}
488 	}
489 	updateLength();
490 	emit dataChanged();
491 }
492 
493 
494 
495 
removeSteps()496 void Pattern::removeSteps()
497 {
498 	int n = MidiTime::stepsPerTact();
499 	if( n < m_steps )
500 	{
501 		for( int i = m_steps - n; i < m_steps; ++i )
502 		{
503 			setStep( i, false );
504 		}
505 		m_steps -= n;
506 		updateLength();
507 		emit dataChanged();
508 	}
509 }
510 
511 
512 
513 
createView(TrackView * _tv)514 TrackContentObjectView * Pattern::createView( TrackView * _tv )
515 {
516 	return new PatternView( this, _tv );
517 }
518 
519 
520 
521 
updateBBTrack()522 void Pattern::updateBBTrack()
523 {
524 	if( getTrack()->trackContainer() == Engine::getBBTrackContainer() )
525 	{
526 		Engine::getBBTrackContainer()->updateBBTrack( this );
527 	}
528 
529 	if( gui && gui->pianoRoll() && gui->pianoRoll()->currentPattern() == this )
530 	{
531 		gui->pianoRoll()->update();
532 	}
533 }
534 
535 
536 
537 
empty()538 bool Pattern::empty()
539 {
540 	for( NoteVector::ConstIterator it = m_notes.begin();
541 						it != m_notes.end(); ++it )
542 	{
543 		if( ( *it )->length() != 0 )
544 		{
545 			return false;
546 		}
547 	}
548 	return true;
549 }
550 
551 
552 
553 
changeTimeSignature()554 void Pattern::changeTimeSignature()
555 {
556 	MidiTime last_pos = MidiTime::ticksPerTact() - 1;
557 	for( NoteVector::ConstIterator cit = m_notes.begin();
558 						cit != m_notes.end(); ++cit )
559 	{
560 		if( ( *cit )->length() < 0 && ( *cit )->pos() > last_pos )
561 		{
562 			last_pos = ( *cit )->pos()+MidiTime::ticksPerTact() /
563 						MidiTime::stepsPerTact();
564 		}
565 	}
566 	last_pos = last_pos.nextFullTact() * MidiTime::ticksPerTact();
567 	m_steps = qMax<tick_t>( MidiTime::stepsPerTact(),
568 				last_pos.getTact() * MidiTime::stepsPerTact() );
569 	updateLength();
570 }
571 
572 
573 
574 
575 
PatternView(Pattern * pattern,TrackView * parent)576 PatternView::PatternView( Pattern* pattern, TrackView* parent ) :
577 	TrackContentObjectView( pattern, parent ),
578 	m_pat( pattern ),
579 	m_paintPixmap()
580 {
581 	connect( gui->pianoRoll(), SIGNAL( currentPatternChanged() ),
582 			this, SLOT( update() ) );
583 
584 	if( s_stepBtnOn0 == NULL )
585 	{
586 		s_stepBtnOn0 = new QPixmap( embed::getIconPixmap(
587 							"step_btn_on_0" ) );
588 	}
589 
590 	if( s_stepBtnOn200 == NULL )
591 	{
592 		s_stepBtnOn200 = new QPixmap( embed::getIconPixmap(
593 							"step_btn_on_200" ) );
594 	}
595 
596 	if( s_stepBtnOff == NULL )
597 	{
598 		s_stepBtnOff = new QPixmap( embed::getIconPixmap(
599 							"step_btn_off" ) );
600 	}
601 
602 	if( s_stepBtnOffLight == NULL )
603 	{
604 		s_stepBtnOffLight = new QPixmap( embed::getIconPixmap(
605 						"step_btn_off_light" ) );
606 	}
607 
608 	update();
609 
610 	setStyle( QApplication::style() );
611 }
612 
613 
614 
615 
616 
617 
~PatternView()618 PatternView::~PatternView()
619 {
620 }
621 
622 
623 
624 
625 
update()626 void PatternView::update()
627 {
628 	if ( m_pat->m_patternType == Pattern::BeatPattern )
629 	{
630 		ToolTip::add( this,
631 			tr( "use mouse wheel to set velocity of a step" ) );
632 	}
633 	else
634 	{
635 		ToolTip::add( this,
636 			tr( "double-click to open in Piano Roll" ) );
637 	}
638 
639 	TrackContentObjectView::update();
640 }
641 
642 
643 
644 
openInPianoRoll()645 void PatternView::openInPianoRoll()
646 {
647 	gui->pianoRoll()->setCurrentPattern( m_pat );
648 	gui->pianoRoll()->parentWidget()->show();
649 	gui->pianoRoll()->show();
650 	gui->pianoRoll()->setFocus();
651 }
652 
653 
654 
655 
resetName()656 void PatternView::resetName()
657 {
658 	m_pat->setName( m_pat->m_instrumentTrack->name() );
659 }
660 
661 
662 
663 
changeName()664 void PatternView::changeName()
665 {
666 	QString s = m_pat->name();
667 	RenameDialog rename_dlg( s );
668 	rename_dlg.exec();
669 	m_pat->setName( s );
670 }
671 
672 
673 
674 
constructContextMenu(QMenu * _cm)675 void PatternView::constructContextMenu( QMenu * _cm )
676 {
677 	QAction * a = new QAction( embed::getIconPixmap( "piano" ),
678 					tr( "Open in piano-roll" ), _cm );
679 	_cm->insertAction( _cm->actions()[0], a );
680 	connect( a, SIGNAL( triggered( bool ) ),
681 					this, SLOT( openInPianoRoll() ) );
682 	_cm->insertSeparator( _cm->actions()[1] );
683 
684 	_cm->addSeparator();
685 
686 	_cm->addAction( embed::getIconPixmap( "edit_erase" ),
687 			tr( "Clear all notes" ), m_pat, SLOT( clear() ) );
688 	_cm->addSeparator();
689 
690 	_cm->addAction( embed::getIconPixmap( "reload" ), tr( "Reset name" ),
691 						this, SLOT( resetName() ) );
692 	_cm->addAction( embed::getIconPixmap( "edit_rename" ),
693 						tr( "Change name" ),
694 						this, SLOT( changeName() ) );
695 
696 	if ( m_pat->type() == Pattern::BeatPattern )
697 	{
698 		_cm->addSeparator();
699 
700 		_cm->addAction( embed::getIconPixmap( "step_btn_add" ),
701 			tr( "Add steps" ), m_pat, SLOT( addSteps() ) );
702 		_cm->addAction( embed::getIconPixmap( "step_btn_remove" ),
703 			tr( "Remove steps" ), m_pat, SLOT( removeSteps() ) );
704 		_cm->addAction( embed::getIconPixmap( "step_btn_duplicate" ),
705 			tr( "Clone Steps" ), m_pat, SLOT( cloneSteps() ) );
706 	}
707 }
708 
709 
710 
711 
mousePressEvent(QMouseEvent * _me)712 void PatternView::mousePressEvent( QMouseEvent * _me )
713 {
714 	if( _me->button() == Qt::LeftButton &&
715 				m_pat->m_patternType == Pattern::BeatPattern &&
716 				( fixedTCOs() || pixelsPerTact() >= 96 ) &&
717 				_me->y() > height() - s_stepBtnOff->height() )
718 
719 	// when mouse button is pressed in beat/bassline -mode
720 
721 	{
722 //	get the step number that was clicked on and
723 //	do calculations in floats to prevent rounding errors...
724 		float tmp = ( ( float(_me->x()) - TCO_BORDER_WIDTH ) *
725 				float( m_pat -> m_steps ) ) / float(width() - TCO_BORDER_WIDTH*2);
726 
727 		int step = int( tmp );
728 
729 //	debugging to ensure we get the correct step...
730 //		qDebug( "Step (%f) %d", tmp, step );
731 
732 		if( step >= m_pat->m_steps )
733 		{
734 			qDebug( "Something went wrong in pattern.cpp: step %d doesn't exist in pattern!", step );
735 			return;
736 		}
737 
738 		Note * n = m_pat->noteAtStep( step );
739 
740 		if( n == NULL )
741 		{
742 			m_pat->addStepNote( step );
743 		}
744 		else // note at step found
745 		{
746 			m_pat->addJournalCheckPoint();
747 			m_pat->setStep( step, false );
748 		}
749 
750 		Engine::getSong()->setModified();
751 		update();
752 
753 		if( gui->pianoRoll()->currentPattern() == m_pat )
754 		{
755 			gui->pianoRoll()->update();
756 		}
757 	}
758 	else
759 
760 	// if not in beat/bassline -mode, let parent class handle the event
761 
762 	{
763 		TrackContentObjectView::mousePressEvent( _me );
764 	}
765 }
766 
mouseDoubleClickEvent(QMouseEvent * _me)767 void PatternView::mouseDoubleClickEvent(QMouseEvent *_me)
768 {
769 	if( _me->button() != Qt::LeftButton )
770 	{
771 		_me->ignore();
772 		return;
773 	}
774 	if( m_pat->m_patternType == Pattern::MelodyPattern || !fixedTCOs() )
775 	{
776 		openInPianoRoll();
777 	}
778 }
779 
780 
781 
782 
wheelEvent(QWheelEvent * _we)783 void PatternView::wheelEvent( QWheelEvent * _we )
784 {
785 	if( m_pat->m_patternType == Pattern::BeatPattern &&
786 				( fixedTCOs() || pixelsPerTact() >= 96 ) &&
787 				_we->y() > height() - s_stepBtnOff->height() )
788 	{
789 //	get the step number that was wheeled on and
790 //	do calculations in floats to prevent rounding errors...
791 		float tmp = ( ( float(_we->x()) - TCO_BORDER_WIDTH ) *
792 				float( m_pat -> m_steps ) ) / float(width() - TCO_BORDER_WIDTH*2);
793 
794 		int step = int( tmp );
795 
796 		if( step >= m_pat->m_steps )
797 		{
798 			return;
799 		}
800 
801 		Note * n = m_pat->noteAtStep( step );
802 		if( !n && _we->delta() > 0 )
803 		{
804 			n = m_pat->addStepNote( step );
805 			n->setVolume( 0 );
806 		}
807 		if( n != NULL )
808 		{
809 			int vol = n->getVolume();
810 
811 			if( _we->delta() > 0 )
812 			{
813 				n->setVolume( qMin( 100, vol + 5 ) );
814 			}
815 			else
816 			{
817 				n->setVolume( qMax( 0, vol - 5 ) );
818 			}
819 
820 			Engine::getSong()->setModified();
821 			update();
822 			if( gui->pianoRoll()->currentPattern() == m_pat )
823 			{
824 				gui->pianoRoll()->update();
825 			}
826 		}
827 		_we->accept();
828 	}
829 	else
830 	{
831 		TrackContentObjectView::wheelEvent( _we );
832 	}
833 }
834 
835 
836 
837 
paintEvent(QPaintEvent *)838 void PatternView::paintEvent( QPaintEvent * )
839 {
840 	QPainter painter( this );
841 
842 	if( !needsUpdate() )
843 	{
844 		painter.drawPixmap( 0, 0, m_paintPixmap );
845 		return;
846 	}
847 
848 	setNeedsUpdate( false );
849 
850 	m_paintPixmap = m_paintPixmap.isNull() == true || m_paintPixmap.size() != size()
851 		? QPixmap( size() ) : m_paintPixmap;
852 
853 	QPainter p( &m_paintPixmap );
854 
855 	QLinearGradient lingrad( 0, 0, 0, height() );
856 	QColor c;
857 	bool muted = m_pat->getTrack()->isMuted() || m_pat->isMuted();
858 	bool current = gui->pianoRoll()->currentPattern() == m_pat;
859 	bool beatPattern = m_pat->m_patternType == Pattern::BeatPattern;
860 
861 	// state: selected, normal, beat pattern, muted
862 	c = isSelected() ? selectedColor() : ( ( !muted && !beatPattern )
863 		? painter.background().color() : ( beatPattern
864 		? BBPatternBackground() : mutedBackgroundColor() ) );
865 
866 	// invert the gradient for the background in the B&B editor
867 	lingrad.setColorAt( beatPattern ? 0 : 1, c.darker( 300 ) );
868 	lingrad.setColorAt( beatPattern ? 1 : 0, c );
869 
870 	// paint a black rectangle under the pattern to prevent glitches with transparent backgrounds
871 	p.fillRect( rect(), QColor( 0, 0, 0 ) );
872 
873 	if( gradient() )
874 	{
875 		p.fillRect( rect(), lingrad );
876 	}
877 	else
878 	{
879 		p.fillRect( rect(), c );
880 	}
881 
882 	const float ppt = fixedTCOs() ?
883 			( parentWidget()->width() - 2 * TCO_BORDER_WIDTH )
884 					/ (float) m_pat->length().getTact() :
885 				( width() - TCO_BORDER_WIDTH )
886 					/ (float) m_pat->length().getTact();
887 
888 	const int x_base = TCO_BORDER_WIDTH;
889 
890 	// melody pattern paint event
891 	if( m_pat->m_patternType == Pattern::MelodyPattern )
892 	{
893 		if( m_pat->m_notes.size() > 0 )
894 		{
895 			// first determine the central tone so that we can
896 			// display the area where most of the m_notes are
897 			// also calculate min/max tones so the tonal range can be
898 			// properly stretched accross the pattern vertically
899 
900 			int central_key = 0;
901 			int max_key = 0;
902 			int min_key = 9999999;
903 			int total_notes = 0;
904 
905 			for( NoteVector::Iterator it = m_pat->m_notes.begin();
906 					it != m_pat->m_notes.end(); ++it )
907 			{
908 				max_key = qMax( max_key, ( *it )->key() );
909 				min_key = qMin( min_key, ( *it )->key() );
910 				central_key += ( *it )->key();
911 				++total_notes;
912 			}
913 
914 			if( total_notes > 0 )
915 			{
916 				central_key = central_key / total_notes;
917 				const int keyrange = qMax( qMax( max_key - central_key, central_key - min_key ), 1 );
918 
919 				// debug code
920 				// qDebug( "keyrange: %d", keyrange );
921 
922 				// determine height of the pattern view, sans borders
923 				const int ht = (height() - 1 - TCO_BORDER_WIDTH * 2) -1;
924 
925 				// determine maximum height value for drawing bounds checking
926 				const int max_ht = height() - 1 - TCO_BORDER_WIDTH;
927 
928 				// set colour based on mute status
929 				p.setPen( muted ? mutedColor() : painter.pen().brush().color() );
930 
931 				// scan through all the notes and draw them on the pattern
932 				for( NoteVector::Iterator it =
933 							m_pat->m_notes.begin();
934 					it != m_pat->m_notes.end(); ++it )
935 				{
936 					// calculate relative y-position
937 					const float y_key =
938 						( float( central_key - ( *it )->key() ) / keyrange + 1.0f ) / 2;
939 					// multiply that by pattern height
940 					const int y_pos = static_cast<int>( TCO_BORDER_WIDTH + y_key * ht ) + 1;
941 
942 					// debug code
943 					// if( ( *it )->length() > 0 ) qDebug( "key %d, central_key %d, y_key %f, y_pos %d", ( *it )->key(), central_key, y_key, y_pos );
944 
945 					// check that note isn't out of bounds, and has a length
946 					if( y_pos >= TCO_BORDER_WIDTH &&
947 								y_pos <= max_ht )
948 					{
949 						// calculate start and end x-coords of the line to be drawn
950 						int length = ( *it )->length();
951 						length = length > 0 ? length : 4;
952 						const int x1 = x_base +
953 							static_cast<int>
954 							( ( *it )->pos() * ( ppt / MidiTime::ticksPerTact() ) );
955 						const int x2 = x_base +
956 							static_cast<int>
957 							( ( ( *it )->pos() + length ) * ( ppt / MidiTime::ticksPerTact() ) );
958 
959 						// check bounds, draw line
960 						if( x1 < width() - TCO_BORDER_WIDTH )
961 							p.drawLine( x1, y_pos,
962 										qMin( x2, width() - TCO_BORDER_WIDTH ), y_pos );
963 					}
964 				}
965 			}
966 		}
967 	}
968 
969 	// beat pattern paint event
970 	else if( beatPattern &&	( fixedTCOs() || ppt >= 96 ) )
971 	{
972 		QPixmap stepon0;
973 		QPixmap stepon200;
974 		QPixmap stepoff;
975 		QPixmap stepoffl;
976 		const int steps = qMax( 1,
977 					m_pat->m_steps );
978 		const int w = width() - 2 * TCO_BORDER_WIDTH;
979 
980 		// scale step graphics to fit the beat pattern length
981 		stepon0 = s_stepBtnOn0->scaled( w / steps,
982 					      s_stepBtnOn0->height(),
983 					      Qt::IgnoreAspectRatio,
984 					      Qt::SmoothTransformation );
985 		stepon200 = s_stepBtnOn200->scaled( w / steps,
986 					      s_stepBtnOn200->height(),
987 					      Qt::IgnoreAspectRatio,
988 					      Qt::SmoothTransformation );
989 		stepoff = s_stepBtnOff->scaled( w / steps,
990 						s_stepBtnOff->height(),
991 						Qt::IgnoreAspectRatio,
992 						Qt::SmoothTransformation );
993 		stepoffl = s_stepBtnOffLight->scaled( w / steps,
994 						s_stepBtnOffLight->height(),
995 						Qt::IgnoreAspectRatio,
996 						Qt::SmoothTransformation );
997 
998 		for( int it = 0; it < steps; it++ )	// go through all the steps in the beat pattern
999 		{
1000 			Note * n = m_pat->noteAtStep( it );
1001 
1002 			// figure out x and y coordinates for step graphic
1003 			const int x = TCO_BORDER_WIDTH + static_cast<int>( it * w / steps );
1004 			const int y = height() - s_stepBtnOff->height() - 1;
1005 
1006 			if( n )
1007 			{
1008 				const int vol = n->getVolume();
1009 				p.drawPixmap( x, y, stepoffl );
1010 				p.drawPixmap( x, y, stepon0 );
1011 				p.setOpacity( sqrt( vol / 200.0 ) );
1012 				p.drawPixmap( x, y, stepon200 );
1013 				p.setOpacity( 1 );
1014 			}
1015 			else if( ( it / 4 ) % 2 )
1016 			{
1017 				p.drawPixmap( x, y, stepoffl );
1018 			}
1019 			else
1020 			{
1021 				p.drawPixmap( x, y, stepoff );
1022 			}
1023 		} // end for loop
1024 
1025 		// draw a transparent rectangle over muted patterns
1026 		if ( muted )
1027 		{
1028 			p.setBrush( mutedBackgroundColor() );
1029 			p.setOpacity( 0.5 );
1030 			p.drawRect( 0, 0, width(), height() );
1031 		}
1032 	}
1033 
1034 	// bar lines
1035 	const int lineSize = 3;
1036 	p.setPen( c.darker( 200 ) );
1037 
1038 	for( tact_t t = 1; t < m_pat->length().getTact(); ++t )
1039 	{
1040 		p.drawLine( x_base + static_cast<int>( ppt * t ) - 1,
1041 				TCO_BORDER_WIDTH, x_base + static_cast<int>(
1042 						ppt * t ) - 1, TCO_BORDER_WIDTH + lineSize );
1043 		p.drawLine( x_base + static_cast<int>( ppt * t ) - 1,
1044 				rect().bottom() - ( lineSize + TCO_BORDER_WIDTH ),
1045 				x_base + static_cast<int>( ppt * t ) - 1,
1046 				rect().bottom() - TCO_BORDER_WIDTH );
1047 	}
1048 
1049 	// pattern name
1050 	p.setRenderHint( QPainter::TextAntialiasing );
1051 
1052 	bool isDefaultName = m_pat->name() == m_pat->instrumentTrack()->name();
1053 
1054 	if( !isDefaultName && m_staticTextName.text() != m_pat->name() )
1055 	{
1056 		m_staticTextName.setText( m_pat->name() );
1057 	}
1058 
1059 	QFont font;
1060 	font.setHintingPreference( QFont::PreferFullHinting );
1061 	font.setPointSize( 8 );
1062 	p.setFont( font );
1063 
1064 	const int textTop = TCO_BORDER_WIDTH + 1;
1065 	const int textLeft = TCO_BORDER_WIDTH + 1;
1066 
1067 	if( !isDefaultName )
1068 	{
1069 		p.setPen( textShadowColor() );
1070 		p.drawStaticText( textLeft + 1, textTop + 1, m_staticTextName );
1071 		p.setPen( textColor() );
1072 		p.drawStaticText( textLeft, textTop, m_staticTextName );
1073 	}
1074 
1075 	// inner border
1076 	if( !( fixedTCOs() && beatPattern ) )
1077 	{
1078 		p.setPen( c.lighter( current ? 160 : 130 ) );
1079 		p.drawRect( 1, 1, rect().right() - TCO_BORDER_WIDTH,
1080 			rect().bottom() - TCO_BORDER_WIDTH );
1081 
1082 	// outer border
1083 	p.setPen( ( current && !beatPattern ) ? c.lighter( 130 ) : c.darker( 300 ) );
1084 	p.drawRect( 0, 0, rect().right(), rect().bottom() );
1085 	}
1086 	// draw the 'muted' pixmap only if the pattern was manualy muted
1087 	if( m_pat->isMuted() )
1088 	{
1089 		const int spacing = TCO_BORDER_WIDTH;
1090 		const int size = 14;
1091 		p.drawPixmap( spacing, height() - ( size + spacing ),
1092 			embed::getIconPixmap( "muted", size, size ) );
1093 	}
1094 
1095 	p.end();
1096 
1097 	painter.drawPixmap( 0, 0, m_paintPixmap );
1098 
1099 }
1100