diff options
-rw-r--r-- | src/dialog/dlgabout.cpp | 3 | ||||
-rw-r--r-- | src/engine/bpmcontrol.cpp | 6 | ||||
-rw-r--r-- | src/engine/enginebuffer.cpp | 2 | ||||
-rw-r--r-- | src/library/banshee/bansheefeature.cpp | 1 | ||||
-rw-r--r-- | src/library/banshee/bansheeplaylistmodel.cpp | 73 | ||||
-rw-r--r-- | src/library/banshee/bansheeplaylistmodel.h | 4 | ||||
-rw-r--r-- | src/library/baseexternallibraryfeature.cpp | 11 | ||||
-rw-r--r-- | src/library/baseexternalplaylistmodel.cpp | 9 | ||||
-rw-r--r-- | src/library/baseexternalplaylistmodel.h | 1 | ||||
-rw-r--r-- | src/library/crate/cratetablemodel.cpp | 4 | ||||
-rw-r--r-- | src/library/librarytablemodel.cpp | 4 | ||||
-rw-r--r-- | src/library/playlisttablemodel.cpp | 4 | ||||
-rw-r--r-- | src/library/searchquery.cpp | 26 | ||||
-rw-r--r-- | src/library/searchquery.h | 6 | ||||
-rw-r--r-- | src/library/searchqueryparser.cpp | 16 | ||||
-rw-r--r-- | src/library/trackmodel.h | 4 | ||||
-rw-r--r-- | src/test/searchqueryparsertest.cpp | 74 | ||||
-rw-r--r-- | src/util/db/dbconnection.cpp | 25 | ||||
-rw-r--r-- | src/util/db/dbconnection.h | 2 | ||||
-rw-r--r-- | src/widget/wtracktableview.cpp | 84 |
20 files changed, 253 insertions, 106 deletions
diff --git a/src/dialog/dlgabout.cpp b/src/dialog/dlgabout.cpp index 390449957f..59c940dc3e 100644 --- a/src/dialog/dlgabout.cpp +++ b/src/dialog/dlgabout.cpp @@ -100,7 +100,8 @@ DlgAbout::DlgAbout(QWidget* parent) : QDialog(parent), Ui::DlgAboutDlg() { << "Paweł Goliński" << "beenisss" << "Bernd Binder" - << "Pradyuman"; + << "Pradyuman" + << "Nikolaus Einhauser"; QStringList specialThanks; specialThanks diff --git a/src/engine/bpmcontrol.cpp b/src/engine/bpmcontrol.cpp index 66bd011d00..db9bb5cd29 100644 --- a/src/engine/bpmcontrol.cpp +++ b/src/engine/bpmcontrol.cpp @@ -219,10 +219,14 @@ void BpmControl::slotTapFilter(double averageLength, int numSamples) { if (numSamples < 4) return; + auto pBeats = m_pBeats; + if (!pBeats) + return; + // (60 seconds per minute) * (1000 milliseconds per second) / (X millis per // beat) = Y beats/minute double averageBpm = 60.0 * 1000.0 / averageLength / calcRateRatio(); - m_pBeats->setBpm(averageBpm); + pBeats->setBpm(averageBpm); } void BpmControl::slotControlBeatSyncPhase(double v) { diff --git a/src/engine/enginebuffer.cpp b/src/engine/enginebuffer.cpp index eac1beed1b..79b8911c8d 100644 --- a/src/engine/enginebuffer.cpp +++ b/src/engine/enginebuffer.cpp @@ -182,7 +182,7 @@ EngineBuffer::EngineBuffer(QString group, UserSettingsPointer pConfig, this, SLOT(slotEjectTrack(double)), Qt::DirectConnection); - m_pTrackLoaded = new ControlObject(ConfigKey(m_group, "track_loaded")); + m_pTrackLoaded = new ControlObject(ConfigKey(m_group, "track_loaded"), false); m_pTrackLoaded->setReadOnly(); // Quantization Controller for enabling and disabling the diff --git a/src/library/banshee/bansheefeature.cpp b/src/library/banshee/bansheefeature.cpp index e0a6b4c75a..bdb16b58e1 100644 --- a/src/library/banshee/bansheefeature.cpp +++ b/src/library/banshee/bansheefeature.cpp @@ -139,6 +139,7 @@ void BansheeFeature::appendTrackIdsFromRightClickIndex(QList<TrackId>* trackIds, if (playlistID > 0) { BansheePlaylistModel* pPlaylistModelToAdd = new BansheePlaylistModel(this, m_pTrackCollection, &m_connection); pPlaylistModelToAdd->setTableModel(playlistID); + pPlaylistModelToAdd->select(); // Copy Tracks int rows = pPlaylistModelToAdd->rowCount(); diff --git a/src/library/banshee/bansheeplaylistmodel.cpp b/src/library/banshee/bansheeplaylistmodel.cpp index c871095bd3..d46661b965 100644 --- a/src/library/banshee/bansheeplaylistmodel.cpp +++ b/src/library/banshee/bansheeplaylistmodel.cpp @@ -11,6 +11,7 @@ #include "mixer/playermanager.h" #define BANSHEE_TABLE "banshee" +#define CLM_TRACK_ID "track_id" #define CLM_VIEW_ORDER "position" #define CLM_ARTIST "artist" #define CLM_TITLE "title" @@ -31,39 +32,50 @@ #define CLM_COMPOSER "composer" #define CLM_PREVIEW "preview" +namespace { +QAtomicInt sTableNumber; +} + BansheePlaylistModel::BansheePlaylistModel(QObject* pParent, TrackCollection* pTrackCollection, BansheeDbConnection* pConnection) : BaseSqlTableModel(pParent, pTrackCollection, "mixxx.db.model.banshee_playlist"), m_pConnection(pConnection), m_playlistId(-1) { + m_tempTableName = BANSHEE_TABLE + QString::number(sTableNumber.fetchAndAddAcquire(1)); } BansheePlaylistModel::~BansheePlaylistModel() { + dropTempTable(); } -void BansheePlaylistModel::setTableModel(int playlistId) { - //qDebug() << "BansheePlaylistModel::setTableModel" << playlistId; - if (m_playlistId == playlistId) { - qDebug() << "Already focused on playlist " << playlistId; - return; - } - +void BansheePlaylistModel::dropTempTable() { if (m_playlistId >= 0) { // Clear old playlist m_playlistId = -1; QSqlQuery query(m_pTrackCollection->database()); - QString strQuery("DELETE FROM " BANSHEE_TABLE); - if (!query.exec(strQuery)) { + QString strQuery("DROP TABLE IF EXISTS %1"); + if (!query.exec(strQuery.arg(m_tempTableName))) { LOG_FAILED_QUERY(query); } } +} + +void BansheePlaylistModel::setTableModel(int playlistId) { + //qDebug() << "BansheePlaylistModel::setTableModel" << this << playlistId; + if (m_playlistId == playlistId) { + qDebug() << "Already focused on playlist " << playlistId; + return; + } + + dropTempTable(); if (playlistId >= 0) { // setup new playlist m_playlistId = playlistId; QSqlQuery query(m_pTrackCollection->database()); - QString strQuery("CREATE TEMP TABLE IF NOT EXISTS " BANSHEE_TABLE - " (" CLM_VIEW_ORDER " INTEGER, " + QString strQuery("CREATE TEMP TABLE IF NOT EXISTS %1" + " (" CLM_TRACK_ID " INTEGER, " + CLM_VIEW_ORDER " INTEGER, " CLM_ARTIST " TEXT, " CLM_TITLE " TEXT, " CLM_DURATION " INTEGER, " @@ -82,12 +94,13 @@ void BansheePlaylistModel::setTableModel(int playlistId) { CLM_PLAYCOUNT" INTEGER, " CLM_COMPOSER " TEXT, " CLM_PREVIEW " TEXT)"); - if (!query.exec(strQuery)) { + if (!query.exec(strQuery.arg(m_tempTableName))) { LOG_FAILED_QUERY(query); } - query.prepare("INSERT INTO " BANSHEE_TABLE - " (" CLM_VIEW_ORDER ", " + QString strQuery2("INSERT INTO %1" + " (" CLM_TRACK_ID ", " + CLM_VIEW_ORDER ", " CLM_ARTIST ", " CLM_TITLE ", " CLM_DURATION ", " @@ -106,6 +119,7 @@ void BansheePlaylistModel::setTableModel(int playlistId) { CLM_PLAYCOUNT ", " CLM_COMPOSER ") " "VALUES (:" + CLM_TRACK_ID ", :" CLM_VIEW_ORDER ", :" CLM_ARTIST ", :" CLM_TITLE ", :" @@ -125,6 +139,7 @@ void BansheePlaylistModel::setTableModel(int playlistId) { CLM_PLAYCOUNT ", :" CLM_COMPOSER ") "); + query.prepare(strQuery2.arg(m_tempTableName)); QList<struct BansheeDbConnection::PlaylistEntry> list = m_pConnection->getPlaylistEntries(playlistId); @@ -133,6 +148,9 @@ void BansheePlaylistModel::setTableModel(int playlistId) { beginInsertRows(QModelIndex(), 0, list.size() - 1); foreach (struct BansheeDbConnection::PlaylistEntry entry, list) { + query.bindValue(":" CLM_TRACK_ID, entry.trackId); + // Note: entry.viewOrder is 0 for all tracks if they have + // never been sorted by the user query.bindValue(":" CLM_VIEW_ORDER, entry.viewOrder + 1); query.bindValue(":" CLM_ARTIST, entry.pArtist->name); query.bindValue(":" CLM_TITLE, entry.pTrack->title); @@ -165,11 +183,14 @@ void BansheePlaylistModel::setTableModel(int playlistId) { } QStringList tableColumns; - tableColumns << CLM_VIEW_ORDER // 0 - << CLM_PREVIEW; + tableColumns + << CLM_TRACK_ID // 0 + << CLM_VIEW_ORDER + << CLM_PREVIEW; // 3 QStringList trackSourceColumns; - trackSourceColumns << CLM_VIEW_ORDER // 0 + trackSourceColumns + << CLM_TRACK_ID // 0 << CLM_ARTIST << CLM_TITLE << CLM_DURATION @@ -189,10 +210,10 @@ void BansheePlaylistModel::setTableModel(int playlistId) { << CLM_COMPOSER; QSharedPointer<BaseTrackCache> trackSource( - new BaseTrackCache(m_pTrackCollection, BANSHEE_TABLE, CLM_VIEW_ORDER, + new BaseTrackCache(m_pTrackCollection, m_tempTableName, CLM_TRACK_ID, trackSourceColumns, false)); - setTable(BANSHEE_TABLE, CLM_VIEW_ORDER, tableColumns, trackSource); + setTable(m_tempTableName, CLM_TRACK_ID, tableColumns, trackSource); setSearch(""); setDefaultSort(fieldIndex(PLAYLISTTRACKSTABLE_POSITION), Qt::AscendingOrder); setSort(defaultSortColumn(), defaultSortOrder()); @@ -327,6 +348,15 @@ TrackPointer BansheePlaylistModel::getTrack(const QModelIndex& index) const { return pTrack; } +TrackId BansheePlaylistModel::getTrackId(const QModelIndex& index) const { + const auto track = getTrack(index); + if (track) { + return track->getId(); + } else { + return TrackId(); + } +} + // Gets the on-disk location of the track at the given location. QString BansheePlaylistModel::getTrackLocation(const QModelIndex& index) const { if (!index.isValid()) { @@ -365,6 +395,7 @@ QString BansheePlaylistModel::getTrackLocation(const QModelIndex& index) const { } bool BansheePlaylistModel::isColumnInternal(int column) { - Q_UNUSED(column); - return false; + return (column == fieldIndex(ColumnCache::COLUMN_PLAYLISTTRACKSTABLE_TRACKID) || + (PlayerManager::numPreviewDecks() == 0 && + column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_PREVIEW))); } diff --git a/src/library/banshee/bansheeplaylistmodel.h b/src/library/banshee/bansheeplaylistmodel.h index efda77a878..d89fc20a8a 100644 --- a/src/library/banshee/bansheeplaylistmodel.h +++ b/src/library/banshee/bansheeplaylistmodel.h @@ -20,6 +20,8 @@ class BansheePlaylistModel : public BaseSqlTableModel { void setTableModel(int playlistId); TrackPointer getTrack(const QModelIndex& index) const final; + TrackId getTrackId(const QModelIndex& index) const final; + QString getTrackLocation(const QModelIndex& index) const final; bool isColumnInternal(int column) final; @@ -41,9 +43,11 @@ class BansheePlaylistModel : public BaseSqlTableModel { private: QString getFieldString(const QModelIndex& index, const QString& fieldName) const; QVariant getFieldVariant(const QModelIndex& index, const QString& fieldName) const; + void dropTempTable(); BansheeDbConnection* m_pConnection; int m_playlistId; + QString m_tempTableName; }; #endif // BANSHEEPLAYLISTMODEL_H diff --git a/src/library/baseexternallibraryfeature.cpp b/src/library/baseexternallibraryfeature.cpp index 87807a62e7..b527c119d3 100644 --- a/src/library/baseexternallibraryfeature.cpp +++ b/src/library/baseexternallibraryfeature.cpp @@ -56,7 +56,7 @@ void BaseExternalLibraryFeature::slotAddToAutoDJTop() { } void BaseExternalLibraryFeature::addToAutoDJ(bool bTop) { - // qDebug() << "slotAddToAutoDJ() row:" << m_lastRightClickedIndex.data(); + //qDebug() << "slotAddToAutoDJ() row:" << m_lastRightClickedIndex.data(); QList<TrackId> trackIds; QString playlist; @@ -96,15 +96,14 @@ void BaseExternalLibraryFeature::slotImportAsMixxxPlaylist() { } // This is a common function for all external Librarys copied to Mixxx DB -void BaseExternalLibraryFeature::appendTrackIdsFromRightClickIndex(QList<TrackId>* trackIds, QString* pPlaylist) { +void BaseExternalLibraryFeature::appendTrackIdsFromRightClickIndex( + QList<TrackId>* trackIds, QString* pPlaylist) { if (!m_lastRightClickedIndex.isValid()) { return; } - // Qt::UserRole asks TreeItemModel for the TreeItem's data. We need to - // use the data because models with nested playlists need to use the - // full path/name of the playlist. - *pPlaylist = m_lastRightClickedIndex.data(Qt::UserRole).toString(); + DEBUG_ASSERT(pPlaylist); + *pPlaylist = m_lastRightClickedIndex.data().toString(); QScopedPointer<BaseSqlTableModel> pPlaylistModelToAdd( getPlaylistModelForPlaylist(*pPlaylist)); diff --git a/src/library/baseexternalplaylistmodel.cpp b/src/library/baseexternalplaylistmodel.cpp index 7529523896..2ba94fe30b 100644 --- a/src/library/baseexternalplaylistmodel.cpp +++ b/src/library/baseexternalplaylistmodel.cpp @@ -65,6 +65,15 @@ TrackPointer BaseExternalPlaylistModel::getTrack(const QModelIndex& index) const return pTrack; } +TrackId BaseExternalPlaylistModel::getTrackId(const QModelIndex& index) const { + const auto track = getTrack(index); + if (track) { + return track->getId(); + } else { + return TrackId(); + } +} + bool BaseExternalPlaylistModel::isColumnInternal(int column) { if (column == fieldIndex(ColumnCache::COLUMN_PLAYLISTTRACKSTABLE_TRACKID) || (PlayerManager::numPreviewDecks() == 0 && diff --git a/src/library/baseexternalplaylistmodel.h b/src/library/baseexternalplaylistmodel.h index 0b8cd3e7b4..51cbb9c94f 100644 --- a/src/library/baseexternalplaylistmodel.h +++ b/src/library/baseexternalplaylistmodel.h @@ -25,6 +25,7 @@ class BaseExternalPlaylistModel : public BaseSqlTableModel { void setPlaylist(QString path_name); TrackPointer getTrack(const QModelIndex& index) const override; + TrackId getTrackId(const QModelIndex& index) const override; bool isColumnInternal(int column) override; Qt::ItemFlags flags(const QModelIndex &index) const override; void trackLoaded(QString group, TrackPointer pTrack) override; diff --git a/src/library/crate/cratetablemodel.cpp b/src/library/crate/cratetablemodel.cpp index 661ac606a4..ca059ffe10 100644 --- a/src/library/crate/cratetablemodel.cpp +++ b/src/library/crate/cratetablemodel.cpp @@ -107,13 +107,11 @@ TrackModel::CapabilitiesFlags CrateTableModel::getCapabilities() const { | TRACKMODELCAPS_ADDTOPLAYLIST | TRACKMODELCAPS_ADDTOCRATE | TRACKMODELCAPS_ADDTOAUTODJ - | TRACKMODELCAPS_IMPORTMETADATA + | TRACKMODELCAPS_EDITMETADATA | TRACKMODELCAPS_LOADTODECK | TRACKMODELCAPS_LOADTOSAMPLER | TRACKMODELCAPS_LOADTOPREVIEWDECK | TRACKMODELCAPS_REMOVE_CRATE - | TRACKMODELCAPS_MANIPULATEBEATS - | TRACKMODELCAPS_CLEAR_BEATS | TRACKMODELCAPS_RESETPLAYED; if (m_selectedCrate.isValid()) { Crate crate; diff --git a/src/library/librarytablemodel.cpp b/src/library/librarytablemodel.cpp index c50a99867b..3d79457bea 100644 --- a/src/library/librarytablemodel.cpp +++ b/src/library/librarytablemodel.cpp @@ -101,12 +101,10 @@ TrackModel::CapabilitiesFlags LibraryTableModel::getCapabilities() const { | TRACKMODELCAPS_ADDTOPLAYLIST | TRACKMODELCAPS_ADDTOCRATE | TRACKMODELCAPS_ADDTOAUTODJ - | TRACKMODELCAPS_IMPORTMETADATA + | TRACKMODELCAPS_EDITMETADATA | TRACKMODELCAPS_LOADTODECK | TRACKMODELCAPS_LOADTOSAMPLER | TRACKMODELCAPS_LOADTOPREVIEWDECK | TRACKMODELCAPS_HIDE - | TRACKMODELCAPS_MANIPULATEBEATS - | TRACKMODELCAPS_CLEAR_BEATS | TRACKMODELCAPS_RESETPLAYED; } diff --git a/src/library/playlisttablemodel.cpp b/src/library/playlisttablemodel.cpp index 8c659f9b93..e1bc686c7d 100644 --- a/src/library/playlisttablemodel.cpp +++ b/src/library/playlisttablemodel.cpp @@ -242,12 +242,10 @@ TrackModel::CapabilitiesFlags PlaylistTableModel::getCapabilities() const { | TRACKMODELCAPS_REORDER | TRACKMODELCAPS_ADDTOCRATE | TRACKMODELCAPS_ADDTOPLAYLIST - | TRACKMODELCAPS_IMPORTMETADATA + | TRACKMODELCAPS_EDITMETADATA | TRACKMODELCAPS_LOADTODECK | TRACKMODELCAPS_LOADTOSAMPLER | TRACKMODELCAPS_LOADTOPREVIEWDECK - | TRACKMODELCAPS_MANIPULATEBEATS - | TRACKMODELCAPS_CLEAR_BEATS | TRACKMODELCAPS_RESETPLAYED; if (m_iPlaylistId != m_pTrackCollection->getPlaylistDAO().getPlaylistIdFromName(AUTODJ_TABLE)) { diff --git a/src/library/searchquery.cpp b/src/library/searchquery.cpp index 13dee24ef8..04e6814e03 100644 --- a/src/library/searchquery.cpp +++ b/src/library/searchquery.cpp @@ -6,6 +6,7 @@ #include "track/keyutils.h" #include "library/dao/trackschema.h" #include "util/db/sqllikewildcards.h" +#include "util/db/dbconnection.h" QVariant getTrackValueForColumn(const TrackPointer& pTrack, const QString& column) { if (column == LIBRARYTABLE_ARTIST) { @@ -141,6 +142,15 @@ QString NotNode::toSql() const { } } +TextFilterNode::TextFilterNode(const QSqlDatabase& database, + const QStringList& sqlColumns, + const QString& argument) + : m_database(database), + m_sqlColumns(sqlColumns), + m_argument(argument) { + mixxx::DbConnection::makeStringLatinLow(&m_argument); +} + bool TextFilterNode::match(const TrackPointer& pTrack) const { for (const auto& sqlColumn: m_sqlColumns) { QVariant value = getTrackValueForColumn(pTrack, sqlColumn); @@ -148,7 +158,9 @@ bool TextFilterNode::match(const TrackPointer& pTrack) const { continue; } - if (value.toString().contains(m_argument, Qt::CaseInsensitive)) { + QString strValue = value.toString(); + mixxx::DbConnection::makeStringLatinLow(&strValue); + if (strValue.contains(m_argument)) { return true; } } @@ -157,8 +169,16 @@ bool TextFilterNode::match(const TrackPointer& pTrack) const { QString TextFilterNode::toSql() const { FieldEscaper escaper(m_database); - QString escapedArgument = escaper.escapeString(kSqlLikeMatchAll + m_argument + kSqlLikeMatchAll); - + QString argument = m_argument; + if (argument.size() > 0) { + if (argument[argument.size() - 1].isSpace()) { + // LIKE eats a trailing space. This can be avoided by adding a '_' + // as a delimiter that matches any following character. + argument.append('_'); + } + } + QString escapedArgument = escaper.escapeString( + kSqlLikeMatchAll + argument + kSqlLikeMatchAll); QStringList searchClauses; for (const auto& sqlColumn: m_sqlColumns) { searchClauses << QString("%1 LIKE %2").arg(sqlColumn, escapedArgument); diff --git a/src/library/searchquery.h b/src/library/searchquery.h index c36f1ad400..20241dec5a 100644 --- a/src/library/searchquery.h +++ b/src/library/searchquery.h @@ -76,11 +76,7 @@ class TextFilterNode : public QueryNode { public: TextFilterNode(const QSqlDatabase& database, const QStringList& sqlColumns, - const QString& argument) - : m_database(database), - m_sqlColumns(sqlColumns), - m_argument(argument) { - } + const QString& argument); bool match(const TrackPointer& pTrack) const override; QString toSql() const override; diff --git a/src/library/searchqueryparser.cpp b/src/library/searchqueryparser.cpp index 7ab0c83cdd..c8b0a47a30 100644 --- a/src/library/searchqueryparser.cpp +++ b/src/library/searchqueryparser.cpp @@ -136,15 +136,16 @@ void SearchQueryParser::parseTokens(QStringList tokens, } else if (m_textFilterMatcher.indexIn(token) != -1) { QString field = m_textFilterMatcher.cap(1); QString argument = getTextArgument( - m_textFilterMatcher.cap(2), &tokens).trimmed(); + m_textFilterMatcher.cap(2), &tokens); if (!argument.isEmpty()) { if (field == "crate") { pNode = std::make_unique<CrateFilterNode>( - &m_pTrackCollection->crates(), argument); + &m_pTrackCollection->crates(), argument); } else { pNode = std::make_unique<TextFilterNode>( - m_pTrackCollection->database(), m_fieldToSqlColumns[field], argument); + m_pTrackCollection->database(), + m_fieldToSqlColumns[field], argument); } } } else if (m_numericFilterMatcher.indexIn(token) != -1) { @@ -154,7 +155,7 @@ void SearchQueryParser::parseTokens(QStringList tokens, if (!argument.isEmpty()) { pNode = std::make_unique<NumericFilterNode>( - m_fieldToSqlColumns[field], argument); + m_fieldToSqlColumns[field], argument); } } else if (m_specialFilterMatcher.indexIn(token) != -1) { bool fuzzy = token.startsWith(kFuzzyPrefix); @@ -190,6 +191,7 @@ void SearchQueryParser::parseTokens(QStringList tokens, } // Don't trigger on a lone minus sign. if (!token.isEmpty()) { + QString argument = getTextArgument(token, &tokens); // For untagged strings we search the track fields as well // as the crate names the track is in. This allows the user // to use crates like tags @@ -197,14 +199,14 @@ void SearchQueryParser::parseTokens(QStringList tokens, std::unique_ptr<OrNode> gNode = std::make_unique<OrNode>(); gNode->addNode(std::make_unique<CrateFilterNode>( - &m_pTrackCollection->crates(), token)); + &m_pTrackCollection->crates(), argument)); gNode->addNode(std::make_unique<TextFilterNode>( - m_pTrackCollection->database(), queryColumns, token)); + m_pTrackCollection->database(), queryColumns, argument)); pNode = std::move(gNode); } else { pNode = std::make_unique<TextFilterNode>( - m_pTrackCollection->database(), queryColumns, token); + m_pTrackCollection->database(), queryColumns, argument); } } } diff --git a/src/library/trackmodel.h b/src/library/trackmodel.h index a877be7ed5..1c4a17fb49 100644 --- a/src/library/trackmodel.h +++ b/src/library/trackmodel.h @@ -35,13 +35,11 @@ class TrackModel { TRACKMODELCAPS_ADDTOCRATE = 0x00008, TRACKMODELCAPS_ADDTOAUTODJ = 0x00010, TRACKMODELCAPS_LOCKED = 0x00020, - TRACKMODELCAPS_IMPORTMETADATA = 0x00040, + TRACKMODELCAPS_EDITMETADATA = 0x00040, TRACKMODELCAPS_LOADTODECK = 0x00080, TRACKMODELCAPS_LOADTOSAMPLER = 0x00100, TRACKMODELCAPS_LOADTOPREVIEWDECK = 0x00200, TRACKMODELCAPS_REMOVE = 0x00400, - TRACKMODELCAPS_MANIPULATEBEATS = 0x00800, - TRACKMODELCAPS_CLEAR_BEATS = 0x01000, TRACKMODELCAPS_RESETPLAYED = 0x02000, TRACKMODELCAPS_HIDE = 0x04000, TRACKMODELCAPS_UNHIDE = 0x08000, diff --git a/src/test/searchqueryparsertest.cpp b/src/test/searchqueryparsertest.cpp index f848ad0119..31c696375d 100644 --- a/src/test/searchqueryparsertest.cpp +++ b/src/test/searchqueryparsertest.cpp @@ -260,6 +260,78 @@ TEST_F(SearchQueryParserTest, TextFilterAllowsSpace) { qPrintable(pQuery->toSql())); } +TEST_F(SearchQueryParserTest, TextFilterQuotes) { + QStringList searchColumns; + searchColumns << "artist" + << "album"; + + auto pQuery( + m_parser.parseQuery("comment:\"asdf ewe\"", searchColumns, "")); + + TrackPointer pTrack(Track::newTemporary()); + pTrack->setArtist("asdf"); + EXPECT_FALSE(pQuery->match(pTrack)); + pTrack->setComment("test ASDF ewetest"); + EXPECT_TRUE(pQuery->match(pTrack)); + + EXPECT_STREQ( + qPrintable(QString("comment LIKE '%asdf ewe%'")), + qPrintable(pQuery->toSql())); +} + +TEST_F(SearchQueryParserTest, TextFilterDecoration) { + QStringList searchColumns; + searchColumns << "artist" + << "album"; + + auto pQuery( + m_parser.parseQuery(QString::fromUtf8("comment:\"asdf\xC2\xB0 ewe\""), searchColumns, "")); // with ˚ + + TrackPointer pTrack(Track::newTemporary()); + pTrack->setArtist("asdf"); + EXPECT_FALSE(pQuery->match(pTrack)); + pTrack->setComment("test ASDF ewetest"); + EXPECT_FALSE(pQuery->match(pTrack)); + + pTrack->setComment(QString::fromUtf8("comment:\"asdf\xC2\xB0 ewe\"")); + EXPECT_TRUE(pQuery->match(pTrack)); + + qDebug() << pQuery->toSql(); + + EXPECT_STREQ( + qPrintable(QString::fromUtf8("comment LIKE '%asdf\xC2\xB0 ewe%'")), + qPrintable(pQuery->toSql())); +} + +TEST_F(SearchQueryParserTest, TextFilterTrailingSpace) { + QStringList searchColumns; + searchColumns << "artist" + << "album"; + + auto pQuery( + m_parser.parseQuery("comment:\"asdf \"", searchColumns, "")); + + TrackPointer pTrack(Track::newTemporary()); + pTrack->setArtist("asdf"); + EXPECT_FALSE(pQuery->match(pTrack)); + pTrack->setComment("test ASDF test"); + EXPECT_TRUE(pQuery->match(pTrack)); + + EXPECT_STREQ( + qPrintable(QString("comment LIKE '%asdf _%'")), + qPrintable(pQuery->toSql())); + + // We allow to search for two consequitve spaces + auto pQuery2( + m_parser.parseQuery("comment:\" \"", searchColumns, "")); + + EXPECT_FALSE(pQuery2->match(pTrack)); + + EXPECT_STREQ( + qPrintable(QString("comment LIKE '% _%'")), + qPrintable(pQuery2->toSql())); +} + TEST_F(SearchQueryParserTest, TextFilterNegation) { QStringList searchColumns; searchColumns << "artist" @@ -447,7 +519,7 @@ TEST_F(SearchQueryParserTest, MultipleFilters) { EXPECT_STREQ( qPrintable(QString("((bpm >= 127.12) AND (bpm <= 129)) AND " "((artist LIKE '%com truise%') OR (album_artist LIKE '%com truise%')) AND " - "((artist LIKE '%Colorvision%') OR (title LIKE '%Colorvision%'))")), + "((artist LIKE '%colorvision%') OR (title LIKE '%colorvision%'))")), qPrintable(pQuery->toSql())); } diff --git a/src/util/db/dbconnection.cpp b/src/util/db/dbconnection.cpp index ee78dfec0f..4a66fa9ff4 100644 --- a/src/util/db/dbconnection.cpp +++ b/src/util/db/dbconnection.cpp @@ -73,7 +73,14 @@ inline int compareLocaleAwareCaseInsensitive( void makeLatinLow(QChar* c, int count) { for (int i = 0; i < count; ++i) { if (c[i].decompositionTag() != QChar::NoDecomposition) { - c[i] = c[i].decomposition()[0]; + QString decomposition = c[i].decomposition(); + if (!decomposition[0].isSpace()) { + // here we remove the decoration brom all characters. + // We want "o" matching "ó" and all other variants but we + // do not decompose decoration only characters like "˚" where + // the base character is a space + c[i] = c[i].decomposition()[0]; + } } if (c[i].isUpper()) { c[i] = c[i].toLower(); @@ -88,12 +95,11 @@ const QChar kSqlLikeEscapeDefault = '\0'; // false (0) if they are different. // This is the original sqlite3 icuLikeCompare rewritten for QChar int likeCompareInner( - const QChar* pattern, // LIKE pattern - int patternSize, - const QChar* string, // The string to compare against - int stringSize, - const QChar esc) { // The escape character - + const QChar* pattern, // LIKE pattern + int patternSize, + const QChar* string, // The string to compare against + int stringSize, + const QChar esc) { // The escape character int iPattern = 0; // Current index in pattern int iString = 0; // Current index in string @@ -376,6 +382,11 @@ int DbConnection::likeCompareLatinLow( esc); } +//static +void DbConnection::makeStringLatinLow(QString* string) { + makeLatinLow(string->data(), string->length()); +} + QDebug operator<<(QDebug debug, const DbConnection& connection) { return debug << connection.name() diff --git a/src/util/db/dbconnection.h b/src/util/db/dbconnection.h index b0f080533c..1b344ac8ff 100644 --- a/src/util/db/dbconnection.h +++ b/src/util/db/dbconnection.h @@ -22,6 +22,8 @@ class DbConnection final { QString* string, QChar esc); + static void makeStringLatinLow(QString* string); + struct Params { QString type; QString hostName; diff --git a/src/widget/wtracktableview.cpp b/src/widget/wtracktableview.cpp index fd92079f1b..d78e273309 100644 --- a/src/widget/wtracktableview.cpp +++ b/src/widget/wtracktableview.cpp @@ -898,16 +898,14 @@ void WTrackTableView::contextMenuEvent(QContextMenuEvent* event) { m_pMenu->addSeparator(); m_pMetadataMenu->clear(); - if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_IMPORTMETADATA)) { + if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_EDITMETADATA)) { m_pMetadataMenu->addAction(m_pImportMetadataFromFileAct); m_pImportMetadataFromMusicBrainzAct->setEnabled(oneSongSelected); m_pMetadataMenu->addAction(m_pImportMetadataFromMusicBrainzAct); m_pMetadataMenu->addAction(m_pExportMetadataAct); - } - m_pClearMetadataMenu->clear(); + m_pClearMetadataMenu->clear(); - if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_CLEAR_BEATS)) { if (trackModel == nullptr) { return; } @@ -928,38 +926,38 @@ void WTrackTableView::contextMenuEvent(QContextMenuEvent* event) { m_pClearMetadataMenu->addAction(m_pClearPlayCountAction); } - //FIXME: Why are clearning the main cue and loop not working? -// m_pClearMetadataMenu->addAction(m_pClearMainCueAction); - m_pClearMetadataMenu->addAction(m_pClearHotCuesAction); -// m_pClearMetadataMenu->addAction(m_pClearLoopAction); - m_pClearMetadataMenu->addAction(m_pClearReplayGainAction); - m_pClearMetadataMenu->addAction(m_pClearWaveformAction); - m_pClearMetadataMenu->addSeparator(); - m_pClearMetadataMenu->addAction(m_pClearAllMetadataAction); - - // Cover art menu only applies if at least one track is selected. - if (indices.size()) { - // We load a single track to get the necessary context for the cover (we use - // last to be consistent with selectionChanged above). - QModelIndex last = indices.last(); - CoverInfo info; - info.source = static_cast<CoverInfo::Source>( - last.sibling(last.row(), m_iCoverSourceColumn).data().toInt()); - info.type = static_cast<CoverInfo::Type>( - last.sibling(last.row(), m_iCoverTypeColumn).data().toInt()); - info.hash = last.sibling(last.row(), m_iCoverHashColumn).data().toUInt(); - info.trackLocation = last.sibling( - last.row(), m_iTrackLocationColumn).data().toString(); - info.coverLocation = last.sibling( |