1 /*
2  * FileBrowser.cpp - implementation of the project-, preset- and
3  *                    sample-file-browser
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 
27 #include <QHBoxLayout>
28 #include <QKeyEvent>
29 #include <QLineEdit>
30 #include <QMenu>
31 #include <QPushButton>
32 #include <QMdiArea>
33 #include <QMdiSubWindow>
34 #include <QMessageBox>
35 #include <QShortcut>
36 
37 #include "FileBrowser.h"
38 #include "BBTrackContainer.h"
39 #include "ConfigManager.h"
40 #include "embed.h"
41 #include "Engine.h"
42 #include "GuiApplication.h"
43 #include "gui_templates.h"
44 #include "ImportFilter.h"
45 #include "Instrument.h"
46 #include "InstrumentTrack.h"
47 #include "MainWindow.h"
48 #include "Mixer.h"
49 #include "PluginFactory.h"
50 #include "PresetPreviewPlayHandle.h"
51 #include "SamplePlayHandle.h"
52 #include "Song.h"
53 #include "StringPairDrag.h"
54 #include "TextFloat.h"
55 
56 
57 
58 enum TreeWidgetItemTypes
59 {
60 	TypeFileItem = QTreeWidgetItem::UserType,
61 	TypeDirectoryItem
62 } ;
63 
64 
65 
FileBrowser(const QString & directories,const QString & filter,const QString & title,const QPixmap & pm,QWidget * parent,bool dirs_as_items,bool recurse)66 FileBrowser::FileBrowser(const QString & directories, const QString & filter,
67 			const QString & title, const QPixmap & pm,
68 			QWidget * parent, bool dirs_as_items, bool recurse ) :
69 	SideBarWidget( title, pm, parent ),
70 	m_directories( directories ),
71 	m_filter( filter ),
72 	m_dirsAsItems( dirs_as_items ),
73 	m_recurse( recurse )
74 {
75 	setWindowTitle( tr( "Browser" ) );
76 	m_l = new FileBrowserTreeWidget( contentParent() );
77 	addContentWidget( m_l );
78 
79 	QWidget * ops = new QWidget( contentParent() );
80 	ops->setFixedHeight( 24 );
81 
82 	QHBoxLayout * opl = new QHBoxLayout( ops );
83 	opl->setMargin( 0 );
84 	opl->setSpacing( 0 );
85 
86 	m_filterEdit = new QLineEdit( ops );
87 #if QT_VERSION >= 0x050000
88 	m_filterEdit->setClearButtonEnabled( true );
89 #endif
90 	connect( m_filterEdit, SIGNAL( textEdited( const QString & ) ),
91 			this, SLOT( filterItems( const QString & ) ) );
92 
93 	QPushButton * reload_btn = new QPushButton(
94 				embed::getIconPixmap( "reload" ),
95 						QString::null, ops );
96 	connect( reload_btn, SIGNAL( clicked() ), this, SLOT( reloadTree() ) );
97 
98 	opl->addWidget( m_filterEdit );
99 	opl->addSpacing( 5 );
100 	opl->addWidget( reload_btn );
101 
102 	addContentWidget( ops );
103 
104 	// Whenever the FileBrowser has focus, Ctrl+F should direct focus to its filter box.
105 	QShortcut *filterFocusShortcut = new QShortcut( QKeySequence( QKeySequence::Find ), this, SLOT(giveFocusToFilter()) );
106 	filterFocusShortcut->setContext(Qt::WidgetWithChildrenShortcut);
107 
108 	reloadTree();
109 	show();
110 }
111 
112 
113 
114 
~FileBrowser()115 FileBrowser::~FileBrowser()
116 {
117 }
118 
119 
120 
121 
filterItems(const QString & filter,QTreeWidgetItem * item)122 bool FileBrowser::filterItems( const QString & filter, QTreeWidgetItem * item )
123 {
124 	// call with item=NULL to filter the entire tree
125 	bool anyMatched = false;
126 
127 	int numChildren = item ? item->childCount() : m_l->topLevelItemCount();
128 	for( int i = 0; i < numChildren; ++i )
129 	{
130 		QTreeWidgetItem * it = item ? item->child( i ) : m_l->topLevelItem(i);
131 
132 		// is directory?
133 		if( it->childCount() )
134 		{
135 			// matches filter?
136 			if( it->text( 0 ).
137 				contains( filter, Qt::CaseInsensitive ) )
138 			{
139 				// yes, then show everything below
140 				it->setHidden( false );
141 				filterItems( QString::null, it );
142 				anyMatched = true;
143 			}
144 			else
145 			{
146 				// only show if item below matches filter
147 				bool didMatch = filterItems( filter, it );
148 				it->setHidden( !didMatch );
149 				anyMatched = anyMatched || didMatch;
150 			}
151 		}
152 		// a standard item (i.e. no file or directory item?)
153 		else if( it->type() == QTreeWidgetItem::Type )
154 		{
155 			// hide if there's any filter
156 			it->setHidden( !filter.isEmpty() );
157 		}
158 		else
159 		{
160 			// file matches filter?
161 			bool didMatch = it->text( 0 ).
162 				contains( filter, Qt::CaseInsensitive );
163 			it->setHidden( !didMatch );
164 			anyMatched = anyMatched || didMatch;
165 		}
166 	}
167 
168 	return anyMatched;
169 }
170 
171 
172 
reloadTree(void)173 void FileBrowser::reloadTree( void )
174 {
175 	const QString text = m_filterEdit->text();
176 	m_filterEdit->clear();
177 	m_l->clear();
178 	QStringList paths = m_directories.split( '*' );
179 	for( QStringList::iterator it = paths.begin(); it != paths.end(); ++it )
180 	{
181 		addItems( *it );
182 	}
183 	expandItems();
184 	m_filterEdit->setText( text );
185 	filterItems( text );
186 }
187 
188 
189 
expandItems(QTreeWidgetItem * item)190 void FileBrowser::expandItems( QTreeWidgetItem * item )
191 {
192     int numChildren = item ? item->childCount() : m_l->topLevelItemCount();
193 	for( int i = 0; i < numChildren; ++i )
194 	{
195 		QTreeWidgetItem * it = item ? item->child( i ) : m_l->topLevelItem(i);
196 		if ( m_recurse )
197 		{
198 			it->setExpanded( true );
199 		}
200 		Directory *d = dynamic_cast<Directory *> ( it );
201 		if( d )
202 		{
203 			d->update();
204 			d->setExpanded( false );
205 		}
206 		if( m_recurse && it->childCount() )
207 		{
208 			expandItems(it);
209 		}
210 	}
211 }
212 
213 
214 
giveFocusToFilter()215 void FileBrowser::giveFocusToFilter()
216 {
217 	if (!m_filterEdit->hasFocus())
218 	{
219 		// give focus to filter text box and highlight its text for quick editing if not previously focused
220 		m_filterEdit->setFocus();
221 		m_filterEdit->selectAll();
222 	}
223 }
224 
225 
226 
addItems(const QString & path)227 void FileBrowser::addItems(const QString & path )
228 {
229 	if( m_dirsAsItems )
230 	{
231 		m_l->addTopLevelItem( new Directory( path, QString::null, m_filter ) );
232 		return;
233 	}
234 
235 	// try to add all directories from file system alphabetically into the tree
236 	QDir cdir( path );
237 	QStringList files = cdir.entryList( QDir::Dirs, QDir::Name );
238 	for( QStringList::const_iterator it = files.constBegin();
239 						it != files.constEnd(); ++it )
240 	{
241 		QString cur_file = *it;
242 		if( cur_file[0] != '.' )
243 		{
244 			bool orphan = true;
245 			for( int i = 0; i < m_l->topLevelItemCount(); ++i )
246 			{
247 				Directory * d = dynamic_cast<Directory *>(
248 						m_l->topLevelItem( i ) );
249 				if( d == NULL || cur_file < d->text( 0 ) )
250 				{
251 					// insert before item, we're done
252 					Directory *dd = new Directory( cur_file, path,
253 												   m_filter );
254 					m_l->insertTopLevelItem( i,dd );
255 					dd->update();
256 					orphan = false;
257 					break;
258 				}
259 				else if( cur_file == d->text( 0 ) )
260 				{
261 					// imagine we have subdirs named "TripleOscillator/xyz" in
262 					// two directories from m_directories
263 					// then only add one tree widget for both
264 					// so we don't add a new Directory - we just
265 					// add the path to the current directory
266 					d->addDirectory( path );
267 					d->update();
268 					orphan = false;
269 					break;
270 				}
271 			}
272 			if( orphan )
273 			{
274 				// it has not yet been added yet, so it's (lexically)
275 				// larger than all other dirs => append it at the bottom
276 				Directory *d = new Directory( cur_file,
277 											  path, m_filter );
278 				d->update();
279 				m_l->addTopLevelItem( d );
280 			}
281 		}
282 	}
283 
284 	files = cdir.entryList( QDir::Files, QDir::Name );
285 	for( QStringList::const_iterator it = files.constBegin();
286 						it != files.constEnd(); ++it )
287 	{
288 		QString cur_file = *it;
289 		if( cur_file[0] != '.' )
290 		{
291 			// TODO: don't insert instead of removing, order changed
292 			// remove existing file-items
293 			QList<QTreeWidgetItem *> existing = m_l->findItems(
294 					cur_file, Qt::MatchFixedString );
295 			if( !existing.empty() )
296 			{
297 				delete existing.front();
298 			}
299 			(void) new FileItem( m_l, cur_file, path );
300 		}
301 	}
302 }
303 
304 
305 
306 
keyPressEvent(QKeyEvent * ke)307 void FileBrowser::keyPressEvent(QKeyEvent * ke )
308 {
309 	if( ke->key() == Qt::Key_F5 )
310 	{
311 		reloadTree();
312 	}
313 	else
314 	{
315 		ke->ignore();
316 	}
317 }
318 
319 
320 
321 
322 
323 
324 
325 
FileBrowserTreeWidget(QWidget * parent)326 FileBrowserTreeWidget::FileBrowserTreeWidget(QWidget * parent ) :
327 	QTreeWidget( parent ),
328 	m_mousePressed( false ),
329 	m_pressPos(),
330 	m_previewPlayHandle( NULL ),
331 	m_pphMutex( QMutex::Recursive ),
332 	m_contextMenuItem( NULL )
333 {
334 	setColumnCount( 1 );
335 	headerItem()->setHidden( true );
336 	setSortingEnabled( false );
337 
338 	connect( this, SIGNAL( itemDoubleClicked( QTreeWidgetItem *, int ) ),
339 			SLOT( activateListItem( QTreeWidgetItem *, int ) ) );
340 	connect( this, SIGNAL( itemCollapsed( QTreeWidgetItem * ) ),
341 				SLOT( updateDirectory( QTreeWidgetItem * ) ) );
342 	connect( this, SIGNAL( itemExpanded( QTreeWidgetItem * ) ),
343 				SLOT( updateDirectory( QTreeWidgetItem * ) ) );
344 
345 }
346 
347 
348 
349 
~FileBrowserTreeWidget()350 FileBrowserTreeWidget::~FileBrowserTreeWidget()
351 {
352 }
353 
354 
355 
356 
contextMenuEvent(QContextMenuEvent * e)357 void FileBrowserTreeWidget::contextMenuEvent(QContextMenuEvent * e )
358 {
359 	FileItem * f = dynamic_cast<FileItem *>( itemAt( e->pos() ) );
360 	if( f != NULL && ( f->handling() == FileItem::LoadAsPreset ||
361 				 f->handling() == FileItem::LoadByPlugin ) )
362 	{
363 		m_contextMenuItem = f;
364 		QMenu contextMenu( this );
365 		contextMenu.addAction( tr( "Send to active instrument-track" ),
366 						this,
367 					SLOT( sendToActiveInstrumentTrack() ) );
368 		contextMenu.addAction( tr( "Open in new instrument-track/"
369 								"Song Editor" ),
370 						this,
371 					SLOT( openInNewInstrumentTrackSE() ) );
372 		contextMenu.addAction( tr( "Open in new instrument-track/"
373 								"B+B Editor" ),
374 						this,
375 					SLOT( openInNewInstrumentTrackBBE() ) );
376 		contextMenu.exec( e->globalPos() );
377 		m_contextMenuItem = NULL;
378 	}
379 }
380 
381 
382 
383 
mousePressEvent(QMouseEvent * me)384 void FileBrowserTreeWidget::mousePressEvent(QMouseEvent * me )
385 {
386 	QTreeWidget::mousePressEvent( me );
387 	if( me->button() != Qt::LeftButton )
388 	{
389 		return;
390 	}
391 
392 	QTreeWidgetItem * i = itemAt( me->pos() );
393 	if ( i )
394 	{
395 		// TODO: Restrict to visible selection
396 //		if ( _me->x() > header()->cellPos( header()->mapToActual( 0 ) )
397 //			+ treeStepSize() * ( i->depth() + ( rootIsDecorated() ?
398 //						1 : 0 ) ) + itemMargin() ||
399 //				_me->x() < header()->cellPos(
400 //						header()->mapToActual( 0 ) ) )
401 //		{
402 			m_pressPos = me->pos();
403 			m_mousePressed = true;
404 //		}
405 	}
406 
407 	FileItem * f = dynamic_cast<FileItem *>( i );
408 	if( f != NULL )
409 	{
410 		m_pphMutex.lock();
411 		if( m_previewPlayHandle != NULL )
412 		{
413 			Engine::mixer()->removePlayHandle(
414 							m_previewPlayHandle );
415 			m_previewPlayHandle = NULL;
416 		}
417 
418 		// in special case of sample-files we do not care about
419 		// handling() rather than directly creating a SamplePlayHandle
420 		if( f->type() == FileItem::SampleFile )
421 		{
422 			TextFloat * tf = TextFloat::displayMessage(
423 					tr( "Loading sample" ),
424 					tr( "Please wait, loading sample for "
425 								"preview..." ),
426 					embed::getIconPixmap( "sample_file",
427 								24, 24 ), 0 );
428 			qApp->processEvents(
429 					QEventLoop::ExcludeUserInputEvents );
430 			SamplePlayHandle * s = new SamplePlayHandle(
431 								f->fullName() );
432 			s->setDoneMayReturnTrue( false );
433 			m_previewPlayHandle = s;
434 			delete tf;
435 		}
436 		else if( ( f->extension ()== "xiz" || f->extension() == "sf2" || f->extension() == "gig" || f->extension() == "pat" ) &&
437 			! pluginFactory->pluginSupportingExtension(f->extension()).isNull() )
438 		{
439 			m_previewPlayHandle = new PresetPreviewPlayHandle( f->fullName(), f->handling() == FileItem::LoadByPlugin );
440 		}
441 		else if( f->type() != FileItem::VstPluginFile &&
442 				( f->handling() == FileItem::LoadAsPreset ||
443 				f->handling() == FileItem::LoadByPlugin ) )
444 		{
445 			DataFile dataFile( f->fullName() );
446 			if( !dataFile.validate( f->extension() ) )
447 			{
448 				QMessageBox::warning( 0, tr ( "Error" ),
449 					f->fullName() + " " + tr( "does not appear to be a valid" ) + " " + f->extension() +
450 									  " " + tr( "file" ),
451 					QMessageBox::Ok, QMessageBox::NoButton );
452 				m_pphMutex.unlock();
453 				return;
454 			}
455 			m_previewPlayHandle = new PresetPreviewPlayHandle( f->fullName(), f->handling() == FileItem::LoadByPlugin, &dataFile );
456 		}
457 		if( m_previewPlayHandle != NULL )
458 		{
459 			if( !Engine::mixer()->addPlayHandle(
460 							m_previewPlayHandle ) )
461 			{
462 				m_previewPlayHandle = NULL;
463 			}
464 		}
465 		m_pphMutex.unlock();
466 	}
467 }
468 
469 
470 
471 
mouseMoveEvent(QMouseEvent * me)472 void FileBrowserTreeWidget::mouseMoveEvent( QMouseEvent * me )
473 {
474 	if( m_mousePressed == true &&
475 		( m_pressPos - me->pos() ).manhattanLength() >
476 					QApplication::startDragDistance() )
477 	{
478 		// make sure any playback is stopped
479 		mouseReleaseEvent( NULL );
480 
481 		FileItem * f = dynamic_cast<FileItem *>( itemAt( m_pressPos ) );
482 		if( f != NULL )
483 		{
484 			switch( f->type() )
485 			{
486 				case FileItem::PresetFile:
487 					new StringPairDrag( f->handling() == FileItem::LoadAsPreset ?
488 							"presetfile" : "pluginpresetfile",
489 							f->fullName(),
490 							embed::getIconPixmap( "preset_file" ), this );
491 					break;
492 
493 				case FileItem::SampleFile:
494 					new StringPairDrag( "samplefile", f->fullName(),
495 							embed::getIconPixmap( "sample_file" ), this );
496 					break;
497 				case FileItem::SoundFontFile:
498 					new StringPairDrag( "soundfontfile", f->fullName(),
499 							embed::getIconPixmap( "soundfont_file" ), this );
500 					break;
501 				case FileItem::PatchFile:
502 					new StringPairDrag( "patchfile", f->fullName(),
503 							embed::getIconPixmap( "sample_file" ), this );
504 					break;
505 				case FileItem::VstPluginFile:
506 					new StringPairDrag( "vstpluginfile", f->fullName(),
507 							embed::getIconPixmap( "vst_plugin_file" ), this );
508 					break;
509 				case FileItem::MidiFile:
510 					new StringPairDrag( "importedproject", f->fullName(),
511 							embed::getIconPixmap( "midi_file" ), this );
512 					break;
513 				case FileItem::ProjectFile:
514 					new StringPairDrag( "projectfile", f->fullName(),
515 							embed::getIconPixmap( "project_file" ), this );
516 					break;
517 
518 				default:
519 					break;
520 			}
521 		}
522 	}
523 }
524 
525 
526 
527 
mouseReleaseEvent(QMouseEvent * me)528 void FileBrowserTreeWidget::mouseReleaseEvent(QMouseEvent * me )
529 {
530 	m_mousePressed = false;
531 
532 	m_pphMutex.lock();
533 	if( m_previewPlayHandle != NULL )
534 	{
535 		// if there're samples shorter than 3 seconds, we don't
536 		// stop them if the user releases mouse-button...
537 		if( m_previewPlayHandle->type() == PlayHandle::TypeSamplePlayHandle )
538 		{
539 			SamplePlayHandle * s = dynamic_cast<SamplePlayHandle *>(
540 							m_previewPlayHandle );
541 			if( s && s->totalFrames() - s->framesDone() <=
542 				static_cast<f_cnt_t>( Engine::mixer()->
543 						processingSampleRate() * 3 ) )
544 			{
545 				s->setDoneMayReturnTrue( true );
546 				m_previewPlayHandle = NULL;
547 				m_pphMutex.unlock();
548 				return;
549 			}
550 		}
551 		Engine::mixer()->removePlayHandle( m_previewPlayHandle );
552 		m_previewPlayHandle = NULL;
553 	}
554 	m_pphMutex.unlock();
555 }
556 
557 
558 
559 
560 
handleFile(FileItem * f,InstrumentTrack * it)561 void FileBrowserTreeWidget::handleFile(FileItem * f, InstrumentTrack * it )
562 {
563 	Engine::mixer()->requestChangeInModel();
564 	switch( f->handling() )
565 	{
566 		case FileItem::LoadAsProject:
567 			if( gui->mainWindow()->mayChangeProject(true) )
568 			{
569 				Engine::getSong()->loadProject( f->fullName() );
570 			}
571 			break;
572 
573 		case FileItem::LoadByPlugin:
574 		{
575 			const QString e = f->extension();
576 			Instrument * i = it->instrument();
577 			if( i == NULL ||
578 				!i->descriptor()->supportsFileType( e ) )
579 			{
580 				i = it->loadInstrument(
581 					pluginFactory->pluginSupportingExtension(e).name() );
582 			}
583 			i->loadFile( f->fullName() );
584 			break;
585 		}
586 
587 		case FileItem::LoadAsPreset:
588 		{
589 			DataFile dataFile( f->fullName() );
590 			InstrumentTrack::removeMidiPortNode( dataFile );
591 			it->setSimpleSerializing();
592 			it->loadSettings( dataFile.content().toElement() );
593 			break;
594 		}
595 
596 		case FileItem::ImportAsProject:
597 			ImportFilter::import( f->fullName(),
598 							Engine::getSong() );
599 			break;
600 
601 		case FileItem::NotSupported:
602 		default:
603 			break;
604 
605 	}
606 	Engine::mixer()->doneChangeInModel();
607 }
608 
609 
610 
611 
activateListItem(QTreeWidgetItem * item,int column)612 void FileBrowserTreeWidget::activateListItem(QTreeWidgetItem * item,
613 								int column )
614 {
615 	FileItem * f = dynamic_cast<FileItem *>( item );
616 	if( f == NULL )
617 	{
618 		return;
619 	}
620 
621 	if( f->handling() == FileItem::LoadAsProject ||
622 		f->handling() == FileItem::ImportAsProject )
623 	{
624 		handleFile( f, NULL );
625 	}
626 	else if( f->handling() != FileItem::NotSupported )
627 	{
628 		InstrumentTrack * it = dynamic_cast<InstrumentTrack *>(
629 				Track::create( Track::InstrumentTrack,
630 					Engine::getBBTrackContainer() ) );
631 		handleFile( f, it );
632 	}
633 }
634 
635 
636 
637 
openInNewInstrumentTrack(TrackContainer * tc)638 void FileBrowserTreeWidget::openInNewInstrumentTrack( TrackContainer* tc )
639 {
640 	if( m_contextMenuItem->handling() == FileItem::LoadAsPreset ||
641 		m_contextMenuItem->handling() == FileItem::LoadByPlugin )
642 	{
643 		InstrumentTrack * it = dynamic_cast<InstrumentTrack *>(
644 				Track::create( Track::InstrumentTrack, tc ) );
645 		handleFile( m_contextMenuItem, it );
646 	}
647 }
648 
649 
650 
651 
openInNewInstrumentTrackBBE(void)652 void FileBrowserTreeWidget::openInNewInstrumentTrackBBE( void )
653 {
654 	openInNewInstrumentTrack( Engine::getBBTrackContainer() );
655 }
656 
657 
658 
659 
openInNewInstrumentTrackSE(void)660 void FileBrowserTreeWidget::openInNewInstrumentTrackSE( void )
661 {
662 	openInNewInstrumentTrack( Engine::getSong() );
663 }
664 
665 
666 
667 
sendToActiveInstrumentTrack(void)668 void FileBrowserTreeWidget::sendToActiveInstrumentTrack( void )
669 {
670 	// get all windows opened in the workspace
671 	QList<QMdiSubWindow*> pl =
672 			gui->mainWindow()->workspace()->
673 				subWindowList( QMdiArea::StackingOrder );
674 	QListIterator<QMdiSubWindow *> w( pl );
675 	w.toBack();
676 	// now we travel through the window-list until we find an
677 	// instrument-track
678 	while( w.hasPrevious() )
679 	{
680 		InstrumentTrackWindow * itw =
681 			dynamic_cast<InstrumentTrackWindow *>(
682 						w.previous()->widget() );
683 		if( itw != NULL && itw->isHidden() == false )
684 		{
685 			handleFile( m_contextMenuItem, itw->model() );
686 			break;
687 		}
688 	}
689 }
690 
691 
692 
693 
updateDirectory(QTreeWidgetItem * item)694 void FileBrowserTreeWidget::updateDirectory(QTreeWidgetItem * item )
695 {
696 	Directory * dir = dynamic_cast<Directory *>( item );
697 	if( dir != NULL )
698 	{
699 		dir->update();
700 	}
701 }
702 
703 
704 
705 
706 
707 
708 QPixmap * Directory::s_folderPixmap = NULL;
709 QPixmap * Directory::s_folderOpenedPixmap = NULL;
710 QPixmap * Directory::s_folderLockedPixmap = NULL;
711 
712 
Directory(const QString & filename,const QString & path,const QString & filter)713 Directory::Directory(const QString & filename, const QString & path,
714 						const QString & filter ) :
715 	QTreeWidgetItem( QStringList( filename ), TypeDirectoryItem ),
716 	m_directories( path ),
717 	m_filter( filter ),
718 	m_dirCount( 0 )
719 {
720 	initPixmaps();
721 
722 	setChildIndicatorPolicy( QTreeWidgetItem::ShowIndicator );
723 
724 	if( !QDir( fullName() ).isReadable() )
725 	{
726 		setIcon( 0, *s_folderLockedPixmap );
727 	}
728 	else
729 	{
730 		setIcon( 0, *s_folderPixmap );
731 	}
732 }
733 
734 
735 
736 
initPixmaps(void)737 void Directory::initPixmaps( void )
738 {
739 	if( s_folderPixmap == NULL )
740 	{
741 		s_folderPixmap = new QPixmap(
742 					embed::getIconPixmap( "folder" ) );
743 	}
744 
745 	if( s_folderOpenedPixmap == NULL )
746 	{
747 		s_folderOpenedPixmap = new QPixmap(
748 				embed::getIconPixmap( "folder_opened" ) );
749 	}
750 
751 	if( s_folderLockedPixmap == NULL )
752 	{
753 		s_folderLockedPixmap = new QPixmap(
754 				embed::getIconPixmap( "folder_locked" ) );
755 	}
756 }
757 
758 
759 
760 
update(void)761 void Directory::update( void )
762 {
763 	if( !isExpanded() )
764 	{
765 		setIcon( 0, *s_folderPixmap );
766 		return;
767 	}
768 
769 	setIcon( 0, *s_folderOpenedPixmap );
770 	if( !childCount() )
771 	{
772 		m_dirCount = 0;
773 		// for all paths leading here, add their items
774 		for( QStringList::iterator it = m_directories.begin();
775 					it != m_directories.end(); ++it )
776 		{
777 			int filesBeforeAdd = childCount() - m_dirCount;
778 			if( addItems( fullName( *it ) ) &&
779 				( *it ).contains(
780 					ConfigManager::inst()->dataDir() ) )
781 			{
782 				// factory file directory is added
783 				// note: those are always added last
784 				int filesNow = childCount() - m_dirCount;
785 				if(filesNow > filesBeforeAdd) // any file appended?
786 				{
787 					QTreeWidgetItem * sep = new QTreeWidgetItem;
788 					sep->setText( 0,
789 						FileBrowserTreeWidget::tr(
790 							"--- Factory files ---" ) );
791 					sep->setIcon( 0, embed::getIconPixmap(
792 								"factory_files" ) );
793 					// add delimeter after last file before appending our files
794 					insertChild( filesBeforeAdd + m_dirCount, sep );
795 				}
796 			}
797 		}
798 	}
799 }
800 
801 
802 
803 
addItems(const QString & path)804 bool Directory::addItems(const QString & path )
805 {
806 	QDir thisDir( path );
807 	if( !thisDir.isReadable() )
808 	{
809 		return false;
810 	}
811 
812 	treeWidget()->setUpdatesEnabled( false );
813 
814 	bool added_something = false;
815 
816 	// try to add all directories from file system alphabetically into the tree
817 	QStringList files = thisDir.entryList( QDir::Dirs, QDir::Name );
818 	for( QStringList::const_iterator it = files.constBegin();
819 						it != files.constEnd(); ++it )
820 	{
821 		QString cur_file = *it;
822 		if( cur_file[0] != '.' )
823 		{
824 			bool orphan = true;
825 			for( int i = 0; i < childCount(); ++i )
826 			{
827 				Directory * d = dynamic_cast<Directory *>(
828 								child( i ) );
829 				if( d == NULL || cur_file < d->text( 0 ) )
830 				{
831 					// insert before item, we're done
832 					insertChild( i, new Directory( cur_file,
833 							path, m_filter ) );
834 					orphan = false;
835 					m_dirCount++;
836 					break;
837 				}
838 				else if( cur_file == d->text( 0 ) )
839 				{
840 					// imagine we have top-level subdirs named "TripleOscillator" in
841 					// two directories from FileBrowser::m_directories
842 					// and imagine both have a sub folder named "xyz"
843 					// then only add one tree widget for both
844 					// so we don't add a new Directory - we just
845 					// add the path to the current directory
846 					d->addDirectory( path );
847 					orphan = false;
848 					break;
849 				}
850 			}
851 			if( orphan )
852 			{
853 				// it has not yet been added yet, so it's (lexically)
854 				// larger than all other dirs => append it at the bottom
855 				addChild( new Directory( cur_file, path,
856 								m_filter ) );
857 				m_dirCount++;
858 			}
859 
860 			added_something = true;
861 		}
862 	}
863 
864 	QList<QTreeWidgetItem*> items;
865 	files = thisDir.entryList( QDir::Files, QDir::Name );
866 	for( QStringList::const_iterator it = files.constBegin();
867 						it != files.constEnd(); ++it )
868 	{
869 		QString cur_file = *it;
870 		if( cur_file[0] != '.' &&
871 				thisDir.match( m_filter, cur_file.toLower() ) )
872 		{
873 			items << new FileItem( cur_file, path );
874 			added_something = true;
875 		}
876 	}
877 	addChildren( items );
878 
879 	treeWidget()->setUpdatesEnabled( true );
880 
881 	return added_something;
882 }
883 
884 
885 
886 
887 QPixmap * FileItem::s_projectFilePixmap = NULL;
888 QPixmap * FileItem::s_presetFilePixmap = NULL;
889 QPixmap * FileItem::s_sampleFilePixmap = NULL;
890 QPixmap * FileItem::s_soundfontFilePixmap = NULL;
891 QPixmap * FileItem::s_vstPluginFilePixmap = NULL;
892 QPixmap * FileItem::s_midiFilePixmap = NULL;
893 QPixmap * FileItem::s_unknownFilePixmap = NULL;
894 
895 
FileItem(QTreeWidget * parent,const QString & name,const QString & path)896 FileItem::FileItem(QTreeWidget * parent, const QString & name,
897 						const QString & path ) :
898 	QTreeWidgetItem( parent, QStringList( name) , TypeFileItem ),
899 	m_path( path )
900 {
901 	determineFileType();
902 	initPixmaps();
903 }
904 
905 
906 
907 
FileItem(const QString & name,const QString & path)908 FileItem::FileItem(const QString & name, const QString & path ) :
909 	QTreeWidgetItem( QStringList( name ), TypeFileItem ),
910 	m_path( path )
911 {
912 	determineFileType();
913 	initPixmaps();
914 }
915 
916 
917 
918 
initPixmaps(void)919 void FileItem::initPixmaps( void )
920 {
921 	if( s_projectFilePixmap == NULL )
922 	{
923 		s_projectFilePixmap = new QPixmap( embed::getIconPixmap(
924 						"project_file", 16, 16 ) );
925 	}
926 
927 	if( s_presetFilePixmap == NULL )
928 	{
929 		s_presetFilePixmap = new QPixmap( embed::getIconPixmap(
930 						"preset_file", 16, 16 ) );
931 	}
932 
933 	if( s_sampleFilePixmap == NULL )
934 	{
935 		s_sampleFilePixmap = new QPixmap( embed::getIconPixmap(
936 						"sample_file", 16, 16 ) );
937 	}
938 
939 	if ( s_soundfontFilePixmap == NULL )
940 	{
941 		s_soundfontFilePixmap = new QPixmap( embed::getIconPixmap(
942 						"soundfont_file", 16, 16 ) );
943 	}
944 
945 	if ( s_vstPluginFilePixmap == NULL )
946 	{
947 		s_vstPluginFilePixmap = new QPixmap( embed::getIconPixmap(
948 						"vst_plugin_file", 16, 16 ) );
949 	}
950 
951 	if( s_midiFilePixmap == NULL )
952 	{
953 		s_midiFilePixmap = new QPixmap( embed::getIconPixmap(
954 							"midi_file", 16, 16 ) );
955 	}
956 
957 	if( s_unknownFilePixmap == NULL )
958 	{
959 		s_unknownFilePixmap = new QPixmap( embed::getIconPixmap(
960 							"unknown_file" ) );
961 	}
962 
963 	switch( m_type )
964 	{
965 		case ProjectFile:
966 			setIcon( 0, *s_projectFilePixmap );
967 			break;
968 		case PresetFile:
969 			setIcon( 0, *s_presetFilePixmap );
970 			break;
971 		case SoundFontFile:
972 			setIcon( 0, *s_soundfontFilePixmap );
973 			break;
974 		case VstPluginFile:
975 			setIcon( 0, *s_vstPluginFilePixmap );
976 			break;
977 		case SampleFile:
978 		case PatchFile:			// TODO
979 			setIcon( 0, *s_sampleFilePixmap );
980 			break;
981 		case MidiFile:
982 			setIcon( 0, *s_midiFilePixmap );
983 			break;
984 		case UnknownFile:
985 		default:
986 			setIcon( 0, *s_unknownFilePixmap );
987 			break;
988 	}
989 }
990 
991 
992 
993 
determineFileType(void)994 void FileItem::determineFileType( void )
995 {
996 	m_handling = NotSupported;
997 
998 	const QString ext = extension();
999 	if( ext == "mmp" || ext == "mpt" || ext == "mmpz" )
1000 	{
1001 		m_type = ProjectFile;
1002 		m_handling = LoadAsProject;
1003 	}
1004 	else if( ext == "xpf" || ext == "xml" )
1005 	{
1006 		m_type = PresetFile;
1007 		m_handling = LoadAsPreset;
1008 	}
1009 	else if( ext == "xiz" && ! pluginFactory->pluginSupportingExtension(ext).isNull() )
1010 	{
1011 		m_type = PresetFile;
1012 		m_handling = LoadByPlugin;
1013 	}
1014 	else if( ext == "sf2" )
1015 	{
1016 		m_type = SoundFontFile;
1017 	}
1018 	else if( ext == "pat" )
1019 	{
1020 		m_type = PatchFile;
1021 	}
1022 	else if( ext == "mid" )
1023 	{
1024 		m_type = MidiFile;
1025 		m_handling = ImportAsProject;
1026 	}
1027 	else if( ext == "dll" )
1028 	{
1029 		m_type = VstPluginFile;
1030 		m_handling = LoadByPlugin;
1031 	}
1032 	else
1033 	{
1034 		m_type = UnknownFile;
1035 	}
1036 
1037 	if( m_handling == NotSupported &&
1038 		!ext.isEmpty() && ! pluginFactory->pluginSupportingExtension(ext).isNull() )
1039 	{
1040 		m_handling = LoadByPlugin;
1041 		// classify as sample if not classified by anything yet but can
1042 		// be handled by a certain plugin
1043 		if( m_type == UnknownFile )
1044 		{
1045 			m_type = SampleFile;
1046 		}
1047 	}
1048 }
1049 
1050 
1051 
1052 
extension(void)1053 QString FileItem::extension( void )
1054 {
1055 	return extension( fullName() );
1056 }
1057 
1058 
1059 
1060 
extension(const QString & file)1061 QString FileItem::extension(const QString & file )
1062 {
1063 	return QFileInfo( file ).suffix().toLower();
1064 }
1065 
1066 
1067 
1068 
1069