1 /*
2  * TrackContainerView.cpp - view-component for TrackContainer
3  *
4  * Copyright (c) 2004-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
5  *
6  * This file is part of LMMS - https://lmms.io
7  *
8  * This program is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU General Public
10  * License as published by the Free Software Foundation; either
11  * version 2 of the License, or (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public
19  * License along with this program (see COPYING); if not, write to the
20  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21  * Boston, MA 02110-1301 USA.
22  *
23  */
24 
25 #include "TrackContainerView.h"
26 
27 #include <cmath>
28 
29 #include <QApplication>
30 #include <QLayout>
31 #include <QMdiArea>
32 #include <QWheelEvent>
33 
34 #include "TrackContainer.h"
35 #include "BBTrack.h"
36 #include "MainWindow.h"
37 #include "Mixer.h"
38 #include "FileBrowser.h"
39 #include "ImportFilter.h"
40 #include "Instrument.h"
41 #include "Song.h"
42 #include "StringPairDrag.h"
43 #include "GuiApplication.h"
44 #include "PluginFactory.h"
45 
46 using namespace std;
47 
TrackContainerView(TrackContainer * _tc)48 TrackContainerView::TrackContainerView( TrackContainer * _tc ) :
49 	QWidget(),
50 	ModelView( NULL, this ),
51 	JournallingObject(),
52 	SerializingObjectHook(),
53 	m_currentPosition( 0, 0 ),
54 	m_tc( _tc ),
55 	m_trackViews(),
56 	m_scrollArea( new scrollArea( this ) ),
57 	m_ppt( DEFAULT_PIXELS_PER_TACT ),
58 	m_rubberBand( new RubberBand( m_scrollArea ) )
59 {
60 	m_tc->setHook( this );
61 	//keeps the direction of the widget, undepended on the locale
62 	setLayoutDirection( Qt::LeftToRight );
63 	QVBoxLayout * layout = new QVBoxLayout( this );
64 	layout->setMargin( 0 );
65 	layout->setSpacing( 0 );
66 	layout->addWidget( m_scrollArea );
67 
68 	QWidget * scrollContent = new QWidget;
69 	m_scrollLayout = new QVBoxLayout( scrollContent );
70 	m_scrollLayout->setMargin( 0 );
71 	m_scrollLayout->setSpacing( 0 );
72 	m_scrollLayout->setSizeConstraint( QLayout::SetMinAndMaxSize );
73 
74 	m_scrollArea->setWidget( scrollContent );
75 
76 	m_scrollArea->show();
77 	m_rubberBand->hide();
78 	m_rubberBand->setEnabled( false );
79 
80 	setAcceptDrops( true );
81 
82 	connect( Engine::getSong(), SIGNAL( timeSignatureChanged( int, int ) ),
83 						this, SLOT( realignTracks() ) );
84 	connect( m_tc, SIGNAL( trackAdded( Track * ) ),
85 			this, SLOT( createTrackView( Track * ) ),
86 			Qt::QueuedConnection );
87 }
88 
89 
90 
91 
~TrackContainerView()92 TrackContainerView::~TrackContainerView()
93 {
94 	while( !m_trackViews.empty() )
95 	{
96 		delete m_trackViews.takeLast();
97 	}
98 }
99 
100 
101 
102 
103 
saveSettings(QDomDocument & _doc,QDomElement & _this)104 void TrackContainerView::saveSettings( QDomDocument & _doc,
105 							QDomElement & _this )
106 {
107 	MainWindow::saveWidgetState( this, _this );
108 }
109 
110 
111 
112 
loadSettings(const QDomElement & _this)113 void TrackContainerView::loadSettings( const QDomElement & _this )
114 {
115 	MainWindow::restoreWidgetState( this, _this );
116 }
117 
118 
119 
120 
addTrackView(TrackView * _tv)121 TrackView * TrackContainerView::addTrackView( TrackView * _tv )
122 {
123 	m_trackViews.push_back( _tv );
124 	m_scrollLayout->addWidget( _tv );
125 	connect( this, SIGNAL( positionChanged( const MidiTime & ) ),
126 				_tv->getTrackContentWidget(),
127 				SLOT( changePosition( const MidiTime & ) ) );
128 	realignTracks();
129 	return( _tv );
130 }
131 
132 
133 
134 
removeTrackView(TrackView * _tv)135 void TrackContainerView::removeTrackView( TrackView * _tv )
136 {
137 	int index = m_trackViews.indexOf( _tv );
138 	if( index != -1 )
139 	{
140 		m_trackViews.removeAt( index );
141 
142 		disconnect( _tv );
143 		m_scrollLayout->removeWidget( _tv );
144 
145 		realignTracks();
146 		if( Engine::getSong() )
147 		{
148 			Engine::getSong()->setModified();
149 		}
150 	}
151 }
152 
153 
154 
155 
moveTrackView(TrackView * trackView,int indexTo)156 void TrackContainerView::moveTrackView( TrackView * trackView, int indexTo )
157 {
158 	// Can't move out of bounds
159 	if ( indexTo >= m_trackViews.size() || indexTo < 0 ) { return; }
160 
161 	// Does not need to move to itself
162 	int indexFrom = m_trackViews.indexOf( trackView );
163 	if ( indexFrom == indexTo ) { return; }
164 
165 	BBTrack::swapBBTracks( trackView->getTrack(),
166 			m_trackViews[indexTo]->getTrack() );
167 
168 	m_scrollLayout->removeWidget( trackView );
169 	m_scrollLayout->insertWidget( indexTo, trackView );
170 
171 	Track * track = m_tc->m_tracks[indexFrom];
172 
173 	m_tc->m_tracks.remove( indexFrom );
174 	m_tc->m_tracks.insert( indexTo, track );
175 	m_trackViews.move( indexFrom, indexTo );
176 
177 	realignTracks();
178 }
179 
180 
181 
182 
moveTrackViewUp(TrackView * trackView)183 void TrackContainerView::moveTrackViewUp( TrackView * trackView )
184 {
185 	int index = m_trackViews.indexOf( trackView );
186 
187 	moveTrackView( trackView, index - 1 );
188 }
189 
190 
191 
192 
moveTrackViewDown(TrackView * trackView)193 void TrackContainerView::moveTrackViewDown( TrackView * trackView )
194 {
195 	int index = m_trackViews.indexOf( trackView );
196 
197 	moveTrackView( trackView, index + 1 );
198 }
199 
scrollToTrackView(TrackView * _tv)200 void TrackContainerView::scrollToTrackView( TrackView * _tv )
201 {
202 	if (!m_trackViews.contains(_tv))
203 	{
204 		qWarning("TrackContainerView::scrollToTrackView: TrackView is not owned by this");
205 	}
206 	else
207 	{
208 		int currentScrollTop = m_scrollArea->verticalScrollBar()->value();
209 		int scrollAreaHeight = m_scrollArea->size().height();
210 		int trackViewTop = _tv->pos().y();
211 		int trackViewBottom = trackViewTop + _tv->size().height();
212 
213 		// displayed_location = widget_location - currentScrollTop
214 		// want to make sure that the widget top has displayed location > 0,
215 		// and widget bottom < scrollAreaHeight
216 		// trackViewTop - scrollY > 0 && trackViewBottom - scrollY < scrollAreaHeight
217 		// therefore scrollY < trackViewTop && scrollY > trackViewBottom - scrollAreaHeight
218 		int newScroll = std::max( trackViewBottom-scrollAreaHeight, std::min(currentScrollTop, trackViewTop) );
219 		m_scrollArea->verticalScrollBar()->setValue(newScroll);
220 	}
221 }
222 
223 
224 
225 
realignTracks()226 void TrackContainerView::realignTracks()
227 {
228 	QWidget * content = m_scrollArea->widget();
229 	content->setFixedWidth( width()
230 				- m_scrollArea->verticalScrollBar()->width() );
231 	content->setFixedHeight( content->minimumSizeHint().height() );
232 
233 	for( trackViewList::iterator it = m_trackViews.begin();
234 						it != m_trackViews.end(); ++it )
235 	{
236 		( *it )->show();
237 		( *it )->update();
238 	}
239 }
240 
241 
242 
243 
createTrackView(Track * _t)244 TrackView * TrackContainerView::createTrackView( Track * _t )
245 {
246 	//m_tc->addJournalCheckPoint();
247 
248 	// Avoid duplicating track views
249 	for( trackViewList::iterator it = m_trackViews.begin();
250 						it != m_trackViews.end(); ++it )
251 	{
252 		if ( ( *it )->getTrack() == _t ) { return ( *it ); }
253 	}
254 
255 	return _t->createView( this );
256 }
257 
258 
259 
260 
deleteTrackView(TrackView * _tv)261 void TrackContainerView::deleteTrackView( TrackView * _tv )
262 {
263 	//m_tc->addJournalCheckPoint();
264 
265 	Track * t = _tv->getTrack();
266 	removeTrackView( _tv );
267 	delete _tv;
268 
269 	Engine::mixer()->requestChangeInModel();
270 	delete t;
271 	Engine::mixer()->doneChangeInModel();
272 }
273 
274 
275 
276 
trackViewAt(const int _y) const277 const TrackView * TrackContainerView::trackViewAt( const int _y ) const
278 {
279 	const int abs_y = _y + m_scrollArea->verticalScrollBar()->value();
280 	int y_cnt = 0;
281 
282 //	debug code
283 //	qDebug( "abs_y %d", abs_y );
284 
285 	for( trackViewList::const_iterator it = m_trackViews.begin();
286 						it != m_trackViews.end(); ++it )
287 	{
288 		const int y_cnt1 = y_cnt;
289 		y_cnt += ( *it )->height();
290 		if( abs_y >= y_cnt1 && abs_y < y_cnt )
291 		{
292 			return( *it );
293 		}
294 	}
295 	return( NULL );
296 }
297 
298 
299 
300 
allowRubberband() const301 bool TrackContainerView::allowRubberband() const
302 {
303 	return( false );
304 }
305 
306 
307 
308 
setPixelsPerTact(int _ppt)309 void TrackContainerView::setPixelsPerTact( int _ppt )
310 {
311 	m_ppt = _ppt;
312 
313 	// tell all TrackContentWidgets to update their background tile pixmap
314 	for( trackViewList::Iterator it = m_trackViews.begin();
315 						it != m_trackViews.end(); ++it )
316 	{
317 		( *it )->getTrackContentWidget()->updateBackground();
318 	}
319 }
320 
321 
322 
323 
clearAllTracks()324 void TrackContainerView::clearAllTracks()
325 {
326 	while( !m_trackViews.empty() )
327 	{
328 		TrackView * tv = m_trackViews.takeLast();
329 		Track * t = tv->getTrack();
330 		delete tv;
331 		delete t;
332 	}
333 }
334 
335 
336 
337 
dragEnterEvent(QDragEnterEvent * _dee)338 void TrackContainerView::dragEnterEvent( QDragEnterEvent * _dee )
339 {
340 	StringPairDrag::processDragEnterEvent( _dee,
341 		QString( "presetfile,pluginpresetfile,samplefile,instrument,"
342 				"importedproject,soundfontfile,patchfile,vstpluginfile,projectfile,"
343 				"track_%1,track_%2" ).
344 						arg( Track::InstrumentTrack ).
345 						arg( Track::SampleTrack ) );
346 }
347 
348 
349 
350 
stopRubberBand()351 void TrackContainerView::stopRubberBand()
352 {
353 	m_rubberBand->hide();
354 	m_rubberBand->setEnabled( false );
355 }
356 
357 
358 
359 
dropEvent(QDropEvent * _de)360 void TrackContainerView::dropEvent( QDropEvent * _de )
361 {
362 	QString type = StringPairDrag::decodeKey( _de );
363 	QString value = StringPairDrag::decodeValue( _de );
364 	if( type == "instrument" )
365 	{
366 		InstrumentTrack * it = dynamic_cast<InstrumentTrack *>(
367 				Track::create( Track::InstrumentTrack,
368 								m_tc ) );
369 		InstrumentLoaderThread *ilt = new InstrumentLoaderThread(
370 					this, it, value );
371 		ilt->start();
372 		//it->toggledInstrumentTrackButton( true );
373 		_de->accept();
374 	}
375 	else if( type == "samplefile" || type == "pluginpresetfile"
376 		|| type == "soundfontfile" || type == "vstpluginfile"
377 		|| type == "patchfile" )
378 	{
379 		InstrumentTrack * it = dynamic_cast<InstrumentTrack *>(
380 				Track::create( Track::InstrumentTrack,
381 								m_tc ) );
382 		Instrument * i = it->loadInstrument(
383 			pluginFactory->pluginSupportingExtension(FileItem::extension(value)).name());
384 		i->loadFile( value );
385 		//it->toggledInstrumentTrackButton( true );
386 		_de->accept();
387 	}
388 	else if( type == "presetfile" )
389 	{
390 		DataFile dataFile( value );
391 		InstrumentTrack * it = dynamic_cast<InstrumentTrack *>(
392 				Track::create( Track::InstrumentTrack,
393 								m_tc ) );
394 		it->setSimpleSerializing();
395 		it->loadSettings( dataFile.content().toElement() );
396 		//it->toggledInstrumentTrackButton( true );
397 		_de->accept();
398 	}
399 	else if( type == "importedproject" )
400 	{
401 		ImportFilter::import( value, m_tc );
402 		_de->accept();
403 	}
404 
405 	else if( type == "projectfile")
406 	{
407 		if( gui->mainWindow()->mayChangeProject(true) )
408 		{
409 			Engine::getSong()->loadProject( value );
410 		}
411 		_de->accept();
412 	}
413 
414 	else if( type.left( 6 ) == "track_" )
415 	{
416 		DataFile dataFile( value.toUtf8() );
417 		Track::create( dataFile.content().firstChild().toElement(), m_tc );
418 		_de->accept();
419 	}
420 }
421 
422 
423 
424 
resizeEvent(QResizeEvent * _re)425 void TrackContainerView::resizeEvent( QResizeEvent * _re )
426 {
427 	realignTracks();
428 	QWidget::resizeEvent( _re );
429 }
430 
431 
432 
433 
rubberBand() const434 RubberBand *TrackContainerView::rubberBand() const
435 {
436 	return m_rubberBand;
437 }
438 
439 
440 
441 
scrollArea(TrackContainerView * _parent)442 TrackContainerView::scrollArea::scrollArea( TrackContainerView * _parent ) :
443 	QScrollArea( _parent ),
444 	m_trackContainerView( _parent )
445 {
446 	setFrameStyle( QFrame::NoFrame );
447 	setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
448 	setVerticalScrollBarPolicy( Qt::ScrollBarAlwaysOn );
449 }
450 
451 
452 
453 
~scrollArea()454 TrackContainerView::scrollArea::~scrollArea()
455 {
456 }
457 
458 
459 
460 
wheelEvent(QWheelEvent * _we)461 void TrackContainerView::scrollArea::wheelEvent( QWheelEvent * _we )
462 {
463 	// always pass wheel-event to parent-widget (song-editor
464 	// bb-editor etc.) because they might want to use it for zooming
465 	// or scrolling left/right if a modifier-key is pressed, otherwise
466 	// they do not accept it and we pass it up to QScrollArea
467 	m_trackContainerView->wheelEvent( _we );
468 	if( !_we->isAccepted() )
469 	{
470 		QScrollArea::wheelEvent( _we );
471 	}
472 }
473 
474 
475 
476 
InstrumentLoaderThread(QObject * parent,InstrumentTrack * it,QString name)477 InstrumentLoaderThread::InstrumentLoaderThread( QObject *parent, InstrumentTrack *it, QString name ) :
478 	QThread( parent ),
479 	m_it( it ),
480 	m_name( name )
481 {
482 	m_containerThread = thread();
483 }
484 
485 
486 
487 
run()488 void InstrumentLoaderThread::run()
489 {
490 	Instrument *i = m_it->loadInstrument( m_name );
491 	QObject *parent = i->parent();
492 	i->setParent( 0 );
493 	i->moveToThread( m_containerThread );
494 	i->setParent( parent );
495 }
496