Mixxx

/home/maxime/Projets/Mixxx/1.10/mixxx/src/library/playlistfeature.cpp

Go to the documentation of this file.
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 }
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Defines