![]() |
Mixxx
|
00001 #include <QtDebug> 00002 #include <QMenu> 00003 #include <QInputDialog> 00004 #include <QFileDialog> 00005 #include <QDesktopServices> 00006 00007 #include "library/playlistfeature.h" 00008 #include "library/parser.h" 00009 #include "library/parserm3u.h" 00010 #include "library/parserpls.h" 00011 00012 00013 #include "widget/wlibrary.h" 00014 #include "widget/wlibrarysidebar.h" 00015 #include "widget/wlibrarytextbrowser.h" 00016 #include "library/trackcollection.h" 00017 #include "library/playlisttablemodel.h" 00018 #include "mixxxkeyboard.h" 00019 #include "treeitem.h" 00020 #include "soundsourceproxy.h" 00021 00022 PlaylistFeature::PlaylistFeature(QObject* parent, 00023 TrackCollection* pTrackCollection, 00024 ConfigObject<ConfigValue>* pConfig) 00025 : LibraryFeature(parent), 00026 m_pTrackCollection(pTrackCollection), 00027 m_playlistDao(pTrackCollection->getPlaylistDAO()), 00028 m_trackDao(pTrackCollection->getTrackDAO()), 00029 m_pConfig(pConfig), 00030 m_playlistTableModel(this, pTrackCollection->getDatabase()) { 00031 m_pPlaylistTableModel = new PlaylistTableModel(this, pTrackCollection, 00032 "mixxx.db.model.playlist"); 00033 00034 m_pCreatePlaylistAction = new QAction(tr("New Playlist"),this); 00035 connect(m_pCreatePlaylistAction, SIGNAL(triggered()), 00036 this, SLOT(slotCreatePlaylist())); 00037 00038 m_pAddToAutoDJAction = new QAction(tr("Add to Auto-DJ Queue"),this); 00039 connect(m_pAddToAutoDJAction, SIGNAL(triggered()), 00040 this, SLOT(slotAddToAutoDJ())); 00041 00042 m_pDeletePlaylistAction = new QAction(tr("Remove"),this); 00043 connect(m_pDeletePlaylistAction, SIGNAL(triggered()), 00044 this, SLOT(slotDeletePlaylist())); 00045 00046 m_pRenamePlaylistAction = new QAction(tr("Rename"),this); 00047 connect(m_pRenamePlaylistAction, SIGNAL(triggered()), 00048 this, SLOT(slotRenamePlaylist())); 00049 00050 m_pLockPlaylistAction = new QAction(tr("Lock"),this); 00051 connect(m_pLockPlaylistAction, SIGNAL(triggered()), 00052 this, SLOT(slotTogglePlaylistLock())); 00053 00054 m_pImportPlaylistAction = new QAction(tr("Import Playlist"),this); 00055 connect(m_pImportPlaylistAction, SIGNAL(triggered()), 00056 this, SLOT(slotImportPlaylist())); 00057 m_pExportPlaylistAction = new QAction(tr("Export Playlist"), this); 00058 connect(m_pExportPlaylistAction, SIGNAL(triggered()), 00059 this, SLOT(slotExportPlaylist())); 00060 00061 // Setup the sidebar playlist model 00062 m_playlistTableModel.setTable("Playlists"); 00063 m_playlistTableModel.setFilter("hidden=0"); 00064 m_playlistTableModel.setSort(m_playlistTableModel.fieldIndex("name"), 00065 Qt::AscendingOrder); 00066 m_playlistTableModel.select(); 00067 00068 //construct child model 00069 TreeItem *rootItem = new TreeItem(); 00070 m_childModel.setRootItem(rootItem); 00071 constructChildModel(); 00072 } 00073 00074 PlaylistFeature::~PlaylistFeature() { 00075 delete m_pPlaylistTableModel; 00076 delete m_pCreatePlaylistAction; 00077 delete m_pDeletePlaylistAction; 00078 delete m_pImportPlaylistAction; 00079 delete m_pAddToAutoDJAction; 00080 delete m_pRenamePlaylistAction; 00081 delete m_pLockPlaylistAction; 00082 } 00083 00084 QVariant PlaylistFeature::title() { 00085 return tr("Playlists"); 00086 } 00087 00088 QIcon PlaylistFeature::getIcon() { 00089 return QIcon(":/images/library/ic_library_playlist.png"); 00090 } 00091 00092 00093 void PlaylistFeature::bindWidget(WLibrarySidebar* sidebarWidget, 00094 WLibrary* libraryWidget, 00095 MixxxKeyboard* keyboard) { 00096 WLibraryTextBrowser* edit = new WLibraryTextBrowser(libraryWidget); 00097 connect(this, SIGNAL(showPage(const QUrl&)), 00098 edit, SLOT(setSource(const QUrl&))); 00099 libraryWidget->registerView("PLAYLISTHOME", edit); 00100 } 00101 00102 void PlaylistFeature::activate() { 00103 emit(showPage(QUrl("qrc:/html/playlists.html"))); 00104 emit(switchToView("PLAYLISTHOME")); 00105 } 00106 00107 void PlaylistFeature::activateChild(const QModelIndex& index) { 00108 //qDebug() << "PlaylistFeature::activateChild()" << index; 00109 00110 //Switch the playlist table model's playlist. 00111 QString playlistName = index.data().toString(); 00112 int playlistId = m_playlistDao.getPlaylistIdFromName(playlistName); 00113 m_pPlaylistTableModel->setPlaylist(playlistId); 00114 emit(showTrackModel(m_pPlaylistTableModel)); 00115 } 00116 00117 void PlaylistFeature::onRightClick(const QPoint& globalPos) { 00118 m_lastRightClickedIndex = QModelIndex(); 00119 00120 //Create the right-click menu 00121 QMenu menu(NULL); 00122 menu.addAction(m_pCreatePlaylistAction); 00123 menu.exec(globalPos); 00124 } 00125 00126 void PlaylistFeature::onRightClickChild(const QPoint& globalPos, QModelIndex index) { 00127 //Save the model index so we can get it in the action slots... 00128 m_lastRightClickedIndex = index; 00129 QString playlistName = index.data().toString(); 00130 int playlistId = m_playlistDao.getPlaylistIdFromName(playlistName); 00131 00132 00133 bool locked = m_playlistDao.isPlaylistLocked(playlistId); 00134 m_pDeletePlaylistAction->setEnabled(!locked); 00135 m_pRenamePlaylistAction->setEnabled(!locked); 00136 00137 m_pLockPlaylistAction->setText(locked ? tr("Unlock") : tr("Lock")); 00138 00139 00140 //Create the right-click menu 00141 QMenu menu(NULL); 00142 menu.addAction(m_pCreatePlaylistAction); 00143 menu.addSeparator(); 00144 menu.addAction(m_pAddToAutoDJAction); 00145 menu.addAction(m_pRenamePlaylistAction); 00146 menu.addAction(m_pDeletePlaylistAction); 00147 menu.addAction(m_pLockPlaylistAction); 00148 menu.addSeparator(); 00149 menu.addAction(m_pImportPlaylistAction); 00150 menu.addAction(m_pExportPlaylistAction); 00151 menu.exec(globalPos); 00152 } 00153 00154 void PlaylistFeature::slotCreatePlaylist() { 00155 QString name; 00156 bool validNameGiven = false; 00157 00158 do { 00159 bool ok = false; 00160 name = QInputDialog::getText(NULL, 00161 tr("New Playlist"), 00162 tr("Playlist name:"), 00163 QLineEdit::Normal, 00164 tr("New Playlist"), 00165 &ok).trimmed(); 00166 00167 if (!ok) 00168 return; 00169 00170 int existingId = m_playlistDao.getPlaylistIdFromName(name); 00171 00172 if (existingId != -1) { 00173 QMessageBox::warning(NULL, 00174 tr("Playlist Creation Failed"), 00175 tr("A playlist by that name already exists.")); 00176 } else if (name.isEmpty()) { 00177 QMessageBox::warning(NULL, 00178 tr("Playlist Creation Failed"), 00179 tr("A playlist cannot have a blank name.")); 00180 } else { 00181 validNameGiven = true; 00182 } 00183 00184 } while (!validNameGiven); 00185 00186 bool playlistCreated = m_playlistDao.createPlaylist(name); 00187 00188 if (playlistCreated) { 00189 clearChildModel(); 00190 m_playlistTableModel.select(); 00191 constructChildModel(); 00192 emit(featureUpdated()); 00193 //Switch the view to the new playlist. 00194 int playlistId = m_playlistDao.getPlaylistIdFromName(name); 00195 m_pPlaylistTableModel->setPlaylist(playlistId); 00196 // TODO(XXX) set sidebar selection 00197 emit(showTrackModel(m_pPlaylistTableModel)); 00198 } 00199 else { 00200 QMessageBox::warning(NULL, 00201 tr("Playlist Creation Failed"), 00202 tr("An unknown error occurred while creating playlist: ") 00203 + name); 00204 } 00205 } 00206 00207 void PlaylistFeature::slotRenamePlaylist() 00208 { 00209 qDebug() << "slotRenamePlaylist()"; 00210 00211 QString oldName = m_lastRightClickedIndex.data().toString(); 00212 int playlistId = m_playlistDao.getPlaylistIdFromName(oldName); 00213 bool locked = m_playlistDao.isPlaylistLocked(playlistId); 00214 00215 if (locked) { 00216 qDebug() << "Skipping playlist rename because playlist" << playlistId << "is locked."; 00217 return; 00218 } 00219 00220 QString newName; 00221 bool validNameGiven = false; 00222 00223 do { 00224 bool ok = false; 00225 newName = QInputDialog::getText(NULL, 00226 tr("Rename Playlist"), 00227 tr("New playlist name:"), 00228 QLineEdit::Normal, 00229 oldName, 00230 &ok).trimmed(); 00231 00232 if (!ok || oldName == newName) { 00233 return; 00234 } 00235 00236 int existingId = m_playlistDao.getPlaylistIdFromName(newName); 00237 00238 if (existingId != -1) { 00239 QMessageBox::warning(NULL, 00240 tr("Renaming Playlist Failed"), 00241 tr("A playlist by that name already exists.")); 00242 } 00243 else if (newName.isEmpty()) { 00244 QMessageBox::warning(NULL, 00245 tr("Renaming Playlist Failed"), 00246 tr("A playlist cannot have a blank name.")); 00247 } 00248 else { 00249 validNameGiven = true; 00250 } 00251 } while (!validNameGiven); 00252 00253 m_playlistDao.renamePlaylist(playlistId, newName); 00254 clearChildModel(); 00255 m_playlistTableModel.select(); 00256 constructChildModel(); 00257 emit(featureUpdated()); 00258 m_pPlaylistTableModel->setPlaylist(playlistId); 00259 } 00260 00261 00262 void PlaylistFeature::slotTogglePlaylistLock() 00263 { 00264 QString playlistName = m_lastRightClickedIndex.data().toString(); 00265 int playlistId = m_playlistDao.getPlaylistIdFromName(playlistName); 00266 bool locked = !m_playlistDao.isPlaylistLocked(playlistId); 00267 00268 if (!m_playlistDao.setPlaylistLocked(playlistId, locked)) { 00269 qDebug() << "Failed to toggle lock of playlistId " << playlistId; 00270 } 00271 00272 TreeItem* playlistItem = m_childModel.getItem(m_lastRightClickedIndex); 00273 playlistItem->setIcon(locked ? QIcon(":/images/library/ic_library_locked.png") : QIcon()); 00274 } 00275 00276 void PlaylistFeature::slotDeletePlaylist() 00277 { 00278 //qDebug() << "slotDeletePlaylist() row:" << m_lastRightClickedIndex.data(); 00279 int playlistId = m_playlistDao.getPlaylistIdFromName(m_lastRightClickedIndex.data().toString()); 00280 bool locked = m_playlistDao.isPlaylistLocked(playlistId); 00281 00282 if (locked) { 00283 qDebug() << "Skipping playlist deletion because playlist" << playlistId << "is locked."; 00284 return; 00285 } 00286 00287 if (m_lastRightClickedIndex.isValid() && 00288 !m_playlistDao.isPlaylistLocked(playlistId)) { 00289 Q_ASSERT(playlistId >= 0); 00290 00291 clearChildModel(); 00292 m_playlistDao.deletePlaylist(playlistId); 00293 m_playlistTableModel.select(); 00294 constructChildModel(); 00295 emit(featureUpdated()); 00296 } 00297 00298 } 00299 00300 bool PlaylistFeature::dropAccept(QUrl url) { 00301 return false; 00302 } 00303 00304 bool PlaylistFeature::dropAcceptChild(const QModelIndex& index, QUrl url) { 00305 //TODO: Filter by supported formats regex and reject anything that doesn't match. 00306 QString playlistName = index.data().toString(); 00307 int playlistId = m_playlistDao.getPlaylistIdFromName(playlistName); 00308 //m_playlistDao.appendTrackToPlaylist(url.toLocalFile(), playlistId); 00309 00310 // If a track is dropped onto a playlist's name, but the track isn't in the 00311 // library, then add the track to the library before adding it to the 00312 // playlist. 00313 QFileInfo file(url.toLocalFile()); 00314 00315 // XXX: Possible WTF alert - Previously we thought we needed toString() here 00316 // but what you actually want in any case when converting a QUrl to a file 00317 // system path is QUrl::toLocalFile(). This is the second time we have 00318 // flip-flopped on this, but I think toLocalFile() should work in any 00319 // case. toString() absolutely does not work when you pass the result to a 00320 // QFileInfo. rryan 9/2010 00321 00322 // Adds track, does not insert duplicates, handles unremoving logic. 00323 int trackId = m_trackDao.addTrack(file, true); 00324 00325 // Do nothing if the location still isn't in the database. 00326 if (trackId < 0) { 00327 return false; 00328 } 00329 00330 // appendTrackToPlaylist doesn't return whether it succeeded, so assume it 00331 // did. 00332 m_playlistDao.appendTrackToPlaylist(trackId, playlistId); 00333 return true; 00334 } 00335 00336 bool PlaylistFeature::dragMoveAccept(QUrl url) { 00337 return false; 00338 } 00339 00340 bool PlaylistFeature::dragMoveAcceptChild(const QModelIndex& index, QUrl url) { 00341 //TODO: Filter by supported formats regex and reject anything that doesn't match. 00342 00343 QString playlistName = index.data().toString(); 00344 int playlistId = m_playlistDao.getPlaylistIdFromName(playlistName); 00345 bool locked = m_playlistDao.isPlaylistLocked(playlistId); 00346 00347 QFileInfo file(url.toLocalFile()); 00348 bool formatSupported = SoundSourceProxy::isFilenameSupported(file.fileName()); 00349 return !locked && formatSupported; 00350 } 00351 00352 TreeItemModel* PlaylistFeature::getChildModel() { 00353 return &m_childModel; 00354 } 00360 void PlaylistFeature::constructChildModel() 00361 { 00362 QList<TreeItem*> data_list; 00363 int nameColumn = m_playlistTableModel.record().indexOf("name"); 00364 int idColumn = m_playlistTableModel.record().indexOf("id"); 00365 00366 //Access the invisible root item 00367 TreeItem* root = m_childModel.getItem(QModelIndex()); 00368 //Create new TreeItems for the playlists in the database 00369 for (int row = 0; row < m_playlistTableModel.rowCount(); ++row) { 00370 QModelIndex ind = m_playlistTableModel.index(row, nameColumn); 00371 QString playlist_name = m_playlistTableModel.data(ind).toString(); 00372 ind = m_playlistTableModel.index(row, idColumn); 00373 int playlist_id = m_playlistTableModel.data(ind).toInt(); 00374 bool locked = m_playlistDao.isPlaylistLocked(playlist_id); 00375 00376 //Create the TreeItem whose parent is the invisible root item 00377 TreeItem* item = new TreeItem(playlist_name, playlist_name, this, root); 00378 item->setIcon(locked ? QIcon(":/images/library/ic_library_locked.png") : QIcon()); 00379 data_list.append(item); 00380 } 00381 00382 //Append all the newly created TreeItems in a dynamic way to the childmodel 00383 m_childModel.insertRows(data_list, 0, m_playlistTableModel.rowCount()); 00384 } 00385 00389 void PlaylistFeature::clearChildModel() 00390 { 00391 m_childModel.removeRows(0,m_playlistTableModel.rowCount()); 00392 } 00393 void PlaylistFeature::slotImportPlaylist() 00394 { 00395 qDebug() << "slotImportPlaylist() row:" ; //<< m_lastRightClickedIndex.data(); 00396 00397 QString playlist_file = QFileDialog::getOpenFileName( 00398 NULL, 00399 tr("Import Playlist"), 00400 QDesktopServices::storageLocation(QDesktopServices::MusicLocation), 00401 tr("Playlist Files (*.m3u *.m3u8 *.pls)")); 00402 // Exit method if user cancelled the open dialog. 00403 if (playlist_file.isNull() || playlist_file.isEmpty()) { 00404 return; 00405 } 00406 00407 Parser* playlist_parser = NULL; 00408 00409 if (playlist_file.endsWith(".m3u", Qt::CaseInsensitive) || 00410 playlist_file.endsWith(".m3u8", Qt::CaseInsensitive)) { 00411 playlist_parser = new ParserM3u(); 00412 } else if (playlist_file.endsWith(".pls", Qt::CaseInsensitive)) { 00413 playlist_parser = new ParserPls(); 00414 } else { 00415 return; 00416 } 00417 QList<QString> entries = playlist_parser->parse(playlist_file); 00418 00419 // Iterate over the List that holds URLs of playlist entires 00420 for (int i = 0; i < entries.size(); ++i) { 00421 m_pPlaylistTableModel->addTrack(QModelIndex(), entries[i]); 00422 } 00423 00424 // delete the parser object 00425 if (playlist_parser) { 00426 delete playlist_parser; 00427 } 00428 } 00429 void PlaylistFeature::onLazyChildExpandation(const QModelIndex &index){ 00430 //Nothing to do because the childmodel is not of lazy nature. 00431 } 00432 00433 void PlaylistFeature::slotExportPlaylist(){ 00434 qDebug() << "Export playlist" << m_lastRightClickedIndex.data(); 00435 QString file_location = QFileDialog::getSaveFileName( 00436 NULL, 00437 tr("Export Playlist"), 00438 QDesktopServices::storageLocation(QDesktopServices::MusicLocation), 00439 tr("M3U Playlist (*.m3u);;M3U8 Playlist (*.m3u8);;PLS Playlist (*.pls)")); 00440 // Exit method if user cancelled the open dialog. 00441 if (file_location.isNull() || file_location.isEmpty()) { 00442 return; 00443 } 00444 // Create and populate a list of files of the playlist 00445 QList<QString> playlist_items; 00446 // Create a new table model since the main one might have an active search. 00447 QScopedPointer<PlaylistTableModel> pPlaylistTableModel( 00448 new PlaylistTableModel(this, m_pTrackCollection, 00449 "mixxx.db.model.playlist_export")); 00450 00451 pPlaylistTableModel->setPlaylist(m_pPlaylistTableModel->getPlaylist()); 00452 pPlaylistTableModel->setSort(0, Qt::AscendingOrder); 00453 pPlaylistTableModel->select(); 00454 int rows = pPlaylistTableModel->rowCount(); 00455 for (int i = 0; i < rows; ++i) { 00456 QModelIndex index = pPlaylistTableModel->index(i, 0); 00457 playlist_items << pPlaylistTableModel->getTrackLocation(index); 00458 } 00459 00460 // check config if relative paths are desired 00461 bool useRelativePath = static_cast<bool>(m_pConfig->getValueString( 00462 ConfigKey("[Library]", "UseRelativePathOnExport")).toInt()); 00463 00464 if (file_location.endsWith(".m3u", Qt::CaseInsensitive)) { 00465 ParserM3u::writeM3UFile(file_location, playlist_items, useRelativePath); 00466 } else if (file_location.endsWith(".pls", Qt::CaseInsensitive)) { 00467 ParserPls::writePLSFile(file_location,playlist_items, 00468 useRelativePath); 00469 } else if (file_location.endsWith(".m3u8", Qt::CaseInsensitive)) { 00470 ParserM3u::writeM3U8File(file_location, playlist_items, 00471 useRelativePath); 00472 } else { 00473 //default export to M3U if file extension is missing 00474 00475 qDebug() << "Playlist export: No file extension specified. Appending .m3u " 00476 << "and exporting to M3U."; 00477 file_location.append(".m3u"); 00478 ParserM3u::writeM3UFile(file_location, playlist_items, useRelativePath); 00479 } 00480 } 00481 00482 void PlaylistFeature::slotAddToAutoDJ() { 00483 //qDebug() << "slotAddToAutoDJ() row:" << m_lastRightClickedIndex.data(); 00484 00485 if (m_lastRightClickedIndex.isValid()) { 00486 int playlistId = m_playlistDao.getPlaylistIdFromName( 00487 m_lastRightClickedIndex.data().toString()); 00488 if (playlistId >= 0) { 00489 m_playlistDao.addToAutoDJQueue(playlistId); 00490 } 00491 } 00492 emit(featureUpdated()); 00493 }