![]() |
Mixxx
|
00001 #include <QItemDelegate> 00002 #include <QtCore> 00003 #include <QtGui> 00004 #include <QtXml> 00005 #include <QModelIndex> 00006 00007 #include "widget/wwidget.h" 00008 #include "widget/wskincolor.h" 00009 #include "widget/wtracktableviewheader.h" 00010 #include "library/librarytablemodel.h" 00011 #include "library/trackcollection.h" 00012 #include "trackinfoobject.h" 00013 #include "controlobject.h" 00014 #include "controlobjectthreadmain.h" 00015 #include "widget/wtracktableview.h" 00016 #include "dlgtrackinfo.h" 00017 #include "soundsourceproxy.h" 00018 00019 WTrackTableView::WTrackTableView(QWidget * parent, 00020 ConfigObject<ConfigValue> * pConfig, 00021 TrackCollection* pTrackCollection) 00022 : WLibraryTableView(parent, pConfig, 00023 ConfigKey(LIBRARY_CONFIGVALUE, 00024 WTRACKTABLEVIEW_VSCROLLBARPOS_KEY)), 00025 m_pConfig(pConfig), 00026 m_pTrackCollection(pTrackCollection), 00027 m_searchThread(this) { 00028 // Give a NULL parent because otherwise it inherits our style which can make 00029 // it unreadable. Bug #673411 00030 m_pTrackInfo = new DlgTrackInfo(NULL); 00031 connect(m_pTrackInfo, SIGNAL(next()), 00032 this, SLOT(slotNextTrackInfo())); 00033 connect(m_pTrackInfo, SIGNAL(previous()), 00034 this, SLOT(slotPrevTrackInfo())); 00035 00036 connect(&m_loadTrackMapper, SIGNAL(mapped(QString)), 00037 this, SLOT(loadSelectionToGroup(QString))); 00038 00039 connect(&m_deckMapper, SIGNAL(mapped(QString)), 00040 this, SLOT(loadSelectionToGroup(QString))); 00041 connect(&m_samplerMapper, SIGNAL(mapped(QString)), 00042 this, SLOT(loadSelectionToGroup(QString))); 00043 00044 m_pNumSamplers = new ControlObjectThreadMain( 00045 ControlObject::getControl(ConfigKey("[Master]", "num_samplers"))); 00046 m_pNumDecks = new ControlObjectThreadMain( 00047 ControlObject::getControl(ConfigKey("[Master]", "num_decks"))); 00048 00049 m_pMenu = new QMenu(this); 00050 00051 m_pSamplerMenu = new QMenu(this); 00052 m_pSamplerMenu->setTitle(tr("Load to Sampler")); 00053 m_pPlaylistMenu = new QMenu(this); 00054 m_pPlaylistMenu->setTitle(tr("Add to Playlist")); 00055 m_pCrateMenu = new QMenu(this); 00056 m_pCrateMenu->setTitle(tr("Add to Crate")); 00057 00058 // Disable editing 00059 //setEditTriggers(QAbstractItemView::NoEditTriggers); 00060 00061 // Create all the context m_pMenu->actions (stuff that shows up when you 00062 //right-click) 00063 createActions(); 00064 00065 //Connect slots and signals to make the world go 'round. 00066 connect(this, SIGNAL(doubleClicked(const QModelIndex &)), 00067 this, SLOT(slotMouseDoubleClicked(const QModelIndex &))); 00068 00069 connect(&m_playlistMapper, SIGNAL(mapped(int)), 00070 this, SLOT(addSelectionToPlaylist(int))); 00071 connect(&m_crateMapper, SIGNAL(mapped(int)), 00072 this, SLOT(addSelectionToCrate(int))); 00073 } 00074 00075 WTrackTableView::~WTrackTableView() 00076 { 00077 WTrackTableViewHeader* pHeader = 00078 dynamic_cast<WTrackTableViewHeader*>(horizontalHeader()); 00079 if (pHeader) { 00080 pHeader->saveHeaderState(); 00081 } 00082 00083 delete m_pReloadMetadataAct; 00084 delete m_pAutoDJAct; 00085 delete m_pRemoveAct; 00086 delete m_pPropertiesAct; 00087 delete m_pMenu; 00088 delete m_pPlaylistMenu; 00089 delete m_pCrateMenu; 00090 //delete m_pRenamePlaylistAct; 00091 delete m_pTrackInfo; 00092 delete m_pNumSamplers; 00093 delete m_pNumDecks; 00094 } 00095 00096 void WTrackTableView::loadTrackModel(QAbstractItemModel *model) { 00097 //qDebug() << "WTrackTableView::loadTrackModel()" << model; 00098 00099 TrackModel* track_model = dynamic_cast<TrackModel*>(model); 00100 00101 Q_ASSERT(model); 00102 Q_ASSERT(track_model); 00103 00104 /* If the model has not changed 00105 * there's no need to exchange the headers 00106 * this will cause a small GUI freeze 00107 */ 00108 if (getTrackModel() == track_model) { 00109 // Re-sort the table even if the track model is the same. This triggers 00110 // a select() if the table is dirty. 00111 doSortByColumn(horizontalHeader()->sortIndicatorSection()); 00112 return; 00113 } 00114 00115 setVisible(false); 00116 00117 // Save the previous track model's header state 00118 WTrackTableViewHeader* oldHeader = 00119 dynamic_cast<WTrackTableViewHeader*>(horizontalHeader()); 00120 if (oldHeader) { 00121 oldHeader->saveHeaderState(); 00122 } 00123 00124 // rryan 12/2009 : Due to a bug in Qt, in order to switch to a model with 00125 // different columns than the old model, we have to create a new horizontal 00126 // header. Also, for some reason the WTrackTableView has to be hidden or 00127 // else problems occur. Since we parent the WtrackTableViewHeader's to the 00128 // WTrackTableView, they are automatically deleted. 00129 WTrackTableViewHeader* header = new WTrackTableViewHeader(Qt::Horizontal, this); 00130 00131 // WTF(rryan) The following saves on unnecessary work on the part of 00132 // WTrackTableHeaderView. setHorizontalHeader() calls setModel() on the 00133 // current horizontal header. If this happens on the old 00134 // WTrackTableViewHeader, then it will save its old state, AND do the work 00135 // of initializing its menus on the new model. We create a new 00136 // WTrackTableViewHeader, so this is wasteful. Setting a temporary 00137 // QHeaderView here saves on setModel() calls. Since we parent the 00138 // QHeaderView to the WTrackTableView, it is automatically deleted. 00139 QHeaderView* tempHeader = new QHeaderView(Qt::Horizontal, this); 00140 /* Tobias Rafreider: DO NOT SET SORTING TO TRUE during header replacement 00141 * Otherwise, setSortingEnabled(1) will immediately trigger sortByColumn() 00142 * For some reason this will cause 4 select statements in series 00143 * from which 3 are redundant --> expensive at all 00144 * 00145 * Sorting columns, however, is possible because we 00146 * enable clickable sorting indicators some lines below. 00147 * Furthermore, we connect signal 'sortIndicatorChanged'. 00148 * 00149 * Fixes Bug #672762 00150 */ 00151 00152 setSortingEnabled(false); 00153 setHorizontalHeader(tempHeader); 00154 00155 setModel(model); 00156 setHorizontalHeader(header); 00157 header->setMovable(true); 00158 header->setClickable(true); 00159 header->setHighlightSections(true); 00160 header->setSortIndicatorShown(true); 00161 00162 // Initialize all column-specific things 00163 for (int i = 0; i < model->columnCount(); ++i) { 00164 // Setup delegates according to what the model tells us 00165 QItemDelegate* delegate = track_model->delegateForColumn(i); 00166 // We need to delete the old delegates, since the docs say the view will 00167 // not take ownership of them. 00168 QAbstractItemDelegate* old_delegate = itemDelegateForColumn(i); 00169 // If delegate is NULL, it will unset the delegate for the column 00170 setItemDelegateForColumn(i, delegate); 00171 delete old_delegate; 00172 00173 // Show or hide the column based on whether it should be shown or not. 00174 if (track_model->isColumnInternal(i)) { 00175 //qDebug() << "Hiding column" << i; 00176 horizontalHeader()->hideSection(i); 00177 } 00178 /* If Mixxx starts the first time or the header states have been cleared 00179 * due to database schema evolution we gonna hide all columns that may 00180 * contain a potential large number of NULL values. This will hide the 00181 * key colum by default unless the user brings it to front 00182 */ 00183 if (track_model->isColumnHiddenByDefault(i) && 00184 !header->hasPersistedHeaderState()) { 00185 //qDebug() << "Hiding column" << i; 00186 horizontalHeader()->hideSection(i); 00187 } 00188 } 00189 00190 // NOTE: Should be a UniqueConnection but that requires Qt 4.6 00191 connect(horizontalHeader(), SIGNAL(sortIndicatorChanged(int, Qt::SortOrder)), 00192 this, SLOT(doSortByColumn(int)), Qt::AutoConnection); 00193 00194 // Stupid hack that assumes column 0 is never visible, but this is a weak 00195 // proxy for "there was a saved column sort order" 00196 if (horizontalHeader()->sortIndicatorSection() > 0) { 00197 // Sort by the saved sort section and order. This line sorts the 00198 // TrackModel and in turn generates a select() 00199 horizontalHeader()->setSortIndicator(horizontalHeader()->sortIndicatorSection(), 00200 horizontalHeader()->sortIndicatorOrder()); 00201 } else { 00202 // No saved order is present. Use the TrackModel's default sort order. 00203 int sortColumn = track_model->defaultSortColumn(); 00204 Qt::SortOrder sortOrder = track_model->defaultSortOrder(); 00205 00206 // If the TrackModel has an invalid or internal column as its default 00207 // sort, find the first non-internal column and sort by that. 00208 while (sortColumn < 0 || track_model->isColumnInternal(sortColumn)) { 00209 sortColumn++; 00210 } 00211 // This line sorts the TrackModel and in turn generates a select() 00212 horizontalHeader()->setSortIndicator(sortColumn, sortOrder); 00213 } 00214 00215 // Set up drag and drop behaviour according to whether or not the track 00216 // model says it supports it. 00217 00218 // Defaults 00219 setAcceptDrops(true); 00220 setDragDropMode(QAbstractItemView::DragOnly); 00221 // Always enable drag for now (until we have a model that doesn't support 00222 // this.) 00223 setDragEnabled(true); 00224 00225 if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_RECEIVEDROPS)) { 00226 setDragDropMode(QAbstractItemView::DragDrop); 00227 setDropIndicatorShown(true); 00228 setAcceptDrops(true); 00229 //viewport()->setAcceptDrops(true); 00230 } 00231 00232 // Possible giant fuckup alert - It looks like Qt has something like these 00233 // caps built-in, see http://doc.trolltech.com/4.5/qt.html#ItemFlag-enum and 00234 // the flags(...) function that we're already using in LibraryTableModel. I 00235 // haven't been able to get it to stop us from using a model as a drag 00236 // target though, so my hax above may not be completely unjustified. 00237 00238 setVisible(true); 00239 } 00240 00241 void WTrackTableView::disableSorting() { 00242 // We have to manually do this because setSortingEnabled(false) does not 00243 // properly disconnect the signals for some reason. 00244 disconnect(horizontalHeader(), SIGNAL(sortIndicatorChanged(int, Qt::SortOrder)), 00245 this, SLOT(doSortByColumn(int))); 00246 horizontalHeader()->setSortIndicatorShown(false); 00247 } 00248 00249 void WTrackTableView::createActions() { 00250 Q_ASSERT(m_pMenu); 00251 Q_ASSERT(m_pSamplerMenu); 00252 00253 m_pRemoveAct = new QAction(tr("Remove"),this); 00254 connect(m_pRemoveAct, SIGNAL(triggered()), this, SLOT(slotRemove())); 00255 00256 m_pPropertiesAct = new QAction(tr("Properties..."), this); 00257 connect(m_pPropertiesAct, SIGNAL(triggered()), this, SLOT(slotShowTrackInfo())); 00258 00259 m_pAutoDJAct = new QAction(tr("Add to Auto DJ Queue"),this); 00260 connect(m_pAutoDJAct, SIGNAL(triggered()), this, SLOT(slotSendToAutoDJ())); 00261 00262 m_pReloadMetadataAct = new QAction(tr("Reload Track Metadata"), this); 00263 connect(m_pReloadMetadataAct, SIGNAL(triggered()), this, SLOT(slotReloadTrackMetadata())); 00264 } 00265 00266 void WTrackTableView::slotMouseDoubleClicked(const QModelIndex &index) { 00267 if (!modelHasCapabilities(TrackModel::TRACKMODELCAPS_LOADTODECK)) { 00268 return; 00269 } 00270 00271 TrackModel* trackModel = getTrackModel(); 00272 TrackPointer pTrack; 00273 if (trackModel && (pTrack = trackModel->getTrack(index))) { 00274 emit(loadTrack(pTrack)); 00275 } 00276 } 00277 00278 void WTrackTableView::loadSelectionToGroup(QString group) { 00279 QModelIndexList indices = selectionModel()->selectedRows(); 00280 if (indices.size() > 0) { 00281 // If the track load override is disabled, check to see if a track is 00282 // playing before trying to load it 00283 if ( !(m_pConfig->getValueString(ConfigKey("[Controls]","AllowTrackLoadToPlayingDeck")).toInt()) ) { 00284 bool groupPlaying = ControlObject::getControl( 00285 ConfigKey(group, "play"))->get() == 1.0f; 00286 00287 if (groupPlaying) 00288 return; 00289 } 00290 00291 QModelIndex index = indices.at(0); 00292 TrackModel* trackModel = getTrackModel(); 00293 TrackPointer pTrack; 00294 if (trackModel && 00295 (pTrack = trackModel->getTrack(index))) { 00296 emit(loadTrackToPlayer(pTrack, group)); 00297 } 00298 } 00299 } 00300 00301 void WTrackTableView::slotRemove() 00302 { 00303 QModelIndexList indices = selectionModel()->selectedRows(); 00304 if (indices.size() > 0) 00305 { 00306 TrackModel* trackModel = getTrackModel(); 00307 if (trackModel) { 00308 trackModel->removeTracks(indices); 00309 } 00310 } 00311 } 00312 00313 void WTrackTableView::slotShowTrackInfo() { 00314 QModelIndexList indices = selectionModel()->selectedRows(); 00315 00316 if (indices.size() > 0) { 00317 showTrackInfo(indices[0]); 00318 } 00319 } 00320 00321 void WTrackTableView::slotNextTrackInfo() { 00322 QModelIndex nextRow = currentTrackInfoIndex.sibling( 00323 currentTrackInfoIndex.row()+1, currentTrackInfoIndex.column()); 00324 if (nextRow.isValid()) 00325 showTrackInfo(nextRow); 00326 } 00327 00328 void WTrackTableView::slotPrevTrackInfo() { 00329 QModelIndex prevRow = currentTrackInfoIndex.sibling( 00330 currentTrackInfoIndex.row()-1, currentTrackInfoIndex.column()); 00331 if (prevRow.isValid()) 00332 showTrackInfo(prevRow); 00333 } 00334 00335 void WTrackTableView::showTrackInfo(QModelIndex index) { 00336 TrackModel* trackModel = getTrackModel(); 00337 00338 if (!trackModel) 00339 return; 00340 00341 TrackPointer pTrack = trackModel->getTrack(index); 00342 // NULL is fine. 00343 m_pTrackInfo->loadTrack(pTrack); 00344 currentTrackInfoIndex = index; 00345 m_pTrackInfo->show(); 00346 } 00347 00348 void WTrackTableView::contextMenuEvent(QContextMenuEvent * event) 00349 { 00350 QModelIndexList indices = selectionModel()->selectedRows(); 00351 00352 // Gray out some stuff if multiple songs were selected. 00353 bool oneSongSelected = indices.size() == 1; 00354 00355 m_pMenu->clear(); 00356 00357 if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_ADDTOAUTODJ)) { 00358 m_pMenu->addAction(m_pAutoDJAct); 00359 m_pMenu->addSeparator(); 00360 } 00361 00362 if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_LOADTODECK)) { 00363 int iNumDecks = m_pNumDecks->get(); 00364 if (iNumDecks > 0) { 00365 for (int i = 1; i <= iNumDecks; ++i) { 00366 QString deckGroup = QString("[Channel%1]").arg(i); 00367 bool deckPlaying = ControlObject::getControl( 00368 ConfigKey(deckGroup, "play"))->get() == 1.0f; 00369 bool deckEnabled = !deckPlaying && oneSongSelected; 00370 QAction* pAction = new QAction(tr("Load to Deck %1").arg(i), m_pMenu); 00371 pAction->setEnabled(deckEnabled); 00372 m_pMenu->addAction(pAction); 00373 m_deckMapper.setMapping(pAction, deckGroup); 00374 connect(pAction, SIGNAL(triggered()), &m_deckMapper, SLOT(map())); 00375 } 00376 } 00377 } 00378 00379 if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_LOADTOSAMPLER)) { 00380 int iNumSamplers = m_pNumSamplers->get(); 00381 if (iNumSamplers > 0) { 00382 m_pSamplerMenu->clear(); 00383 for (int i = 1; i <= iNumSamplers; ++i) { 00384 QString samplerGroup = QString("[Sampler%1]").arg(i); 00385 bool samplerPlaying = ControlObject::getControl( 00386 ConfigKey(samplerGroup, "play"))->get() == 1.0f; 00387 bool samplerEnabled = !samplerPlaying && oneSongSelected; 00388 QAction* pAction = new QAction(tr("Sampler %1").arg(i), m_pSamplerMenu); 00389 pAction->setEnabled(samplerEnabled); 00390 m_pSamplerMenu->addAction(pAction); 00391 m_samplerMapper.setMapping(pAction, samplerGroup); 00392 connect(pAction, SIGNAL(triggered()), &m_samplerMapper, SLOT(map())); 00393 } 00394 m_pMenu->addMenu(m_pSamplerMenu); 00395 } 00396 } 00397 00398 m_pMenu->addSeparator(); 00399 00400 if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_ADDTOPLAYLIST)) { 00401 m_pPlaylistMenu->clear(); 00402 PlaylistDAO& playlistDao = m_pTrackCollection->getPlaylistDAO(); 00403 int numPlaylists = playlistDao.playlistCount(); 00404 00405 for (int i = 0; i < numPlaylists; ++i) { 00406 int iPlaylistId = playlistDao.getPlaylistId(i); 00407 00408 if (!playlistDao.isHidden(iPlaylistId)) { 00409 00410 QString playlistName = playlistDao.getPlaylistName(iPlaylistId); 00411 // No leak because making the menu the parent means they will be 00412 // auto-deleted 00413 QAction* pAction = new QAction(playlistName, m_pPlaylistMenu); 00414 bool locked = playlistDao.isPlaylistLocked(iPlaylistId); 00415 pAction->setEnabled(!locked); 00416 m_pPlaylistMenu->addAction(pAction); 00417 m_playlistMapper.setMapping(pAction, iPlaylistId); 00418 connect(pAction, SIGNAL(triggered()), &m_playlistMapper, SLOT(map())); 00419 } 00420 } 00421 00422 m_pMenu->addMenu(m_pPlaylistMenu); 00423 } 00424 00425 if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_ADDTOCRATE)) { 00426 m_pCrateMenu->clear(); 00427 CrateDAO& crateDao = m_pTrackCollection->getCrateDAO(); 00428 int numCrates = crateDao.crateCount(); 00429 for (int i = 0; i < numCrates; ++i) { 00430 int iCrateId = crateDao.getCrateId(i); 00431 // No leak because making the menu the parent means they will be 00432 // auto-deleted 00433 QAction* pAction = new QAction(crateDao.crateName(iCrateId), m_pCrateMenu); 00434 bool locked = crateDao.isCrateLocked(iCrateId); 00435 pAction->setEnabled(!locked); 00436 m_pCrateMenu->addAction(pAction); 00437 m_crateMapper.setMapping(pAction, iCrateId); 00438 connect(pAction, SIGNAL(triggered()), &m_crateMapper, SLOT(map())); 00439 } 00440 00441 m_pMenu->addMenu(m_pCrateMenu); 00442 } 00443 00444 bool locked = modelHasCapabilities(TrackModel::TRACKMODELCAPS_LOCKED); 00445 m_pRemoveAct->setEnabled(!locked); 00446 m_pMenu->addSeparator(); 00447 if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_REMOVE)) { 00448 m_pMenu->addAction(m_pRemoveAct); 00449 } 00450 if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_RELOADMETADATA)) { 00451 m_pMenu->addAction(m_pReloadMetadataAct); 00452 } 00453 m_pPropertiesAct->setEnabled(oneSongSelected); 00454 m_pMenu->addAction(m_pPropertiesAct); 00455 00456 //Create the right-click menu 00457 m_pMenu->popup(event->globalPos()); 00458 } 00459 00460 void WTrackTableView::onSearch(const QString& text) { 00461 TrackModel* trackModel = getTrackModel(); 00462 if (trackModel) { 00463 m_searchThread.enqueueSearch(trackModel, text); 00464 } 00465 } 00466 00467 void WTrackTableView::onSearchStarting() { 00468 saveVScrollBarPos(); 00469 } 00470 00471 void WTrackTableView::onSearchCleared() { 00472 restoreVScrollBarPos(); 00473 TrackModel* trackModel = getTrackModel(); 00474 if (trackModel) { 00475 m_searchThread.enqueueSearch(trackModel, ""); 00476 } 00477 } 00478 00479 void WTrackTableView::onShow() { 00480 } 00481 00482 void WTrackTableView::mouseMoveEvent(QMouseEvent* pEvent) { 00483 Q_UNUSED(pEvent); 00484 TrackModel* trackModel = getTrackModel(); 00485 if (!trackModel) 00486 return; 00487 00488 // Iterate over selected rows and append each item's location url to a list 00489 QList<QUrl> locationUrls; 00490 QModelIndexList indices = selectionModel()->selectedRows(); 00491 foreach (QModelIndex index, indices) { 00492 if (!index.isValid()) { 00493 continue; 00494 } 00495 QUrl url = QUrl::fromLocalFile(trackModel->getTrackLocation(index)); 00496 if (!url.isValid()) { 00497 qDebug() << this << "ERROR: invalid url" << url; 00498 continue; 00499 } 00500 locationUrls.append(url); 00501 } 00502 00503 if (locationUrls.empty()) { 00504 return; 00505 } 00506 00507 QMimeData* mimeData = new QMimeData(); 00508 mimeData->setUrls(locationUrls); 00509 00510 QDrag* drag = new QDrag(this); 00511 drag->setMimeData(mimeData); 00512 drag->setPixmap(QPixmap(":images/library/ic_library_drag_and_drop.png")); 00513 drag->exec(Qt::CopyAction); 00514 } 00515 00516 00518 void WTrackTableView::dragEnterEvent(QDragEnterEvent * event) 00519 { 00520 //qDebug() << "dragEnterEvent" << event->mimeData()->formats(); 00521 if (event->mimeData()->hasUrls()) 00522 { 00523 if (event->source() == this) { 00524 if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_REORDER)) { 00525 event->acceptProposedAction(); 00526 } else { 00527 event->ignore(); 00528 } 00529 } else { 00530 QList<QUrl> urls(event->mimeData()->urls()); 00531 bool anyAccepted = false; 00532 foreach (QUrl url, urls) { 00533 QFileInfo file(url.toLocalFile()); 00534 if (SoundSourceProxy::isFilenameSupported(file.fileName())) 00535 anyAccepted = true; 00536 } 00537 if (anyAccepted) { 00538 event->acceptProposedAction(); 00539 } else { 00540 event->ignore(); 00541 } 00542 } 00543 } else { 00544 event->ignore(); 00545 } 00546 } 00547 00552 void WTrackTableView::dragMoveEvent(QDragMoveEvent * event) { 00553 // Needed to allow auto-scrolling 00554 WLibraryTableView::dragMoveEvent(event); 00555 00556 //qDebug() << "dragMoveEvent" << event->mimeData()->formats(); 00557 if (event->mimeData()->hasUrls()) 00558 { 00559 if (event->source() == this) { 00560 if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_REORDER)) { 00561 event->acceptProposedAction(); 00562 } else { 00563 event->ignore(); 00564 } 00565 } else { 00566 event->acceptProposedAction(); 00567 } 00568 } else { 00569 event->ignore(); 00570 } 00571 } 00572 00574 void WTrackTableView::dropEvent(QDropEvent * event) 00575 { 00576 if (event->mimeData()->hasUrls()) { 00577 QList<QUrl> urls(event->mimeData()->urls()); 00578 QUrl url; 00579 QModelIndex selectedIndex; //Index of a selected track (iterator) 00580 00581 //TODO: Filter out invalid URLs (eg. files that aren't supported audio filetypes, etc.) 00582 00583 //Save the vertical scrollbar position. Adding new tracks and moving tracks in 00584 //the SQL data models causes a select() (ie. generation of a new result set), 00585 //which causes view to reset itself. A view reset causes the widget to scroll back 00586 //up to the top, which is confusing when you're dragging and dropping. :) 00587 saveVScrollBarPos(); 00588 00589 //The model index where the track or tracks are destined to go. :) 00590 //(the "drop" position in a drag-and-drop) 00591 QModelIndex destIndex = indexAt(event->pos()); 00592 00593 00594 //qDebug() << "destIndex.row() is" << destIndex.row(); 00595 00596 //Drag and drop within this widget (track reordering) 00597 if (event->source() == this) 00598 { 00599 //For an invalid destination (eg. dropping a track beyond 00600 //the end of the playlist), place the track(s) at the end 00601 //of the playlist. 00602 if (destIndex.row() == -1) { 00603 int destRow = model()->rowCount() - 1; 00604 destIndex = model()->index(destRow, 0); 00605 } 00606 //Note the above code hides an ambiguous case when a 00607 //playlist is empty. For that reason, we can't factor that 00608 //code out to be common for both internal reordering 00609 //and external drag-and-drop. With internal reordering, 00610 //you can't have an empty playlist. :) 00611 00612 //qDebug() << "track reordering" << __FILE__ << __LINE__; 00613 00614 //Save a list of row (just plain ints) so we don't get screwed over 00615 //when the QModelIndexes all become invalid (eg. after moveTrack() 00616 //or addTrack()) 00617 QModelIndexList indices = selectionModel()->selectedRows(); 00618 00619 QList<int> selectedRows; 00620 QModelIndex idx; 00621 foreach (idx, indices) 00622 { 00623 selectedRows.append(idx.row()); 00624 } 00625 00626 00635 if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_REORDER)) { 00636 TrackModel* trackModel = getTrackModel(); 00637 00638 //The model indices are sorted so that we remove the tracks from the table 00639 //in ascending order. This is necessary because if track A is above track B in 00640 //the table, and you remove track A, the model index for track B will change. 00641 //Sorting the indices first means we don't have to worry about this. 00642 //qSort(m_selectedIndices); 00643 //qSort(m_selectedIndices.begin(), m_selectedIndices.end(), qGreater<QModelIndex>()); 00644 qSort(selectedRows); 00645 int maxRow = 0; 00646 int minRow = 0; 00647 if (!selectedRows.isEmpty()) { 00648 maxRow = selectedRows.last(); 00649 minRow = selectedRows.first(); 00650 } 00651 int selectedRowCount = selectedRows.count(); 00652 int firstRowToSelect = destIndex.row(); 00653 00654 //If you drag a contiguous selection of multiple tracks and drop 00655 //them somewhere inside that same selection, do nothing. 00656 if (destIndex.row() >= minRow && destIndex.row() <= maxRow) 00657 return; 00658 00659 //If we're moving the tracks _up_, then reverse the order of the row selection 00660 //to make the algorithm below work without added complexity. 00661 if (destIndex.row() < minRow) { 00662 qSort(selectedRows.begin(), selectedRows.end(), qGreater<int>()); 00663 } 00664 00665 if (destIndex.row() > maxRow) 00666 { 00667 //Shuffle the row we're going to start making a new selection at: 00668 firstRowToSelect = firstRowToSelect - selectedRowCount + 1; 00669 } 00670 00671 //For each row that needs to be moved... 00672 while (!selectedRows.isEmpty()) 00673 { 00674 int movedRow = selectedRows.takeFirst(); //Remember it's row index 00675 //Move it 00676 trackModel->moveTrack(model()->index(movedRow, 0), destIndex); 00677 00678 //Shuffle the row indices for rows that got bumped up 00679 //into the void we left, or down because of the new spot 00680 //we're taking. 00681 for (int i = 0; i < selectedRows.count(); i++) 00682 { 00683 if ((selectedRows[i] > movedRow) && 00684 (destIndex.row() > selectedRows[i])) { 00685 selectedRows[i] = selectedRows[i] - 1; 00686 } 00687 else if ((selectedRows[i] < movedRow) && 00688 (destIndex.row() < selectedRows[i])) { 00689 selectedRows[i] = selectedRows[i] + 1; 00690 } 00691 } 00692 } 00693 00694 //Highlight the moved rows again (restoring the selection) 00695 //QModelIndex newSelectedIndex = destIndex; 00696 for (int i = 0; i < selectedRowCount; i++) 00697 { 00698 this->selectionModel()->select(model()->index(firstRowToSelect + i, 0), 00699 QItemSelectionModel::Select | QItemSelectionModel::Rows); 00700 } 00701 00702 } 00703 } 00704 else 00705 { 00706 //Reset the selected tracks (if you had any tracks highlighted, it 00707 //clears them) 00708 this->selectionModel()->clear(); 00709 00710 //Drag-and-drop from an external application 00711 //eg. dragging a track from Windows Explorer onto the track table. 00712 TrackModel* trackModel = getTrackModel(); 00713 if (trackModel) { 00714 int numNewRows = urls.count(); //XXX: Crappy, assumes all URLs are valid songs. 00715 // Should filter out invalid URLs at the start of this function. 00716 00717 int selectionStartRow = destIndex.row(); //Have to do this here because the index is invalid after addTrack 00718 00719 //Make a new selection starting from where the first track was dropped, and select 00720 //all the dropped tracks 00721 00722 //If the track was dropped into an empty playlist, start at row 0 not -1 :) 00723 if ((destIndex.row() == -1) && (model()->rowCount() == 0)) 00724 { 00725 selectionStartRow = 0; 00726 } 00727 //If the track was dropped beyond the end of a playlist, then we need 00728 //to fudge the destination a bit... 00729 else if ((destIndex.row() == -1) && (model()->rowCount() > 0)) 00730 { 00731 //qDebug() << "Beyond end of playlist"; 00732 //qDebug() << "rowcount is:" << model()->rowCount(); 00733 selectionStartRow = model()->rowCount(); 00734 } 00735 00736 //Add all the dropped URLs/tracks to the track model (playlist/crate) 00737 foreach (url, urls) 00738 { 00739 QFileInfo file(url.toLocalFile()); 00740 if (!trackModel->addTrack(destIndex, file.absoluteFilePath())) 00741 numNewRows--; //# of rows to select must be decremented if we skipped some tracks 00742 } 00743 00744 //Create the selection, but only if the track model supports reordering. 00745 //(eg. crates don't support reordering/indexes) 00746 if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_REORDER)) { 00747 for (int i = selectionStartRow; i < selectionStartRow + numNewRows; i++) 00748 { 00749 this->selectionModel()->select(model()->index(i, 0), QItemSelectionModel::Select | 00750 QItemSelectionModel::Rows); 00751 } 00752 } 00753 } 00754 } 00755 00756 event->acceptProposedAction(); 00757 00758 restoreVScrollBarPos(); 00759 00760 } else { 00761 event->ignore(); 00762 } 00763 } 00764 00765 TrackModel* WTrackTableView::getTrackModel() { 00766 TrackModel* trackModel = dynamic_cast<TrackModel*>(model()); 00767 return trackModel; 00768 } 00769 00770 bool WTrackTableView::modelHasCapabilities(TrackModel::CapabilitiesFlags capabilities) { 00771 TrackModel* trackModel = getTrackModel(); 00772 return trackModel && 00773 (trackModel->getCapabilities() & capabilities) == capabilities; 00774 } 00775 00776 void WTrackTableView::keyPressEvent(QKeyEvent* event) { 00777 if (event->key() == Qt::Key_Return) { 00778 // It is not a good idea if 'key_return' 00779 // causes a track to load since we allow in-line editing 00780 // of table items in general 00781 return; 00782 } else if (event->key() == Qt::Key_BracketLeft) { 00783 loadSelectionToGroup("[Channel1]"); 00784 } else if (event->key() == Qt::Key_BracketRight) { 00785 loadSelectionToGroup("[Channel2]"); 00786 } else { 00787 QTableView::keyPressEvent(event); 00788 } 00789 } 00790 00791 void WTrackTableView::loadSelectedTrack() { 00792 QModelIndexList indexes = selectionModel()->selectedRows(); 00793 if (indexes.size() > 0) { 00794 slotMouseDoubleClicked(indexes.at(0)); 00795 } 00796 } 00797 00798 void WTrackTableView::loadSelectedTrackToGroup(QString group) { 00799 loadSelectionToGroup(group); 00800 } 00801 00802 void WTrackTableView::slotSendToAutoDJ() { 00803 if (!modelHasCapabilities(TrackModel::TRACKMODELCAPS_ADDTOAUTODJ)) { 00804 return; 00805 } 00806 00807 PlaylistDAO& playlistDao = m_pTrackCollection->getPlaylistDAO(); 00808 int iAutoDJPlaylistId = playlistDao.getPlaylistIdFromName(AUTODJ_TABLE); 00809 00810 if (iAutoDJPlaylistId == -1) 00811 return; 00812 00813 QModelIndexList indices = selectionModel()->selectedRows(); 00814 00815 TrackModel* trackModel = getTrackModel(); 00816 foreach (QModelIndex index, indices) { 00817 TrackPointer pTrack; 00818 if (trackModel && 00819 (pTrack = trackModel->getTrack(index))) { 00820 int iTrackId = pTrack->getId(); 00821 if (iTrackId != -1) { 00822 playlistDao.appendTrackToPlaylist(iTrackId, iAutoDJPlaylistId); 00823 } 00824 } 00825 } 00826 } 00827 00828 void WTrackTableView::slotReloadTrackMetadata() { 00829 if (!modelHasCapabilities(TrackModel::TRACKMODELCAPS_RELOADMETADATA)) { 00830 return; 00831 } 00832 00833 if (QMessageBox::warning( 00834 NULL, tr("Reload Track Metadata"), 00835 tr("Reloading track metadata on a loaded track may cause abrupt volume changes. Are you sure?"), 00836 QMessageBox::Yes | QMessageBox::No, 00837 QMessageBox::No) == QMessageBox::No) { 00838 return; 00839 } 00840 00841 QModelIndexList indices = selectionModel()->selectedRows(); 00842 00843 TrackModel* trackModel = getTrackModel(); 00844 00845 if (trackModel == NULL) { 00846 return; 00847 } 00848 00849 foreach (QModelIndex index, indices) { 00850 TrackPointer pTrack = trackModel->getTrack(index); 00851 if (pTrack) { 00852 pTrack->parse(); 00853 } 00854 } 00855 } 00856 00857 void WTrackTableView::addSelectionToPlaylist(int iPlaylistId) { 00858 PlaylistDAO& playlistDao = m_pTrackCollection->getPlaylistDAO(); 00859 TrackModel* trackModel = getTrackModel(); 00860 00861 QModelIndexList indices = selectionModel()->selectedRows(); 00862 00863 foreach (QModelIndex index, indices) { 00864 TrackPointer pTrack; 00865 if (trackModel && 00866 (pTrack = trackModel->getTrack(index))) { 00867 int iTrackId = pTrack->getId(); 00868 if (iTrackId != -1) { 00869 playlistDao.appendTrackToPlaylist(iTrackId, iPlaylistId); 00870 } 00871 } 00872 } 00873 } 00874 00875 void WTrackTableView::addSelectionToCrate(int iCrateId) { 00876 CrateDAO& crateDao = m_pTrackCollection->getCrateDAO(); 00877 TrackModel* trackModel = getTrackModel(); 00878 00879 QModelIndexList indices = selectionModel()->selectedRows(); 00880 foreach (QModelIndex index, indices) { 00881 TrackPointer pTrack; 00882 if (trackModel && 00883 (pTrack = trackModel->getTrack(index))) { 00884 int iTrackId = pTrack->getId(); 00885 if (iTrackId != -1) { 00886 crateDao.addTrackToCrate(iTrackId, iCrateId); 00887 } 00888 } 00889 } 00890 } 00891 00892 void WTrackTableView::doSortByColumn(int headerSection) { 00893 TrackModel* trackModel = getTrackModel(); 00894 QAbstractItemModel* itemModel = model(); 00895 00896 if (trackModel == NULL || itemModel == NULL) 00897 return; 00898 00899 // Save the selection 00900 QModelIndexList selection = selectionModel()->selectedRows(); 00901 QSet<int> trackIds; 00902 foreach (QModelIndex index, selection) { 00903 int trackId = trackModel->getTrackId(index); 00904 trackIds.insert(trackId); 00905 } 00906 00907 sortByColumn(headerSection); 00908 00909 QItemSelectionModel* currentSelection = selectionModel(); 00910 00911 // Find a visible column 00912 int visibleColumn = 0; 00913 while (isColumnHidden(visibleColumn) && visibleColumn < itemModel->columnCount()) { 00914 visibleColumn++; 00915 } 00916 00917 currentSelection->reset(); // remove current selection 00918 00919 QModelIndex first; 00920 foreach (int trackId, trackIds) { 00921 00922 // TODO(rryan) slowly fixing the issues with BaseSqlTableModel. This 00923 // code is broken for playlists because it assumes each trackid is in 00924 // the table once. This will erroneously select all instances of the 00925 // track for playlists, but it works fine for every other view. The way 00926 // to fix this that we should do is to delegate the selection saving to 00927 // the TrackModel. This will allow the playlist table model to use the 00928 // table index as the unique id instead of this code stupidly using 00929 // trackid. 00930 QLinkedList<int> rows = trackModel->getTrackRows(trackId); 00931 foreach (int row, rows) { 00932 QModelIndex tl = itemModel->index(row, visibleColumn); 00933 currentSelection->select(tl, QItemSelectionModel::Rows | QItemSelectionModel::Select); 00934 00935 if (!first.isValid()) { 00936 first = tl; 00937 } 00938 } 00939 } 00940 00941 if (first.isValid()) { 00942 scrollTo(first, QAbstractItemView::EnsureVisible); 00943 //scrollTo(first, QAbstractItemView::PositionAtCenter); 00944 } 00945 }