Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -342,12 +342,14 @@ add_library(mixxx-lib STATIC EXCLUDE_FROM_ALL
src/library/banshee/bansheedbconnection.cpp
src/library/banshee/bansheefeature.cpp
src/library/banshee/bansheeplaylistmodel.cpp
src/library/basecoverartdelegate.cpp
src/library/baseexternallibraryfeature.cpp
src/library/baseexternalplaylistmodel.cpp
src/library/baseexternaltrackmodel.cpp
src/library/baseplaylistfeature.cpp
src/library/basesqltablemodel.cpp
src/library/basetrackcache.cpp
src/library/basetracktablemodel.cpp
src/library/bpmdelegate.cpp
src/library/browse/browsefeature.cpp
src/library/browse/browsetablemodel.cpp
Expand Down
2 changes: 2 additions & 0 deletions build/depends.py
Original file line number Diff line number Diff line change
Expand Up @@ -1027,6 +1027,7 @@ def sources(self, build):
"src/library/externaltrackcollection.cpp",
"src/library/basesqltablemodel.cpp",
"src/library/basetrackcache.cpp",
"src/library/basetracktablemodel.cpp",
"src/library/columncache.cpp",
"src/library/librarytablemodel.cpp",
"src/library/searchquery.cpp",
Expand Down Expand Up @@ -1121,6 +1122,7 @@ def sources(self, build):
"src/library/bpmdelegate.cpp",
"src/library/previewbuttondelegate.cpp",
"src/library/colordelegate.cpp",
"src/library/basecoverartdelegate.cpp",
"src/library/coverartdelegate.cpp",
"src/library/locationdelegate.cpp",
"src/library/tableitemdelegate.cpp",
Expand Down
65 changes: 8 additions & 57 deletions src/library/banshee/bansheeplaylistmodel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -221,13 +221,6 @@ void BansheePlaylistModel::setTableModel(int playlistId) {
setSort(defaultSortColumn(), defaultSortOrder());
}

bool BansheePlaylistModel::setData(const QModelIndex& index, const QVariant& value, int role) {
Q_UNUSED(index);
Q_UNUSED(value);
Q_UNUSED(role);
return false;
}

TrackModel::CapabilitiesFlags BansheePlaylistModel::getCapabilities() const {
return TRACKMODELCAPS_NONE
| TRACKMODELCAPS_ADDTOPLAYLIST
Expand All @@ -238,65 +231,23 @@ TrackModel::CapabilitiesFlags BansheePlaylistModel::getCapabilities() const {
}

Qt::ItemFlags BansheePlaylistModel::flags(const QModelIndex &index) const {
return readWriteFlags(index);
}

Qt::ItemFlags BansheePlaylistModel::readWriteFlags(const QModelIndex &index) const {
if (!index.isValid()) {
return Qt::ItemIsEnabled;
}

Qt::ItemFlags defaultFlags = QAbstractItemModel::flags(index);

// Enable dragging songs from this data model to elsewhere (like the waveform
// widget to load a track into a Player).
defaultFlags |= Qt::ItemIsDragEnabled;

return defaultFlags;
}

Qt::ItemFlags BansheePlaylistModel::readOnlyFlags(const QModelIndex &index) const
{
Qt::ItemFlags defaultFlags = QAbstractItemModel::flags(index);
if (!index.isValid())
return Qt::ItemIsEnabled;

//Enable dragging songs from this data model to elsewhere (like the waveform widget to
//load a track into a Player).
defaultFlags |= Qt::ItemIsDragEnabled;

return defaultFlags;
return readOnlyFlags(index);
}

void BansheePlaylistModel::tracksChanged(QSet<TrackId> trackIds) {
Q_UNUSED(trackIds);
}

void BansheePlaylistModel::trackLoaded(QString group, TrackPointer pTrack) {
if (group == m_previewDeckGroup) {
// If there was a previously loaded track, refresh its rows so the
// preview state will update.
if (m_previewDeckTrackId.isValid()) {
const int numColumns = columnCount();
QLinkedList<int> rows = getTrackRows(m_previewDeckTrackId);
m_previewDeckTrackId = TrackId(); // invalidate
foreach (int row, rows) {
QModelIndex left = index(row, 0);
QModelIndex right = index(row, numColumns);
emit dataChanged(left, right);
}
}
if (pTrack) {
for (int row = 0; row < rowCount(); ++row) {
const QUrl rowUrl(getFieldString(index(row, 0), CLM_URI));
if (TrackFile::fromUrl(rowUrl) == pTrack->getFileInfo()) {
m_previewDeckTrackId =
TrackId(getFieldVariant(index(row, 0), CLM_VIEW_ORDER));
break;
}
TrackId BansheePlaylistModel::doGetTrackId(const TrackPointer& pTrack) const {
if (pTrack) {
for (int row = 0; row < rowCount(); ++row) {
const QUrl rowUrl(getFieldString(index(row, 0), CLM_URI));
if (TrackFile::fromUrl(rowUrl) == pTrack->getFileInfo()) {
return TrackId(getFieldVariant(index(row, 0), CLM_VIEW_ORDER));
}
}
}
return TrackId();
}

QVariant BansheePlaylistModel::getFieldVariant(const QModelIndex& index,
Expand Down
11 changes: 2 additions & 9 deletions src/library/banshee/bansheeplaylistmodel.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,19 +28,12 @@ class BansheePlaylistModel : public BaseSqlTableModel {
Qt::ItemFlags flags(const QModelIndex &index) const final;
CapabilitiesFlags getCapabilities() const final;

bool setData(const QModelIndex& index, const QVariant& value, int role=Qt::EditRole) final;

protected:
// Use this if you want a model that is read-only.
Qt::ItemFlags readOnlyFlags(const QModelIndex &index) const final;
// Use this if you want a model that can be changed
Qt::ItemFlags readWriteFlags(const QModelIndex &index) const final;

private slots:
void tracksChanged(QSet<TrackId> trackIds);
void trackLoaded(QString group, TrackPointer pTrack);

private:
TrackId doGetTrackId(const TrackPointer& pTrack) const final;

QString getFieldString(const QModelIndex& index, const QString& fieldName) const;
QVariant getFieldVariant(const QModelIndex& index, const QString& fieldName) const;
void dropTempTable();
Expand Down
143 changes: 143 additions & 0 deletions src/library/basecoverartdelegate.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
#include "library/coverartdelegate.h"

#include <QPainter>
#include <algorithm>

#include "library/coverartcache.h"
#include "library/dao/trackschema.h"
#include "library/trackmodel.h"
#include "util/logger.h"
#include "widget/wlibrarytableview.h"

namespace {

const mixxx::Logger kLogger("BaseCoverArtDelegate");

inline TrackModel* asTrackModel(
QTableView* pTableView) {
auto* pTrackModel =
dynamic_cast<TrackModel*>(pTableView->model());
DEBUG_ASSERT(pTrackModel);
return pTrackModel;
}

} // anonymous namespace

BaseCoverArtDelegate::BaseCoverArtDelegate(QTableView* parent)
: TableItemDelegate(parent),
m_pTrackModel(asTrackModel(parent)),
m_pCache(CoverArtCache::instance()),
m_inhibitLazyLoading(false) {
if (m_pCache) {
connect(m_pCache,
&CoverArtCache::coverFound,
this,
&BaseCoverArtDelegate::slotCoverFound);
} else {
kLogger.warning()
<< "Caching of cover art is not available";
}
}

void BaseCoverArtDelegate::emitRowsChanged(
QList<int>&& rows) {
if (rows.isEmpty()) {
return;
}
// Sort in ascending order...
std::sort(rows.begin(), rows.end());
// ...and then deduplicate...
rows.erase(std::unique(rows.begin(), rows.end()), rows.end());
// ...before emitting the signal.
DEBUG_ASSERT(!rows.isEmpty());
emit rowsChanged(std::move(rows));
}

void BaseCoverArtDelegate::slotInhibitLazyLoading(
bool inhibitLazyLoading) {
m_inhibitLazyLoading = inhibitLazyLoading;
if (m_inhibitLazyLoading || m_cacheMissRows.isEmpty()) {
return;
}
// If we can request non-cache covers now, request updates
// for all rows that were cache misses since the last time.
auto staleRows = m_cacheMissRows;
// Reset the member variable before mutating the aggregated
// rows list (-> implicit sharing) and emitting a signal that
// in turn may trigger new signals for BaseCoverArtDelegate!
m_cacheMissRows = QList<int>();
emitRowsChanged(std::move(staleRows));
}

void BaseCoverArtDelegate::slotCoverFound(
const QObject* pRequestor,
const CoverInfo& coverInfo,
const QPixmap& pixmap,
mixxx::cache_key_t requestedImageHash,
bool coverInfoUpdated) {
Q_UNUSED(pixmap);
if (pRequestor != this) {
return;
}
if (coverInfoUpdated) {
const auto pTrack =
loadTrackByLocation(coverInfo.trackLocation);
if (pTrack) {
kLogger.info()
<< "Updating cover info of track"
<< coverInfo.trackLocation;
pTrack->setCoverInfo(coverInfo);
}
}
QList<int> refreshRows = m_pendingCacheRows.values(requestedImageHash);
m_pendingCacheRows.remove(requestedImageHash);
emitRowsChanged(std::move(refreshRows));
}

TrackPointer BaseCoverArtDelegate::loadTrackByLocation(
const QString& trackLocation) const {
VERIFY_OR_DEBUG_ASSERT(m_pTrackModel) {
return TrackPointer();
}
return m_pTrackModel->getTrackByRef(
TrackRef::fromFileInfo(trackLocation));
}

void BaseCoverArtDelegate::paintItem(
QPainter* painter,
const QStyleOptionViewItem& option,
const QModelIndex& index) const {
paintItemBackground(painter, option, index);

CoverInfo coverInfo = coverInfoForIndex(index);
if (CoverImageUtils::isValidHash(coverInfo.hash)) {
VERIFY_OR_DEBUG_ASSERT(m_pCache) {
return;
}
const double scaleFactor =
getDevicePixelRatioF(static_cast<QWidget*>(parent()));
QPixmap pixmap = m_pCache->tryLoadCover(
this,
coverInfo,
option.rect.width() * scaleFactor,
m_inhibitLazyLoading ? CoverArtCache::Loading::CachedOnly : CoverArtCache::Loading::Default);
if (pixmap.isNull()) {
// Cache miss
if (m_inhibitLazyLoading) {
// We are requesting cache-only covers and got a cache
// miss. Record this row so that when we switch to requesting
// non-cache we can request an update.
m_cacheMissRows.append(index.row());
} else {
// If we asked for a non-cache image and got a null pixmap,
// then our request was queued.
m_pendingCacheRows.insertMulti(coverInfo.hash, index.row());
}
} else {
// Cache hit
pixmap.setDevicePixelRatio(scaleFactor);
painter->drawPixmap(option.rect.topLeft(), pixmap);
return;
}
}
}
75 changes: 75 additions & 0 deletions src/library/basecoverartdelegate.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
#pragma once

#include <QHash>
#include <QList>
#include <QTableView>

#include "library/tableitemdelegate.h"
#include "track/track.h"
#include "util/cache.h"

class CoverArtCache;
class TrackModel;

class BaseCoverArtDelegate : public TableItemDelegate {
Q_OBJECT

public:
explicit BaseCoverArtDelegate(
QTableView* parent);
~BaseCoverArtDelegate() override = default;

void paintItem(
QPainter* painter,
const QStyleOptionViewItem& option,
const QModelIndex& index) const final;

signals:
// Sent when rows need to be refreshed
void rowsChanged(
QList<int> rows);

public slots:
// Advise the delegate to temporarily inhibit lazy loading
// of cover images and to only display those cover images
// that have already been cached. Otherwise only the solid
// (background) color is painted.
//
// It is useful to handle cases when the user scroll down
// very fast or when they hold an arrow key. In thise case
// it is NOT desirable to start multiple expensive file
// system operations in worker threads for loading and
// scaling cover images that are not even displayed after
// scrolling beyond them.
void slotInhibitLazyLoading(
bool inhibitLazyLoading);

private slots:
void slotCoverFound(
const QObject* pRequestor,
const CoverInfo& coverInfo,
const QPixmap& pixmap,
mixxx::cache_key_t requestedImageHash,
bool coverInfoUpdated);

protected:
TrackModel* const m_pTrackModel;

private:
void emitRowsChanged(
QList<int>&& rows);

TrackPointer loadTrackByLocation(
const QString& trackLocation) const;

virtual CoverInfo coverInfoForIndex(
const QModelIndex& index) const = 0;

CoverArtCache* const m_pCache;
bool m_inhibitLazyLoading;

// We need to record rows in paint() (which is const) so
// these are marked mutable.
mutable QList<int> m_cacheMissRows;
mutable QHash<mixxx::cache_key_t, int> m_pendingCacheRows;
};
Loading