Mixxx

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

Go to the documentation of this file.
00001 #include <QtCore>
00002 #include <QtGui>
00003 #include <QtSql>
00004 #include <QDateTime>
00005 #include "library/trackcollection.h"
00006 #include "library/playlisttablemodel.h"
00007 #include "library/queryutil.h"
00008 
00009 #include "mixxxutils.cpp"
00010 
00011 PlaylistTableModel::PlaylistTableModel(QObject* parent,
00012                                        TrackCollection* pTrackCollection,
00013                                        QString settingsNamespace)
00014         : BaseSqlTableModel(parent, pTrackCollection,
00015                             pTrackCollection->getDatabase(),
00016                             settingsNamespace),
00017           m_pTrackCollection(pTrackCollection),
00018           m_playlistDao(m_pTrackCollection->getPlaylistDAO()),
00019           m_trackDao(m_pTrackCollection->getTrackDAO()),
00020           m_iPlaylistId(-1) {
00021     connect(this, SIGNAL(doSearch(const QString&)),
00022             this, SLOT(slotSearch(const QString&)));
00023 }
00024 
00025 PlaylistTableModel::~PlaylistTableModel() {
00026 }
00027 
00028 void PlaylistTableModel::setPlaylist(int playlistId) {
00029     //qDebug() << "PlaylistTableModel::setPlaylist" << playlistId;
00030 
00031     if (m_iPlaylistId == playlistId) {
00032         qDebug() << "Already focused on playlist " << playlistId;
00033         return;
00034     }
00035 
00036     m_iPlaylistId = playlistId;
00037     QString playlistTableName = "playlist_" + QString::number(m_iPlaylistId);
00038     QSqlQuery query(m_pTrackCollection->getDatabase());
00039     FieldEscaper escaper(m_pTrackCollection->getDatabase());
00040 
00041     QStringList columns;
00042     columns << PLAYLISTTRACKSTABLE_TRACKID
00043             << PLAYLISTTRACKSTABLE_POSITION;
00044 
00045     // We drop files that have been explicitly deleted from mixxx
00046     // (mixxx_deleted=0) from the view. There was a bug in <= 1.9.0 where
00047     // removed files were not removed from playlists, so some users will have
00048     // libraries where this is the case.
00049     QString queryString = QString(
00050         "CREATE TEMPORARY VIEW IF NOT EXISTS %1 AS "
00051         "SELECT %2 FROM PlaylistTracks "
00052         "INNER JOIN library ON library.id = PlaylistTracks.track_id "
00053         "WHERE PlaylistTracks.playlist_id = %3 AND library.mixxx_deleted = 0")
00054             .arg(escaper.escapeString(playlistTableName))
00055             .arg(columns.join(","))
00056             .arg(playlistId);
00057     query.prepare(queryString);
00058     if (!query.exec()) {
00059         LOG_FAILED_QUERY(query);
00060     }
00061 
00062     setTable(playlistTableName, columns[0], columns,
00063              m_pTrackCollection->getTrackSource("default"));
00064     initHeaderData();
00065     setSearch("");
00066     setDefaultSort(fieldIndex("position"), Qt::AscendingOrder);
00067 }
00068 
00069 bool PlaylistTableModel::addTrack(const QModelIndex& index, QString location) {
00070     const int positionColumn = fieldIndex(PLAYLISTTRACKSTABLE_POSITION);
00071     int position = index.sibling(index.row(), positionColumn).data().toInt();
00072 
00073     // Handle weird cases like a drag-and-drop to an invalid index
00074     if (position <= 0) {
00075         position = rowCount() + 1;
00076     }
00077 
00078     // If a track is dropped but it isn't in the library, then add it because
00079     // the user probably dropped a file from outside Mixxx into this playlist.
00080     QFileInfo fileInfo(location);
00081 
00082     // Adds track, does not insert duplicates, handles unremoving logic.
00083     int trackId = m_trackDao.addTrack(fileInfo, true);
00084 
00085     // Do nothing if the location still isn't in the database.
00086     if (trackId < 0) {
00087         return false;
00088     }
00089 
00090     m_playlistDao.insertTrackIntoPlaylist(trackId, m_iPlaylistId, position);
00091 
00092     // TODO(rryan) signal an add to the base, don't select
00093     select(); //Repopulate the data model.
00094     return true;
00095 }
00096 
00097 TrackPointer PlaylistTableModel::getTrack(const QModelIndex& index) const {
00098     //FIXME: use position instead of location for playlist tracks?
00099 
00100     //const int locationColumnIndex = this->fieldIndex(LIBRARYTABLE_LOCATION);
00101     //QString location = index.sibling(index.row(), locationColumnIndex).data().toString();
00102     int trackId = getTrackId(index);
00103     return m_trackDao.getTrack(trackId);
00104 }
00105 
00106 void PlaylistTableModel::removeTrack(const QModelIndex& index) {
00107     if (m_playlistDao.isPlaylistLocked(m_iPlaylistId)) {
00108         return;
00109     }
00110 
00111     const int positionColumnIndex = fieldIndex(PLAYLISTTRACKSTABLE_POSITION);
00112     int position = index.sibling(index.row(), positionColumnIndex).data().toInt();
00113     m_playlistDao.removeTrackFromPlaylist(m_iPlaylistId, position);
00114     select(); //Repopulate the data model.
00115 }
00116 
00117 void PlaylistTableModel::removeTracks(const QModelIndexList& indices) {
00118     bool locked = m_playlistDao.isPlaylistLocked(m_iPlaylistId);
00119 
00120     if (locked) {
00121         return;
00122     }
00123 
00124     const int positionColumnIndex = fieldIndex(PLAYLISTTRACKSTABLE_POSITION);
00125 
00126     QList<int> trackPositions;
00127     foreach (QModelIndex index, indices) {
00128         int trackPosition = index.sibling(index.row(), positionColumnIndex).data().toInt();
00129         trackPositions.append(trackPosition);
00130     }
00131 
00132     qSort(trackPositions);
00133     QListIterator<int> iterator(trackPositions);
00134     iterator.toBack();
00135 
00136     while (iterator.hasPrevious()) {
00137         m_playlistDao.removeTrackFromPlaylist(m_iPlaylistId, iterator.previous());
00138     }
00139 
00140     // Have to re-lookup every track b/c their playlist ranks might have changed
00141     select();
00142 }
00143 
00144 void PlaylistTableModel::moveTrack(const QModelIndex& sourceIndex,
00145                                    const QModelIndex& destIndex) {
00146     //QSqlRecord sourceRecord = this->record(sourceIndex.row());
00147     //sourceRecord.setValue("position", destIndex.row());
00148     //this->removeRows(sourceIndex.row(), 1);
00149 
00150     //this->insertRecord(destIndex.row(), sourceRecord);
00151 
00152     //TODO: execute a real query to DELETE the sourceIndex.row() row from the PlaylistTracks table.
00153     //int newPosition = destIndex.row();
00154     //int oldPosition = sourceIndex.row();
00155     //const int positionColumnIndex = this->fieldIndex(PLAYLISTTRACKSTABLE_POSITION);
00156     //int newPosition = index.sibling(destIndex.row(), positionColumnIndex).data().toInt();
00157     //int oldPosition = index.sibling(sourceIndex.row(), positionColumnIndex).data().toInt();
00158 
00159 
00160     int playlistPositionColumn = fieldIndex(PLAYLISTTRACKSTABLE_POSITION);
00161 
00162     // this->record(destIndex.row()).value(PLAYLISTTRACKSTABLE_POSITION).toInt();
00163     int newPosition = destIndex.sibling(destIndex.row(), playlistPositionColumn).data().toInt();
00164     // this->record(sourceIndex.row()).value(PLAYLISTTRACKSTABLE_POSITION).toInt();
00165     int oldPosition = sourceIndex.sibling(sourceIndex.row(), playlistPositionColumn).data().toInt();
00166 
00167 
00168     //qDebug() << "old pos" << oldPosition << "new pos" << newPosition;
00169 
00170     //Invalid for the position to be 0 or less.
00171     if (newPosition < 0)
00172         return;
00173     else if (newPosition == 0) //Dragged out of bounds, which is past the end of the rows...
00174         newPosition = rowCount();
00175 
00176     //Start the transaction
00177     m_pTrackCollection->getDatabase().transaction();
00178 
00179     //Find out the highest position existing in the playlist so we know what
00180     //position this track should have.
00181     QSqlQuery query;
00182 
00183     //Insert the song into the PlaylistTracks table
00184 
00198     QString queryString;
00199     if (newPosition < oldPosition) {
00200         queryString =
00201             QString("UPDATE PlaylistTracks SET position=-1 "
00202                     "WHERE position=%1 AND "
00203                     "playlist_id=%2").arg(oldPosition).arg(m_iPlaylistId);
00204         query.exec(queryString);
00205         //qDebug() << queryString;
00206 
00207         queryString = QString("UPDATE PlaylistTracks SET position=position-1 "
00208                             "WHERE position > %1 AND "
00209                             "playlist_id=%2").arg(oldPosition).arg(m_iPlaylistId);
00210         query.exec(queryString);
00211 
00212         queryString = QString("UPDATE PlaylistTracks SET position=position+1 "
00213                             "WHERE position >= %1 AND " //position < %2 AND "
00214                             "playlist_id=%3").arg(newPosition).arg(m_iPlaylistId);
00215         query.exec(queryString);
00216 
00217         queryString = QString("UPDATE PlaylistTracks SET position=%1 "
00218                             "WHERE position=-1 AND "
00219                             "playlist_id=%2").arg(newPosition).arg(m_iPlaylistId);
00220         query.exec(queryString);
00221     }
00222     else if (newPosition > oldPosition)
00223     {
00224         queryString = QString("UPDATE PlaylistTracks SET position=-1 "
00225                               "WHERE position = %1 AND "
00226                               "playlist_id=%2").arg(oldPosition).arg(m_iPlaylistId);
00227         //qDebug() << queryString;
00228         query.exec(queryString);
00229 
00230         queryString = QString("UPDATE PlaylistTracks SET position=position-1 "
00231                               "WHERE position > %1 AND position <= %2 AND "
00232                               "playlist_id=%3").arg(oldPosition).arg(newPosition).arg(m_iPlaylistId);
00233         query.exec(queryString);
00234 
00235         queryString = QString("UPDATE PlaylistTracks SET position=%1 "
00236                               "WHERE position=-1 AND "
00237                               "playlist_id=%2").arg(newPosition).arg(m_iPlaylistId);
00238         query.exec(queryString);
00239     }
00240 
00241     m_pTrackCollection->getDatabase().commit();
00242 
00243     //Print out any SQL error, if there was one.
00244     if (query.lastError().isValid()) {
00245         qDebug() << query.lastError();
00246     }
00247 
00248     select();
00249 }
00250 
00251 void PlaylistTableModel::shuffleTracks(const QModelIndex& currentIndex) {
00252     int numOfTracks = rowCount();
00253     int seed = QDateTime::currentDateTime().toTime_t();
00254     qsrand(seed);
00255     QSqlQuery query(m_pTrackCollection->getDatabase());
00256     const int positionColumnIndex = fieldIndex(PLAYLISTTRACKSTABLE_POSITION);
00257     int currentPosition = currentIndex.sibling(currentIndex.row(), positionColumnIndex).data().toInt();
00258     int shuffleStartIndex = currentPosition + 1;
00259 
00260     m_pTrackCollection->getDatabase().transaction();
00261 
00262     // This is a simple Fisher-Yates shuffling algorithm
00263     for (int i=numOfTracks-1; i >= shuffleStartIndex; i--)
00264     {
00265         int random = int(qrand() / (RAND_MAX + 1.0) * (numOfTracks + 1 - shuffleStartIndex) + shuffleStartIndex);
00266         qDebug() << "Swapping tracks " << i << " and " << random;
00267         QString swapQuery = "UPDATE PlaylistTracks SET position=%1 WHERE position=%2 AND playlist_id=%3";
00268         query.exec(swapQuery.arg(-1).arg(i).arg(m_iPlaylistId));
00269         query.exec(swapQuery.arg(i).arg(random).arg(m_iPlaylistId));
00270         query.exec(swapQuery.arg(random).arg(-1).arg(m_iPlaylistId));
00271 
00272         if (query.lastError().isValid())
00273             qDebug() << query.lastError();
00274     }
00275 
00276     m_pTrackCollection->getDatabase().commit();
00277     // TODO(XXX) set dirty because someday select() will only do work on dirty.
00278     select();
00279 }
00280 
00281 void PlaylistTableModel::search(const QString& searchText) {
00282     // qDebug() << "PlaylistTableModel::search()" << searchText
00283     //          << QThread::currentThread();
00284     emit(doSearch(searchText));
00285 }
00286 
00287 void PlaylistTableModel::slotSearch(const QString& searchText) {
00288     BaseSqlTableModel::search(
00289         searchText, LibraryTableModel::DEFAULT_LIBRARYFILTER);
00290 }
00291 
00292 bool PlaylistTableModel::isColumnInternal(int column) {
00293     if (column == fieldIndex(PLAYLISTTRACKSTABLE_TRACKID) ||
00294         column == fieldIndex(LIBRARYTABLE_PLAYED) ||
00295         column == fieldIndex(LIBRARYTABLE_MIXXXDELETED) ||
00296         column == fieldIndex(TRACKLOCATIONSTABLE_FSDELETED)) {
00297         return true;
00298     }
00299     return false;
00300 }
00301 bool PlaylistTableModel::isColumnHiddenByDefault(int column) {
00302     if (column == fieldIndex(LIBRARYTABLE_KEY)) {
00303         return true;
00304     }
00305     return false;
00306 }
00307 
00308 QItemDelegate* PlaylistTableModel::delegateForColumn(const int i) {
00309     return NULL;
00310 }
00311 
00312 TrackModel::CapabilitiesFlags PlaylistTableModel::getCapabilities() const {
00313     TrackModel::CapabilitiesFlags caps = TRACKMODELCAPS_NONE
00314             | TRACKMODELCAPS_RECEIVEDROPS
00315             | TRACKMODELCAPS_REORDER
00316             | TRACKMODELCAPS_ADDTOCRATE
00317             | TRACKMODELCAPS_ADDTOPLAYLIST
00318             | TRACKMODELCAPS_RELOADMETADATA
00319             | TRACKMODELCAPS_LOADTODECK
00320             | TRACKMODELCAPS_LOADTOSAMPLER
00321             | TRACKMODELCAPS_REMOVE;
00322 
00323     // Only allow Add to AutoDJ if we aren't currently showing the AutoDJ queue.
00324     if (m_iPlaylistId != m_playlistDao.getPlaylistIdFromName(AUTODJ_TABLE)) {
00325         caps |= TRACKMODELCAPS_ADDTOAUTODJ;
00326     }
00327 
00328     bool locked = m_playlistDao.isPlaylistLocked(m_iPlaylistId);
00329 
00330     if (locked) {
00331         caps |= TRACKMODELCAPS_LOCKED;
00332     }
00333 
00334     return caps;
00335 }
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Defines