1 /*
2  * Track.cpp - implementation of classes concerning tracks -> necessary for
3  *             all track-like objects (beat/bassline, sample-track...)
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 Track.cpp
27  *  \brief All classes concerning tracks and track-like objects
28  */
29 
30 /*
31  * \mainpage Track classes
32  *
33  * \section introduction Introduction
34  *
35  * \todo fill this out
36  */
37 
38 #include "Track.h"
39 
40 #include <assert.h>
41 
42 #include <QLayout>
43 #include <QMenu>
44 #include <QMouseEvent>
45 #include <QPainter>
46 #include <QStyleOption>
47 
48 
49 #include "AutomationPattern.h"
50 #include "AutomationTrack.h"
51 #include "AutomationEditor.h"
52 #include "BBEditor.h"
53 #include "BBTrack.h"
54 #include "BBTrackContainer.h"
55 #include "ConfigManager.h"
56 #include "Clipboard.h"
57 #include "embed.h"
58 #include "Engine.h"
59 #include "GuiApplication.h"
60 #include "FxMixerView.h"
61 #include "gui_templates.h"
62 #include "MainWindow.h"
63 #include "Mixer.h"
64 #include "ProjectJournal.h"
65 #include "SampleTrack.h"
66 #include "Song.h"
67 #include "SongEditor.h"
68 #include "StringPairDrag.h"
69 #include "TextFloat.h"
70 
71 
72 /*! The width of the resize grip in pixels
73  */
74 const int RESIZE_GRIP_WIDTH = 4;
75 
76 
77 /*! A pointer for that text bubble used when moving segments, etc.
78  *
79  * In a number of situations, LMMS displays a floating text bubble
80  * beside the cursor as you move or resize elements of a track about.
81  * This pointer keeps track of it, as you only ever need one at a time.
82  */
83 TextFloat * TrackContentObjectView::s_textFloat = NULL;
84 
85 
86 // ===========================================================================
87 // TrackContentObject
88 // ===========================================================================
89 /*! \brief Create a new TrackContentObject
90  *
91  *  Creates a new track content object for the given track.
92  *
93  * \param _track The track that will contain the new object
94  */
TrackContentObject(Track * track)95 TrackContentObject::TrackContentObject( Track * track ) :
96 	Model( track ),
97 	m_track( track ),
98 	m_name( QString::null ),
99 	m_startPosition(),
100 	m_length(),
101 	m_mutedModel( false, this, tr( "Mute" ) ),
102 	m_selectViewOnCreate( false )
103 {
104 	if( getTrack() )
105 	{
106 		getTrack()->addTCO( this );
107 	}
108 	setJournalling( false );
109 	movePosition( 0 );
110 	changeLength( 0 );
111 	setJournalling( true );
112 }
113 
114 
115 
116 
117 /*! \brief Destroy a TrackContentObject
118  *
119  *  Destroys the given track content object.
120  *
121  */
~TrackContentObject()122 TrackContentObject::~TrackContentObject()
123 {
124 	emit destroyedTCO();
125 
126 	if( getTrack() )
127 	{
128 		getTrack()->removeTCO( this );
129 	}
130 }
131 
132 
133 
134 
135 /*! \brief Move this TrackContentObject's position in time
136  *
137  *  If the track content object has moved, update its position.  We
138  *  also add a journal entry for undo and update the display.
139  *
140  * \param _pos The new position of the track content object.
141  */
movePosition(const MidiTime & pos)142 void TrackContentObject::movePosition( const MidiTime & pos )
143 {
144 	if( m_startPosition != pos )
145 	{
146 		Engine::mixer()->requestChangeInModel();
147 		m_startPosition = pos;
148 		Engine::mixer()->doneChangeInModel();
149 		Engine::getSong()->updateLength();
150 		emit positionChanged();
151 	}
152 }
153 
154 
155 
156 
157 /*! \brief Change the length of this TrackContentObject
158  *
159  *  If the track content object's length has chaanged, update it.  We
160  *  also add a journal entry for undo and update the display.
161  *
162  * \param _length The new length of the track content object.
163  */
changeLength(const MidiTime & length)164 void TrackContentObject::changeLength( const MidiTime & length )
165 {
166 	m_length = length;
167 	Engine::getSong()->updateLength();
168 	emit lengthChanged();
169 }
170 
comparePosition(const TrackContentObject * a,const TrackContentObject * b)171 bool TrackContentObject::comparePosition(const TrackContentObject *a, const TrackContentObject *b)
172 {
173 	return a->startPosition() < b->startPosition();
174 }
175 
176 
177 
178 
179 /*! \brief Copy this TrackContentObject to the clipboard.
180  *
181  *  Copies this track content object to the clipboard.
182  */
copy()183 void TrackContentObject::copy()
184 {
185 	Clipboard::copy( this );
186 }
187 
188 
189 
190 
191 /*! \brief Pastes this TrackContentObject into a track.
192  *
193  *  Pastes this track content object into a track.
194  *
195  * \param _je The journal entry to undo
196  */
paste()197 void TrackContentObject::paste()
198 {
199 	if( Clipboard::getContent( nodeName() ) != NULL )
200 	{
201 		const MidiTime pos = startPosition();
202 		restoreState( *( Clipboard::getContent( nodeName() ) ) );
203 		movePosition( pos );
204 	}
205 	AutomationPattern::resolveAllIDs();
206 	GuiApplication::instance()->automationEditor()->m_editor->updateAfterPatternChange();
207 }
208 
209 
210 
211 
212 /*! \brief Mutes this TrackContentObject
213  *
214  *  Restore the previous state of this track content object.  This will
215  *  restore the position or the length of the track content object
216  *  depending on what was changed.
217  *
218  * \param _je The journal entry to undo
219  */
toggleMute()220 void TrackContentObject::toggleMute()
221 {
222 	m_mutedModel.setValue( !m_mutedModel.value() );
223 	emit dataChanged();
224 }
225 
226 
227 
228 
229 
230 
231 
232 // ===========================================================================
233 // trackContentObjectView
234 // ===========================================================================
235 /*! \brief Create a new trackContentObjectView
236  *
237  *  Creates a new track content object view for the given
238  *  track content object in the given track view.
239  *
240  * \param _tco The track content object to be displayed
241  * \param _tv  The track view that will contain the new object
242  */
TrackContentObjectView(TrackContentObject * tco,TrackView * tv)243 TrackContentObjectView::TrackContentObjectView( TrackContentObject * tco,
244 							TrackView * tv ) :
245 	selectableObject( tv->getTrackContentWidget() ),
246 	ModelView( NULL, this ),
247 	m_tco( tco ),
248 	m_trackView( tv ),
249 	m_action( NoAction ),
250 	m_initialMousePos( QPoint( 0, 0 ) ),
251 	m_initialMouseGlobalPos( QPoint( 0, 0 ) ),
252 	m_hint( NULL ),
253 	m_mutedColor( 0, 0, 0 ),
254 	m_mutedBackgroundColor( 0, 0, 0 ),
255 	m_selectedColor( 0, 0, 0 ),
256 	m_textColor( 0, 0, 0 ),
257 	m_textShadowColor( 0, 0, 0 ),
258 	m_BBPatternBackground( 0, 0, 0 ),
259 	m_gradient( true ),
260 	m_mouseHotspotHand( 0, 0 ),
261 	m_cursorSetYet( false ),
262 	m_needsUpdate( true )
263 {
264 	if( s_textFloat == NULL )
265 	{
266 		s_textFloat = new TextFloat;
267 		s_textFloat->setPixmap( embed::getIconPixmap( "clock" ) );
268 	}
269 
270 	setAttribute( Qt::WA_OpaquePaintEvent, true );
271 	setAttribute( Qt::WA_DeleteOnClose, true );
272 	setFocusPolicy( Qt::StrongFocus );
273 	setCursor( QCursor( embed::getIconPixmap( "hand" ), m_mouseHotspotHand.width(), m_mouseHotspotHand.height() ) );
274 	move( 0, 0 );
275 	show();
276 
277 	setFixedHeight( tv->getTrackContentWidget()->height() - 1);
278 	setAcceptDrops( true );
279 	setMouseTracking( true );
280 
281 	connect( m_tco, SIGNAL( lengthChanged() ),
282 			this, SLOT( updateLength() ) );
283 	connect( gui->songEditor()->m_editor->zoomingModel(), SIGNAL( dataChanged() ), this, SLOT( updateLength() ) );
284 	connect( m_tco, SIGNAL( positionChanged() ),
285 			this, SLOT( updatePosition() ) );
286 	connect( m_tco, SIGNAL( destroyedTCO() ), this, SLOT( close() ) );
287 	setModel( m_tco );
288 
289 	m_trackView->getTrackContentWidget()->addTCOView( this );
290 	updateLength();
291 	updatePosition();
292 }
293 
294 
295 
296 
297 /*! \brief Destroy a trackContentObjectView
298  *
299  *  Destroys the given track content object view.
300  *
301  */
~TrackContentObjectView()302 TrackContentObjectView::~TrackContentObjectView()
303 {
304 	delete m_hint;
305 	// we have to give our track-container the focus because otherwise the
306 	// op-buttons of our track-widgets could become focus and when the user
307 	// presses space for playing song, just one of these buttons is pressed
308 	// which results in unwanted effects
309 	m_trackView->trackContainerView()->setFocus();
310 }
311 
312 
313 /*! \brief Update a TrackContentObjectView
314  *
315  *  TCO's get drawn only when needed,
316  *  and when a TCO is updated,
317  *  it needs to be redrawn.
318  *
319  */
update()320 void TrackContentObjectView::update()
321 {
322 	if( !m_cursorSetYet )
323 	{
324 		setCursor( QCursor( embed::getIconPixmap( "hand" ), m_mouseHotspotHand.width(), m_mouseHotspotHand.height() ) );
325 		m_cursorSetYet = true;
326 	}
327 
328 	if( fixedTCOs() )
329 	{
330 		updateLength();
331 	}
332 	m_needsUpdate = true;
333 	selectableObject::update();
334 }
335 
336 
337 
338 /*! \brief Does this trackContentObjectView have a fixed TCO?
339  *
340  *  Returns whether the containing trackView has fixed
341  *  TCOs.
342  *
343  * \todo What the hell is a TCO here - track content object?  And in
344  *  what circumstance are they fixed?
345  */
fixedTCOs()346 bool TrackContentObjectView::fixedTCOs()
347 {
348 	return m_trackView->trackContainerView()->fixedTCOs();
349 }
350 
351 
352 
353 // qproperty access functions, to be inherited & used by TCOviews
354 //! \brief CSS theming qproperty access method
mutedColor() const355 QColor TrackContentObjectView::mutedColor() const
356 { return m_mutedColor; }
357 
mutedBackgroundColor() const358 QColor TrackContentObjectView::mutedBackgroundColor() const
359 { return m_mutedBackgroundColor; }
360 
selectedColor() const361 QColor TrackContentObjectView::selectedColor() const
362 { return m_selectedColor; }
363 
textColor() const364 QColor TrackContentObjectView::textColor() const
365 { return m_textColor; }
366 
textShadowColor() const367 QColor TrackContentObjectView::textShadowColor() const
368 { return m_textShadowColor; }
369 
BBPatternBackground() const370 QColor TrackContentObjectView::BBPatternBackground() const
371 { return m_BBPatternBackground; }
372 
gradient() const373 bool TrackContentObjectView::gradient() const
374 { return m_gradient; }
375 
376 //! \brief CSS theming qproperty access method
setMutedColor(const QColor & c)377 void TrackContentObjectView::setMutedColor( const QColor & c )
378 { m_mutedColor = QColor( c ); }
379 
setMutedBackgroundColor(const QColor & c)380 void TrackContentObjectView::setMutedBackgroundColor( const QColor & c )
381 { m_mutedBackgroundColor = QColor( c ); }
382 
setSelectedColor(const QColor & c)383 void TrackContentObjectView::setSelectedColor( const QColor & c )
384 { m_selectedColor = QColor( c ); }
385 
setTextColor(const QColor & c)386 void TrackContentObjectView::setTextColor( const QColor & c )
387 { m_textColor = QColor( c ); }
388 
setTextShadowColor(const QColor & c)389 void TrackContentObjectView::setTextShadowColor( const QColor & c )
390 { m_textShadowColor = QColor( c ); }
391 
setBBPatternBackground(const QColor & c)392 void TrackContentObjectView::setBBPatternBackground( const QColor & c )
393 { m_BBPatternBackground = QColor( c ); }
394 
setGradient(const bool & b)395 void TrackContentObjectView::setGradient( const bool & b )
396 { m_gradient = b; }
397 
setMouseHotspotHand(const QSize & s)398 void TrackContentObjectView::setMouseHotspotHand(const QSize & s)
399 {
400 	m_mouseHotspotHand = s;
401 }
402 
403 // access needsUpdate member variable
needsUpdate()404 bool TrackContentObjectView::needsUpdate()
405 { return m_needsUpdate; }
setNeedsUpdate(bool b)406 void TrackContentObjectView::setNeedsUpdate( bool b )
407 { m_needsUpdate = b; }
408 
409 /*! \brief Close a trackContentObjectView
410  *
411  *  Closes a track content object view by asking the track
412  *  view to remove us and then asking the QWidget to close us.
413  *
414  * \return Boolean state of whether the QWidget was able to close.
415  */
close()416 bool TrackContentObjectView::close()
417 {
418 	m_trackView->getTrackContentWidget()->removeTCOView( this );
419 	return QWidget::close();
420 }
421 
422 
423 
424 
425 /*! \brief Removes a trackContentObjectView from its track view.
426  *
427  *  Like the close() method, this asks the track view to remove this
428  *  track content object view.  However, the track content object is
429  *  scheduled for later deletion rather than closed immediately.
430  *
431  */
remove()432 void TrackContentObjectView::remove()
433 {
434 	m_trackView->getTrack()->addJournalCheckPoint();
435 
436 	// delete ourself
437 	close();
438 	m_tco->deleteLater();
439 }
440 
441 
442 
443 
444 /*! \brief Cut this trackContentObjectView from its track to the clipboard.
445  *
446  *  Perform the 'cut' action of the clipboard - copies the track content
447  *  object to the clipboard and then removes it from the track.
448  */
cut()449 void TrackContentObjectView::cut()
450 {
451 	m_tco->copy();
452 	remove();
453 }
454 
455 
456 
457 
458 /*! \brief Updates a trackContentObjectView's length
459  *
460  *  If this track content object view has a fixed TCO, then we must
461  *  keep the width of our parent.  Otherwise, calculate our width from
462  *  the track content object's length in pixels adding in the border.
463  *
464  */
updateLength()465 void TrackContentObjectView::updateLength()
466 {
467 	if( fixedTCOs() )
468 	{
469 		setFixedWidth( parentWidget()->width() );
470 	}
471 	else
472 	{
473 		setFixedWidth(
474 		static_cast<int>( m_tco->length() * pixelsPerTact() /
475 					MidiTime::ticksPerTact() ) + 1 /*+
476 						TCO_BORDER_WIDTH * 2-1*/ );
477 	}
478 	m_trackView->trackContainerView()->update();
479 }
480 
481 
482 
483 
484 /*! \brief Updates a trackContentObjectView's position.
485  *
486  *  Ask our track view to change our position.  Then make sure that the
487  *  track view is updated in case this position has changed the track
488  *  view's length.
489  *
490  */
updatePosition()491 void TrackContentObjectView::updatePosition()
492 {
493 	m_trackView->getTrackContentWidget()->changePosition();
494 	// moving a TCO can result in change of song-length etc.,
495 	// therefore we update the track-container
496 	m_trackView->trackContainerView()->update();
497 }
498 
499 
500 
501 /*! \brief Change the trackContentObjectView's display when something
502  *  being dragged enters it.
503  *
504  *  We need to notify Qt to change our display if something being
505  *  dragged has entered our 'airspace'.
506  *
507  * \param dee The QDragEnterEvent to watch.
508  */
dragEnterEvent(QDragEnterEvent * dee)509 void TrackContentObjectView::dragEnterEvent( QDragEnterEvent * dee )
510 {
511 	TrackContentWidget * tcw = getTrackView()->getTrackContentWidget();
512 	MidiTime tcoPos = MidiTime( m_tco->startPosition().getTact(), 0 );
513 	if( tcw->canPasteSelection( tcoPos, dee ) == false )
514 	{
515 		dee->ignore();
516 	}
517 	else
518 	{
519 		StringPairDrag::processDragEnterEvent( dee, "tco_" +
520 					QString::number( m_tco->getTrack()->type() ) );
521 	}
522 }
523 
524 
525 
526 
527 /*! \brief Handle something being dropped on this trackContentObjectView.
528  *
529  *  When something has been dropped on this trackContentObjectView, and
530  *  it's a track content object, then use an instance of our dataFile reader
531  *  to take the xml of the track content object and turn it into something
532  *  we can write over our current state.
533  *
534  * \param de The QDropEvent to handle.
535  */
dropEvent(QDropEvent * de)536 void TrackContentObjectView::dropEvent( QDropEvent * de )
537 {
538 	QString type = StringPairDrag::decodeKey( de );
539 	QString value = StringPairDrag::decodeValue( de );
540 
541 	// Track must be the same type to paste into
542 	if( type != ( "tco_" + QString::number( m_tco->getTrack()->type() ) ) )
543 	{
544 		return;
545 	}
546 
547 	// Defer to rubberband paste if we're in that mode
548 	if( m_trackView->trackContainerView()->allowRubberband() == true )
549 	{
550 		TrackContentWidget * tcw = getTrackView()->getTrackContentWidget();
551 		MidiTime tcoPos = MidiTime( m_tco->startPosition().getTact(), 0 );
552 		if( tcw->pasteSelection( tcoPos, de ) == true )
553 		{
554 			de->accept();
555 		}
556 		return;
557 	}
558 
559 	// Don't allow pasting a tco into itself.
560 	QObject* qwSource = de->source();
561 	if( qwSource != NULL &&
562 	    dynamic_cast<TrackContentObjectView *>( qwSource ) == this )
563 	{
564 		return;
565 	}
566 
567 	// Copy state into existing tco
568 	DataFile dataFile( value.toUtf8() );
569 	MidiTime pos = m_tco->startPosition();
570 	QDomElement tcos = dataFile.content().firstChildElement( "tcos" );
571 	m_tco->restoreState( tcos.firstChildElement().firstChildElement() );
572 	m_tco->movePosition( pos );
573 	AutomationPattern::resolveAllIDs();
574 	de->accept();
575 }
576 
577 
578 
579 
580 /*! \brief Handle a dragged selection leaving our 'airspace'.
581  *
582  * \param e The QEvent to watch.
583  */
leaveEvent(QEvent * e)584 void TrackContentObjectView::leaveEvent( QEvent * e )
585 {
586 	if( cursor().shape() != Qt::BitmapCursor )
587 	{
588 		setCursor( QCursor( embed::getIconPixmap( "hand" ), m_mouseHotspotHand.width(), m_mouseHotspotHand.height() ) );
589 	}
590 	if( e != NULL )
591 	{
592 		QWidget::leaveEvent( e );
593 	}
594 }
595 
596 /*! \brief Create a DataFile suitable for copying multiple trackContentObjects.
597  *
598  *	trackContentObjects in the vector are written to the "tcos" node in the
599  *  DataFile.  The trackContentObjectView's initial mouse position is written
600  *  to the "initialMouseX" node in the DataFile.  When dropped on a track,
601  *  this is used to create copies of the TCOs.
602  *
603  * \param tcos The trackContectObjects to save in a DataFile
604  */
createTCODataFiles(const QVector<TrackContentObjectView * > & tcoViews) const605 DataFile TrackContentObjectView::createTCODataFiles(
606     				const QVector<TrackContentObjectView *> & tcoViews) const
607 {
608 	Track * t = m_trackView->getTrack();
609 	TrackContainer * tc = t->trackContainer();
610 	DataFile dataFile( DataFile::DragNDropData );
611 	QDomElement tcoParent = dataFile.createElement( "tcos" );
612 
613 	typedef QVector<TrackContentObjectView *> tcoViewVector;
614 	for( tcoViewVector::const_iterator it = tcoViews.begin();
615 			it != tcoViews.end(); ++it )
616 	{
617 		// Insert into the dom under the "tcos" element
618 		Track* tcoTrack = ( *it )->m_trackView->getTrack();
619 		int trackIndex = tc->tracks().indexOf( tcoTrack );
620 		QDomElement tcoElement = dataFile.createElement( "tco" );
621 		tcoElement.setAttribute( "trackIndex", trackIndex );
622 		tcoElement.setAttribute( "trackType", tcoTrack->type() );
623 		tcoElement.setAttribute( "trackName", tcoTrack->name() );
624 		( *it )->m_tco->saveState( dataFile, tcoElement );
625 		tcoParent.appendChild( tcoElement );
626 	}
627 
628 	dataFile.content().appendChild( tcoParent );
629 
630 	// Add extra metadata needed for calculations later
631 	int initialTrackIndex = tc->tracks().indexOf( t );
632 	if( initialTrackIndex < 0 )
633 	{
634 		printf("Failed to find selected track in the TrackContainer.\n");
635 		return dataFile;
636 	}
637 	QDomElement metadata = dataFile.createElement( "copyMetadata" );
638 	// initialTrackIndex is the index of the track that was touched
639 	metadata.setAttribute( "initialTrackIndex", initialTrackIndex );
640 	metadata.setAttribute( "trackContainerId", tc->id() );
641 	// grabbedTCOPos is the pos of the tact containing the TCO we grabbed
642 	metadata.setAttribute( "grabbedTCOPos", m_tco->startPosition() );
643 
644 	dataFile.content().appendChild( metadata );
645 
646 	return dataFile;
647 }
648 
649 /*! \brief Handle a mouse press on this trackContentObjectView.
650  *
651  *  Handles the various ways in which a trackContentObjectView can be
652  *  used with a click of a mouse button.
653  *
654  *  * If our container supports rubber band selection then handle
655  *    selection events.
656  *  * or if shift-left button, add this object to the selection
657  *  * or if ctrl-left button, start a drag-copy event
658  *  * or if just plain left button, resize if we're resizeable
659  *  * or if ctrl-middle button, mute the track content object
660  *  * or if middle button, maybe delete the track content object.
661  *
662  * \param me The QMouseEvent to handle.
663  */
mousePressEvent(QMouseEvent * me)664 void TrackContentObjectView::mousePressEvent( QMouseEvent * me )
665 {
666 	setInitialMousePos( me->pos() );
667 	if( m_trackView->trackContainerView()->allowRubberband() == true &&
668 	    me->button() == Qt::LeftButton )
669 	{
670 		if( m_trackView->trackContainerView()->rubberBandActive() == true )
671 		{
672 			// Propagate to trackView for rubberbanding
673 			selectableObject::mousePressEvent( me );
674 		}
675 		else if ( me->modifiers() & Qt::ControlModifier )
676 		{
677 			if( isSelected() == true )
678 			{
679 				m_action = CopySelection;
680 			}
681 			else
682 			{
683 				m_action = ToggleSelected;
684 			}
685 		}
686 		else if( !me->modifiers() )
687 		{
688 			if( isSelected() == true )
689 			{
690 				m_action = MoveSelection;
691 			}
692 		}
693 	}
694 	else if( me->button() == Qt::LeftButton &&
695 			 me->modifiers() & Qt::ControlModifier )
696 	{
697 		// start drag-action
698 		QVector<TrackContentObjectView *> tcoViews;
699 		tcoViews.push_back( this );
700 		DataFile dataFile = createTCODataFiles( tcoViews );
701 		QPixmap thumbnail = QPixmap::grabWidget( this ).scaled(
702 						128, 128,
703 						Qt::KeepAspectRatio,
704 						Qt::SmoothTransformation );
705 		new StringPairDrag( QString( "tco_%1" ).arg(
706 						m_tco->getTrack()->type() ),
707 					dataFile.toString(), thumbnail, this );
708 	}
709 	else if( me->button() == Qt::LeftButton &&
710 		/* (me->modifiers() & Qt::ShiftModifier) &&*/
711 							fixedTCOs() == false )
712 	{
713 		m_tco->addJournalCheckPoint();
714 
715 		// move or resize
716 		m_tco->setJournalling( false );
717 
718 		setInitialMousePos( me->pos() );
719 
720 		if( m_tco->getAutoResize() || me->x() < width() - RESIZE_GRIP_WIDTH )
721 		{
722 			m_action = Move;
723 			setCursor( Qt::SizeAllCursor );
724 			delete m_hint;
725 			m_hint = TextFloat::displayMessage( tr( "Hint" ),
726 					tr( "Press <%1> and drag to make "
727 							"a copy." ).arg(
728 								#ifdef LMMS_BUILD_APPLE
729 								"⌘"),
730 								#else
731 								"Ctrl"),
732 								#endif
733 					embed::getIconPixmap( "hint" ), 0 );
734 			s_textFloat->setTitle( tr( "Current position" ) );
735 			s_textFloat->setText( QString( "%1:%2" ).
736 					arg( m_tco->startPosition().getTact() + 1 ).
737 					arg( m_tco->startPosition().getTicks() %
738 							MidiTime::ticksPerTact() ) );
739 			s_textFloat->moveGlobal( this, QPoint( width() + 2, height() + 2 ) );
740 		}
741 		else
742 		{
743 			m_action = Resize;
744 			setCursor( Qt::SizeHorCursor );
745 			delete m_hint;
746 			m_hint = TextFloat::displayMessage( tr( "Hint" ),
747 					tr( "Press <%1> for free "
748 							"resizing." ).arg(
749 								#ifdef LMMS_BUILD_APPLE
750 								"⌘"),
751 								#else
752 								"Ctrl"),
753 								#endif
754 					embed::getIconPixmap( "hint" ), 0 );
755 			s_textFloat->setTitle( tr( "Current length" ) );
756 			s_textFloat->setText( tr( "%1:%2 (%3:%4 to %5:%6)" ).
757 					arg( m_tco->length().getTact() ).
758 					arg( m_tco->length().getTicks() %
759 							MidiTime::ticksPerTact() ).
760 					arg( m_tco->startPosition().getTact() + 1 ).
761 					arg( m_tco->startPosition().getTicks() %
762 							MidiTime::ticksPerTact() ).
763 					arg( m_tco->endPosition().getTact() + 1 ).
764 					arg( m_tco->endPosition().getTicks() %
765 							MidiTime::ticksPerTact() ) );
766 			s_textFloat->moveGlobal( this, QPoint( width() + 2, height() + 2) );
767 		}
768 //		s_textFloat->reparent( this );
769 		s_textFloat->show();
770 	}
771 	else if( me->button() == Qt::RightButton )
772 	{
773 		if( me->modifiers() & Qt::ControlModifier )
774 		{
775 			m_tco->toggleMute();
776 		}
777 		else if( me->modifiers() & Qt::ShiftModifier && fixedTCOs() == false )
778 		{
779 			remove();
780 		}
781 	}
782 	else if( me->button() == Qt::MidButton )
783 	{
784 		if( me->modifiers() & Qt::ControlModifier )
785 		{
786 			m_tco->toggleMute();
787 		}
788 		else if( fixedTCOs() == false )
789 		{
790 			remove();
791 		}
792 	}
793 }
794 
795 
796 
797 
798 /*! \brief Handle a mouse movement (drag) on this trackContentObjectView.
799  *
800  *  Handles the various ways in which a trackContentObjectView can be
801  *  used with a mouse drag.
802  *
803  *  * If in move mode, move ourselves in the track,
804  *  * or if in move-selection mode, move the entire selection,
805  *  * or if in resize mode, resize ourselves,
806  *  * otherwise ???
807  *
808  * \param me The QMouseEvent to handle.
809  * \todo what does the final else case do here?
810  */
mouseMoveEvent(QMouseEvent * me)811 void TrackContentObjectView::mouseMoveEvent( QMouseEvent * me )
812 {
813 	if( m_action == CopySelection )
814 	{
815 		if( mouseMovedDistance( me, 2 ) == true &&
816 		    m_trackView->trackContainerView()->allowRubberband() == true &&
817 		    m_trackView->trackContainerView()->rubberBandActive() == false &&
818 		    ( me->modifiers() & Qt::ControlModifier ) )
819 		{
820 			// Clear the action here because mouseReleaseEvent will not get
821 			// triggered once we go into drag.
822 			m_action = NoAction;
823 
824 			// Collect all selected TCOs
825 			QVector<TrackContentObjectView *> tcoViews;
826 			QVector<selectableObject *> so =
827 				m_trackView->trackContainerView()->selectedObjects();
828 			for( QVector<selectableObject *>::iterator it = so.begin();
829 					it != so.end(); ++it )
830 			{
831 				TrackContentObjectView * tcov =
832 					dynamic_cast<TrackContentObjectView *>( *it );
833 				if( tcov != NULL )
834 				{
835 					tcoViews.push_back( tcov );
836 				}
837 			}
838 
839 			// Write the TCOs to the DataFile for copying
840 			DataFile dataFile = createTCODataFiles( tcoViews );
841 
842 			// TODO -- thumbnail for all selected
843 			QPixmap thumbnail = QPixmap::grabWidget( this ).scaled(
844 				128, 128,
845 				Qt::KeepAspectRatio,
846 				Qt::SmoothTransformation );
847 			new StringPairDrag( QString( "tco_%1" ).arg(
848 								m_tco->getTrack()->type() ),
849 								dataFile.toString(), thumbnail, this );
850 		}
851 	}
852 
853 	if( me->modifiers() & Qt::ControlModifier )
854 	{
855 		delete m_hint;
856 		m_hint = NULL;
857 	}
858 
859 	const float ppt = m_trackView->trackContainerView()->pixelsPerTact();
860 	if( m_action == Move )
861 	{
862 		const int x = mapToParent( me->pos() ).x() - m_initialMousePos.x();
863 		MidiTime t = qMax( 0, (int)
864 			m_trackView->trackContainerView()->currentPosition()+
865 				static_cast<int>( x * MidiTime::ticksPerTact() /
866 									ppt ) );
867 		if( ! ( me->modifiers() & Qt::ControlModifier )
868 		   && me->button() == Qt::NoButton )
869 		{
870 			t = t.toNearestTact();
871 		}
872 		m_tco->movePosition( t );
873 		m_trackView->getTrackContentWidget()->changePosition();
874 		s_textFloat->setText( QString( "%1:%2" ).
875 				arg( m_tco->startPosition().getTact() + 1 ).
876 				arg( m_tco->startPosition().getTicks() %
877 						MidiTime::ticksPerTact() ) );
878 		s_textFloat->moveGlobal( this, QPoint( width() + 2, height() + 2 ) );
879 	}
880 	else if( m_action == MoveSelection )
881 	{
882 		const int dx = me->x() - m_initialMousePos.x();
883 		const bool snap = !(me->modifiers() & Qt::AltModifier) &&
884 					me->button() == Qt::NoButton;
885 		QVector<selectableObject *> so =
886 			m_trackView->trackContainerView()->selectedObjects();
887 		QVector<TrackContentObject *> tcos;
888 		int smallestPos = 0;
889 		MidiTime dtick = MidiTime( static_cast<int>( dx *
890 					MidiTime::ticksPerTact() / ppt ) );
891 		if( snap )
892 		{
893 			dtick = dtick.toNearestTact();
894 		}
895 		// find out smallest position of all selected objects for not
896 		// moving an object before zero
897 		for( QVector<selectableObject *>::iterator it = so.begin();
898 							it != so.end(); ++it )
899 		{
900 			TrackContentObjectView * tcov =
901 				dynamic_cast<TrackContentObjectView *>( *it );
902 			if( tcov == NULL )
903 			{
904 				continue;
905 			}
906 			TrackContentObject * tco = tcov->m_tco;
907 			tcos.push_back( tco );
908 			smallestPos = qMin<int>( smallestPos,
909 					(int)tco->startPosition() + dtick );
910 		}
911 		dtick -= smallestPos;
912 		if( snap )
913 		{
914 			dtick = dtick.toAbsoluteTact(); // round toward 0
915 		}
916 		for( QVector<TrackContentObject *>::iterator it = tcos.begin();
917 							it != tcos.end(); ++it )
918 		{
919 			( *it )->movePosition( ( *it )->startPosition() + dtick );
920 		}
921 	}
922 	else if( m_action == Resize )
923 	{
924 		MidiTime t = qMax( MidiTime::ticksPerTact() / 16, static_cast<int>( me->x() * MidiTime::ticksPerTact() / ppt ) );
925 		if( ! ( me->modifiers() & Qt::ControlModifier ) && me->button() == Qt::NoButton )
926 		{
927 			t = qMax<int>( MidiTime::ticksPerTact(), t.toNearestTact() );
928 		}
929 		m_tco->changeLength( t );
930 		s_textFloat->setText( tr( "%1:%2 (%3:%4 to %5:%6)" ).
931 				arg( m_tco->length().getTact() ).
932 				arg( m_tco->length().getTicks() %
933 						MidiTime::ticksPerTact() ).
934 				arg( m_tco->startPosition().getTact() + 1 ).
935 				arg( m_tco->startPosition().getTicks() %
936 						MidiTime::ticksPerTact() ).
937 				arg( m_tco->endPosition().getTact() + 1 ).
938 				arg( m_tco->endPosition().getTicks() %
939 						MidiTime::ticksPerTact() ) );
940 		s_textFloat->moveGlobal( this, QPoint( width() + 2, height() + 2) );
941 	}
942 	else
943 	{
944 		if( me->x() > width() - RESIZE_GRIP_WIDTH && !me->buttons() && !m_tco->getAutoResize() )
945 		{
946 			setCursor( Qt::SizeHorCursor );
947 		}
948 		else
949 		{
950 			leaveEvent( NULL );
951 		}
952 	}
953 }
954 
955 
956 
957 
958 /*! \brief Handle a mouse release on this trackContentObjectView.
959  *
960  *  If we're in move or resize mode, journal the change as appropriate.
961  *  Then tidy up.
962  *
963  * \param me The QMouseEvent to handle.
964  */
mouseReleaseEvent(QMouseEvent * me)965 void TrackContentObjectView::mouseReleaseEvent( QMouseEvent * me )
966 {
967 	// If the CopySelection was chosen as the action due to mouse movement,
968 	// it will have been cleared.  At this point Toggle is the desired action.
969 	// An active StringPairDrag will prevent this method from being called,
970 	// so a real CopySelection would not have occurred.
971 	if( m_action == CopySelection ||
972 	    ( m_action == ToggleSelected && mouseMovedDistance( me, 2 ) == false ) )
973 	{
974 		setSelected( !isSelected() );
975 	}
976 
977 	if( m_action == Move || m_action == Resize )
978 	{
979 		m_tco->setJournalling( true );
980 	}
981 	m_action = NoAction;
982 	delete m_hint;
983 	m_hint = NULL;
984 	s_textFloat->hide();
985 	leaveEvent( NULL );
986 	selectableObject::mouseReleaseEvent( me );
987 }
988 
989 
990 
991 
992 /*! \brief Set up the context menu for this trackContentObjectView.
993  *
994  *  Set up the various context menu events that can apply to a
995  *  track content object view.
996  *
997  * \param cme The QContextMenuEvent to add the actions to.
998  */
contextMenuEvent(QContextMenuEvent * cme)999 void TrackContentObjectView::contextMenuEvent( QContextMenuEvent * cme )
1000 {
1001 	if( cme->modifiers() )
1002 	{
1003 		return;
1004 	}
1005 
1006 	QMenu contextMenu( this );
1007 	if( fixedTCOs() == false )
1008 	{
1009 		contextMenu.addAction( embed::getIconPixmap( "cancel" ),
1010 					tr( "Delete (middle mousebutton)" ),
1011 						this, SLOT( remove() ) );
1012 		contextMenu.addSeparator();
1013 		contextMenu.addAction( embed::getIconPixmap( "edit_cut" ),
1014 					tr( "Cut" ), this, SLOT( cut() ) );
1015 	}
1016 	contextMenu.addAction( embed::getIconPixmap( "edit_copy" ),
1017 					tr( "Copy" ), m_tco, SLOT( copy() ) );
1018 	contextMenu.addAction( embed::getIconPixmap( "edit_paste" ),
1019 					tr( "Paste" ), m_tco, SLOT( paste() ) );
1020 	contextMenu.addSeparator();
1021 	contextMenu.addAction( embed::getIconPixmap( "muted" ),
1022 				tr( "Mute/unmute (<%1> + middle click)" ).arg(
1023 					#ifdef LMMS_BUILD_APPLE
1024 					"⌘"),
1025 					#else
1026 					"Ctrl"),
1027 					#endif
1028 						m_tco, SLOT( toggleMute() ) );
1029 	constructContextMenu( &contextMenu );
1030 
1031 	contextMenu.exec( QCursor::pos() );
1032 }
1033 
1034 
1035 
1036 
1037 
1038 /*! \brief How many pixels a tact (bar) takes for this trackContentObjectView.
1039  *
1040  * \return the number of pixels per tact (bar).
1041  */
pixelsPerTact()1042 float TrackContentObjectView::pixelsPerTact()
1043 {
1044 	return m_trackView->trackContainerView()->pixelsPerTact();
1045 }
1046 
1047 
1048 
1049 
1050 /*! \brief Detect whether the mouse moved more than n pixels on screen.
1051  *
1052  * \param _me The QMouseEvent.
1053  * \param distance The threshold distance that the mouse has moved to return true.
1054  */
mouseMovedDistance(QMouseEvent * me,int distance)1055 bool TrackContentObjectView::mouseMovedDistance( QMouseEvent * me, int distance )
1056 {
1057 	QPoint dPos = mapToGlobal( me->pos() ) - m_initialMouseGlobalPos;
1058 	const int pixelsMoved = dPos.manhattanLength();
1059 	return ( pixelsMoved > distance || pixelsMoved < -distance );
1060 }
1061 
1062 
1063 
1064 
1065 // ===========================================================================
1066 // trackContentWidget
1067 // ===========================================================================
1068 /*! \brief Create a new trackContentWidget
1069  *
1070  *  Creates a new track content widget for the given track.
1071  *  The content widget comprises the 'grip bar' and the 'tools' button
1072  *  for the track's context menu.
1073  *
1074  * \param parent The parent track.
1075  */
TrackContentWidget(TrackView * parent)1076 TrackContentWidget::TrackContentWidget( TrackView * parent ) :
1077 	QWidget( parent ),
1078 	m_trackView( parent ),
1079 	m_darkerColor( Qt::SolidPattern ),
1080 	m_lighterColor( Qt::SolidPattern ),
1081 	m_gridColor( Qt::SolidPattern ),
1082 	m_embossColor( Qt::SolidPattern )
1083 {
1084 	setAcceptDrops( true );
1085 
1086 	connect( parent->trackContainerView(),
1087 			SIGNAL( positionChanged( const MidiTime & ) ),
1088 			this, SLOT( changePosition( const MidiTime & ) ) );
1089 
1090 	setStyle( QApplication::style() );
1091 
1092 	updateBackground();
1093 }
1094 
1095 
1096 
1097 
1098 /*! \brief Destroy this trackContentWidget
1099  *
1100  *  Destroys the trackContentWidget.
1101  */
~TrackContentWidget()1102 TrackContentWidget::~TrackContentWidget()
1103 {
1104 }
1105 
1106 
1107 
1108 
updateBackground()1109 void TrackContentWidget::updateBackground()
1110 {
1111 	const int tactsPerBar = 4;
1112 	const TrackContainerView * tcv = m_trackView->trackContainerView();
1113 
1114 	// Assume even-pixels-per-tact. Makes sense, should be like this anyways
1115 	int ppt = static_cast<int>( tcv->pixelsPerTact() );
1116 
1117 	int w = ppt * tactsPerBar;
1118 	int h = height();
1119 	m_background = QPixmap( w * 2, height() );
1120 	QPainter pmp( &m_background );
1121 
1122 	pmp.fillRect( 0, 0, w, h, darkerColor() );
1123 	pmp.fillRect( w, 0, w , h, lighterColor() );
1124 
1125 	// draw lines
1126 	// vertical lines
1127 	pmp.setPen( QPen( gridColor(), 1 ) );
1128 	for( float x = 0; x < w * 2; x += ppt )
1129 	{
1130 		pmp.drawLine( QLineF( x, 0.0, x, h ) );
1131 	}
1132 
1133 	pmp.setPen( QPen( embossColor(), 1 ) );
1134 	for( float x = 1.0; x < w * 2; x += ppt )
1135 	{
1136 		pmp.drawLine( QLineF( x, 0.0, x, h ) );
1137 	}
1138 
1139 	// horizontal line
1140 	pmp.setPen( QPen( gridColor(), 1 ) );
1141 	pmp.drawLine( 0, h-1, w*2, h-1 );
1142 
1143 	pmp.end();
1144 
1145 	// Force redraw
1146 	update();
1147 }
1148 
1149 
1150 
1151 
1152 /*! \brief Adds a trackContentObjectView to this widget.
1153  *
1154  *  Adds a(nother) trackContentObjectView to our list of views.  We also
1155  *  check that our position is up-to-date.
1156  *
1157  * \param tcov The trackContentObjectView to add.
1158  */
addTCOView(TrackContentObjectView * tcov)1159 void TrackContentWidget::addTCOView( TrackContentObjectView * tcov )
1160 {
1161 	TrackContentObject * tco = tcov->getTrackContentObject();
1162 
1163 	m_tcoViews.push_back( tcov );
1164 
1165 	tco->saveJournallingState( false );
1166 	changePosition();
1167 	tco->restoreJournallingState();
1168 }
1169 
1170 
1171 
1172 
1173 /*! \brief Removes the given trackContentObjectView to this widget.
1174  *
1175  *  Removes the given trackContentObjectView from our list of views.
1176  *
1177  * \param tcov The trackContentObjectView to add.
1178  */
removeTCOView(TrackContentObjectView * tcov)1179 void TrackContentWidget::removeTCOView( TrackContentObjectView * tcov )
1180 {
1181 	tcoViewVector::iterator it = qFind( m_tcoViews.begin(),
1182 						m_tcoViews.end(),
1183 						tcov );
1184 	if( it != m_tcoViews.end() )
1185 	{
1186 		m_tcoViews.erase( it );
1187 		Engine::getSong()->setModified();
1188 	}
1189 }
1190 
1191 
1192 
1193 
1194 /*! \brief Update ourselves by updating all the tCOViews attached.
1195  *
1196  */
update()1197 void TrackContentWidget::update()
1198 {
1199 	for( tcoViewVector::iterator it = m_tcoViews.begin();
1200 				it != m_tcoViews.end(); ++it )
1201 	{
1202 		( *it )->setFixedHeight( height() - 1 );
1203 		( *it )->update();
1204 	}
1205 	QWidget::update();
1206 }
1207 
1208 
1209 
1210 
1211 // resposible for moving track-content-widgets to appropriate position after
1212 // change of visible viewport
1213 /*! \brief Move the trackContentWidget to a new place in time
1214  *
1215  * \param newPos The MIDI time to move to.
1216  */
changePosition(const MidiTime & newPos)1217 void TrackContentWidget::changePosition( const MidiTime & newPos )
1218 {
1219 	if( m_trackView->trackContainerView() == gui->getBBEditor()->trackContainerView() )
1220 	{
1221 		const int curBB = Engine::getBBTrackContainer()->currentBB();
1222 		setUpdatesEnabled( false );
1223 
1224 		// first show TCO for current BB...
1225 		for( tcoViewVector::iterator it = m_tcoViews.begin();
1226 						it != m_tcoViews.end(); ++it )
1227 		{
1228 		if( ( *it )->getTrackContentObject()->
1229                             startPosition().getTact() == curBB )
1230 			{
1231 				( *it )->move( 0, ( *it )->y() );
1232 				( *it )->raise();
1233 				( *it )->show();
1234 			}
1235 			else
1236 			{
1237 				( *it )->lower();
1238 			}
1239 		}
1240 		// ...then hide others to avoid flickering
1241 		for( tcoViewVector::iterator it = m_tcoViews.begin();
1242 					it != m_tcoViews.end(); ++it )
1243 		{
1244 			if( ( *it )->getTrackContentObject()->
1245 	                            startPosition().getTact() != curBB )
1246 			{
1247 				( *it )->hide();
1248 			}
1249 		}
1250 		setUpdatesEnabled( true );
1251 		return;
1252 	}
1253 
1254 	MidiTime pos = newPos;
1255 	if( pos < 0 )
1256 	{
1257 		pos = m_trackView->trackContainerView()->currentPosition();
1258 	}
1259 
1260 	const int begin = pos;
1261 	const int end = endPosition( pos );
1262 	const float ppt = m_trackView->trackContainerView()->pixelsPerTact();
1263 
1264 	setUpdatesEnabled( false );
1265 	for( tcoViewVector::iterator it = m_tcoViews.begin();
1266 						it != m_tcoViews.end(); ++it )
1267 	{
1268 		TrackContentObjectView * tcov = *it;
1269 		TrackContentObject * tco = tcov->getTrackContentObject();
1270 
1271 		tco->changeLength( tco->length() );
1272 
1273 		const int ts = tco->startPosition();
1274 		const int te = tco->endPosition()-3;
1275 		if( ( ts >= begin && ts <= end ) ||
1276 			( te >= begin && te <= end ) ||
1277 			( ts <= begin && te >= end ) )
1278 		{
1279 			tcov->move( static_cast<int>( ( ts - begin ) * ppt /
1280 						MidiTime::ticksPerTact() ),
1281 								tcov->y() );
1282 			if( !tcov->isVisible() )
1283 			{
1284 				tcov->show();
1285 			}
1286 		}
1287 		else
1288 		{
1289 			tcov->move( -tcov->width()-10, tcov->y() );
1290 		}
1291 	}
1292 	setUpdatesEnabled( true );
1293 
1294 	// redraw background
1295 //	update();
1296 }
1297 
1298 
1299 
1300 
1301 /*! \brief Return the position of the trackContentWidget in Tacts.
1302  *
1303  * \param mouseX the mouse's current X position in pixels.
1304  */
getPosition(int mouseX)1305 MidiTime TrackContentWidget::getPosition( int mouseX )
1306 {
1307 	TrackContainerView * tv = m_trackView->trackContainerView();
1308 	return MidiTime( tv->currentPosition() +
1309 					 mouseX *
1310 					 MidiTime::ticksPerTact() /
1311 					 static_cast<int>( tv->pixelsPerTact() ) );
1312 }
1313 
1314 
1315 
1316 
1317 /*! \brief Respond to a drag enter event on the trackContentWidget
1318  *
1319  * \param dee the Drag Enter Event to respond to
1320  */
dragEnterEvent(QDragEnterEvent * dee)1321 void TrackContentWidget::dragEnterEvent( QDragEnterEvent * dee )
1322 {
1323 	MidiTime tcoPos = getPosition( dee->pos().x() );
1324 	if( canPasteSelection( tcoPos, dee ) == false )
1325 	{
1326 		dee->ignore();
1327 	}
1328 	else
1329 	{
1330 		StringPairDrag::processDragEnterEvent( dee, "tco_" +
1331 						QString::number( getTrack()->type() ) );
1332 	}
1333 }
1334 
1335 
1336 
1337 
1338 /*! \brief Returns whether a selection of TCOs can be pasted into this
1339  *
1340  * \param tcoPos the position of the TCO slot being pasted on
1341  * \param de the DropEvent generated
1342  */
canPasteSelection(MidiTime tcoPos,const QDropEvent * de)1343 bool TrackContentWidget::canPasteSelection( MidiTime tcoPos, const QDropEvent* de )
1344 {
1345 	const QMimeData * mimeData = de->mimeData();
1346 
1347 	Track * t = getTrack();
1348 	QString type = StringPairDrag::decodeMimeKey( mimeData );
1349 	QString value = StringPairDrag::decodeMimeValue( mimeData );
1350 
1351 	// We can only paste into tracks of the same type
1352 	if( type != ( "tco_" + QString::number( t->type() ) ) ||
1353 		m_trackView->trackContainerView()->fixedTCOs() == true )
1354 	{
1355 		return false;
1356 	}
1357 
1358 	// value contains XML needed to reconstruct TCOs and place them
1359 	DataFile dataFile( value.toUtf8() );
1360 
1361 	// Extract the metadata and which TCO was grabbed
1362 	QDomElement metadata = dataFile.content().firstChildElement( "copyMetadata" );
1363 	QDomAttr tcoPosAttr = metadata.attributeNode( "grabbedTCOPos" );
1364 	MidiTime grabbedTCOPos = tcoPosAttr.value().toInt();
1365 	MidiTime grabbedTCOTact = MidiTime( grabbedTCOPos.getTact(), 0 );
1366 
1367 	// Extract the track index that was originally clicked
1368 	QDomAttr tiAttr = metadata.attributeNode( "initialTrackIndex" );
1369 	const int initialTrackIndex = tiAttr.value().toInt();
1370 
1371 	// Get the current track's index
1372 	const TrackContainer::TrackList tracks = t->trackContainer()->tracks();
1373 	const int currentTrackIndex = tracks.indexOf( t );
1374 
1375 	// Don't paste if we're on the same tact
1376 	auto sourceTrackContainerId = metadata.attributeNode( "trackContainerId" ).value().toUInt();
1377 	if( de->source() && sourceTrackContainerId == t->trackContainer()->id() &&
1378 			tcoPos == grabbedTCOTact && currentTrackIndex == initialTrackIndex )
1379 	{
1380 		return false;
1381 	}
1382 
1383 	// Extract the tco data
1384 	QDomElement tcoParent = dataFile.content().firstChildElement( "tcos" );
1385 	QDomNodeList tcoNodes = tcoParent.childNodes();
1386 
1387 	// Determine if all the TCOs will land on a valid track
1388 	for( int i = 0; i < tcoNodes.length(); i++ )
1389 	{
1390 		QDomElement tcoElement = tcoNodes.item( i ).toElement();
1391 		int trackIndex = tcoElement.attributeNode( "trackIndex" ).value().toInt();
1392 		int finalTrackIndex = trackIndex + currentTrackIndex - initialTrackIndex;
1393 
1394 		// Track must be in TrackContainer's tracks
1395 		if( finalTrackIndex < 0 || finalTrackIndex >= tracks.size() )
1396 		{
1397 			return false;
1398 		}
1399 
1400 		// Track must be of the same type
1401 		auto startTrackType = tcoElement.attributeNode("trackType").value().toInt();
1402 		Track * endTrack = tracks.at( finalTrackIndex );
1403 		if( startTrackType != endTrack->type() )
1404 		{
1405 			return false;
1406 		}
1407 	}
1408 
1409 	return true;
1410 }
1411 
1412 /*! \brief Pastes a selection of TCOs onto the track
1413  *
1414  * \param tcoPos the position of the TCO slot being pasted on
1415  * \param de the DropEvent generated
1416  */
pasteSelection(MidiTime tcoPos,QDropEvent * de)1417 bool TrackContentWidget::pasteSelection( MidiTime tcoPos, QDropEvent * de )
1418 {
1419 	if( canPasteSelection( tcoPos, de ) == false )
1420 	{
1421 		return false;
1422 	}
1423 
1424 	QString type = StringPairDrag::decodeKey( de );
1425 	QString value = StringPairDrag::decodeValue( de );
1426 
1427 	getTrack()->addJournalCheckPoint();
1428 
1429 	// value contains XML needed to reconstruct TCOs and place them
1430 	DataFile dataFile( value.toUtf8() );
1431 
1432 	// Extract the tco data
1433 	QDomElement tcoParent = dataFile.content().firstChildElement( "tcos" );
1434 	QDomNodeList tcoNodes = tcoParent.childNodes();
1435 
1436 	// Extract the track index that was originally clicked
1437 	QDomElement metadata = dataFile.content().firstChildElement( "copyMetadata" );
1438 	QDomAttr tiAttr = metadata.attributeNode( "initialTrackIndex" );
1439 	int initialTrackIndex = tiAttr.value().toInt();
1440 	QDomAttr tcoPosAttr = metadata.attributeNode( "grabbedTCOPos" );
1441 	MidiTime grabbedTCOPos = tcoPosAttr.value().toInt();
1442 	MidiTime grabbedTCOTact = MidiTime( grabbedTCOPos.getTact(), 0 );
1443 
1444 	// Snap the mouse position to the beginning of the dropped tact, in ticks
1445 	const TrackContainer::TrackList tracks = getTrack()->trackContainer()->tracks();
1446 	const int currentTrackIndex = tracks.indexOf( getTrack() );
1447 
1448 	bool allowRubberband = m_trackView->trackContainerView()->allowRubberband();
1449 
1450 	// Unselect the old group
1451 	if( allowRubberband == true )
1452 	{
1453 		const QVector<selectableObject *> so =
1454 			m_trackView->trackContainerView()->selectedObjects();
1455 		for( QVector<selectableObject *>::const_iterator it = so.begin();
1456 		    	it != so.end(); ++it )
1457 		{
1458 			( *it )->setSelected( false );
1459 		}
1460 	}
1461 
1462 	// TODO -- Need to draw the hovericon either way, or ghost the TCOs
1463 	// onto their final position.
1464 
1465 	for( int i = 0; i<tcoNodes.length(); i++ )
1466 	{
1467 		QDomElement outerTCOElement = tcoNodes.item( i ).toElement();
1468 		QDomElement tcoElement = outerTCOElement.firstChildElement();
1469 
1470 		int trackIndex = outerTCOElement.attributeNode( "trackIndex" ).value().toInt();
1471 		int finalTrackIndex = trackIndex + ( currentTrackIndex - initialTrackIndex );
1472 		Track * t = tracks.at( finalTrackIndex );
1473 
1474 		// Compute the final position by moving the tco's pos by
1475 		// the number of tacts between the first TCO and the mouse drop TCO
1476 		MidiTime oldPos = tcoElement.attributeNode( "pos" ).value().toInt();
1477 		MidiTime offset = oldPos - MidiTime( oldPos.getTact(), 0 );
1478 		MidiTime oldTact = MidiTime( oldPos.getTact(), 0 );
1479 		MidiTime delta = offset + ( oldTact - grabbedTCOTact );
1480 		MidiTime pos = tcoPos + delta;
1481 
1482 		TrackContentObject * tco = t->createTCO( pos );
1483 		tco->restoreState( tcoElement );
1484 		tco->movePosition( pos );
1485 		if( allowRubberband == true )
1486 		{
1487 			tco->selectViewOnCreate( true );
1488 		}
1489 		//check tco name, if the same as source track name dont copy
1490 		QString sourceTrackName = outerTCOElement.attributeNode( "trackName" ).value();
1491 		if( tco->name() == sourceTrackName )
1492 		{
1493 			tco->setName( "" );
1494 		}
1495 	}
1496 
1497 	AutomationPattern::resolveAllIDs();
1498 
1499 	return true;
1500 }
1501 
1502 
1503 /*! \brief Respond to a drop event on the trackContentWidget
1504  *
1505  * \param de the Drop Event to respond to
1506  */
dropEvent(QDropEvent * de)1507 void TrackContentWidget::dropEvent( QDropEvent * de )
1508 {
1509 	MidiTime tcoPos = MidiTime( getPosition( de->pos().x() ).getTact(), 0 );
1510 	if( pasteSelection( tcoPos, de ) == true )
1511 	{
1512 		de->accept();
1513 	}
1514 }
1515 
1516 
1517 
1518 
1519 /*! \brief Respond to a mouse press on the trackContentWidget
1520  *
1521  * \param me the mouse press event to respond to
1522  */
mousePressEvent(QMouseEvent * me)1523 void TrackContentWidget::mousePressEvent( QMouseEvent * me )
1524 {
1525 	if( m_trackView->trackContainerView()->allowRubberband() == true )
1526 	{
1527 		QWidget::mousePressEvent( me );
1528 	}
1529 	else if( me->modifiers() & Qt::ShiftModifier )
1530 	{
1531 		QWidget::mousePressEvent( me );
1532 	}
1533 	else if( me->button() == Qt::LeftButton &&
1534 			!m_trackView->trackContainerView()->fixedTCOs() )
1535 	{
1536 		getTrack()->addJournalCheckPoint();
1537 		const MidiTime pos = getPosition( me->x() ).getTact() *
1538 						MidiTime::ticksPerTact();
1539 		TrackContentObject * tco = getTrack()->createTCO( pos );
1540 
1541 		tco->saveJournallingState( false );
1542 		tco->movePosition( pos );
1543 		tco->restoreJournallingState();
1544 	}
1545 }
1546 
1547 
1548 
1549 
1550 /*! \brief Repaint the trackContentWidget on command
1551  *
1552  * \param pe the Paint Event to respond to
1553  */
paintEvent(QPaintEvent * pe)1554 void TrackContentWidget::paintEvent( QPaintEvent * pe )
1555 {
1556 	// Assume even-pixels-per-tact. Makes sense, should be like this anyways
1557 	const TrackContainerView * tcv = m_trackView->trackContainerView();
1558 	int ppt = static_cast<int>( tcv->pixelsPerTact() );
1559 	QPainter p( this );
1560 	// Don't draw background on BB-Editor
1561 	if( m_trackView->trackContainerView() != gui->getBBEditor()->trackContainerView() )
1562 	{
1563 		p.drawTiledPixmap( rect(), m_background, QPoint(
1564 				tcv->currentPosition().getTact() * ppt, 0 ) );
1565 	}
1566 }
1567 
1568 
1569 
1570 
1571 /*! \brief Updates the background tile pixmap on size changes.
1572  *
1573  * \param resizeEvent the resize event to pass to base class
1574  */
resizeEvent(QResizeEvent * resizeEvent)1575 void TrackContentWidget::resizeEvent( QResizeEvent * resizeEvent )
1576 {
1577 	// Update backgroud
1578 	updateBackground();
1579 	// Force redraw
1580 	QWidget::resizeEvent( resizeEvent );
1581 }
1582 
1583 
1584 
1585 
1586 /*! \brief Return the track shown by the trackContentWidget
1587  *
1588  */
getTrack()1589 Track * TrackContentWidget::getTrack()
1590 {
1591 	return m_trackView->getTrack();
1592 }
1593 
1594 
1595 
1596 
1597 /*! \brief Return the end position of the trackContentWidget in Tacts.
1598  *
1599  * \param posStart the starting position of the Widget (from getPosition())
1600  */
endPosition(const MidiTime & posStart)1601 MidiTime TrackContentWidget::endPosition( const MidiTime & posStart )
1602 {
1603 	const float ppt = m_trackView->trackContainerView()->pixelsPerTact();
1604 	const int w = width();
1605 	return posStart + static_cast<int>( w * MidiTime::ticksPerTact() / ppt );
1606 }
1607 
1608 
1609 
1610 
1611 // qproperty access methods
1612 //! \brief CSS theming qproperty access method
darkerColor() const1613 QBrush TrackContentWidget::darkerColor() const
1614 { return m_darkerColor; }
1615 
1616 //! \brief CSS theming qproperty access method
lighterColor() const1617 QBrush TrackContentWidget::lighterColor() const
1618 { return m_lighterColor; }
1619 
1620 //! \brief CSS theming qproperty access method
gridColor() const1621 QBrush TrackContentWidget::gridColor() const
1622 { return m_gridColor; }
1623 
1624 //! \brief CSS theming qproperty access method
embossColor() const1625 QBrush TrackContentWidget::embossColor() const
1626 { return m_embossColor; }
1627 
1628 //! \brief CSS theming qproperty access method
setDarkerColor(const QBrush & c)1629 void TrackContentWidget::setDarkerColor( const QBrush & c )
1630 { m_darkerColor = c; }
1631 
1632 //! \brief CSS theming qproperty access method
setLighterColor(const QBrush & c)1633 void TrackContentWidget::setLighterColor( const QBrush & c )
1634 { m_lighterColor = c; }
1635 
1636 //! \brief CSS theming qproperty access method
setGridColor(const QBrush & c)1637 void TrackContentWidget::setGridColor( const QBrush & c )
1638 { m_gridColor = c; }
1639 
1640 //! \brief CSS theming qproperty access method
setEmbossColor(const QBrush & c)1641 void TrackContentWidget::setEmbossColor( const QBrush & c )
1642 { m_embossColor = c; }
1643 
1644 
1645 // ===========================================================================
1646 // trackOperationsWidget
1647 // ===========================================================================
1648 
1649 
1650 QPixmap * TrackOperationsWidget::s_grip = NULL;     /*!< grip pixmap */
1651 
1652 
1653 /*! \brief Create a new trackOperationsWidget
1654  *
1655  * The trackOperationsWidget is the grip and the mute button of a track.
1656  *
1657  * \param parent the trackView to contain this widget
1658  */
TrackOperationsWidget(TrackView * parent)1659 TrackOperationsWidget::TrackOperationsWidget( TrackView * parent ) :
1660 	QWidget( parent ),             /*!< The parent widget */
1661 	m_trackView( parent )          /*!< The parent track view */
1662 {
1663 	if( s_grip == NULL )
1664 	{
1665 		s_grip = new QPixmap( embed::getIconPixmap(
1666 							"track_op_grip" ) );
1667 	}
1668 
1669 	ToolTip::add( this, tr( "Press <%1> while clicking on move-grip "
1670 				"to begin a new drag'n'drop-action." ).arg(
1671 					#ifdef LMMS_BUILD_APPLE
1672 					"⌘") );
1673 					#else
1674 					"Ctrl") );
1675 					#endif
1676 
1677 	QMenu * toMenu = new QMenu( this );
1678 	toMenu->setFont( pointSize<9>( toMenu->font() ) );
1679 	connect( toMenu, SIGNAL( aboutToShow() ), this, SLOT( updateMenu() ) );
1680 
1681 
1682 	setObjectName( "automationEnabled" );
1683 
1684 
1685 	m_trackOps = new QPushButton( this );
1686 	m_trackOps->move( 12, 1 );
1687 	m_trackOps->setFocusPolicy( Qt::NoFocus );
1688 	m_trackOps->setMenu( toMenu );
1689 	ToolTip::add( m_trackOps, tr( "Actions for this track" ) );
1690 
1691 
1692 	m_muteBtn = new PixmapButton( this, tr( "Mute" ) );
1693 	m_muteBtn->setActiveGraphic( embed::getIconPixmap( "led_off" ) );
1694 	m_muteBtn->setInactiveGraphic( embed::getIconPixmap( "led_green" ) );
1695 	m_muteBtn->setCheckable( true );
1696 
1697 	m_soloBtn = new PixmapButton( this, tr( "Solo" ) );
1698 	m_soloBtn->setActiveGraphic( embed::getIconPixmap( "led_red" ) );
1699 	m_soloBtn->setInactiveGraphic( embed::getIconPixmap( "led_off" ) );
1700 	m_soloBtn->setCheckable( true );
1701 
1702 	if( ConfigManager::inst()->value( "ui",
1703 					  "compacttrackbuttons" ).toInt() )
1704 	{
1705 		m_muteBtn->move( 46, 0 );
1706 		m_soloBtn->move( 46, 16 );
1707 	}
1708 	else
1709 	{
1710 		m_muteBtn->move( 46, 8 );
1711 		m_soloBtn->move( 62, 8 );
1712 	}
1713 
1714 	m_muteBtn->show();
1715 	ToolTip::add( m_muteBtn, tr( "Mute this track" ) );
1716 
1717 	m_soloBtn->show();
1718 	ToolTip::add( m_soloBtn, tr( "Solo" ) );
1719 
1720 	connect( this, SIGNAL( trackRemovalScheduled( TrackView * ) ),
1721 			m_trackView->trackContainerView(),
1722 				SLOT( deleteTrackView( TrackView * ) ),
1723 							Qt::QueuedConnection );
1724 }
1725 
1726 
1727 
1728 
1729 /*! \brief Destroy an existing trackOperationsWidget
1730  *
1731  */
~TrackOperationsWidget()1732 TrackOperationsWidget::~TrackOperationsWidget()
1733 {
1734 }
1735 
1736 
1737 
1738 
1739 /*! \brief Respond to trackOperationsWidget mouse events
1740  *
1741  *  If it's the left mouse button, and Ctrl is held down, and we're
1742  *  not a Beat+Bassline Editor track, then start a new drag event to
1743  *  copy this track.
1744  *
1745  *  Otherwise, ignore all other events.
1746  *
1747  *  \param me The mouse event to respond to.
1748  */
mousePressEvent(QMouseEvent * me)1749 void TrackOperationsWidget::mousePressEvent( QMouseEvent * me )
1750 {
1751 	if( me->button() == Qt::LeftButton &&
1752 		me->modifiers() & Qt::ControlModifier &&
1753 			m_trackView->getTrack()->type() != Track::BBTrack )
1754 	{
1755 		DataFile dataFile( DataFile::DragNDropData );
1756 		m_trackView->getTrack()->saveState( dataFile, dataFile.content() );
1757 		new StringPairDrag( QString( "track_%1" ).arg(
1758 					m_trackView->getTrack()->type() ),
1759 			dataFile.toString(), QPixmap::grabWidget(
1760 				m_trackView->getTrackSettingsWidget() ),
1761 									this );
1762 	}
1763 	else if( me->button() == Qt::LeftButton )
1764 	{
1765 		// track-widget (parent-widget) initiates track-move
1766 		me->ignore();
1767 	}
1768 }
1769 
1770 
1771 
1772 
1773 /*! \brief Repaint the trackOperationsWidget
1774  *
1775  *  If we're not moving, and in the Beat+Bassline Editor, then turn
1776  *  automation on or off depending on its previous state and show
1777  *  ourselves.
1778  *
1779  *  Otherwise, hide ourselves.
1780  *
1781  *  \todo Flesh this out a bit - is it correct?
1782  *  \param pe The paint event to respond to
1783  */
paintEvent(QPaintEvent * pe)1784 void TrackOperationsWidget::paintEvent( QPaintEvent * pe )
1785 {
1786 	QPainter p( this );
1787 	p.fillRect( rect(), palette().brush(QPalette::Background) );
1788 
1789 	if( m_trackView->isMovingTrack() == false )
1790 	{
1791 		p.drawPixmap( 2, 2, *s_grip );
1792 		m_trackOps->show();
1793 		m_muteBtn->show();
1794 	}
1795 	else
1796 	{
1797 		m_trackOps->hide();
1798 		m_muteBtn->hide();
1799 	}
1800 }
1801 
1802 
1803 
1804 
1805 /*! \brief Clone this track
1806  *
1807  */
cloneTrack()1808 void TrackOperationsWidget::cloneTrack()
1809 {
1810 	TrackContainerView *tcView = m_trackView->trackContainerView();
1811 
1812 	Track *newTrack = m_trackView->getTrack()->clone();
1813 	TrackView *newTrackView = tcView->createTrackView( newTrack );
1814 
1815 	int index = tcView->trackViews().indexOf( m_trackView );
1816 	int i = tcView->trackViews().size();
1817 	while ( i != index + 1 )
1818 	{
1819 		tcView->moveTrackView( newTrackView, i - 1 );
1820 		i--;
1821 	}
1822 }
1823 
1824 
1825 /*! \brief Clear this track - clears all TCOs from the track */
clearTrack()1826 void TrackOperationsWidget::clearTrack()
1827 {
1828 	Track * t = m_trackView->getTrack();
1829 	t->addJournalCheckPoint();
1830 	t->lock();
1831 	t->deleteTCOs();
1832 	t->unlock();
1833 }
1834 
1835 
1836 
1837 /*! \brief Remove this track from the track list
1838  *
1839  */
removeTrack()1840 void TrackOperationsWidget::removeTrack()
1841 {
1842 	emit trackRemovalScheduled( m_trackView );
1843 }
1844 
1845 
1846 
1847 
1848 /*! \brief Update the trackOperationsWidget context menu
1849  *
1850  *  For all track types, we have the Clone and Remove options.
1851  *  For instrument-tracks we also offer the MIDI-control-menu
1852  *  For automation tracks, extra options: turn on/off recording
1853  *  on all TCOs (same should be added for sample tracks when
1854  *  sampletrack recording is implemented)
1855  */
updateMenu()1856 void TrackOperationsWidget::updateMenu()
1857 {
1858 	QMenu * toMenu = m_trackOps->menu();
1859 	toMenu->clear();
1860 	toMenu->addAction( embed::getIconPixmap( "edit_copy", 16, 16 ),
1861 						tr( "Clone this track" ),
1862 						this, SLOT( cloneTrack() ) );
1863 	toMenu->addAction( embed::getIconPixmap( "cancel", 16, 16 ),
1864 						tr( "Remove this track" ),
1865 						this, SLOT( removeTrack() ) );
1866 
1867 	if( ! m_trackView->trackContainerView()->fixedTCOs() )
1868 	{
1869 		toMenu->addAction( tr( "Clear this track" ), this, SLOT( clearTrack() ) );
1870 	}
1871 	if( InstrumentTrackView * trackView = dynamic_cast<InstrumentTrackView *>( m_trackView ) )
1872 	{
1873 		QMenu *fxMenu = trackView->createFxMenu( tr( "FX %1: %2" ), tr( "Assign to new FX Channel" ));
1874 		toMenu->addMenu(fxMenu);
1875 
1876 		toMenu->addSeparator();
1877 		toMenu->addMenu( trackView->midiMenu() );
1878 	}
1879 	if( dynamic_cast<AutomationTrackView *>( m_trackView ) )
1880 	{
1881 		toMenu->addAction( tr( "Turn all recording on" ), this, SLOT( recordingOn() ) );
1882 		toMenu->addAction( tr( "Turn all recording off" ), this, SLOT( recordingOff() ) );
1883 	}
1884 }
1885 
1886 
recordingOn()1887 void TrackOperationsWidget::recordingOn()
1888 {
1889 	AutomationTrackView * atv = dynamic_cast<AutomationTrackView *>( m_trackView );
1890 	if( atv )
1891 	{
1892 		const Track::tcoVector & tcov = atv->getTrack()->getTCOs();
1893 		for( Track::tcoVector::const_iterator it = tcov.begin(); it != tcov.end(); ++it )
1894 		{
1895 			AutomationPattern * ap = dynamic_cast<AutomationPattern *>( *it );
1896 			if( ap ) { ap->setRecording( true ); }
1897 		}
1898 		atv->update();
1899 	}
1900 }
1901 
1902 
recordingOff()1903 void TrackOperationsWidget::recordingOff()
1904 {
1905 	AutomationTrackView * atv = dynamic_cast<AutomationTrackView *>( m_trackView );
1906 	if( atv )
1907 	{
1908 		const Track::tcoVector & tcov = atv->getTrack()->getTCOs();
1909 		for( Track::tcoVector::const_iterator it = tcov.begin(); it != tcov.end(); ++it )
1910 		{
1911 			AutomationPattern * ap = dynamic_cast<AutomationPattern *>( *it );
1912 			if( ap ) { ap->setRecording( false ); }
1913 		}
1914 		atv->update();
1915 	}
1916 }
1917 
1918 
1919 // ===========================================================================
1920 // track
1921 // ===========================================================================
1922 
1923 /*! \brief Create a new (empty) track object
1924  *
1925  *  The track object is the whole track, linking its contents, its
1926  *  automation, name, type, and so forth.
1927  *
1928  * \param type The type of track (Song Editor or Beat+Bassline Editor)
1929  * \param tc The track Container object to encapsulate in this track.
1930  *
1931  * \todo check the definitions of all the properties - are they OK?
1932  */
Track(TrackTypes type,TrackContainer * tc)1933 Track::Track( TrackTypes type, TrackContainer * tc ) :
1934 	Model( tc ),                   /*!< The track Model */
1935 	m_trackContainer( tc ),        /*!< The track container object */
1936 	m_type( type ),                /*!< The track type */
1937 	m_name(),                       /*!< The track's name */
1938 	m_mutedModel( false, this, tr( "Mute" ) ),
1939 					 /*!< For controlling track muting */
1940 	m_soloModel( false, this, tr( "Solo" ) ),
1941 					/*!< For controlling track soloing */
1942 	m_simpleSerializingMode( false ),
1943 	m_trackContentObjects()         /*!< The track content objects (segments) */
1944 {
1945 	m_trackContainer->addTrack( this );
1946 	m_height = -1;
1947 }
1948 
1949 
1950 
1951 
1952 /*! \brief Destroy this track
1953  *
1954  *  If the track container is a Beat+Bassline container, step through
1955  *  its list of tracks and remove us.
1956  *
1957  *  Then delete the TrackContentObject's contents, remove this track from
1958  *  the track container.
1959  *
1960  *  Finally step through this track's automation and forget all of them.
1961  */
~Track()1962 Track::~Track()
1963 {
1964 	lock();
1965 	emit destroyedTrack();
1966 
1967 	while( !m_trackContentObjects.isEmpty() )
1968 	{
1969 		delete m_trackContentObjects.last();
1970 	}
1971 
1972 	m_trackContainer->removeTrack( this );
1973 	unlock();
1974 }
1975 
1976 
1977 
1978 
1979 /*! \brief Create a track based on the given track type and container.
1980  *
1981  *  \param tt The type of track to create
1982  *  \param tc The track container to attach to
1983  */
create(TrackTypes tt,TrackContainer * tc)1984 Track * Track::create( TrackTypes tt, TrackContainer * tc )
1985 {
1986 	Engine::mixer()->requestChangeInModel();
1987 
1988 	Track * t = NULL;
1989 
1990 	switch( tt )
1991 	{
1992 		case InstrumentTrack: t = new ::InstrumentTrack( tc ); break;
1993 		case BBTrack: t = new ::BBTrack( tc ); break;
1994 		case SampleTrack: t = new ::SampleTrack( tc ); break;
1995 //		case EVENT_TRACK:
1996 //		case VIDEO_TRACK:
1997 		case AutomationTrack: t = new ::AutomationTrack( tc ); break;
1998 		case HiddenAutomationTrack:
1999 						t = new ::AutomationTrack( tc, true ); break;
2000 		default: break;
2001 	}
2002 
2003 	if( tc == Engine::getBBTrackContainer() && t )
2004 	{
2005 		t->createTCOsForBB( Engine::getBBTrackContainer()->numOfBBs()
2006 									- 1 );
2007 	}
2008 
2009 	tc->updateAfterTrackAdd();
2010 
2011 	Engine::mixer()->doneChangeInModel();
2012 
2013 	return t;
2014 }
2015 
2016 
2017 
2018 
2019 /*! \brief Create a track inside TrackContainer from track type in a QDomElement and restore state from XML
2020  *
2021  *  \param element The QDomElement containing the type of track to create
2022  *  \param tc The track container to attach to
2023  */
create(const QDomElement & element,TrackContainer * tc)2024 Track * Track::create( const QDomElement & element, TrackContainer * tc )
2025 {
2026 	Engine::mixer()->requestChangeInModel();
2027 
2028 	Track * t = create(
2029 		static_cast<TrackTypes>( element.attribute( "type" ).toInt() ),
2030 									tc );
2031 	if( t != NULL )
2032 	{
2033 		t->restoreState( element );
2034 	}
2035 
2036 	Engine::mixer()->doneChangeInModel();
2037 
2038 	return t;
2039 }
2040 
2041 
2042 
2043 
2044 /*! \brief Clone a track from this track
2045  *
2046  */
clone()2047 Track * Track::clone()
2048 {
2049 	QDomDocument doc;
2050 	QDomElement parent = doc.createElement( "clone" );
2051 	saveState( doc, parent );
2052 	return create( parent.firstChild().toElement(), m_trackContainer );
2053 }
2054 
2055 
2056 
2057 
2058 
2059 
2060 /*! \brief Save this track's settings to file
2061  *
2062  *  We save the track type and its muted state and solo state, then append the track-
2063  *  specific settings.  Then we iterate through the trackContentObjects
2064  *  and save all their states in turn.
2065  *
2066  *  \param doc The QDomDocument to use to save
2067  *  \param element The The QDomElement to save into
2068  *  \todo Does this accurately describe the parameters?  I think not!?
2069  *  \todo Save the track height
2070  */
saveSettings(QDomDocument & doc,QDomElement & element)2071 void Track::saveSettings( QDomDocument & doc, QDomElement & element )
2072 {
2073 	if( !m_simpleSerializingMode )
2074 	{
2075 		element.setTagName( "track" );
2076 	}
2077 	element.setAttribute( "type", type() );
2078 	element.setAttribute( "name", name() );
2079 	m_mutedModel.saveSettings( doc, element, "muted" );
2080 	m_soloModel.saveSettings( doc, element, "solo" );
2081 
2082 	if( m_height >= MINIMAL_TRACK_HEIGHT )
2083 	{
2084 		element.setAttribute( "height", m_height );
2085 	}
2086 
2087 	QDomElement tsDe = doc.createElement( nodeName() );
2088 	// let actual track (InstrumentTrack, bbTrack, sampleTrack etc.) save
2089 	// its settings
2090 	element.appendChild( tsDe );
2091 	saveTrackSpecificSettings( doc, tsDe );
2092 
2093 	if( m_simpleSerializingMode )
2094 	{
2095 		m_simpleSerializingMode = false;
2096 		return;
2097 	}
2098 
2099 	// now save settings of all TCO's
2100 	for( tcoVector::const_iterator it = m_trackContentObjects.begin();
2101 				it != m_trackContentObjects.end(); ++it )
2102 	{
2103 		( *it )->saveState( doc, element );
2104 	}
2105 }
2106 
2107 
2108 
2109 
2110 /*! \brief Load the settings from a file
2111  *
2112  *  We load the track's type and muted state and solo state, then clear out our
2113  *  current TrackContentObject.
2114  *
2115  *  Then we step through the QDomElement's children and load the
2116  *  track-specific settings and trackContentObjects states from it
2117  *  one at a time.
2118  *
2119  *  \param element the QDomElement to load track settings from
2120  *  \todo Load the track height.
2121  */
loadSettings(const QDomElement & element)2122 void Track::loadSettings( const QDomElement & element )
2123 {
2124 	if( element.attribute( "type" ).toInt() != type() )
2125 	{
2126 		qWarning( "Current track-type does not match track-type of "
2127 							"settings-node!\n" );
2128 	}
2129 
2130 	setName( element.hasAttribute( "name" ) ? element.attribute( "name" ) :
2131 			element.firstChild().toElement().attribute( "name" ) );
2132 
2133 	m_mutedModel.loadSettings( element, "muted" );
2134 	m_soloModel.loadSettings( element, "solo" );
2135 
2136 	if( m_simpleSerializingMode )
2137 	{
2138 		QDomNode node = element.firstChild();
2139 		while( !node.isNull() )
2140 		{
2141 			if( node.isElement() && node.nodeName() == nodeName() )
2142 			{
2143 				loadTrackSpecificSettings( node.toElement() );
2144 				break;
2145 			}
2146 			node = node.nextSibling();
2147 		}
2148 		m_simpleSerializingMode = false;
2149 		return;
2150 	}
2151 
2152 	while( !m_trackContentObjects.empty() )
2153 	{
2154 		delete m_trackContentObjects.front();
2155 //		m_trackContentObjects.erase( m_trackContentObjects.begin() );
2156 	}
2157 
2158 	QDomNode node = element.firstChild();
2159 	while( !node.isNull() )
2160 	{
2161 		if( node.isElement() )
2162 		{
2163 			if( node.nodeName() == nodeName() )
2164 			{
2165 				loadTrackSpecificSettings( node.toElement() );
2166 			}
2167 			else if( node.nodeName() != "muted"
2168 			&& node.nodeName() != "solo"
2169 			&& !node.toElement().attribute( "metadata" ).toInt() )
2170 			{
2171 				TrackContentObject * tco = createTCO(
2172 								MidiTime( 0 ) );
2173 				tco->restoreState( node.toElement() );
2174 				saveJournallingState( false );
2175 				restoreJournallingState();
2176 			}
2177 		}
2178 		node = node.nextSibling();
2179 	}
2180 
2181 	if( element.attribute( "height" ).toInt() >= MINIMAL_TRACK_HEIGHT &&
2182 		element.attribute( "height" ).toInt() <= DEFAULT_TRACK_HEIGHT )	// workaround for #3585927, tobydox/2012-11-11
2183 	{
2184 		m_height = element.attribute( "height" ).toInt();
2185 	}
2186 }
2187 
2188 
2189 
2190 
2191 /*! \brief Add another TrackContentObject into this track
2192  *
2193  *  \param tco The TrackContentObject to attach to this track.
2194  */
addTCO(TrackContentObject * tco)2195 TrackContentObject * Track::addTCO( TrackContentObject * tco )
2196 {
2197 	m_trackContentObjects.push_back( tco );
2198 
2199 	emit trackContentObjectAdded( tco );
2200 
2201 	return tco;		// just for convenience
2202 }
2203 
2204 
2205 
2206 
2207 /*! \brief Remove a given TrackContentObject from this track
2208  *
2209  *  \param tco The TrackContentObject to remove from this track.
2210  */
removeTCO(TrackContentObject * tco)2211 void Track::removeTCO( TrackContentObject * tco )
2212 {
2213 	tcoVector::iterator it = qFind( m_trackContentObjects.begin(),
2214 					m_trackContentObjects.end(),
2215 					tco );
2216 	if( it != m_trackContentObjects.end() )
2217 	{
2218 		m_trackContentObjects.erase( it );
2219 		if( Engine::getSong() )
2220 		{
2221 			Engine::getSong()->updateLength();
2222 			Engine::getSong()->setModified();
2223 		}
2224 	}
2225 }
2226 
2227 
2228 /*! \brief Remove all TCOs from this track */
deleteTCOs()2229 void Track::deleteTCOs()
2230 {
2231 	while( ! m_trackContentObjects.isEmpty() )
2232 	{
2233 		delete m_trackContentObjects.first();
2234 	}
2235 }
2236 
2237 
2238 /*! \brief Return the number of trackContentObjects we contain
2239  *
2240  *  \return the number of trackContentObjects we currently contain.
2241  */
numOfTCOs()2242 int Track::numOfTCOs()
2243 {
2244 	return m_trackContentObjects.size();
2245 }
2246 
2247 
2248 
2249 
2250 /*! \brief Get a TrackContentObject by number
2251  *
2252  *  If the TCO number is less than our TCO array size then fetch that
2253  *  numbered object from the array.  Otherwise we warn the user that
2254  *  we've somehow requested a TCO that is too large, and create a new
2255  *  TCO for them.
2256  *  \param tcoNum The number of the TrackContentObject to fetch.
2257  *  \return the given TrackContentObject or a new one if out of range.
2258  *  \todo reject TCO numbers less than zero.
2259  *  \todo if we create a TCO here, should we somehow attach it to the
2260  *     track?
2261  */
getTCO(int tcoNum)2262 TrackContentObject * Track::getTCO( int tcoNum )
2263 {
2264 	if( tcoNum < m_trackContentObjects.size() )
2265 	{
2266 		return m_trackContentObjects[tcoNum];
2267 	}
2268 	printf( "called Track::getTCO( %d ), "
2269 			"but TCO %d doesn't exist\n", tcoNum, tcoNum );
2270 	return createTCO( tcoNum * MidiTime::ticksPerTact() );
2271 
2272 }
2273 
2274 
2275 
2276 
2277 /*! \brief Determine the given TrackContentObject's number in our array.
2278  *
2279  *  \param tco The TrackContentObject to search for.
2280  *  \return its number in our array.
2281  */
getTCONum(const TrackContentObject * tco)2282 int Track::getTCONum( const TrackContentObject * tco )
2283 {
2284 //	for( int i = 0; i < getTrackContentWidget()->numOfTCOs(); ++i )
2285 	tcoVector::iterator it = qFind( m_trackContentObjects.begin(),
2286 					m_trackContentObjects.end(),
2287 					tco );
2288 	if( it != m_trackContentObjects.end() )
2289 	{
2290 /*		if( getTCO( i ) == _tco )
2291 		{
2292 			return i;
2293 		}*/
2294 		return it - m_trackContentObjects.begin();
2295 	}
2296 	qWarning( "Track::getTCONum(...) -> _tco not found!\n" );
2297 	return 0;
2298 }
2299 
2300 
2301 
2302 
2303 /*! \brief Retrieve a list of trackContentObjects that fall within a period.
2304  *
2305  *  Here we're interested in a range of trackContentObjects that intersect
2306  *  the given time period.
2307  *
2308  *  We return the TCOs we find in order by time, earliest TCOs first.
2309  *
2310  *  \param tcoV The list to contain the found trackContentObjects.
2311  *  \param start The MIDI start time of the range.
2312  *  \param end   The MIDI endi time of the range.
2313  */
getTCOsInRange(tcoVector & tcoV,const MidiTime & start,const MidiTime & end)2314 void Track::getTCOsInRange( tcoVector & tcoV, const MidiTime & start,
2315 							const MidiTime & end )
2316 {
2317 	for( TrackContentObject* tco : m_trackContentObjects )
2318 	{
2319 		int s = tco->startPosition();
2320 		int e = tco->endPosition();
2321 		if( ( s <= end ) && ( e >= start ) )
2322 		{
2323 			// TCO is within given range
2324 			// Insert sorted by TCO's position
2325 			tcoV.insert(std::upper_bound(tcoV.begin(), tcoV.end(), tco, TrackContentObject::comparePosition),
2326 						tco);
2327 		}
2328 	}
2329 }
2330 
2331 
2332 
2333 
2334 /*! \brief Swap the position of two trackContentObjects.
2335  *
2336  *  First, we arrange to swap the positions of the two TCOs in the
2337  *  trackContentObjects list.  Then we swap their start times as well.
2338  *
2339  *  \param tcoNum1 The first TrackContentObject to swap.
2340  *  \param tcoNum2 The second TrackContentObject to swap.
2341  */
swapPositionOfTCOs(int tcoNum1,int tcoNum2)2342 void Track::swapPositionOfTCOs( int tcoNum1, int tcoNum2 )
2343 {
2344 	qSwap( m_trackContentObjects[tcoNum1],
2345 					m_trackContentObjects[tcoNum2] );
2346 
2347 	const MidiTime pos = m_trackContentObjects[tcoNum1]->startPosition();
2348 
2349 	m_trackContentObjects[tcoNum1]->movePosition(
2350 			m_trackContentObjects[tcoNum2]->startPosition() );
2351 	m_trackContentObjects[tcoNum2]->movePosition( pos );
2352 }
2353 
2354 
2355 
2356 
createTCOsForBB(int bb)2357 void Track::createTCOsForBB( int bb )
2358 {
2359 	while( numOfTCOs() < bb + 1 )
2360 	{
2361 		MidiTime position = MidiTime( numOfTCOs(), 0 );
2362 		TrackContentObject * tco = createTCO( position );
2363 		tco->movePosition( position );
2364 		tco->changeLength( MidiTime( 1, 0 ) );
2365 	}
2366 }
2367 
2368 
2369 
2370 
2371 /*! \brief Move all the trackContentObjects after a certain time later by one bar.
2372  *
2373  *  \param pos The time at which we want to insert the bar.
2374  *  \todo if we stepped through this list last to first, and the list was
2375  *    in ascending order by TCO time, once we hit a TCO that was earlier
2376  *    than the insert time, we could fall out of the loop early.
2377  */
insertTact(const MidiTime & pos)2378 void Track::insertTact( const MidiTime & pos )
2379 {
2380 	// we'll increase the position of every TCO, positioned behind pos, by
2381 	// one tact
2382 	for( tcoVector::iterator it = m_trackContentObjects.begin();
2383 				it != m_trackContentObjects.end(); ++it )
2384 	{
2385 		if( ( *it )->startPosition() >= pos )
2386 		{
2387 			( *it )->movePosition( (*it)->startPosition() +
2388 						MidiTime::ticksPerTact() );
2389 		}
2390 	}
2391 }
2392 
2393 
2394 
2395 
2396 /*! \brief Move all the trackContentObjects after a certain time earlier by one bar.
2397  *
2398  *  \param pos The time at which we want to remove the bar.
2399  */
removeTact(const MidiTime & pos)2400 void Track::removeTact( const MidiTime & pos )
2401 {
2402 	// we'll decrease the position of every TCO, positioned behind pos, by
2403 	// one tact
2404 	for( tcoVector::iterator it = m_trackContentObjects.begin();
2405 				it != m_trackContentObjects.end(); ++it )
2406 	{
2407 		if( ( *it )->startPosition() >= pos )
2408 		{
2409 			( *it )->movePosition( qMax( ( *it )->startPosition() -
2410 						MidiTime::ticksPerTact(), 0 ) );
2411 		}
2412 	}
2413 }
2414 
2415 
2416 
2417 
2418 /*! \brief Return the length of the entire track in bars
2419  *
2420  *  We step through our list of TCOs and determine their end position,
2421  *  keeping track of the latest time found in ticks.  Then we return
2422  *  that in bars by dividing by the number of ticks per bar.
2423  */
length() const2424 tact_t Track::length() const
2425 {
2426 	// find last end-position
2427 	tick_t last = 0;
2428 	for( tcoVector::const_iterator it = m_trackContentObjects.begin();
2429 				it != m_trackContentObjects.end(); ++it )
2430 	{
2431 		if( Engine::getSong()->isExporting() &&
2432 				( *it )->isMuted() )
2433 		{
2434 			continue;
2435 		}
2436 
2437 		const tick_t cur = ( *it )->endPosition();
2438 		if( cur > last )
2439 		{
2440 			last = cur;
2441 		}
2442 	}
2443 
2444 	return last / MidiTime::ticksPerTact();
2445 }
2446 
2447 
2448 
2449 /*! \brief Invert the track's solo state.
2450  *
2451  *  We have to go through all the tracks determining if any other track
2452  *  is already soloed.  Then we have to save the mute state of all tracks,
2453  *  and set our mute state to on and all the others to off.
2454  */
toggleSolo()2455 void Track::toggleSolo()
2456 {
2457 	const TrackContainer::TrackList & tl = m_trackContainer->tracks();
2458 
2459 	bool soloBefore = false;
2460 	for( TrackContainer::TrackList::const_iterator it = tl.begin();
2461 							it != tl.end(); ++it )
2462 	{
2463 		if( *it != this )
2464 		{
2465 			if( ( *it )->m_soloModel.value() )
2466 			{
2467 				soloBefore = true;
2468 				break;
2469 			}
2470 		}
2471 	}
2472 
2473 	const bool solo = m_soloModel.value();
2474 	for( TrackContainer::TrackList::const_iterator it = tl.begin();
2475 							it != tl.end(); ++it )
2476 	{
2477 		if( solo )
2478 		{
2479 			// save mute-state in case no track was solo before
2480 			if( !soloBefore )
2481 			{
2482 				( *it )->m_mutedBeforeSolo = ( *it )->isMuted();
2483 			}
2484 			( *it )->setMuted( *it == this ? false : true );
2485 			if( *it != this )
2486 			{
2487 				( *it )->m_soloModel.setValue( false );
2488 			}
2489 		}
2490 		else if( !soloBefore )
2491 		{
2492 			( *it )->setMuted( ( *it )->m_mutedBeforeSolo );
2493 		}
2494 	}
2495 }
2496 
2497 
2498 
2499 
getMutedModel()2500 BoolModel *Track::getMutedModel()
2501 {
2502 	return &m_mutedModel;
2503 }
2504 
2505 
2506 
2507 
2508 
2509 
2510 // ===========================================================================
2511 // trackView
2512 // ===========================================================================
2513 
2514 /*! \brief Create a new track View.
2515  *
2516  *  The track View is handles the actual display of the track, including
2517  *  displaying its various widgets and the track segments.
2518  *
2519  *  \param track The track to display.
2520  *  \param tcv The track Container View for us to be displayed in.
2521  *  \todo Is my description of these properties correct?
2522  */
TrackView(Track * track,TrackContainerView * tcv)2523 TrackView::TrackView( Track * track, TrackContainerView * tcv ) :
2524 	QWidget( tcv->contentWidget() ),   /*!< The Track Container View's content widget. */
2525 	ModelView( NULL, this ),            /*!< The model view of this track */
2526 	m_track( track ),                  /*!< The track we're displaying */
2527 	m_trackContainerView( tcv ),       /*!< The track Container View we're displayed in */
2528 	m_trackOperationsWidget( this ),    /*!< Our trackOperationsWidget */
2529 	m_trackSettingsWidget( this ),      /*!< Our trackSettingsWidget */
2530 	m_trackContentWidget( this ),       /*!< Our trackContentWidget */
2531 	m_action( NoAction )                /*!< The action we're currently performing */
2532 {
2533 	setAutoFillBackground( true );
2534 	QPalette pal;
2535 	pal.setColor( backgroundRole(), QColor( 32, 36, 40 ) );
2536 	setPalette( pal );
2537 
2538 	m_trackSettingsWidget.setAutoFillBackground( true );
2539 
2540 	QHBoxLayout * layout = new QHBoxLayout( this );
2541 	layout->setMargin( 0 );
2542 	layout->setSpacing( 0 );
2543 	layout->addWidget( &m_trackOperationsWidget );
2544 	layout->addWidget( &m_trackSettingsWidget );
2545 	layout->addWidget( &m_trackContentWidget, 1 );
2546 	setFixedHeight( m_track->getHeight() );
2547 
2548 	resizeEvent( NULL );
2549 
2550 	setAcceptDrops( true );
2551 	setAttribute( Qt::WA_DeleteOnClose, true );
2552 
2553 
2554 	connect( m_track, SIGNAL( destroyedTrack() ), this, SLOT( close() ) );
2555 	connect( m_track,
2556 		SIGNAL( trackContentObjectAdded( TrackContentObject * ) ),
2557 			this, SLOT( createTCOView( TrackContentObject * ) ),
2558 			Qt::QueuedConnection );
2559 
2560 	connect( &m_track->m_mutedModel, SIGNAL( dataChanged() ),
2561 			&m_trackContentWidget, SLOT( update() ) );
2562 
2563 	connect( &m_track->m_soloModel, SIGNAL( dataChanged() ),
2564 			m_track, SLOT( toggleSolo() ), Qt::DirectConnection );
2565 	// create views for already existing TCOs
2566 	for( Track::tcoVector::iterator it =
2567 					m_track->m_trackContentObjects.begin();
2568 			it != m_track->m_trackContentObjects.end(); ++it )
2569 	{
2570 		createTCOView( *it );
2571 	}
2572 
2573 	m_trackContainerView->addTrackView( this );
2574 }
2575 
2576 
2577 
2578 
2579 /*! \brief Destroy this track View.
2580  *
2581  */
~TrackView()2582 TrackView::~TrackView()
2583 {
2584 }
2585 
2586 
2587 
2588 
2589 /*! \brief Resize this track View.
2590  *
2591  *  \param re the Resize Event to handle.
2592  */
resizeEvent(QResizeEvent * re)2593 void TrackView::resizeEvent( QResizeEvent * re )
2594 {
2595 	if( ConfigManager::inst()->value( "ui",
2596 					  "compacttrackbuttons" ).toInt() )
2597 	{
2598 		m_trackOperationsWidget.setFixedSize( TRACK_OP_WIDTH_COMPACT, height() - 1 );
2599 		m_trackSettingsWidget.setFixedSize( DEFAULT_SETTINGS_WIDGET_WIDTH_COMPACT, height() - 1 );
2600 	}
2601 	else
2602 	{
2603 		m_trackOperationsWidget.setFixedSize( TRACK_OP_WIDTH, height() - 1 );
2604 		m_trackSettingsWidget.setFixedSize( DEFAULT_SETTINGS_WIDGET_WIDTH, height() - 1 );
2605 	}
2606 	m_trackContentWidget.setFixedHeight( height() );
2607 }
2608 
2609 
2610 
2611 
2612 /*! \brief Update this track View and all its content objects.
2613  *
2614  */
update()2615 void TrackView::update()
2616 {
2617 	m_trackContentWidget.update();
2618 	if( !m_trackContainerView->fixedTCOs() )
2619 	{
2620 		m_trackContentWidget.changePosition();
2621 	}
2622 	QWidget::update();
2623 }
2624 
2625 
2626 
2627 
2628 /*! \brief Close this track View.
2629  *
2630  */
close()2631 bool TrackView::close()
2632 {
2633 	m_trackContainerView->removeTrackView( this );
2634 	return QWidget::close();
2635 }
2636 
2637 
2638 
2639 
2640 /*! \brief Register that the model of this track View has changed.
2641  *
2642  */
modelChanged()2643 void TrackView::modelChanged()
2644 {
2645 	m_track = castModel<Track>();
2646 	assert( m_track != NULL );
2647 	connect( m_track, SIGNAL( destroyedTrack() ), this, SLOT( close() ) );
2648 	m_trackOperationsWidget.m_muteBtn->setModel( &m_track->m_mutedModel );
2649 	m_trackOperationsWidget.m_soloBtn->setModel( &m_track->m_soloModel );
2650 	ModelView::modelChanged();
2651 	setFixedHeight( m_track->getHeight() );
2652 }
2653 
2654 
2655 
2656 
2657 /*! \brief Start a drag event on this track View.
2658  *
2659  *  \param dee the DragEnterEvent to start.
2660  */
dragEnterEvent(QDragEnterEvent * dee)2661 void TrackView::dragEnterEvent( QDragEnterEvent * dee )
2662 {
2663 	StringPairDrag::processDragEnterEvent( dee, "track_" +
2664 					QString::number( m_track->type() ) );
2665 }
2666 
2667 
2668 
2669 
2670 /*! \brief Accept a drop event on this track View.
2671  *
2672  *  We only accept drop events that are of the same type as this track.
2673  *  If so, we decode the data from the drop event by just feeding it
2674  *  back into the engine as a state.
2675  *
2676  *  \param de the DropEvent to handle.
2677  */
dropEvent(QDropEvent * de)2678 void TrackView::dropEvent( QDropEvent * de )
2679 {
2680 	QString type = StringPairDrag::decodeKey( de );
2681 	QString value = StringPairDrag::decodeValue( de );
2682 	if( type == ( "track_" + QString::number( m_track->type() ) ) )
2683 	{
2684 		// value contains our XML-data so simply create a
2685 		// DataFile which does the rest for us...
2686 		DataFile dataFile( value.toUtf8() );
2687 		Engine::mixer()->requestChangeInModel();
2688 		m_track->restoreState( dataFile.content().firstChild().toElement() );
2689 		Engine::mixer()->doneChangeInModel();
2690 		de->accept();
2691 	}
2692 }
2693 
2694 
2695 
2696 
2697 /*! \brief Handle a mouse press event on this track View.
2698  *
2699  *  If this track container supports rubber band selection, let the
2700  *  widget handle that and don't bother with any other handling.
2701  *
2702  *  If the left mouse button is pressed, we handle two things.  If
2703  *  SHIFT is pressed, then we resize vertically.  Otherwise we start
2704  *  the process of moving this track to a new position.
2705  *
2706  *  Otherwise we let the widget handle the mouse event as normal.
2707  *
2708  *  \param me the MouseEvent to handle.
2709  */
mousePressEvent(QMouseEvent * me)2710 void TrackView::mousePressEvent( QMouseEvent * me )
2711 {
2712 	// If previously dragged too small, restore on shift-leftclick
2713 	if( height() < DEFAULT_TRACK_HEIGHT &&
2714 		me->modifiers() & Qt::ShiftModifier &&
2715 		me->button() == Qt::LeftButton )
2716 	{
2717 		setFixedHeight( DEFAULT_TRACK_HEIGHT );
2718 		m_track->setHeight( DEFAULT_TRACK_HEIGHT );
2719 	}
2720 
2721 
2722 	int widgetTotal = ConfigManager::inst()->value( "ui",
2723 							"compacttrackbuttons" ).toInt()==1 ?
2724 		DEFAULT_SETTINGS_WIDGET_WIDTH_COMPACT + TRACK_OP_WIDTH_COMPACT :
2725 		DEFAULT_SETTINGS_WIDGET_WIDTH + TRACK_OP_WIDTH;
2726 	if( m_trackContainerView->allowRubberband() == true  && me->x() > widgetTotal )
2727 	{
2728 		QWidget::mousePressEvent( me );
2729 	}
2730 	else if( me->button() == Qt::LeftButton )
2731 	{
2732 		if( me->modifiers() & Qt::ShiftModifier )
2733 		{
2734 			m_action = ResizeTrack;
2735 			QCursor::setPos( mapToGlobal( QPoint( me->x(),
2736 								height() ) ) );
2737 			QCursor c( Qt::SizeVerCursor);
2738 			QApplication::setOverrideCursor( c );
2739 		}
2740 		else
2741 		{
2742 			m_action = MoveTrack;
2743 
2744 			QCursor c( Qt::SizeAllCursor );
2745 			QApplication::setOverrideCursor( c );
2746 			// update because in move-mode, all elements in
2747 			// track-op-widgets are hidden as a visual feedback
2748 			m_trackOperationsWidget.update();
2749 		}
2750 
2751 		me->accept();
2752 	}
2753 	else
2754 	{
2755 		QWidget::mousePressEvent( me );
2756 	}
2757 }
2758 
2759 
2760 
2761 
2762 /*! \brief Handle a mouse move event on this track View.
2763  *
2764  *  If this track container supports rubber band selection, let the
2765  *  widget handle that and don't bother with any other handling.
2766  *
2767  *  Otherwise if we've started the move process (from mousePressEvent())
2768  *  then move ourselves into that position, reordering the track list
2769  *  with moveTrackViewUp() and moveTrackViewDown() to suit.  We make a
2770  *  note of this in the undo journal in case the user wants to undo this
2771  *  move.
2772  *
2773  *  Likewise if we've started a resize process, handle this too, making
2774  *  sure that we never go below the minimum track height.
2775  *
2776  *  \param me the MouseEvent to handle.
2777  */
mouseMoveEvent(QMouseEvent * me)2778 void TrackView::mouseMoveEvent( QMouseEvent * me )
2779 {
2780 	int widgetTotal = ConfigManager::inst()->value( "ui",
2781 							"compacttrackbuttons" ).toInt()==1 ?
2782 		DEFAULT_SETTINGS_WIDGET_WIDTH_COMPACT + TRACK_OP_WIDTH_COMPACT :
2783 		DEFAULT_SETTINGS_WIDGET_WIDTH + TRACK_OP_WIDTH;
2784 	if( m_trackContainerView->allowRubberband() == true && me->x() > widgetTotal )
2785 	{
2786 		QWidget::mouseMoveEvent( me );
2787 	}
2788 	else if( m_action == MoveTrack )
2789 	{
2790 		// look which track-widget the mouse-cursor is over
2791 		const int yPos =
2792 			m_trackContainerView->contentWidget()->mapFromGlobal( me->globalPos() ).y();
2793 		const TrackView * trackAtY = m_trackContainerView->trackViewAt( yPos );
2794 
2795 		// debug code
2796 		//	qDebug( "y position %d", yPos );
2797 
2798 		// a track-widget not equal to ourself?
2799 		if( trackAtY != NULL && trackAtY != this )
2800 		{
2801 			// then move us up/down there!
2802 			if( me->y() < 0 )
2803 			{
2804 				m_trackContainerView->moveTrackViewUp( this );
2805 			}
2806 			else
2807 			{
2808 				m_trackContainerView->moveTrackViewDown( this );
2809 			}
2810 		}
2811 	}
2812 	else if( m_action == ResizeTrack )
2813 	{
2814 		setFixedHeight( qMax<int>( me->y(), MINIMAL_TRACK_HEIGHT ) );
2815 		m_trackContainerView->realignTracks();
2816 		m_track->setHeight( height() );
2817 	}
2818 
2819 	if( height() < DEFAULT_TRACK_HEIGHT )
2820 	{
2821 		ToolTip::add( this, m_track->m_name );
2822 	}
2823 }
2824 
2825 
2826 
2827 /*! \brief Handle a mouse release event on this track View.
2828  *
2829  *  \param me the MouseEvent to handle.
2830  */
mouseReleaseEvent(QMouseEvent * me)2831 void TrackView::mouseReleaseEvent( QMouseEvent * me )
2832 {
2833 	m_action = NoAction;
2834 	while( QApplication::overrideCursor() != NULL )
2835 	{
2836 		QApplication::restoreOverrideCursor();
2837 	}
2838 	m_trackOperationsWidget.update();
2839 
2840 	QWidget::mouseReleaseEvent( me );
2841 }
2842 
2843 
2844 
2845 
2846 /*! \brief Repaint this track View.
2847  *
2848  *  \param pe the PaintEvent to start.
2849  */
paintEvent(QPaintEvent * pe)2850 void TrackView::paintEvent( QPaintEvent * pe )
2851 {
2852 	QStyleOption opt;
2853 	opt.initFrom( this );
2854 	QPainter p( this );
2855 	style()->drawPrimitive( QStyle::PE_Widget, &opt, &p, this );
2856 }
2857 
2858 
2859 
2860 
2861 /*! \brief Create a TrackContentObject View in this track View.
2862  *
2863  *  \param tco the TrackContentObject to create the view for.
2864  *  \todo is this a good description for what this method does?
2865  */
createTCOView(TrackContentObject * tco)2866 void TrackView::createTCOView( TrackContentObject * tco )
2867 {
2868 	TrackContentObjectView * tv = tco->createView( this );
2869 	if( tco->getSelectViewOnCreate() == true )
2870 	{
2871 		tv->setSelected( true );
2872 	}
2873 	tco->selectViewOnCreate( false );
2874 }
2875