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 ¤t, 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 }