1 #include "library/browse/browsetablemodel.h"
2 
3 #include <QMessageBox>
4 #include <QMetaType>
5 #include <QStringList>
6 #include <QTableView>
7 #include <QUrl>
8 #include <QtConcurrentRun>
9 #include <QtSql>
10 
11 #include "control/controlobject.h"
12 #include "library/browse/browsethread.h"
13 #include "library/previewbuttondelegate.h"
14 #include "library/trackcollection.h"
15 #include "library/trackcollectionmanager.h"
16 #include "mixer/playerinfo.h"
17 #include "mixer/playermanager.h"
18 #include "moc_browsetablemodel.cpp"
19 #include "track/track.h"
20 #include "util/compatibility.h"
21 #include "widget/wlibrarytableview.h"
22 
BrowseTableModel(QObject * parent,TrackCollectionManager * pTrackCollectionManager,RecordingManager * pRecordingManager)23 BrowseTableModel::BrowseTableModel(QObject* parent,
24                                    TrackCollectionManager* pTrackCollectionManager,
25                                    RecordingManager* pRecordingManager)
26         : TrackModel(pTrackCollectionManager->internalCollection()->database(),
27                      "mixxx.db.model.browse"),
28           QStandardItemModel(parent),
29           m_pTrackCollectionManager(pTrackCollectionManager),
30           m_pRecordingManager(pRecordingManager),
31           m_previewDeckGroup(PlayerManager::groupForPreviewDeck(0)) {
32     QStringList headerLabels;
33     /// The order of the columns appended here must exactly match the ordering
34     /// of the enum that is used for indexing.
35     listAppendOrReplaceAt(&headerLabels, COLUMN_PREVIEW, tr("Preview"));
36     listAppendOrReplaceAt(&headerLabels, COLUMN_FILENAME, tr("Filename"));
37     listAppendOrReplaceAt(&headerLabels, COLUMN_ARTIST, tr("Artist"));
38     listAppendOrReplaceAt(&headerLabels, COLUMN_TITLE, tr("Title"));
39     listAppendOrReplaceAt(&headerLabels, COLUMN_ALBUM, tr("Album"));
40     listAppendOrReplaceAt(&headerLabels, COLUMN_TRACK_NUMBER, tr("Track #"));
41     listAppendOrReplaceAt(&headerLabels, COLUMN_YEAR, tr("Year"));
42     listAppendOrReplaceAt(&headerLabels, COLUMN_GENRE, tr("Genre"));
43     listAppendOrReplaceAt(&headerLabels, COLUMN_COMPOSER, tr("Composer"));
44     listAppendOrReplaceAt(&headerLabels, COLUMN_COMMENT, tr("Comment"));
45     listAppendOrReplaceAt(&headerLabels, COLUMN_DURATION, tr("Duration"));
46     listAppendOrReplaceAt(&headerLabels, COLUMN_BPM, tr("BPM"));
47     listAppendOrReplaceAt(&headerLabels, COLUMN_KEY, tr("Key"));
48     listAppendOrReplaceAt(&headerLabels, COLUMN_TYPE, tr("Type"));
49     listAppendOrReplaceAt(&headerLabels, COLUMN_BITRATE, tr("Bitrate"));
50     listAppendOrReplaceAt(&headerLabels, COLUMN_NATIVELOCATION, tr("Location"));
51     listAppendOrReplaceAt(&headerLabels, COLUMN_ALBUMARTIST, tr("Album Artist"));
52     listAppendOrReplaceAt(&headerLabels, COLUMN_GROUPING, tr("Grouping"));
53     listAppendOrReplaceAt(&headerLabels, COLUMN_FILE_MODIFIED_TIME, tr("File Modified"));
54     listAppendOrReplaceAt(&headerLabels, COLUMN_FILE_CREATION_TIME, tr("File Created"));
55     listAppendOrReplaceAt(&headerLabels, COLUMN_REPLAYGAIN, tr("ReplayGain"));
56 
57     addSearchColumn(COLUMN_FILENAME);
58     addSearchColumn(COLUMN_ARTIST);
59     addSearchColumn(COLUMN_ALBUM);
60     addSearchColumn(COLUMN_TITLE);
61     addSearchColumn(COLUMN_GENRE);
62     addSearchColumn(COLUMN_COMPOSER);
63     addSearchColumn(COLUMN_KEY);
64     addSearchColumn(COLUMN_COMMENT);
65     addSearchColumn(COLUMN_ALBUMARTIST);
66     addSearchColumn(COLUMN_GROUPING);
67     addSearchColumn(COLUMN_FILE_MODIFIED_TIME);
68     addSearchColumn(COLUMN_FILE_CREATION_TIME);
69 
70     setDefaultSort(COLUMN_FILENAME, Qt::AscendingOrder);
71 
72     for (int i = 0; i < static_cast<int>(TrackModel::SortColumnId::IdMax); ++i) {
73         m_columnIndexBySortColumnId[i] = -1;
74     }
75     m_columnIndexBySortColumnId[static_cast<int>(
76             TrackModel::SortColumnId::Filename)] = COLUMN_FILENAME;
77     m_columnIndexBySortColumnId[static_cast<int>(
78             TrackModel::SortColumnId::Artist)] = COLUMN_ARTIST;
79     m_columnIndexBySortColumnId[static_cast<int>(
80             TrackModel::SortColumnId::Title)] = COLUMN_TITLE;
81     m_columnIndexBySortColumnId[static_cast<int>(
82             TrackModel::SortColumnId::Album)] = COLUMN_ALBUM;
83     m_columnIndexBySortColumnId[static_cast<int>(
84             TrackModel::SortColumnId::AlbumArtist)] =
85             COLUMN_ALBUMARTIST;
86     m_columnIndexBySortColumnId[static_cast<int>(
87             TrackModel::SortColumnId::Year)] = COLUMN_YEAR;
88     m_columnIndexBySortColumnId[static_cast<int>(
89             TrackModel::SortColumnId::Genre)] = COLUMN_GENRE;
90     m_columnIndexBySortColumnId[static_cast<int>(
91             TrackModel::SortColumnId::Composer)] = COLUMN_COMPOSER;
92     m_columnIndexBySortColumnId[static_cast<int>(
93             TrackModel::SortColumnId::Grouping)] = COLUMN_GROUPING;
94     m_columnIndexBySortColumnId[static_cast<int>(
95             TrackModel::SortColumnId::TrackNumber)] =
96             COLUMN_TRACK_NUMBER;
97     m_columnIndexBySortColumnId[static_cast<int>(
98             TrackModel::SortColumnId::FileType)] = COLUMN_TYPE;
99     m_columnIndexBySortColumnId[static_cast<int>(
100             TrackModel::SortColumnId::NativeLocation)] =
101             COLUMN_NATIVELOCATION;
102     m_columnIndexBySortColumnId[static_cast<int>(
103             TrackModel::SortColumnId::Comment)] = COLUMN_COMMENT;
104     m_columnIndexBySortColumnId[static_cast<int>(
105             TrackModel::SortColumnId::Duration)] = COLUMN_DURATION;
106     m_columnIndexBySortColumnId[static_cast<int>(
107             TrackModel::SortColumnId::BitRate)] = COLUMN_BITRATE;
108     m_columnIndexBySortColumnId[static_cast<int>(
109             TrackModel::SortColumnId::Bpm)] = COLUMN_BPM;
110     m_columnIndexBySortColumnId[static_cast<int>(
111             TrackModel::SortColumnId::ReplayGain)] =
112             COLUMN_REPLAYGAIN;
113     m_columnIndexBySortColumnId[static_cast<int>(
114             TrackModel::SortColumnId::Key)] = COLUMN_KEY;
115     m_columnIndexBySortColumnId[static_cast<int>(
116             TrackModel::SortColumnId::Preview)] = COLUMN_PREVIEW;
117     m_columnIndexBySortColumnId[static_cast<int>(
118             TrackModel::SortColumnId::Grouping)] = COLUMN_GROUPING;
119     m_columnIndexBySortColumnId[static_cast<int>(
120             TrackModel::SortColumnId::FileModifiedTime)] =
121             COLUMN_FILE_MODIFIED_TIME;
122     m_columnIndexBySortColumnId[static_cast<int>(
123             TrackModel::SortColumnId::FileCreationTime)] =
124             COLUMN_FILE_CREATION_TIME;
125 
126     m_sortColumnIdByColumnIndex.clear();
127     for (int i = static_cast<int>(TrackModel::SortColumnId::IdMin);
128             i < static_cast<int>(TrackModel::SortColumnId::IdMax);
129             ++i) {
130         TrackModel::SortColumnId sortColumn = static_cast<TrackModel::SortColumnId>(i);
131         int columnIndex = m_columnIndexBySortColumnId[static_cast<int>(sortColumn)];
132         if (columnIndex >= 0) {
133             m_sortColumnIdByColumnIndex.insert(columnIndex, sortColumn);
134         }
135     }
136 
137     setHorizontalHeaderLabels(headerLabels);
138     // register the QList<T> as a metatype since we use QueuedConnection below
139     qRegisterMetaType< QList< QList<QStandardItem*> > >(
140         "QList< QList<QStandardItem*> >");
141     qRegisterMetaType<BrowseTableModel*>("BrowseTableModel*");
142 
143     m_pBrowseThread = BrowseThread::getInstanceRef();
144     connect(m_pBrowseThread.data(),
145             &BrowseThread::clearModel,
146             this,
147             &BrowseTableModel::slotClear,
148             Qt::QueuedConnection);
149 
150     connect(m_pBrowseThread.data(),
151             &BrowseThread::rowsAppended,
152             this,
153             &BrowseTableModel::slotInsert,
154             Qt::QueuedConnection);
155 
156     connect(&PlayerInfo::instance(),
157             &PlayerInfo::trackLoaded,
158             this,
159             &BrowseTableModel::trackLoaded);
160     trackLoaded(m_previewDeckGroup, PlayerInfo::instance().getTrackInfo(m_previewDeckGroup));
161 }
162 
~BrowseTableModel()163 BrowseTableModel::~BrowseTableModel() {
164 }
165 
columnIndexFromSortColumnId(TrackModel::SortColumnId column) const166 int BrowseTableModel::columnIndexFromSortColumnId(TrackModel::SortColumnId column) const {
167     if (column < TrackModel::SortColumnId::IdMin ||
168             column >= TrackModel::SortColumnId::IdMax) {
169         return -1;
170     }
171 
172     return m_columnIndexBySortColumnId[static_cast<int>(column)];
173 }
174 
sortColumnIdFromColumnIndex(int index) const175 TrackModel::SortColumnId BrowseTableModel::sortColumnIdFromColumnIndex(int index) const {
176     return m_sortColumnIdByColumnIndex.value(index, TrackModel::SortColumnId::Invalid);
177 }
178 
searchColumns() const179 const QList<int>& BrowseTableModel::searchColumns() const {
180     return m_searchColumns;
181 }
182 
addSearchColumn(int index)183 void BrowseTableModel::addSearchColumn(int index) {
184     m_searchColumns.push_back(index);
185 }
186 
setPath(const MDir & path)187 void BrowseTableModel::setPath(const MDir& path) {
188     m_current_directory = path;
189     m_pBrowseThread->executePopulation(m_current_directory, this);
190 }
191 
getTrack(const QModelIndex & index) const192 TrackPointer BrowseTableModel::getTrack(const QModelIndex& index) const {
193     return getTrackByRef(TrackRef::fromFileInfo(getTrackLocation(index)));
194 }
195 
getTrackByRef(const TrackRef & trackRef) const196 TrackPointer BrowseTableModel::getTrackByRef(const TrackRef& trackRef) const {
197     if (m_pRecordingManager->getRecordingLocation() == trackRef.getLocation()) {
198         QMessageBox::critical(
199                 nullptr, tr("Mixxx Library"), tr("Could not load the following file because"
200                                                  " it is in use by Mixxx or another application.") +
201                         "\n" + trackRef.getLocation());
202         return TrackPointer();
203     }
204     // NOTE(uklotzde, 2015-12-08): Accessing tracks from the browse view
205     // will implicitly add them to the library. Is this really what we
206     // want here??
207     // NOTE(rryan, 2015-12-27): This was intentional at the time since
208     // some people use Browse instead of the library and we want to let
209     // them edit the tracks in a way that persists across sessions
210     // and we didn't want to edit the files on disk by default
211     // unless the user opts in to that.
212     return m_pTrackCollectionManager->getOrAddTrack(trackRef);
213 }
214 
getTrackLocation(const QModelIndex & index) const215 QString BrowseTableModel::getTrackLocation(const QModelIndex& index) const {
216     int row = index.row();
217 
218     QModelIndex index2 = this->index(row, COLUMN_NATIVELOCATION);
219     QString nativeLocation = data(index2).toString();
220     QString location = QDir::fromNativeSeparators(nativeLocation);
221     return location;
222 }
223 
getTrackId(const QModelIndex & index) const224 TrackId BrowseTableModel::getTrackId(const QModelIndex& index) const {
225     TrackPointer pTrack = getTrack(index);
226     if (pTrack) {
227         return pTrack->getId();
228     } else {
229         qWarning()
230                 << "Track is not available in library"
231                 << getTrackLocation(index);
232         return TrackId();
233     }
234 }
235 
getCoverInfo(const QModelIndex & index) const236 CoverInfo BrowseTableModel::getCoverInfo(const QModelIndex& index) const {
237     TrackPointer pTrack = getTrack(index);
238     if (pTrack) {
239         return CoverInfo(pTrack->getCoverInfo(), getTrackLocation(index));
240     } else {
241         qWarning()
242                 << "Track is not available in library"
243                 << getTrackLocation(index);
244         return CoverInfo();
245     }
246 }
getTrackRows(TrackId trackId) const247 const QVector<int> BrowseTableModel::getTrackRows(TrackId trackId) const {
248     Q_UNUSED(trackId);
249     // We can't implement this as it stands.
250     return QVector<int>();
251 }
252 
search(const QString & searchText,const QString & extraFilter)253 void BrowseTableModel::search(const QString& searchText, const QString& extraFilter) {
254     Q_UNUSED(extraFilter);
255     Q_UNUSED(searchText);
256 }
257 
currentSearch() const258 const QString BrowseTableModel::currentSearch() const {
259     return QString("");
260 }
261 
isColumnInternal(int)262 bool BrowseTableModel::isColumnInternal(int) {
263     return false;
264 }
265 
isColumnHiddenByDefault(int column)266 bool BrowseTableModel::isColumnHiddenByDefault(int column) {
267     if (column == COLUMN_COMPOSER ||
268             column == COLUMN_TRACK_NUMBER ||
269             column == COLUMN_YEAR ||
270             column == COLUMN_GROUPING ||
271             column == COLUMN_NATIVELOCATION ||
272             column == COLUMN_ALBUMARTIST ||
273             column == COLUMN_FILE_CREATION_TIME ||
274             column == COLUMN_REPLAYGAIN) {
275         return true;
276     }
277     return false;
278 }
279 
moveTrack(const QModelIndex &,const QModelIndex &)280 void BrowseTableModel::moveTrack(const QModelIndex&, const QModelIndex&) {
281 }
282 
removeTracks(const QModelIndexList &)283 void BrowseTableModel::removeTracks(const QModelIndexList&) {
284 }
285 
mimeData(const QModelIndexList & indexes) const286 QMimeData* BrowseTableModel::mimeData(const QModelIndexList &indexes) const {
287     QMimeData *mimeData = new QMimeData();
288     QList<QUrl> urls;
289 
290     // Ok, so the list of indexes we're given contains separates indexes for
291     // each column, so even if only one row is selected, we'll have like 7
292     // indexes.  We need to only count each row once:
293     QList<int> rows;
294 
295     foreach (QModelIndex index, indexes) {
296         if (index.isValid()) {
297             if (!rows.contains(index.row())) {
298                 rows.push_back(index.row());
299                 QUrl url = TrackFile(getTrackLocation(index)).toUrl();
300                 if (!url.isValid()) {
301                     qDebug() << "ERROR invalid url" << url;
302                     continue;
303                 }
304                 urls.append(url);
305             }
306         }
307     }
308     mimeData->setUrls(urls);
309     return mimeData;
310 }
311 
slotClear(BrowseTableModel * caller_object)312 void BrowseTableModel::slotClear(BrowseTableModel* caller_object) {
313     if (caller_object == this) {
314         removeRows(0, rowCount());
315     }
316 }
317 
slotInsert(const QList<QList<QStandardItem * >> & rows,BrowseTableModel * caller_object)318 void BrowseTableModel::slotInsert(const QList< QList<QStandardItem*> >& rows,
319                                   BrowseTableModel* caller_object) {
320     // There exists more than one BrowseTableModel in Mixxx We only want to
321     // receive items here, this object has 'ordered' by the BrowserThread
322     // (singleton)
323     if (caller_object == this) {
324         //qDebug() << "BrowseTableModel::slotInsert";
325         for (int i = 0; i < rows.size(); ++i) {
326             appendRow(rows.at(i));
327         }
328     }
329 }
330 
getCapabilities() const331 TrackModel::CapabilitiesFlags BrowseTableModel::getCapabilities() const {
332     // See src/library/trackmodel.h for the list of TRACKMODELCAPS
333     return TRACKMODELCAPS_NONE
334             | TRACKMODELCAPS_ADDTOPLAYLIST
335             | TRACKMODELCAPS_ADDTOCRATE
336             | TRACKMODELCAPS_ADDTOAUTODJ
337             | TRACKMODELCAPS_LOADTODECK
338             | TRACKMODELCAPS_LOADTOPREVIEWDECK
339             | TRACKMODELCAPS_LOADTOSAMPLER;
340 }
341 
flags(const QModelIndex & index) const342 Qt::ItemFlags BrowseTableModel::flags(const QModelIndex &index) const {
343     Qt::ItemFlags defaultFlags = QAbstractItemModel::flags(index);
344 
345     // Enable dragging songs from this data model to elsewhere (like the
346     // waveform widget to load a track into a Player).
347     defaultFlags |= Qt::ItemIsDragEnabled;
348 
349     int column = index.column();
350 
351     switch (column) {
352     case COLUMN_FILENAME:
353     case COLUMN_BITRATE:
354     case COLUMN_DURATION:
355     case COLUMN_TYPE:
356     case COLUMN_FILE_MODIFIED_TIME:
357     case COLUMN_FILE_CREATION_TIME:
358     case COLUMN_REPLAYGAIN:
359         // read-only
360         return defaultFlags;
361     default:
362         // editable
363         return defaultFlags | Qt::ItemIsEditable;
364     }
365 }
366 
setData(const QModelIndex & index,const QVariant & value,int role)367 bool BrowseTableModel::setData(
368         const QModelIndex& index,
369         const QVariant& value,
370         int role) {
371     Q_UNUSED(role);
372 
373     QStandardItem* item = itemFromIndex(index);
374     DEBUG_ASSERT(nullptr != item);
375 
376     TrackPointer pTrack(getTrack(index));
377     if (!pTrack) {
378         qWarning() << "BrowseTableModel::setData():"
379                 << "Failed to resolve track"
380                 << getTrackLocation(index);
381         // restore previous item content
382         item->setText(index.data().toString());
383         item->setToolTip(item->text());
384         return false;
385     }
386 
387     // check if one the item were edited
388     int col = index.column();
389     switch (col) {
390     case COLUMN_ARTIST:
391         pTrack->setArtist(value.toString());
392         break;
393     case COLUMN_TITLE:
394         pTrack->setTitle(value.toString());
395         break;
396     case COLUMN_ALBUM:
397         pTrack->setAlbum(value.toString());
398         break;
399     case COLUMN_BPM:
400         pTrack->trySetBpm(value.toDouble());
401         break;
402     case COLUMN_KEY:
403         pTrack->setKeyText(value.toString());
404         break;
405     case COLUMN_TRACK_NUMBER:
406         pTrack->setTrackNumber(value.toString());
407         break;
408     case COLUMN_COMMENT:
409         pTrack->setComment(value.toString());
410         break;
411     case COLUMN_GENRE:
412         pTrack->setGenre(value.toString());
413         break;
414     case COLUMN_COMPOSER:
415         pTrack->setComposer(value.toString());
416         break;
417     case COLUMN_YEAR:
418         pTrack->setYear(value.toString());
419         break;
420     case COLUMN_ALBUMARTIST:
421         pTrack->setAlbumArtist(value.toString());
422         break;
423     case COLUMN_GROUPING:
424         pTrack->setGrouping(value.toString());
425         break;
426     default:
427         qWarning() << "BrowseTableModel::setData():"
428             << "No tagger column";
429         // restore previous item context
430         item->setText(index.data().toString());
431         item->setToolTip(item->text());
432         return false;
433     }
434 
435     item->setText(value.toString());
436     item->setToolTip(item->text());
437     return true;
438 }
439 
trackLoaded(const QString & group,TrackPointer pTrack)440 void BrowseTableModel::trackLoaded(const QString& group, TrackPointer pTrack) {
441     if (group == m_previewDeckGroup) {
442         for (int row = 0; row < rowCount(); ++row) {
443             QModelIndex i = index(row, COLUMN_PREVIEW);
444             if (i.data().toBool()) {
445                 QStandardItem* item = itemFromIndex(i);
446                 item->setText("0");
447             }
448         }
449         if (pTrack) {
450             QString trackLocation = pTrack->getLocation();
451             for (int row = 0; row < rowCount(); ++row) {
452                 QModelIndex i = index(row, COLUMN_PREVIEW);
453                 QString location = getTrackLocation(i);
454                 if (location == trackLocation) {
455                     QStandardItem* item = itemFromIndex(i);
456                     item->setText("1");
457                     break;
458                 }
459             }
460         }
461     }
462 }
463 
isColumnSortable(int column) const464 bool BrowseTableModel::isColumnSortable(int column) const {
465     return COLUMN_PREVIEW != column;
466 }
467 
delegateForColumn(const int i,QObject * pParent)468 QAbstractItemDelegate* BrowseTableModel::delegateForColumn(const int i, QObject* pParent) {
469     WLibraryTableView* pTableView = qobject_cast<WLibraryTableView*>(pParent);
470     DEBUG_ASSERT(pTableView);
471     if (PlayerManager::numPreviewDecks() > 0 && i == COLUMN_PREVIEW) {
472         return new PreviewButtonDelegate(pTableView, i);
473     }
474     return nullptr;
475 }
476