/* This file is part of the KDE project Copyright (C) 2004 Mark Kretschmann Copyright (C) 2003 Alexander Dymo Copyright (C) 2003 Roberto Raggi Copyright (C) 2001 Christoph Cullmann Copyright (C) 2001 Joseph Wenninger Copyright (C) 2001 Anders Lund This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Steet, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "amarok.h" #include "browserToolBar.h" #include "clicklineedit.h" #include "enginecontroller.h" #include "filebrowser.h" #include "k3bexporter.h" #include #include #include "kbookmarkhandler.h" #include #include #include #include #include #include #include ///@see SearchPane #include ///@see ctor #include #include #include "mediabrowser.h" #include "medium.h" #include "mydirlister.h" #include "mydiroperator.h" #include "playlist.h" #include "playlistbrowser.h" #include "playlistloader.h" #include "playlistwindow.h" #include "collectionbrowser.h" #include "statusbar.h" #include "tagdialog.h" #include #include #include #include #include #include //BEGIN Constructor/destructor FileBrowser::FileBrowser( const char * name, Medium * medium ) : TQVBox( 0, name ) { TDEActionCollection *actionCollection; SearchPane *searchPane; KURL *location; // Try to keep filebrowser working even if not in a medium context // so if a medium object not passed in, keep earlier behavior if (!medium) { m_medium = 0; location = new KURL( Amarok::config( "Filebrowser" )->readPathEntry( "Location", TQDir::homeDirPath() ) ); KFileItem *currentFolder = new KFileItem( KFileItem::Unknown, KFileItem::Unknown, *location ); //TDEIO sucks, NetAccess::exists puts up a dialog and has annoying error message boxes //if there is a problem so there is no point in using it anyways. //so... setting the diroperator to ~ is the least sucky option if ( !location->isLocalFile() || !currentFolder->isReadable() ) { delete location; location = new KURL( TQDir::homeDirPath() ) ; } } else{ m_medium = medium; location = new KURL( m_medium->mountPoint() ); } TDEActionCollection* ac = new TDEActionCollection( this ); KStdAction::selectAll( TQT_TQOBJECT(this), TQT_SLOT( selectAll() ), ac, "filebrowser_select_all" ); TDEToolBar *toolbar = new Browser::ToolBar( this ); { //Filter LineEdit TDEToolBar* searchToolBar = new Browser::ToolBar( this ); TDEToolBarButton *button = new TDEToolBarButton( "locationbar_erase", 0, searchToolBar ); m_filter = new ClickLineEdit( i18n( "Enter search terms here" ), searchToolBar ); searchToolBar->setStretchableWidget( m_filter ); connect( button, TQT_SIGNAL(clicked()), m_filter, TQT_SLOT(clear()) ); TQToolTip::add( button, i18n( "Clear search field" ) ); TQToolTip::add( m_filter, i18n( "Enter space-separated terms to search in the directory-listing" ) ); } { //Directory Listing TQVBox *container; TQHBox *box; container = new TQVBox( this ); container->setFrameStyle( m_filter->frameStyle() ); container->setMargin( 3 ); container->setSpacing( 2 ); container->setBackgroundMode( TQt::PaletteBase ); box = new TQHBox( container ); box->setMargin( 3 ); box->setBackgroundMode( TQt::PaletteBase ); //folder selection combo box m_combo = new KURLComboBox( KURLComboBox::Directories, true, box, "path combo" ); if (!m_medium){ m_combo->setCompletionObject( new KURLCompletion( KURLCompletion::DirCompletion ) ); m_combo->setAutoDeleteCompletionObject( true ); } m_combo->setMaxItems( 9 ); m_combo->setURLs( Amarok::config( "Filebrowser" )->readPathListEntry( "Dir History" ) ); if (!m_medium) m_combo->lineEdit()->setText( location->path() ); else m_combo->lineEdit()->setText( "/" ); //The main widget with file listings and that m_dir = new MyDirOperator( *location, container, m_medium ); m_dir->setEnableDirHighlighting( true ); m_dir->setMode( KFile::Mode((int)KFile::Files | (int)KFile::Directory) ); //allow selection of multiple files + dirs m_dir->setOnlyDoubleClickSelectsFiles( true ); //Amarok type settings m_dir->readConfig( Amarok::config( "Filebrowser" ) ); m_dir->setView( KFile::Default ); //will set userconfigured view, will load URL m_dir->setSizePolicy( TQSizePolicy::Preferred, TQSizePolicy::Expanding ); m_dir->setAcceptDrops( true ); //Automatically open folder after hovering above it...probably a good thing //but easily disabled by commenting this line out //Disabled for now because can't show . and .. folders. //TODO: Find out a way to fix this? //m_dir->setDropOptions( KFileView::AutoOpenDirs ); static_cast(m_dir->viewWidget())->setFrameStyle( TQFrame::NoFrame ); static_cast(m_dir->viewWidget())->setSpacing( 1 ); actionCollection = m_dir->actionCollection(); searchPane = new SearchPane( this ); setStretchFactor( container, 2 ); } { TQPopupMenu* const menu = static_cast(actionCollection->action("popupMenu"))->popupMenu(); menu->clear(); menu->insertItem( SmallIconSet( Amarok::icon( "files" ) ), i18n( "&Load" ), MakePlaylist ); menu->insertItem( SmallIconSet( Amarok::icon( "add_playlist" ) ), i18n( "&Append to Playlist" ), AppendToPlaylist ); menu->insertItem( SmallIconSet( Amarok::icon( "queue_track" ) ), i18n( "&Queue Track" ), QueueTrack ); menu->insertItem( SmallIconSet( Amarok::icon( "queue_track" ) ), i18n( "&Queue Tracks" ), QueueTracks ); menu->insertItem( SmallIconSet( Amarok::icon( "save" ) ), i18n( "&Save as Playlist..." ), SavePlaylist ); menu->insertSeparator(); if (!m_medium) menu->insertItem( SmallIconSet( Amarok::icon( "device" ) ), i18n( "&Transfer to Media Device" ), MediaDevice ); menu->insertItem( SmallIconSet( Amarok::icon( "collection" ) ), i18n( "&Organize Files..." ), OrganizeFiles ); menu->insertItem( SmallIconSet( Amarok::icon( "collection" ) ), i18n( "&Copy Files to Collection..." ), CopyToCollection ); menu->insertItem( SmallIconSet( Amarok::icon( "collection" ) ), i18n( "&Move Files to Collection..." ), MoveToCollection ); menu->insertItem( SmallIconSet( Amarok::icon( "burn" ) ), i18n("Burn to CD..."), BurnCd ); menu->insertSeparator(); menu->insertItem( i18n( "&Select All Files" ), SelectAllFiles ); menu->insertSeparator(); actionCollection->action( "delete" )->setIcon( Amarok::icon( "remove" ) ); actionCollection->action( "delete" )->plug( menu ); menu->insertSeparator(); menu->insertItem( SmallIconSet( Amarok::icon( "info" ) ), i18n( "Edit Track &Information..." ), EditTags ); actionCollection->action( "properties" )->plug( menu ); menu->setItemEnabled( BurnCd, K3bExporter::isAvailable() ); connect( menu, TQT_SIGNAL(aboutToShow()), TQT_SLOT(prepareContextMenu()) ); connect( menu, TQT_SIGNAL(activated( int )), TQT_SLOT(contextMenuActivated( int )) ); } { TDEActionMenu *a; a = static_cast( actionCollection->action( "sorting menu" ) ); a->setIcon( Amarok::icon( "configure" ) ); a->setDelayed( false ); //TODO should be done by KDirOperator actionCollection->action( "delete" )->setShortcut( TDEShortcut( SHIFT + Key_Delete ) ); a = new TDEActionMenu( i18n("Bookmarks"), "bookmark", actionCollection, "bookmarks" ); a->setDelayed( false ); new KBookmarkHandler( m_dir, a->popupMenu() ); } { if ( TDEAction *a = actionCollection->action( "up" ) ) a->plug( toolbar ); if ( TDEAction *a = actionCollection->action( "back" ) ) a->plug( toolbar ); if ( TDEAction *a = actionCollection->action( "forward" ) ) a->plug( toolbar ); if ( TDEAction *a = actionCollection->action( "home" ) ) a->plug( toolbar ); if ( TDEAction *a = actionCollection->action( "reload" ) ) { a->setIcon( Amarok::icon( "refresh" ) ); a->plug( toolbar ); } toolbar->insertLineSeparator(); if ( TDEAction *a = actionCollection->action( "short view" ) ) a->plug( toolbar ); if ( TDEAction *a = actionCollection->action( "detailed view" ) ) a->plug( toolbar ); toolbar->insertLineSeparator(); if ( TDEAction *a = actionCollection->action( "sorting menu" ) ) a->plug( toolbar ); if ( TDEAction *a = actionCollection->action( "bookmarks" ) ) a->plug( toolbar ); TDEAction *gotoCurrent = new TDEAction( i18n("Go To Current Track Folder"), Amarok::icon( "music" ), 0, TQT_TQOBJECT(this), TQT_SLOT( gotoCurrentFolder() ), actionCollection ); gotoCurrent->plug( toolbar ); disconnect( actionCollection->action( "up" ), TQT_SIGNAL( activated() ), m_dir, TQT_SLOT( cdUp() ) ); connect( actionCollection->action( "up" ), TQT_SIGNAL( activated() ), m_dir, TQT_SLOT( myCdUp() ) ); disconnect( actionCollection->action( "home" ), TQT_SIGNAL( activated() ), m_dir, TQT_SLOT( home() ) ); connect( actionCollection->action( "home" ), TQT_SIGNAL( activated() ), m_dir, TQT_SLOT( myHome() ) ); } connect( m_filter, TQT_SIGNAL(textChanged( const TQString& )), TQT_SLOT(setFilter( const TQString& )) ); connect( m_combo, TQT_SIGNAL(urlActivated( const KURL& )), TQT_SLOT(setUrl( const KURL& )) ); connect( m_combo, TQT_SIGNAL(returnPressed( const TQString& )), TQT_SLOT(setUrl( const TQString& )) ); connect( m_dir, TQT_SIGNAL(viewChanged( KFileView* )), TQT_SLOT(slotViewChanged( KFileView* )) ); connect( m_dir, TQT_SIGNAL(fileSelected( const KFileItem* )), TQT_SLOT(activate( const KFileItem* )) ); connect( m_dir, TQT_SIGNAL(urlEntered( const KURL& )), TQT_SLOT(urlChanged( const KURL& )) ); connect( m_dir, TQT_SIGNAL(urlEntered( const KURL& )), searchPane, TQT_SLOT(urlChanged( const KURL& )) ); connect( m_dir, TQT_SIGNAL(dropped( const KFileItem*, TQDropEvent*, const KURL::List& )), TQT_SLOT(dropped( const KFileItem*, TQDropEvent*, const KURL::List& )) ); setSpacing( 4 ); setFocusProxy( m_dir ); //so the dirOperator is focused when we get focus events // Toolbar is more than 250px wide, BrowserBar doesn't allow that. -> Resizing issues. setMinimumWidth( 250 /* toolbar->sizeHint().width() */ ); } FileBrowser::~FileBrowser() { TDEConfig* const c = Amarok::config( "Filebrowser" ); m_dir->writeConfig( c ); //uses currently set group c->writePathEntry( "Location", m_dir->url().url() ); c->writePathEntry( "Dir History", m_combo->urls() ); } //END Constructor/Destructor void FileBrowser::setUrl( const KURL &url ) { m_dir->setFocus(); if (!m_medium) m_dir->setURL( url, true ); else { TQString urlpath = url.isLocalFile() ? url.path() : url.prettyURL(); KURL newURL( urlpath.prepend( m_medium->mountPoint() ).remove("..") ); //debug() << "set-url-kurl: changing to: " << newURL.path() << endl; m_dir->setURL( newURL, true ); } } void FileBrowser::setUrl( const TQString &url ) { if (!m_medium) m_dir->setURL( KURL(url), true ); else{ KURL newURL( TQString(url).prepend( m_medium->mountPoint() ).remove("..") ); //debug() << "set-url-qstring: changing to: " << newURL.path() << endl; m_dir->setURL( newURL, true ); } } //BEGIN Private Methods KURL::List FileBrowser::selectedItems() { KURL::List list; for( KFileItemListIterator it( m_dir->selectedItems()->count() ? *m_dir->selectedItems() : *m_dir->view()->items() ); *it; ++it ) list.append( (*it)->url() ); return list; } void FileBrowser::playlistFromURLs( const KURL::List &urls ) { TQString suggestion; if( urls.count() == 1 && TQFileInfo( urls.first().path() ).isDir() ) suggestion = urls.first().fileName(); else suggestion = i18n( "Untitled" ); const TQString path = PlaylistDialog::getSaveFileName( suggestion ); if( path.isEmpty() ) return; if( PlaylistBrowser::savePlaylist( path, urls ) ) { //FIXME: uncomment after string freeze //Amarok::StatusBar::instance()->shortMessage( "Playlist saved to playlist browser" ); } } //END Private Methods //BEGIN Public Slots void FileBrowser::setFilter( const TQString &text ) { if ( text.isEmpty() ) m_dir->clearFilter(); else { TQString filter; const TQStringList terms = TQStringList::split( ' ', text ); foreach( terms ) { filter += '*'; filter += *it; } filter += '*'; m_dir->setNameFilter( filter ); } m_dir->updateDir(); } void FileBrowser::dropped( const KFileItem* /*item*/, TQDropEvent* event, const KURL::List &urls){ //Do nothing right now event->ignore(); //Run into const problems iterating over the list, so copy it to a malleable one //(besides, need to filter for local giles) KURL::List list(urls); for ( KURL::List::iterator it = list.begin(); it != list.end(); ){ if ( m_medium && !(*it).isLocalFile() ) it = list.erase( it ); else{ debug() << "Dropped: " << (*it) << endl; it++; } } } //END Public Slots //BEGIN Private Slots inline void FileBrowser::urlChanged( const KURL &u ) { //the DirOperator's URL has changed TQString url = u.isLocalFile() ? u.path() : u.prettyURL(); if( m_medium ){ //remove the leading mountPoint value url.remove( 0, m_medium->mountPoint().length() ); } TQStringList urls = m_combo->urls(); urls.remove( url ); urls.prepend( url ); m_combo->setURLs( urls, KURLComboBox::RemoveBottom ); } inline void FileBrowser::slotViewChanged( KFileView *view ) { if( view->widget()->inherits( "TDEListView" ) ) { using namespace Amarok::ColorScheme; static_cast(view->widget())->setAlternateBackground( AltBase ); } } inline void FileBrowser::activate( const KFileItem *item ) { Playlist::instance()->insertMedia( item->url(), Playlist::DefaultOptions ); } inline void FileBrowser::prepareContextMenu() { const KFileItemList &items = *m_dir->selectedItems(); static_cast(m_dir->actionCollection()->action("popupMenu"))->popupMenu()->setItemVisible( SavePlaylist, items.count() > 1 || ( items.count() == 1 && items.getFirst()->isDir() ) ); static_cast(m_dir->actionCollection()->action("popupMenu"))->popupMenu()->setItemVisible( QueueTrack, items.count() == 1 ); static_cast(m_dir->actionCollection()->action("popupMenu"))->popupMenu()->setItemVisible( QueueTracks, items.count() > 1 ); static_cast(m_dir->actionCollection()->action("popupMenu"))->popupMenu()->setItemVisible( MediaDevice, MediaBrowser::isAvailable() ); static_cast(m_dir->actionCollection()->action("popupMenu"))->popupMenu()->setItemVisible( MoveToCollection, !CollectionDB::instance()->isDirInCollection( url().path() ) ); static_cast(m_dir->actionCollection()->action("popupMenu"))->popupMenu()->setItemVisible( CopyToCollection, !CollectionDB::instance()->isDirInCollection( url().path() ) ); static_cast(m_dir->actionCollection()->action("popupMenu"))->popupMenu()->setItemVisible( OrganizeFiles, CollectionDB::instance()->isDirInCollection( url().path() ) ); } inline void FileBrowser::contextMenuActivated( int id ) { switch( id ) { case MakePlaylist: Playlist::instance()->insertMedia( selectedItems(), Playlist::Replace ); break; case SavePlaylist: playlistFromURLs( selectedItems() ); break; case AppendToPlaylist: Playlist::instance()->insertMedia( selectedItems() ); break; case QueueTrack: case QueueTracks: Playlist::instance()->insertMedia( selectedItems(), Playlist::Queue ); break; case EditTags: { KURL::List list = Amarok::recursiveUrlExpand( selectedItems() ); TagDialog *dialog = NULL; if( list.count() == 1 ) { dialog = new TagDialog( list.first(), this ); } else { dialog = new TagDialog( list, this ); } dialog->show(); } break; case CopyToCollection: CollectionView::instance()->organizeFiles( selectedItems(), i18n( "Copy Files To Collection" ), true ); break; case MoveToCollection: CollectionView::instance()->organizeFiles( selectedItems(), i18n( "Move Files To Collection" ), false ); break; case OrganizeFiles: CollectionView::instance()->organizeFiles( selectedItems(), i18n( "Organize Collection Files" ), false ); break; case MediaDevice: MediaBrowser::queue()->addURLs( selectedItems() ); break; case SelectAllFiles: selectAll(); break; case BurnCd: K3bExporter::instance()->exportTracks( selectedItems() ); break; } } inline void FileBrowser::gotoCurrentFolder() { const KURL &url = EngineController::instance()->bundle().url(); KURL dirURL = KURL::fromPathOrURL( url.directory() ); m_combo->setURL( dirURL ); setUrl( dirURL ); } //END Private Slots void FileBrowser::selectAll() { KFileItemList list( *m_dir->view()->items() ); // Select all items which represent files for( KFileItem* item = list.first(); item; item = list.next() ) m_dir->view()->setSelected( item, item->isFile() ); } #include #include #include class KURLView : public TDEListView { public: KURLView( TQWidget *parent ) : TDEListView( parent ) { reinterpret_cast(header())->hide(); addColumn( TQString() ); setResizeMode( TDEListView::LastColumn ); setDragEnabled( true ); setSelectionMode( TQListView::Extended ); } class Item : public TDEListViewItem { public: Item( const KURL &url, KURLView *parent ) : TDEListViewItem( parent, url.fileName() ), m_url( url ) {} KURL m_url; }; virtual TQDragObject *dragObject() { TQPtrList items = selectedItems(); KURL::List urls; for( Item *item = static_cast( items.first() ); item; item = static_cast( items.next() ) ) urls += item->m_url; return new KURLDrag( urls, this ); } virtual void viewportPaintEvent( TQPaintEvent *e ) { TDEListView::viewportPaintEvent( e ); if ( childCount() == 0 ) { TQPainter p( viewport() ); if ( m_text.isEmpty() ) { //TODO Perhaps it's time to put this in some header, as we use it in three places now TQSimpleRichText t( i18n( "
" "Enter a search term above; you can use wildcards like * and ?" "
" ), font() ); t.setWidth( width() - 50 ); const uint w = t.width() + 20; const uint h = t.height() + 20; p.setBrush( colorGroup().background() ); p.drawRoundRect( 15, 15, w, h, (8*200)/w, (8*200)/h ); t.draw( &p, 20, 20, TQRect(), colorGroup() ); } else { p.setPen( palette().color( TQPalette::Disabled, TQColorGroup::Text ) ); p.drawText( rect(), TQt::AlignCenter | TQt::WordBreak, m_text ); } } } void unsetText() { setText( TQString() ); } void setText( const TQString &text ) { m_text = text; viewport()->update(); } private: TQString m_text; }; SearchPane::SearchPane( FileBrowser *parent ) : TQVBox( parent ) , m_lineEdit( 0 ) , m_listView( 0 ) , m_lister( 0 ) { TQFrame *container = new TQVBox( this, "container" ); container->hide(); { TQFrame *box = new TQHBox( container ); box->setMargin( 5 ); box->setBackgroundMode( TQt::PaletteBase ); m_lineEdit = new ClickLineEdit( i18n("Search here..."), box ); connect( m_lineEdit, TQT_SIGNAL(textChanged( const TQString& )), TQT_SLOT(searchTextChanged( const TQString& )) ); m_listView = new KURLView( container ); container->setFrameStyle( m_listView->frameStyle() ); container->setMargin( 5 ); container->setBackgroundMode( TQt::PaletteBase ); m_listView->setFrameStyle( TQFrame::NoFrame ); connect( m_listView, TQT_SIGNAL(executed( TQListViewItem* )), TQT_SLOT(activate( TQListViewItem* )) ); } KPushButton *button = new KPushButton( KGuiItem( i18n("&Show Search Panel"), "edit-find" ), this ); button->setToggleButton( true ); connect( button, TQT_SIGNAL(toggled( bool )), TQT_SLOT(toggle( bool )) ); m_lister = new MyDirLister( true /*delay mimetypes*/ ); insertChild( m_lister ); connect( m_lister, TQT_SIGNAL(newItems( const KFileItemList& )), TQT_SLOT(searchMatches( const KFileItemList& )) ); connect( m_lister, TQT_SIGNAL(completed()), TQT_SLOT(searchComplete()) ); } void SearchPane::toggle( bool toggled ) { if ( toggled ) m_lineEdit->setFocus(); TQT_TQWIDGET(child("container"))->setShown( toggled ); } void SearchPane::urlChanged( const KURL& ) { searchTextChanged( m_lineEdit->text() ); } void SearchPane::searchTextChanged( const TQString &text ) { //TODO if user changes search directory then we need to update the search too m_lister->stop(); m_listView->clear(); m_dirs.clear(); if ( text.isEmpty() ) { m_listView->unsetText(); return; } m_filter = TQRegExp( text.contains( "*" ) ? text : '*'+text+'*', false, true ); m_lister->openURL( searchURL() ); m_listView->setText( i18n( "Searching..." ) ); } void SearchPane::searchMatches( const KFileItemList &list ) { for( KFileItemList::ConstIterator it = list.begin(), end = list.end(); it != end; ++it ) { if( (*it)->isDir() ) m_dirs += (*it)->url(); else if( m_filter.exactMatch( (*it)->name() ) ) new KURLView::Item( (*it)->url(), static_cast( m_listView ) ); } } void SearchPane::searchComplete() { //KDirLister crashes if you call openURL() from a slot //connected to KDirLister::complete() //TODO fix crappy KDElibs TQTimer::singleShot( 0, this, TQT_SLOT(_searchComplete()) ); } void SearchPane::_searchComplete() { if ( !m_dirs.isEmpty() ) { KURL url = m_dirs.first(); m_dirs.pop_front(); m_lister->openURL( url ); } else m_listView->setText( i18n("No results found") ); //only displayed if the listview is empty } void SearchPane::activate( TQListViewItem *item ) { Playlist::instance()->insertMedia( static_cast(item)->m_url, Playlist::DirectPlay ); } #include "filebrowser.moc"