File indexing completed on 2024-05-19 04:48:40

0001 /****************************************************************************************
0002  * Copyright (c) 2008 Nikolaj Hald Nielsen <nhn@kde.org>                                *
0003  * Copyright (c) 2010 Bart Cerneels <bart.cerneels@kde.org>                             *
0004  *                                                                                      *
0005  * This program is free software; you can redistribute it and/or modify it under        *
0006  * the terms of the GNU General Public License as published by the Free Software        *
0007  * Foundation; either version 2 of the License, or (at your option) any later           *
0008  * version.                                                                             *
0009  *                                                                                      *
0010  * This program is distributed in the hope that it will be useful, but WITHOUT ANY      *
0011  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A      *
0012  * PARTICULAR PURPOSE. See the GNU General Public License for more details.             *
0013  *                                                                                      *
0014  * You should have received a copy of the GNU General Public License along with         *
0015  * this program.  If not, see <http://www.gnu.org/licenses/>.                           *
0016  ****************************************************************************************/
0017 
0018 #define DEBUG_PREFIX "PlaylistBrowserView"
0019 
0020 #include "PlaylistBrowserView.h"
0021 
0022 #include "MainWindow.h"
0023 #include "PaletteHandler.h"
0024 #include "PopupDropperFactory.h"
0025 #include "SvgHandler.h"
0026 #include "amarokconfig.h"
0027 #include "browsers/playlistbrowser/PlaylistBrowserModel.h"
0028 #include "browsers/playlistbrowser/PlaylistsByProviderProxy.h"
0029 #include "browsers/playlistbrowser/PlaylistsInFoldersProxy.h"
0030 #include "context/ContextView.h"
0031 #include "core/support/Debug.h"
0032 #include "core-impl/playlists/types/file/PlaylistFileSupport.h"
0033 #include "playlist/PlaylistModel.h"
0034 #include "playlistmanager/PlaylistManager.h"
0035 #include "widgets/PrettyTreeRoles.h"
0036 
0037 #include <QCheckBox>
0038 #include <QFileDialog>
0039 #include <QKeyEvent>
0040 #include <QLabel>
0041 #include <QMenu>
0042 #include <QMessageBox>
0043 #include <QMouseEvent>
0044 
0045 #include <KConfigGroup>
0046 
0047 #include <algorithm>
0048 
0049 using namespace PlaylistBrowserNS;
0050 
0051 PlaylistBrowserNS::PlaylistBrowserView::PlaylistBrowserView( QAbstractItemModel *model,
0052                                                              QWidget *parent )
0053     : Amarok::PrettyTreeView( parent )
0054     , m_pd( nullptr )
0055     , m_ongoingDrag( false )
0056 {
0057     DEBUG_BLOCK
0058     setModel( model );
0059     setSelectionMode( QAbstractItemView::ExtendedSelection );
0060     setSelectionBehavior( QAbstractItemView::SelectItems );
0061     setDragDropMode( QAbstractItemView::DragDrop );
0062     setAcceptDrops( true );
0063     setEditTriggers( QAbstractItemView::EditKeyPressed );
0064     setMouseTracking( true ); // needed for highlighting provider action icons
0065 
0066     m_createEmptyPlaylistAction = new QAction( QIcon::fromTheme( QStringLiteral("media-track-add-amarok") ),
0067                                                i18n( "Create an Empty Playlist" ), this );
0068     connect( m_createEmptyPlaylistAction, &QAction::triggered, this, &PlaylistBrowserView::slotCreateEmptyPlaylist );
0069 
0070     m_appendAction = new QAction( QIcon::fromTheme( QStringLiteral("media-track-add-amarok") ),
0071             i18n( "&Add to Playlist" ), this );
0072     m_appendAction->setProperty( "popupdropper_svg_id", "append" );
0073     connect( m_appendAction, &QAction::triggered, this, &PlaylistBrowserView::slotAppend );
0074 
0075     m_loadAction = new QAction( QIcon::fromTheme( QStringLiteral("folder-open") ), i18nc( "Replace the currently "
0076             "loaded tracks with these", "&Replace Playlist" ), this );
0077     m_loadAction->setProperty( "popupdropper_svg_id", "load" );
0078     connect( m_loadAction, &QAction::triggered, this, &PlaylistBrowserView::slotLoad );
0079 
0080     m_setNewAction = new QAction( QIcon::fromTheme( QStringLiteral("rating") ), i18nc( "toggle the \"new\" status "
0081             " of this podcast episode", "&New" ), this );
0082     m_setNewAction->setProperty( "popupdropper_svg_id", "new" );
0083     m_setNewAction->setCheckable( true );
0084     connect( m_setNewAction, &QAction::triggered, this, &PlaylistBrowserView::slotSetNew );
0085 
0086     m_renamePlaylistAction = new QAction( QIcon::fromTheme( QStringLiteral("media-track-edit-amarok") ),
0087             i18n( "&Rename..." ), this );
0088     m_renamePlaylistAction->setProperty( "popupdropper_svg_id", "edit" );
0089     // key shortcut is only for display purposes here, actual one is determined by View in Model/View classes
0090     m_renamePlaylistAction->setShortcut( Qt::Key_F2 );
0091     connect( m_renamePlaylistAction, &QAction::triggered, this, &PlaylistBrowserView::slotRename );
0092 
0093     m_deletePlaylistAction = new QAction( QIcon::fromTheme( QStringLiteral("media-track-remove-amarok") ),
0094             i18n( "&Delete..." ), this );
0095     m_deletePlaylistAction->setProperty( "popupdropper_svg_id", "delete" );
0096     // key shortcut is only for display purposes here, actual one is determined by View in Model/View classes
0097     m_deletePlaylistAction->setShortcut( Qt::Key_Delete );
0098     connect( m_deletePlaylistAction, &QAction::triggered, this, &PlaylistBrowserView::slotDelete );
0099 
0100     m_removeTracksAction = new QAction( QIcon::fromTheme( QStringLiteral("media-track-remove-amarok") ),
0101             QStringLiteral( "<placeholder>" ), this );
0102     m_removeTracksAction->setProperty( "popupdropper_svg_id", "delete" );
0103     // key shortcut is only for display purposes here, actual one is determined by View in Model/View classes
0104     m_removeTracksAction->setShortcut( Qt::Key_Delete );
0105     connect( m_removeTracksAction, &QAction::triggered, this, &PlaylistBrowserView::slotRemoveTracks );
0106 
0107     m_exportAction = new QAction( QIcon::fromTheme( QStringLiteral("document-export-amarok") ),
0108             i18n( "&Export As..." ), this );
0109     connect( m_exportAction, &QAction::triggered, this, &PlaylistBrowserView::slotExport );
0110 
0111     m_separatorAction = new QAction( this );
0112     m_separatorAction->setSeparator( true );
0113 }
0114 
0115 void
0116 PlaylistBrowserNS::PlaylistBrowserView::setModel( QAbstractItemModel *model )
0117 {
0118     if( this->model() )
0119         disconnect( this->model(), nullptr, this, nullptr );
0120     Amarok::PrettyTreeView::setModel( model );
0121 
0122     connect( this->model(), SIGNAL(renameIndex(QModelIndex)), SLOT(edit(QModelIndex)) );
0123 }
0124 
0125 void
0126 PlaylistBrowserNS::PlaylistBrowserView::mouseReleaseEvent( QMouseEvent *event )
0127 {
0128     if( m_pd )
0129     {
0130         connect( m_pd, &PopupDropper::fadeHideFinished, m_pd, &QObject::deleteLater );
0131         m_pd->hide();
0132         m_pd = nullptr;
0133     }
0134 
0135     QModelIndex index = indexAt( event->pos() );
0136     if( !index.isValid() )
0137     {
0138         PrettyTreeView::mouseReleaseEvent( event );
0139         return;
0140     }
0141 
0142     if( event->button() == Qt::MidButton )
0143     {
0144         insertIntoPlaylist( index, Playlist::OnMiddleClickOnSelectedItems );
0145         event->accept();
0146         return;
0147     }
0148 
0149     PrettyTreeView::mouseReleaseEvent( event );
0150 }
0151 
0152 void PlaylistBrowserNS::PlaylistBrowserView::startDrag( Qt::DropActions supportedActions )
0153 {
0154     // Waah? when a parent item is dragged, startDrag is called a bunch of times
0155     if( m_ongoingDrag )
0156         return;
0157     m_ongoingDrag = true;
0158 
0159     if( !m_pd )
0160         m_pd = The::popupDropperFactory()->createPopupDropper( Context::ContextView::self() );
0161 
0162     if( m_pd && m_pd->isHidden() )
0163     {
0164         QActionList actions = actionsFor( selectedIndexes() );
0165         foreach( QAction *action, actions )
0166             m_pd->addItem( The::popupDropperFactory()->createItem( action ) );
0167 
0168         m_pd->show();
0169     }
0170 
0171     QTreeView::startDrag( supportedActions );
0172 
0173     // We keep the items that the actions need to be applied to.
0174     // Clear the data from all actions now that the PUD has executed.
0175     resetActionTargets();
0176 
0177     if( m_pd )
0178     {
0179         connect( m_pd, &PopupDropper::fadeHideFinished, m_pd, &PopupDropper::clear );
0180         m_pd->hide();
0181     }
0182     m_ongoingDrag = false;
0183 }
0184 
0185 void
0186 PlaylistBrowserNS::PlaylistBrowserView::keyPressEvent( QKeyEvent *event )
0187 {
0188     QModelIndexList indices = selectedIndexes();
0189     // mind bug 305203
0190     if( indices.isEmpty() || state() != QAbstractItemView::NoState )
0191     {
0192         Amarok::PrettyTreeView::keyPressEvent( event );
0193         return;
0194     }
0195 
0196     switch( event->key() )
0197     {
0198         //activated() only works for current index, not all selected
0199         case Qt::Key_Enter:
0200         case Qt::Key_Return:
0201             insertIntoPlaylist( indices, Playlist::OnReturnPressedOnSelectedItems );
0202             return;
0203         case Qt::Key_Delete:
0204         {
0205             QActionList actions = actionsFor( indices ); // sets action targets
0206             if( actions.contains( m_removeTracksAction ) )
0207                 m_removeTracksAction->trigger();
0208             else if( actions.contains( m_deletePlaylistAction ) )
0209                 m_deletePlaylistAction->trigger();
0210             resetActionTargets();
0211             return;
0212         }
0213         default:
0214             break;
0215     }
0216     Amarok::PrettyTreeView::keyPressEvent( event );
0217 }
0218 
0219 void
0220 PlaylistBrowserNS::PlaylistBrowserView::mouseDoubleClickEvent( QMouseEvent *event )
0221 {
0222     if( event->button() == Qt::MidButton )
0223     {
0224         event->accept();
0225         return;
0226     }
0227 
0228     QModelIndex index = indexAt( event->pos() );
0229     if( !index.isValid() )
0230     {
0231         event->accept();
0232         return;
0233     }
0234 
0235     // code copied in CollectionTreeView::mouseDoubleClickEvent(), keep in sync
0236     // mind bug 279513
0237     bool isExpandable = model()->hasChildren( index );
0238     bool wouldExpand = !visualRect( index ).contains( event->pos() ) || // clicked outside item, perhaps on expander icon
0239                        ( isExpandable && !style()->styleHint( QStyle::SH_ItemView_ActivateItemOnSingleClick, nullptr, this ) ); // we're in doubleClick
0240     if( event->button() == Qt::LeftButton &&
0241         event->modifiers() == Qt::NoModifier &&
0242         !wouldExpand )
0243     {
0244         insertIntoPlaylist( index, Playlist::OnDoubleClickOnSelectedItems );
0245         event->accept();
0246         return;
0247     }
0248 
0249     PrettyTreeView::mouseDoubleClickEvent( event );
0250 }
0251 
0252 void PlaylistBrowserNS::PlaylistBrowserView::contextMenuEvent( QContextMenuEvent *event )
0253 {
0254     QModelIndex clickedIdx = indexAt( event->pos() );
0255 
0256     QModelIndexList indices;
0257     if( clickedIdx.isValid() && selectedIndexes().contains( clickedIdx ) )
0258         indices << selectedIndexes();
0259     else if( clickedIdx.isValid() )
0260         indices << clickedIdx;
0261 
0262     QActionList actions = actionsFor( indices );
0263     if( actions.isEmpty() )
0264     {
0265         resetActionTargets();
0266         return;
0267     }
0268 
0269     QMenu menu;
0270     foreach( QAction *action, actions )
0271         menu.addAction( action );
0272     menu.exec( mapToGlobal( event->pos() ) );
0273 
0274     // We keep the items that the action need to be applied to.
0275     // Clear the data from all actions now that the context menu has executed.
0276     resetActionTargets();
0277 }
0278 
0279 QList<QAction *>
0280 PlaylistBrowserNS::PlaylistBrowserView::actionsFor( const QModelIndexList &indexes )
0281 {
0282     resetActionTargets();
0283     if( indexes.isEmpty() )
0284         return QActionList();
0285 
0286     using namespace Playlists;
0287     QSet<PlaylistProvider *> providers, writableProviders;
0288     QActionList actions;
0289     QModelIndexList newPodcastEpisodes, oldPodcastEpisodes;
0290     foreach( const QModelIndex &idx, indexes )
0291     {
0292         // direct provider actions:
0293         actions << idx.data( PrettyTreeRoles::DecoratorRole ).value<QActionList>();
0294 
0295         PlaylistProvider *provider = idx.data( PlaylistBrowserModel::ProviderRole ).value<PlaylistProvider *>();
0296         if( provider )
0297             providers << provider;
0298         bool isWritable =  provider ? provider->isWritable() : false;
0299         if( isWritable )
0300             writableProviders |= provider;
0301         Meta::TrackPtr track = idx.data( PlaylistBrowserModel::TrackRole ).value<Meta::TrackPtr>();
0302         PlaylistPtr playlist = idx.data( PlaylistBrowserModel::PlaylistRole ).value<PlaylistPtr>();
0303         if( !track && playlist ) // a playlist (must check it is not a track)
0304         {
0305             m_actionPlaylists << playlist;
0306             if( isWritable )
0307                 m_writableActionPlaylists << playlist;
0308         }
0309         if( track )
0310         {
0311             m_actionTracks.insert( playlist, idx.row() );
0312             if( isWritable )
0313                 m_writableActionTracks.insert( playlist, idx.row() );
0314         }
0315 
0316         QVariant episodeIsNew = idx.data( PlaylistBrowserModel::EpisodeIsNewRole );
0317         if( episodeIsNew.type() == QVariant::Bool )
0318         {
0319             if( episodeIsNew.toBool() )
0320                 newPodcastEpisodes << idx;
0321             else
0322                 oldPodcastEpisodes << idx;
0323         }
0324     }
0325     // all actions taking provider have only sense with one provider
0326     if( writableProviders.count() == 1 )
0327         m_writableActionProvider = writableProviders.values().first();
0328 
0329     // process per-provider actions
0330     foreach( PlaylistProvider *provider, providers )
0331     {
0332         // prepare arguments and get relevant actions
0333         PlaylistList providerPlaylists;
0334         foreach( const PlaylistPtr &playlist, m_actionPlaylists )
0335         {
0336             if( playlist->provider() == provider )
0337                 providerPlaylists << playlist;
0338         }
0339         actions << provider->playlistActions( providerPlaylists );
0340 
0341         QMultiHash<PlaylistPtr, int> playlistTracks;
0342         QHashIterator<PlaylistPtr, int> it( m_actionTracks );
0343         while( it.hasNext() )
0344         {
0345             it.next();
0346             if( it.key()->provider() == provider )
0347                 playlistTracks.insert( it.key(), it.value() );
0348         }
0349         actions << provider->trackActions( playlistTracks );
0350     }
0351 
0352     // separate model actions from standard actions we provide (at the top)
0353     QActionList standardActions;
0354     if( m_actionPlaylists.isEmpty() && m_actionTracks.isEmpty() && m_writableActionProvider )
0355         standardActions << m_createEmptyPlaylistAction;
0356     if( !m_actionPlaylists.isEmpty() || !m_actionTracks.isEmpty() )
0357         standardActions << m_appendAction << m_loadAction;
0358     if( !newPodcastEpisodes.isEmpty() || !oldPodcastEpisodes.isEmpty() )
0359     {
0360         m_setNewAction->setChecked( oldPodcastEpisodes.isEmpty() );
0361         m_setNewAction->setData( QVariant::fromValue( newPodcastEpisodes + oldPodcastEpisodes ) );
0362         standardActions << m_setNewAction;
0363     }
0364     if( m_writableActionPlaylists.count() == 1 && m_actionTracks.isEmpty() )
0365         standardActions << m_renamePlaylistAction;
0366     if( !m_writableActionPlaylists.isEmpty() && m_actionTracks.isEmpty() )
0367         standardActions << m_deletePlaylistAction;
0368     if( m_actionPlaylists.isEmpty() && !m_writableActionTracks.isEmpty() )
0369     {
0370         const int actionTrackCount = m_writableActionTracks.count();
0371         const int playlistCount = m_writableActionTracks.uniqueKeys().count();
0372         if( playlistCount > 1 )
0373             m_removeTracksAction->setText( i18nc( "%1: number of tracks. %2: number of playlists",
0374                 "Remove %1 From %2", i18ncp ("First part of 'Remove %1 From %2'", "a Track",
0375                 "%1 Tracks", actionTrackCount), i18ncp ("Second part of 'Remove %1 From %2'", "1 Playlist",
0376                 "%1 Playlists", playlistCount ) ) );
0377         else
0378             m_removeTracksAction->setText( i18ncp( "%2 is saved playlist name",
0379                 "Remove a Track From %2", "Remove %1 Tracks From %2", actionTrackCount,
0380                 m_writableActionTracks.uniqueKeys().first()->prettyName() ) );
0381         standardActions << m_removeTracksAction;
0382     }
0383     if( m_actionPlaylists.count() == 1 && m_actionTracks.isEmpty() )
0384         standardActions << m_exportAction;
0385     standardActions << m_separatorAction;
0386 
0387     return standardActions + actions;
0388 }
0389 
0390 void
0391 PlaylistBrowserView::resetActionTargets()
0392 {
0393     m_writableActionProvider = nullptr;
0394     m_actionPlaylists.clear();
0395     m_writableActionPlaylists.clear();
0396     m_actionTracks.clear();
0397     m_writableActionTracks.clear();
0398 }
0399 
0400 void
0401 PlaylistBrowserNS::PlaylistBrowserView::currentChanged( const QModelIndex &current,
0402                                                         const QModelIndex &previous )
0403 {
0404     Q_UNUSED( previous )
0405     Q_EMIT currentItemChanged( current );
0406     Amarok::PrettyTreeView::currentChanged( current, previous );
0407 }
0408 
0409 void
0410 PlaylistBrowserView::slotCreateEmptyPlaylist()
0411 {
0412     // m_actionProvider may be null, which is fine
0413     The::playlistManager()->save( Meta::TrackList(), Amarok::generatePlaylistName(
0414             Meta::TrackList() ), m_writableActionProvider );
0415 }
0416 
0417 void
0418 PlaylistBrowserView::slotAppend()
0419 {
0420     insertIntoPlaylist( Playlist::OnAppendToPlaylistAction );
0421 }
0422 
0423 void
0424 PlaylistBrowserView::slotLoad()
0425 {
0426     insertIntoPlaylist( Playlist::OnReplacePlaylistAction );
0427 }
0428 
0429 void
0430 PlaylistBrowserView::slotSetNew( bool newState )
0431 {
0432     QModelIndexList indices = m_setNewAction->data().value<QModelIndexList>();
0433     foreach( const QModelIndex &idx, indices )
0434         model()->setData( idx, newState, PlaylistBrowserModel::EpisodeIsNewRole );
0435 }
0436 
0437 void
0438 PlaylistBrowserView::slotRename()
0439 {
0440     if( m_writableActionPlaylists.count() != 1 )
0441     {
0442         warning() << __PRETTY_FUNCTION__ << "m_writableActionPlaylists.count() is not 1";
0443         return;
0444     }
0445     Playlists::PlaylistPtr playlist = m_writableActionPlaylists.at( 0 );
0446 
0447     // TODO: this makes a rather complicated round-trip and ends up in edit(QModelIndex)
0448     // here -- simplify that
0449     The::playlistManager()->rename( playlist );
0450 }
0451 
0452 void
0453 PlaylistBrowserView::slotDelete()
0454 {
0455     if( m_writableActionPlaylists.isEmpty() )
0456         return;
0457 
0458     using namespace Playlists;
0459     QHash<PlaylistProvider *, PlaylistList> providerPlaylists;
0460     foreach( const PlaylistPtr &playlist, m_writableActionPlaylists )
0461     {
0462         if( playlist->provider() )
0463             providerPlaylists[ playlist->provider() ] << playlist;
0464     }
0465     QStringList providerNames;
0466     foreach( const PlaylistProvider *provider, providerPlaylists.keys() )
0467         providerNames << provider->prettyName();
0468 
0469     auto button = QMessageBox::question( The::mainWindow(),
0470                                          i18n( "Confirm Playlist Deletion" ),
0471                                          i18nc( "%1 is playlist provider pretty name",
0472                                                 "Delete playlist from %1.", providerNames.join( QStringLiteral(", ") ) ),
0473                                          QMessageBox::Yes | QMessageBox::No,
0474                                          QMessageBox::Yes );
0475 
0476     if( button == QMessageBox::Yes )
0477     {
0478         foreach( PlaylistProvider *provider, providerPlaylists.keys() )
0479             provider->deletePlaylists( providerPlaylists.value( provider ) );
0480     }
0481 }
0482 
0483 void
0484 PlaylistBrowserView::slotRemoveTracks()
0485 {
0486     foreach( Playlists::PlaylistPtr playlist, m_writableActionTracks.uniqueKeys() )
0487     {
0488         QList<int> trackIndices = m_writableActionTracks.values( playlist );
0489         std::sort( trackIndices.begin(), trackIndices.end() );
0490         int removed = 0;
0491         foreach( int trackIndex, trackIndices )
0492         {
0493             playlist->removeTrack( trackIndex - removed /* account for already removed */ );
0494             removed++;
0495         }
0496     }
0497 }
0498 
0499 void
0500 PlaylistBrowserView::slotExport()
0501 {
0502     if( m_actionPlaylists.count() != 1 )
0503     {
0504         warning() << __PRETTY_FUNCTION__ << "m_actionPlaylists.count() is not 1";
0505         return;
0506     }
0507     Playlists::PlaylistPtr playlist = m_actionPlaylists.at( 0 );
0508 
0509     // --- display save location dialog
0510     // compare with MainWindow::exportPlaylist
0511     // TODO: have this code only once
0512     QFileDialog fileDialog;
0513     fileDialog.restoreState( Amarok::config( QStringLiteral("playlist-export-dialog") ).readEntry( "state", QByteArray() ) );
0514 
0515     // FIXME: Make checkbox visible in dialog
0516     QCheckBox *saveRelativeCheck = new QCheckBox( i18n("Use relative path for &saving"), &fileDialog );
0517     saveRelativeCheck->setChecked( AmarokConfig::relativePlaylist() );
0518 
0519     QStringList supportedMimeTypes;
0520 
0521     supportedMimeTypes << QStringLiteral("video/x-ms-asf"); //ASX
0522     supportedMimeTypes << QStringLiteral("audio/x-mpegurl"); //M3U
0523     supportedMimeTypes << QStringLiteral("audio/x-scpls"); //PLS
0524     supportedMimeTypes << QStringLiteral("application/xspf+xml"); //XSPF
0525 
0526     fileDialog.setMimeTypeFilters( supportedMimeTypes );
0527     fileDialog.setAcceptMode( QFileDialog::AcceptSave );
0528     fileDialog.setFileMode( QFileDialog::AnyFile );
0529     fileDialog.setWindowTitle( i18n("Save As") );
0530     fileDialog.setObjectName( QStringLiteral("PlaylistExport") );
0531 
0532     int result = fileDialog.exec();
0533     QString playlistPath = fileDialog.selectedFiles().value( 0 );
0534     if( result == QDialog::Accepted && !playlistPath.isEmpty() )
0535         Playlists::exportPlaylistFile( playlist->tracks(), QUrl::fromLocalFile( playlistPath ) );
0536 
0537     Amarok::config( QStringLiteral("playlist-export-dialog") ).writeEntry( "state", fileDialog.saveState() );
0538 }
0539 
0540 void
0541 PlaylistBrowserView::insertIntoPlaylist( const QModelIndex &index, Playlist::AddOptions options )
0542 {
0543     insertIntoPlaylist( QModelIndexList() << index, options );
0544 }
0545 
0546 void
0547 PlaylistBrowserView::insertIntoPlaylist( const QModelIndexList &list, Playlist::AddOptions options )
0548 {
0549     actionsFor( list ); // sets action targets
0550     insertIntoPlaylist( options );
0551     resetActionTargets();
0552 }
0553 
0554 void
0555 PlaylistBrowserView::insertIntoPlaylist( Playlist::AddOptions options )
0556 {
0557     Meta::TrackList tracks;
0558 
0559     // add tracks for fully-selected playlists:
0560     foreach( Playlists::PlaylistPtr playlist, m_actionPlaylists )
0561     {
0562         tracks << playlist->tracks();
0563     }
0564 
0565     // filter-out tracks from playlists that are selected, add lone tracks:
0566     foreach( Playlists::PlaylistPtr playlist, m_actionTracks.uniqueKeys() )
0567     {
0568         if( m_actionPlaylists.contains( playlist ) )
0569             continue;
0570 
0571         Meta::TrackList playlistTracks = playlist->tracks();
0572         QList<int> positions = m_actionTracks.values( playlist );
0573         std::sort( positions.begin(), positions.end() );
0574         foreach( int position, positions )
0575         {
0576             if( position >= 0 && position < playlistTracks.count() )
0577                 tracks << playlistTracks.at( position );
0578         }
0579     }
0580 
0581     if( !tracks.isEmpty() )
0582         The::playlistController()->insertOptioned( tracks, options );
0583 }