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