Mixxx

/home/maxime/Projets/Mixxx/1.10/mixxx/src/widget/wtracktableview.cpp

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