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