diff --git a/src/library/autodj/dlgautodj.h b/src/library/autodj/dlgautodj.h index aefe0e08639..ec45a8fc3dd 100644 --- a/src/library/autodj/dlgautodj.h +++ b/src/library/autodj/dlgautodj.h @@ -36,8 +36,8 @@ class DlgAutoDJ : public QWidget, public Ui::DlgAutoDJ, public LibraryView { void saveCurrentViewState() override { m_pTrackTableView->saveCurrentViewState(); }; - bool restoreCurrentViewState() override { - return m_pTrackTableView->restoreCurrentViewState(); + bool restoreCurrentViewState(bool fromSearch = false) override { + return m_pTrackTableView->restoreCurrentViewState(fromSearch); }; public slots: diff --git a/src/library/basesqltablemodel.cpp b/src/library/basesqltablemodel.cpp index b6b422a33d7..bbeed256b66 100644 --- a/src/library/basesqltablemodel.cpp +++ b/src/library/basesqltablemodel.cpp @@ -191,6 +191,7 @@ void BaseSqlTableModel::select() { if (!m_bInitialized) { return; } + beginResetModel(); // We should be able to detect when a select() would be a no-op. The DAO's // do not currently broadcast signals for when common things happen. In the // future, we can turn this check on and avoid a lot of needless @@ -329,7 +330,7 @@ void BaseSqlTableModel::select() { std::move(trackIdToRows)); // Both rowInfo and trackIdToRows (might) have been moved and // must not be used afterwards! - + endResetModel(); qDebug() << this << "select() took" << time.elapsed().debugMillisWithUnit() << m_rowInfo.size(); } diff --git a/src/library/dlganalysis.h b/src/library/dlganalysis.h index e088f2ac6f8..6373f9b156b 100644 --- a/src/library/dlganalysis.h +++ b/src/library/dlganalysis.h @@ -37,8 +37,8 @@ class DlgAnalysis : public QWidget, public Ui::DlgAnalysis, public virtual Libra void saveCurrentViewState() override { m_pAnalysisLibraryTableView->saveCurrentViewState(); }; - bool restoreCurrentViewState() override { - return m_pAnalysisLibraryTableView->restoreCurrentViewState(); + bool restoreCurrentViewState(bool fromSearch = false) override { + return m_pAnalysisLibraryTableView->restoreCurrentViewState(fromSearch); }; public slots: diff --git a/src/library/dlghidden.h b/src/library/dlghidden.h index b861afd6a01..c2684ecd717 100644 --- a/src/library/dlghidden.h +++ b/src/library/dlghidden.h @@ -28,8 +28,8 @@ class DlgHidden : public QWidget, public Ui::DlgHidden, public LibraryView { void saveCurrentViewState() override { m_pTrackTableView->saveCurrentViewState(); }; - bool restoreCurrentViewState() override { - return m_pTrackTableView->restoreCurrentViewState(); + bool restoreCurrentViewState(bool fromSearch = false) override { + return m_pTrackTableView->restoreCurrentViewState(fromSearch); }; public slots: diff --git a/src/library/dlgmissing.h b/src/library/dlgmissing.h index ba008ebc520..55b31b453ec 100644 --- a/src/library/dlgmissing.h +++ b/src/library/dlgmissing.h @@ -28,8 +28,8 @@ class DlgMissing : public QWidget, public Ui::DlgMissing, public LibraryView { void saveCurrentViewState() override { m_pTrackTableView->saveCurrentViewState(); }; - bool restoreCurrentViewState() override { - return m_pTrackTableView->restoreCurrentViewState(); + bool restoreCurrentViewState(bool fromSearch = false) override { + return m_pTrackTableView->restoreCurrentViewState(fromSearch); }; public slots: diff --git a/src/library/libraryview.h b/src/library/libraryview.h index a438cac5fab..6ebee3435cb 100644 --- a/src/library/libraryview.h +++ b/src/library/libraryview.h @@ -27,7 +27,8 @@ class LibraryView { virtual void slotAddToAutoDJTop() {}; virtual void slotAddToAutoDJReplace() {}; virtual void saveCurrentViewState(){}; - virtual bool restoreCurrentViewState() { + virtual bool restoreCurrentViewState(bool fromSearch = false) { + Q_UNUSED(fromSearch); return false; }; diff --git a/src/library/recording/dlgrecording.cpp b/src/library/recording/dlgrecording.cpp index f15d678f405..bed2d6f6a89 100644 --- a/src/library/recording/dlgrecording.cpp +++ b/src/library/recording/dlgrecording.cpp @@ -72,7 +72,7 @@ DlgRecording::DlgRecording( connect(&m_browseModel, &BrowseTableModel::restoreModelState, m_pTrackTableView, - &WTrackTableView::restoreCurrentViewState); + &WTrackTableView::slotRestoreCurrentViewState); QBoxLayout* box = qobject_cast(layout()); VERIFY_OR_DEBUG_ASSERT(box) { //Assumes the form layout is a QVBox/QHBoxLayout! diff --git a/src/library/recording/dlgrecording.h b/src/library/recording/dlgrecording.h index d1e29ad0081..50d88ad0337 100644 --- a/src/library/recording/dlgrecording.h +++ b/src/library/recording/dlgrecording.h @@ -36,8 +36,8 @@ class DlgRecording : public QWidget, public Ui::DlgRecording, public virtual Lib void saveCurrentViewState() override { m_pTrackTableView->saveCurrentViewState(); }; - bool restoreCurrentViewState() override { - return m_pTrackTableView->restoreCurrentViewState(); + bool restoreCurrentViewState(bool fromSearch = false) override { + return m_pTrackTableView->restoreCurrentViewState(fromSearch); }; public slots: diff --git a/src/library/searchqueryparser.cpp b/src/library/searchqueryparser.cpp index 85352b3ad3f..3f030bd9c65 100644 --- a/src/library/searchqueryparser.cpp +++ b/src/library/searchqueryparser.cpp @@ -1,11 +1,14 @@ #include "library/searchqueryparser.h" -#include "util/compatibility.h" +#include #include "track/keyutils.h" +#include "util/compatibility.h" constexpr char kNegatePrefix[] = "-"; constexpr char kFuzzyPrefix[] = "~"; +// see https://stackoverflow.com/questions/1310473/regex-matching-spaces-but-not-in-strings +const QRegExp kSplitRegexp = QRegExp(" (?=[^\"]*(\"[^\"]*\"[^\"]*)*$)"); SearchQueryParser::SearchQueryParser(TrackCollection* pTrackCollection) : m_pTrackCollection(pTrackCollection) { @@ -258,9 +261,50 @@ std::unique_ptr SearchQueryParser::parseQuery(const QString& query, } if (!query.isEmpty()) { + // FIXME(poelzi): refactor into a tokanizer that properly understands + // how search queries are build up and parses " correctly. QStringList tokens = query.split(" "); parseTokens(tokens, searchColumns, pQuery.get()); } return pQuery; } + +QStringList SearchQueryParser::splitQuery(const QString& query) { + QStringList splitted = query.split(kSplitRegexp); + return splitted; +} + +bool SearchQueryParser::isReducedTerm(const QString& original, const QString& changed) { + // seperate search query into tokens + QStringList oList = SearchQueryParser::splitQuery(original); + QStringList nList = SearchQueryParser::splitQuery(changed); + + // we sort the lists for length so the comperator will pop the longest match first + std::sort(oList.begin(), oList.end(), [=](const QString& v1, const QString& v2) { + return v1.length() > v2.length(); + }); + std::sort(nList.begin(), nList.end(), [=](const QString& v1, const QString& v2) { + return v1.length() > v2.length(); + }); + + for (int i = 0; i < oList.length(); i++) { + const QString& oword = oList.at(i); + for (int j = 0; j < nList.length(); j++) { + const QString& nword = nList.at(j); + if ((oword.startsWith("-") && oword.startsWith(nword)) || + (!nword.contains(":") && oword.contains(nword)) || + (nword.contains(":") && oword.startsWith(nword))) { + // we found a match and can remove the search term list + nList.removeAt(j); + break; + } + } + } + // if the new search query list contains no more terms, we have a reduced + // search term + if (nList.empty()) { + return true; + } + return false; +} diff --git a/src/library/searchqueryparser.h b/src/library/searchqueryparser.h index 9878149c0a7..dc99cc36f28 100644 --- a/src/library/searchqueryparser.h +++ b/src/library/searchqueryparser.h @@ -19,6 +19,10 @@ class SearchQueryParser { const QStringList& searchColumns, const QString& extraFilter) const; + static QStringList splitQuery(const QString& query); + /// splits the query into a list of terms + static bool isReducedTerm(const QString& original, const QString& changed); + /// checks if the changed search query is less specific then the original term private: void parseTokens(QStringList tokens, diff --git a/src/test/searchqueryparsertest.cpp b/src/test/searchqueryparsertest.cpp index 8eda8b2371e..a60e5bd70a8 100644 --- a/src/test/searchqueryparsertest.cpp +++ b/src/test/searchqueryparsertest.cpp @@ -990,3 +990,90 @@ TEST_F(SearchQueryParserTest, CrateFilterWithCrateFilterAndNegation){ ") AND (NOT (" + m_crateFilterQuery.arg(searchTermB) + "))"), qPrintable(pQueryB->toSql())); } + +TEST_F(SearchQueryParserTest, SplitTest) { + QStringList rv = SearchQueryParser::splitQuery(QString("a test b")); + QStringList ex = QStringList() << "a" + << "test" + << "b"; + qDebug() << rv << ex; + EXPECT_EQ(rv, ex); + + QStringList rv2 = SearchQueryParser::splitQuery(QString("a \"test ' b\" x")); + QStringList ex2 = QStringList() << "a" + << "\"test ' b\"" + << "x"; + qDebug() << rv2 << ex2; + EXPECT_EQ(rv2, ex2); + + QStringList rv3 = SearchQueryParser::splitQuery(QString("a x")); + QStringList ex3 = QStringList() << "a" + << "x"; + qDebug() << rv3 << ex3; + EXPECT_EQ(rv3, ex3); + + QStringList rv4 = SearchQueryParser::splitQuery( + QString("a crate:x title:\"S p A C e\" ~key:2m")); + QStringList ex4 = QStringList() << "a" + << "crate:x" + << "title:\"S p A C e\"" + << "~key:2m"; + qDebug() << rv4 << ex4; + EXPECT_EQ(rv4, ex4); +} + +TEST_F(SearchQueryParserTest, testIsReduced) { + // Generate a file name for the temporary file + EXPECT_TRUE(SearchQueryParser::isReducedTerm( + QStringLiteral("searchme"), + QStringLiteral("searchm"))); + + EXPECT_TRUE(SearchQueryParser::isReducedTerm( + QStringLiteral("A B C"), + QStringLiteral("A C"))); + + EXPECT_FALSE(SearchQueryParser::isReducedTerm( + QStringLiteral("A B C"), + QStringLiteral("A D C"))); + + EXPECT_TRUE(SearchQueryParser::isReducedTerm( + QStringLiteral("Abba1 Abba2 Abb"), + QStringLiteral("Abba1 Abba Abb"))); + + EXPECT_FALSE(SearchQueryParser::isReducedTerm( + QStringLiteral("Abba1 Abba2 Abb"), + QStringLiteral("Abba1 Aba Abb"))); + + EXPECT_TRUE(SearchQueryParser::isReducedTerm( + QStringLiteral("Abba1"), + QStringLiteral(""))); + + EXPECT_TRUE(SearchQueryParser::isReducedTerm( + QStringLiteral("Abba1"), + QStringLiteral("bba"))); + + EXPECT_TRUE(SearchQueryParser::isReducedTerm( + QStringLiteral("crate:abc"), + QStringLiteral("crate:ab"))); + + // FIXME(poelzi): the architecture of the query parser makes it + // very hard to correctly understand the search query since the + // interpretation and lexing is done in the same step. + // The query parser should be correctly split into lexing and query + // construction + // EXPECT_TRUE(StringUtils::isReducedTerm( + // QStringLiteral("crate:\"a b c\""), + // QStringLiteral("crate:\"a b\""))); + + EXPECT_FALSE(SearchQueryParser::isReducedTerm( + QStringLiteral("crate:\"a b c\""), + QStringLiteral("crate:\"a c\""))); + + EXPECT_FALSE(SearchQueryParser::isReducedTerm( + QStringLiteral("-crate:\"a b c\""), + QStringLiteral("crate:\"a b c\""))); + + EXPECT_FALSE(SearchQueryParser::isReducedTerm( + QStringLiteral("-crate:\"a b c\""), + QStringLiteral("crate:\"a b c\""))); +} diff --git a/src/widget/wlibrarytableview.cpp b/src/widget/wlibrarytableview.cpp index 248ff710f90..ac7d28a7ab5 100644 --- a/src/widget/wlibrarytableview.cpp +++ b/src/widget/wlibrarytableview.cpp @@ -103,7 +103,7 @@ void WLibraryTableView::moveSelection(int delta) { delta++; } } - // this wired combination works when switching between models + // this weired combination works when switching between models setCurrentIndex(newIndex); currentSelection->select(newIndex, QItemSelectionModel::ClearAndSelect); // why does scrollTo not work ? @@ -126,16 +126,7 @@ void WLibraryTableView::saveTrackModelState(const QAbstractItemModel* model, con // qDebug() << "save: saveTrackModelState:" << key << model << verticalScrollBar()->value() << " | "; state->verticalScrollPosition = verticalScrollBar()->value(); state->horizontalScrollPosition = horizontalScrollBar()->value(); - if (!selectionModel()->selectedIndexes().isEmpty()) { - state->selectionIndex = selectionModel()->selectedIndexes(); - } else { - state->selectionIndex = QModelIndexList(); - } - if (selectionModel()->currentIndex().isValid()) { - state->currentIndex = selectionModel()->currentIndex(); - } else { - state->currentIndex = QModelIndex(); - } + fillModelState(*state); m_modelStateCache.insert(key, state, 1); WTrackTableViewHeader* pHeader = qobject_cast(horizontalHeader()); @@ -145,16 +136,18 @@ void WLibraryTableView::saveTrackModelState(const QAbstractItemModel* model, con } bool WLibraryTableView::restoreTrackModelState( - const QAbstractItemModel* model, const QString& key) { + const QAbstractItemModel* model, const QString& key, bool fromSearch) { updateGeometries(); //qDebug() << "restoreTrackModelState:" << key << model << m_vModelState.keys(); if (model == nullptr) { return false; } - WTrackTableViewHeader* pHeader = qobject_cast(horizontalHeader()); - if (pHeader) { - pHeader->restoreHeaderState(); + if (!fromSearch) { + WTrackTableViewHeader* pHeader = qobject_cast(horizontalHeader()); + if (pHeader) { + pHeader->restoreHeaderState(); + } } ModelState* state = m_modelStateCache.take(key); @@ -165,19 +158,40 @@ bool WLibraryTableView::restoreTrackModelState( verticalScrollBar()->setValue(state->verticalScrollPosition); horizontalScrollBar()->setValue(state->horizontalScrollPosition); + applyModelState(*state); + // reinsert the state into the cache + m_modelStateCache.insert(key, state, 1); + return true; +} + +void WLibraryTableView::fillModelState(ModelState& state) { + if (!selectionModel()->selectedIndexes().isEmpty()) { + state.selectionIndex = selectionModel()->selectedIndexes(); + } else { + state.selectionIndex = QModelIndexList(); + } + if (selectionModel()->currentIndex().isValid()) { + state.currentIndex = selectionModel()->currentIndex(); + } else { + state.currentIndex = QModelIndex(); + } +} + +bool WLibraryTableView::applyModelState(ModelState& state) { auto selection = selectionModel(); selection->clearSelection(); - if (!state->selectionIndex.isEmpty()) { - for (auto index : qAsConst(state->selectionIndex)) { + if (!state.selectionIndex.isEmpty()) { + for (auto index : qAsConst(state.selectionIndex)) { selection->select(index, QItemSelectionModel::Select); } } - if (state->currentIndex.isValid()) { - selection->setCurrentIndex(state->currentIndex, QItemSelectionModel::NoUpdate); + if (state.currentIndex.isValid()) { + selection->setCurrentIndex(state.currentIndex, + QItemSelectionModel::Select | + QItemSelectionModel::SelectCurrent); + return true; } - // reinsert the state into the cache - m_modelStateCache.insert(key, state, 1); - return true; + return false; } void WLibraryTableView::setTrackTableFont(const QFont& font) { @@ -208,13 +222,13 @@ void WLibraryTableView::saveCurrentViewState() { saveTrackModelState(currentModel, key); } -bool WLibraryTableView::restoreCurrentViewState() { +bool WLibraryTableView::restoreCurrentViewState(bool fromSearch) { const QAbstractItemModel* currentModel = model(); QString key = getStateKey(); if (!currentModel || key.isEmpty()) { return false; } - return restoreTrackModelState(currentModel, key); + return restoreTrackModelState(currentModel, key, fromSearch); } void WLibraryTableView::focusInEvent(QFocusEvent* event) { diff --git a/src/widget/wlibrarytableview.h b/src/widget/wlibrarytableview.h index 14b8dcdc6ce..33ee13fe4e5 100644 --- a/src/widget/wlibrarytableview.h +++ b/src/widget/wlibrarytableview.h @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -15,11 +16,13 @@ class TrackModel; class WLibraryTableView : public QTableView, public virtual LibraryView { Q_OBJECT - + protected: struct ModelState { int horizontalScrollPosition; int verticalScrollPosition; QModelIndexList selectionIndex; + QList selectionTracks; + TrackId currentTrack; QModelIndex currentIndex; }; @@ -38,12 +41,14 @@ class WLibraryTableView : public QTableView, public virtual LibraryView { /// @brief restoreTrackModelState function finds scrollbar value associated with model /// by given key and restores it /// @param key unique for trackmodel - bool restoreTrackModelState(const QAbstractItemModel* model, const QString& key); + bool restoreTrackModelState(const QAbstractItemModel* model, + const QString& key, + bool fromSearch); /// @brief clears the state cache until it's size is = kClearModelStatesLowWatermark void saveCurrentViewState() override; /// @brief restores current view state. /// @return true if restore succeeded - bool restoreCurrentViewState() override; + bool restoreCurrentViewState(bool fromSearch = false) override; signals: void loadTrack(TrackPointer pTrack); @@ -60,6 +65,8 @@ class WLibraryTableView : public QTableView, public virtual LibraryView { protected: void focusInEvent(QFocusEvent* event) override; virtual QString getStateKey() const = 0; + virtual void fillModelState(ModelState& state); + virtual bool applyModelState(ModelState& state); private: const UserSettingsPointer m_pConfig; diff --git a/src/widget/wtracktableview.cpp b/src/widget/wtracktableview.cpp index 5be21721f69..5d7492e4c04 100644 --- a/src/widget/wtracktableview.cpp +++ b/src/widget/wtracktableview.cpp @@ -9,6 +9,7 @@ #include "control/controlobject.h" #include "library/dao/trackschema.h" #include "library/librarytablemodel.h" +#include "library/searchqueryparser.h" #include "library/trackcollection.h" #include "library/trackcollectionmanager.h" #include "mixer/playermanager.h" @@ -71,6 +72,10 @@ WTrackTableView::WTrackTableView(QWidget* parent, &QShortcut::activated, this, QOverload<>::of(&WTrackTableView::setFocus)); + connect(this, + &WTrackTableView::scrollToCurrent, + this, + &WTrackTableView::slotScrollToCurrent); } WTrackTableView::~WTrackTableView() { @@ -454,8 +459,25 @@ void WTrackTableView::onSearch(const QString& text) { TrackModel* trackModel = getTrackModel(); if (trackModel) { saveCurrentViewState(); + bool isLessSpecific = SearchQueryParser::isReducedTerm(trackModel->currentSearch(), text); + QList selectedTracks = getSelectedTrackIds(); + TrackId currentTrack = getCurrentTrackId(); trackModel->search(text); - restoreCurrentViewState(); + if (isLessSpecific) { + // if the user removed query terms, we try to select the same + // tracks as before + setCurrentTrackId(currentTrack); + setSelectedTracks(selectedTracks); + } else { + // the user created a more specific search query, try to restore a + // previous state + if (!restoreCurrentViewState(true)) { + // we found no saved state for this query, try to select the + // tracks last active, if they are part of the result set + setCurrentTrackId(currentTrack); + setSelectedTracks(selectedTracks); + } + } } } @@ -803,6 +825,26 @@ QList WTrackTableView::getSelectedTrackIds() const { return trackIds; } +TrackId WTrackTableView::getCurrentTrackId() const { + QItemSelectionModel* pSelectionModel = selectionModel(); + VERIFY_OR_DEBUG_ASSERT(pSelectionModel != nullptr) { + qWarning() << "No selected tracks available"; + return TrackId(); + } + + TrackModel* pTrackModel = getTrackModel(); + VERIFY_OR_DEBUG_ASSERT(pTrackModel != nullptr) { + qWarning() << "No selected tracks available"; + return TrackId(); + } + + const QModelIndex current = selectionModel()->currentIndex(); + if (current.isValid()) { + return pTrackModel->getTrackId(current); + } + return TrackId(); +} + void WTrackTableView::setSelectedTracks(const QList& trackIds) { QItemSelectionModel* pSelectionModel = selectionModel(); VERIFY_OR_DEBUG_ASSERT(pSelectionModel != nullptr) { @@ -826,6 +868,46 @@ void WTrackTableView::setSelectedTracks(const QList& trackIds) { } } +bool WTrackTableView::setCurrentTrackId(const TrackId& trackId) { + QItemSelectionModel* pSelectionModel = selectionModel(); + VERIFY_OR_DEBUG_ASSERT(pSelectionModel != nullptr) { + qWarning() << "No selected tracks available"; + return false; + } + + TrackModel* pTrackModel = getTrackModel(); + VERIFY_OR_DEBUG_ASSERT(pTrackModel != nullptr) { + qWarning() << "No selected tracks available"; + return false; + } + const QVector gts = pTrackModel->getTrackRows(trackId); + if (gts.empty()) { + return false; + } + + const QModelIndex idx = model()->index(gts[0], 0); + pSelectionModel->setCurrentIndex(idx, + QItemSelectionModel::SelectCurrent | QItemSelectionModel::Select); + selectRow(idx.row()); + // we need to emit the scrollTo event after the current loop finishes + // otherwise some early check in scrollTo will fail and no scroll happens + emit(scrollToCurrent()); + + return true; +} + +void WTrackTableView::slotScrollToCurrent() { + QItemSelectionModel* pSelectionModel = selectionModel(); + VERIFY_OR_DEBUG_ASSERT(pSelectionModel != nullptr) { + qWarning() << "No selected tracks available"; + return; + } + QModelIndex current = pSelectionModel->currentIndex(); + if (current.isValid()) { + scrollTo(current); + } +} + void WTrackTableView::addToAutoDJ(PlaylistDAO::AutoDJSendLoc loc) { auto* trackModel = getTrackModel(); if (!trackModel->hasCapabilities(TrackModel::TRACKMODELCAPS_ADDTOAUTODJ)) { @@ -1007,3 +1089,43 @@ QString WTrackTableView::getStateKey() const { void WTrackTableView::keyNotationChanged() { QWidget::update(); } + +void WTrackTableView::fillModelState(ModelState& state) { + auto selection = selectionModel(); + state.selectionIndex = QModelIndexList(); + if (!selection->selectedIndexes().isEmpty()) { + state.selectionTracks = getSelectedTrackIds(); + } else { + state.selectionTracks.clear(); + } + if (selection->currentIndex().isValid()) { + state.currentIndex = selection->currentIndex(); + state.currentTrack = getCurrentTrackId(); + } else { + state.currentIndex = QModelIndex(); + state.currentTrack = TrackId(); + } +} + +bool WTrackTableView::applyModelState(ModelState& state) { + auto selection = selectionModel(); + selection->clearSelection(); + if (!state.selectionTracks.isEmpty()) { + setSelectedTracks(state.selectionTracks); + } + if (state.currentTrack.isValid()) { + if (setCurrentTrackId(state.currentTrack)) { + // we found the track that was saved + qDebug() << "found and selected track"; + return true; + } + qDebug() << "track not found"; + } + if (state.currentIndex.isValid()) { + selection->setCurrentIndex(state.currentIndex, + QItemSelectionModel::SelectCurrent | + QItemSelectionModel::Select); + return true; + } + return false; +} diff --git a/src/widget/wtracktableview.h b/src/widget/wtracktableview.h index 30ff1f84f08..17267b5c926 100644 --- a/src/widget/wtracktableview.h +++ b/src/widget/wtracktableview.h @@ -42,6 +42,8 @@ class WTrackTableView : public WLibraryTableView { TrackModel::SortColumnId getColumnIdFromCurrentIndex() override; QList getSelectedTrackIds() const; void setSelectedTracks(const QList& tracks); + TrackId getCurrentTrackId() const; + bool setCurrentTrackId(const TrackId& trackId); double getBackgroundColorOpacity() const { return m_backgroundColorOpacity; @@ -79,9 +81,15 @@ class WTrackTableView : public WLibraryTableView { void slotSortingChanged(int headerSection, Qt::SortOrder order); void keyNotationChanged(); + void slotScrollToCurrent(); + + signals: + void scrollToCurrent(); protected: QString getStateKey() const override; + void fillModelState(ModelState& state) override; + bool applyModelState(ModelState& state) override; private: void addToAutoDJ(PlaylistDAO::AutoDJSendLoc loc);