![]() |
Mixxx
|
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 }