Skip to content

Commit f99815a

Browse files
committed
Playlists: keep correct track selection (position) when sorting
1 parent e471992 commit f99815a

8 files changed

Lines changed: 133 additions & 14 deletions

src/library/baseexternalplaylistmodel.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ BaseExternalPlaylistModel::BaseExternalPlaylistModel(QObject* parent,
2626
m_playlistTracksTable(playlistTracksTable),
2727
m_trackSource(trackSource),
2828
m_currentPlaylistId(kInvalidPlaylistId) {
29+
setPositionColumn(PLAYLISTTRACKSTABLE_POSITION);
2930
}
3031

3132
BaseExternalPlaylistModel::~BaseExternalPlaylistModel() {
@@ -136,7 +137,7 @@ void BaseExternalPlaylistModel::setPlaylistById(int playlistId) {
136137
// The ordering of columns is relevant (see below)!
137138
auto playlistViewColumns = QStringList{
138139
QStringLiteral("track_id"),
139-
QStringLiteral("position"),
140+
PLAYLISTTRACKSTABLE_POSITION,
140141
QStringLiteral("'' AS ") + LIBRARYTABLE_PREVIEW};
141142
const auto queryString =
142143
QStringLiteral(

src/library/basesqltablemodel.cpp

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -164,15 +164,18 @@ void BaseSqlTableModel::clearRows() {
164164
beginRemoveRows(QModelIndex(), 0, m_rowInfo.size() - 1);
165165
m_rowInfo.clear();
166166
m_trackIdToRows.clear();
167+
m_trackPosToRow.clear();
167168
endRemoveRows();
168169
}
169170
DEBUG_ASSERT(m_rowInfo.isEmpty());
170171
DEBUG_ASSERT(m_trackIdToRows.isEmpty());
172+
DEBUG_ASSERT(m_trackPosToRow.isEmpty());
171173
}
172174

173175
void BaseSqlTableModel::replaceRows(
174176
QVector<RowInfo>&& rows,
175-
TrackId2Rows&& trackIdToRows) {
177+
TrackId2Rows&& trackIdToRows,
178+
TrackPos2Row&& trackPosToRows) {
176179
// NOTE(uklotzde): Use r-value references for parameters here, because
177180
// conceptually those parameters should replace the corresponding internal
178181
// member variables. Currently Qt4/5 doesn't support move semantics and
@@ -182,12 +185,16 @@ void BaseSqlTableModel::replaceRows(
182185
// its container types in the future this code becomes even more efficient.
183186
DEBUG_ASSERT(rows.empty() == trackIdToRows.empty());
184187
DEBUG_ASSERT(rows.size() >= trackIdToRows.size());
188+
if (!m_positionColumn.isEmpty()) {
189+
DEBUG_ASSERT(rows.size() == trackPosToRows.size());
190+
}
185191
if (rows.isEmpty()) {
186192
clearRows();
187193
} else {
188194
beginInsertRows(QModelIndex(), 0, rows.size() - 1);
189195
m_rowInfo = rows;
190196
m_trackIdToRows = trackIdToRows;
197+
m_trackPosToRow = trackPosToRows;
191198
endInsertRows();
192199
}
193200
}
@@ -246,6 +253,7 @@ void BaseSqlTableModel::select() {
246253
QVector<RowInfo> rowInfos;
247254
QSet<TrackId> trackIds;
248255
int idColumn = -1;
256+
int posColumn = -1;
249257
while (query.next()) {
250258
QSqlRecord sqlRecord = query.record();
251259

@@ -271,6 +279,18 @@ void BaseSqlTableModel::select() {
271279
rowInfo.trackId = trackId;
272280
// current position defines the ordering
273281
rowInfo.order = rowInfos.size();
282+
283+
if (posColumn < 0 && !m_positionColumn.isEmpty()) {
284+
posColumn = sqlRecord.indexOf(m_positionColumn);
285+
}
286+
if (posColumn >= 0) {
287+
bool ok = false;
288+
int pos = sqlRecord.value(posColumn).toInt(&ok);
289+
if (ok) {
290+
rowInfo.position = pos;
291+
}
292+
}
293+
274294
rowInfo.metadata.reserve(sqlRecord.count());
275295
for (int i = 0; i < m_tableColumns.size(); ++i) {
276296
rowInfo.metadata.push_back(sqlRecord.value(i));
@@ -330,10 +350,24 @@ void BaseSqlTableModel::select() {
330350
// number of total rows returned by the query
331351
DEBUG_ASSERT(trackIdToRows.size() <= rowInfos.size());
332352

353+
TrackPos2Row trackPosToRows;
354+
if (posColumn >= 0) {
355+
// We expect as many positions as we have rows
356+
trackPosToRows.reserve(rowInfos.size());
357+
for (int i = 0; i < rowInfos.size(); ++i) {
358+
const RowInfo& rowInfo = rowInfos[i];
359+
trackPosToRows.insert(rowInfo.position, i);
360+
}
361+
}
362+
if (!m_positionColumn.isEmpty()) {
363+
DEBUG_ASSERT(trackPosToRows.size() == rowInfos.size());
364+
}
365+
333366
// We're done! Issue the update signals and replace the main maps.
334367
replaceRows(
335368
std::move(rowInfos),
336-
std::move(trackIdToRows));
369+
std::move(trackIdToRows),
370+
std::move(trackPosToRows));
337371
// Both rowInfo and trackIdToRows (might) have been moved and
338372
// must not be used afterwards!
339373

src/library/basesqltablemodel.h

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@ class BaseSqlTableModel : public BaseTrackTableModel {
5656
const QVector<int> getTrackRows(TrackId trackId) const override {
5757
return m_trackIdToRows.value(trackId);
5858
}
59+
int getTrackRowByPosition(int position) const override {
60+
return m_trackPosToRow.value(position);
61+
}
5962

6063
void search(const QString& searchText, const QString& extraFilter = QString()) override;
6164
const QString currentSearch() const override;
@@ -92,6 +95,9 @@ class BaseSqlTableModel : public BaseTrackTableModel {
9295
QString trackIdColumn,
9396
QStringList tableColumns,
9497
QSharedPointer<BaseTrackCache> trackSource);
98+
void setPositionColumn(QString posColName) {
99+
m_positionColumn = std::move(posColName);
100+
}
95101
void initHeaderProperties() override;
96102
virtual void initSortColumnMapping();
97103

@@ -106,6 +112,7 @@ class BaseSqlTableModel : public BaseTrackTableModel {
106112
QString m_tableOrderBy;
107113
int m_columnIndexBySortColumnId[static_cast<int>(TrackModel::SortColumnId::IdMax)];
108114
QMap<int, TrackModel::SortColumnId> m_sortColumnIdByColumnIndex;
115+
QString m_positionColumn;
109116

110117
private slots:
111118
void tracksChanged(const QSet<TrackId>& trackIds);
@@ -122,6 +129,7 @@ class BaseSqlTableModel : public BaseTrackTableModel {
122129
struct RowInfo {
123130
TrackId trackId;
124131
int order;
132+
int position; // used by playlist models only
125133
QVector<QVariant> metadata;
126134

127135
bool operator<(const RowInfo& other) const {
@@ -136,11 +144,13 @@ class BaseSqlTableModel : public BaseTrackTableModel {
136144
};
137145

138146
typedef QHash<TrackId, QVector<int>> TrackId2Rows;
147+
typedef QHash<int, int> TrackPos2Row;
139148

140149
void clearRows();
141150
void replaceRows(
142151
QVector<RowInfo>&& rows,
143-
TrackId2Rows&& trackIdToRows);
152+
TrackId2Rows&& trackIdToRows,
153+
TrackPos2Row&& trackPosToRows);
144154

145155
QVector<RowInfo> m_rowInfo;
146156

@@ -151,6 +161,7 @@ class BaseSqlTableModel : public BaseTrackTableModel {
151161
bool m_bInitialized;
152162
QHash<TrackId, int> m_trackSortOrder;
153163
TrackId2Rows m_trackIdToRows;
164+
TrackPos2Row m_trackPosToRow;
154165
QString m_currentSearch;
155166
QString m_currentSearchFilter;
156167
QVector<QHash<int, QVariant>> m_headerInfo;

src/library/playlisttablemodel.cpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ PlaylistTableModel::PlaylistTableModel(QObject* parent,
2424
&PlaylistDAO::tracksChanged,
2525
this,
2626
&PlaylistTableModel::playlistsChanged);
27+
setPositionColumn(PLAYLISTTRACKSTABLE_POSITION);
2728
}
2829

2930
void PlaylistTableModel::initSortColumnMapping() {
@@ -323,6 +324,20 @@ void PlaylistTableModel::shuffleTracks(const QModelIndexList& shuffle, const QMo
323324
m_pTrackCollectionManager->internalCollection()->getPlaylistDAO().shuffleTracks(m_iPlaylistId, positions, allIds);
324325
}
325326

327+
const QList<int> PlaylistTableModel::getSelectedPositions(const QModelIndexList& indices) const {
328+
if (indices.isEmpty()) {
329+
return {};
330+
}
331+
QList<int> positions;
332+
// TODO Transpose m_trackPosToRow ?? Would it be faster?
333+
const int positionColumn = fieldIndex(ColumnCache::COLUMN_PLAYLISTTRACKSTABLE_POSITION);
334+
for (auto idx : indices) {
335+
int pos = idx.siblingAtColumn(positionColumn).data().toInt();
336+
positions.append(pos);
337+
}
338+
return positions;
339+
}
340+
326341
mixxx::Duration PlaylistTableModel::getTotalDuration(const QModelIndexList& indices) {
327342
if (indices.isEmpty()) {
328343
return mixxx::Duration::empty();

src/library/playlisttablemodel.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ class PlaylistTableModel final : public TrackSetTableModel {
3535

3636
/// Get the total duration of all tracks referenced by the given model indices
3737
mixxx::Duration getTotalDuration(const QModelIndexList& indices);
38+
const QList<int> getSelectedPositions(const QModelIndexList& indices) const override;
3839

3940
Capabilities getCapabilities() const final;
4041

src/library/trackmodel.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,15 @@ class TrackModel {
128128
// Gets the rows of the track in the current result set. Returns an
129129
// empty list if the track ID is not present in the result set.
130130
virtual const QVector<int> getTrackRows(TrackId trackId) const = 0;
131+
virtual int getTrackRowByPosition(int position) const {
132+
Q_UNUSED(position);
133+
return -1;
134+
}
135+
136+
virtual const QList<int> getSelectedPositions(const QModelIndexList& indices) const {
137+
Q_UNUSED(indices);
138+
return {};
139+
}
131140

132141
virtual void search(const QString& searchText, const QString& extraFilter=QString()) = 0;
133142
virtual const QString currentSearch() const = 0;

src/widget/wtracktableview.cpp

Lines changed: 57 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1307,7 +1307,19 @@ void WTrackTableView::doSortByColumn(int headerSection, Qt::SortOrder sortOrder)
13071307
}
13081308

13091309
// Save the selection
1310-
const QList<TrackId> selectedTrackIds = getSelectedTrackIds();
1310+
// If this is track model that may contain a track multiple times (a playlist),
1311+
// we store the positions in order to reselect only the current selection,
1312+
// not all occurrences of selected tracks.
1313+
QList<TrackId> selectedTrackIds;
1314+
QList<int> selectedTrackPositions;
1315+
bool usePositions = pTrackModel->hasCapabilities(TrackModel::Capability::Reorder);
1316+
if (usePositions) {
1317+
const QModelIndexList indices = getSelectedRows();
1318+
selectedTrackPositions = pTrackModel->getSelectedPositions(indices);
1319+
} else {
1320+
selectedTrackIds = getSelectedTrackIds();
1321+
}
1322+
13111323
int savedHScrollBarPos = horizontalScrollBar()->value();
13121324
// Save the column of focused table cell.
13131325
// The cell is not necessarily part of the selection, but even if it's
@@ -1320,13 +1332,56 @@ void WTrackTableView::doSortByColumn(int headerSection, Qt::SortOrder sortOrder)
13201332

13211333
sortByColumn(headerSection, sortOrder);
13221334

1323-
selectTracksById(selectedTrackIds, prevColumn);
1335+
if (usePositions) {
1336+
selectTracksByPosition(selectedTrackPositions, prevColumn);
1337+
} else {
1338+
selectTracksById(selectedTrackIds, prevColumn);
1339+
}
13241340

13251341
// This seems to be broken since at least Qt 5.12: no scrolling is issued
13261342
// scrollTo(first, QAbstractItemView::EnsureVisible);
13271343
horizontalScrollBar()->setValue(savedHScrollBarPos);
13281344
}
13291345

1346+
void WTrackTableView::selectTracksByPosition(const QList<int>& positions, int prevColumn) {
1347+
if (positions.isEmpty()) {
1348+
return;
1349+
}
1350+
TrackModel* pTrackModel = getTrackModel();
1351+
QItemSelectionModel* pSelectionModel = selectionModel();
1352+
pSelectionModel->reset(); // remove current selection
1353+
1354+
// Find previously selected tracks and store respective rows for reselection.
1355+
QList<int> rows;
1356+
for (int pos : positions) {
1357+
rows.append(pTrackModel->getTrackRowByPosition(pos));
1358+
}
1359+
1360+
// Select the first row of the previous selection.
1361+
// This scrolls to that row and with the leftmost cell being focused we have
1362+
// a starting point (currentIndex) for navigation with Up/Down keys.
1363+
// Replaces broken scrollTo() (see comment below)
1364+
if (!rows.isEmpty()) {
1365+
selectRow(rows.first());
1366+
}
1367+
1368+
// Refocus the cell in the column that was focused before sorting.
1369+
// With this, any Up/Down key press moves the selection and keeps the
1370+
// horizontal scrollbar position we will restore below.
1371+
QModelIndex restoreIndex = model()->index(currentIndex().row(), prevColumn);
1372+
if (restoreIndex.isValid()) {
1373+
setCurrentIndex(restoreIndex);
1374+
}
1375+
1376+
// Restore previous selection (doesn't affect focused cell).
1377+
for (int row : rows) {
1378+
pSelectionModel->select(model()->index(row, prevColumn),
1379+
QItemSelectionModel::Select | QItemSelectionModel::Rows);
1380+
}
1381+
}
1382+
1383+
// Don't use this on playlists since they may contain a TrackId multiple times.
1384+
// See doSortByColumn.
13301385
void WTrackTableView::selectTracksById(const QList<TrackId>& trackIds, int prevColum) {
13311386
TrackModel* pTrackModel = getTrackModel();
13321387
QAbstractItemModel* pItemModel = model();
@@ -1337,14 +1392,6 @@ void WTrackTableView::selectTracksById(const QList<TrackId>& trackIds, int prevC
13371392
// Find previously selected tracks and store respective rows for reselection.
13381393
QMap<int, int> selectedRows;
13391394
for (const auto& trackId : trackIds) {
1340-
// TODO(rryan) slowly fixing the issues with BaseSqlTableModel. This
1341-
// code is broken for playlists because it assumes each trackid is in
1342-
// the table once. This will erroneously select all instances of the
1343-
// track for playlists, but it works fine for every other view. The way
1344-
// to fix this that we should do is to delegate the selection saving to
1345-
// the TrackModel. This will allow the playlist table model to use the
1346-
// table index as the unique id instead of this code stupidly using
1347-
// trackid.
13481395
const auto rows = pTrackModel->getTrackRows(trackId);
13491396
for (int row : rows) {
13501397
// Restore sort order by rows, so the following commands will act as expected

src/widget/wtracktableview.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ class WTrackTableView : public WLibraryTableView {
5353
void copySelectedTracks();
5454
void pasteTracks(const QModelIndex& index);
5555
void selectTracksById(const QList<TrackId>& tracks, int prevColumn);
56+
void selectTracksByPosition(const QList<int>& positions, int prevColum);
5657

5758
double getBackgroundColorOpacity() const {
5859
return m_backgroundColorOpacity;

0 commit comments

Comments
 (0)