Skip to content

Commit d36954a

Browse files
author
Milkii Brewster
committed
restore last selected library feature on restart
implements persistent library sidebar selection with debounced save strategy. saves selection and scroll position 3 seconds after last change to balance performance and crash resilience. - uses feature icon name + child data for robust cross-session matching - falls back to default selection if saved item not found - debounced timer prevents config spam during rapid navigation - scroll position persisted with selection state - uses VERIFY_OR_DEBUG_ASSERT for validation addresses ronso0 feedback: - 3 second debounce timing balances crash protection with performance - scroll position saved with selection (not just on shutdown) - proper error handling with debug assertions - code organization optimized config keys: - [Library] last_selected_feature: feature icon name - [Library] last_selected_child: child item data (playlist/crate id) - [Library] SidebarScrollPosition: vertical scroll position
1 parent a3ee814 commit d36954a

5 files changed

Lines changed: 200 additions & 19 deletions

File tree

src/library/library.cpp

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ Library::Library(
6969
m_pConfig(pConfig),
7070
m_pDbConnectionPool(std::move(pDbConnectionPool)),
7171
m_pTrackCollectionManager(pTrackCollectionManager),
72-
m_pSidebarModel(make_parented<SidebarModel>(this)),
72+
m_pSidebarModel(make_parented<SidebarModel>(pConfig, this)),
7373
m_pLibraryControl(make_parented<LibraryControl>(this)),
7474
m_pLibraryWidget(nullptr),
7575
m_pKeyNotation(std::make_unique<ControlObject>(
@@ -341,12 +341,16 @@ void Library::bindSearchboxWidget(WSearchLineEdit* pSearchboxWidget) {
341341
void Library::bindSidebarWidget(WLibrarySidebar* pSidebarWidget) {
342342
m_pLibraryControl->bindSidebarWidget(pSidebarWidget);
343343

344-
// Setup the sources view
344+
pSidebarWidget->setup(m_pConfig);
345345
pSidebarWidget->setModel(m_pSidebarModel);
346346
connect(m_pSidebarModel,
347347
&SidebarModel::selectIndex,
348348
pSidebarWidget,
349349
&WLibrarySidebar::selectIndex);
350+
connect(m_pSidebarModel,
351+
&SidebarModel::saveScrollPosition,
352+
pSidebarWidget,
353+
&WLibrarySidebar::saveScrollPosition);
350354
connect(pSidebarWidget,
351355
&WLibrarySidebar::pressed,
352356
m_pSidebarModel,
@@ -601,6 +605,8 @@ void Library::slotCreateCrate() {
601605
}
602606

603607
void Library::onSkinLoadFinished() {
608+
// Try to restore last selection, fallback to default if not found
609+
m_pSidebarModel->restoreLastSelection();
604610
// Enable the default selection when a new skin is loaded.
605611
m_pSidebarModel->activateDefaultSelection();
606612
}

src/library/sidebarmodel.cpp

Lines changed: 146 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,26 @@ constexpr bool kDebug = false;
2222

2323
} // anonymous namespace
2424

25-
SidebarModel::SidebarModel(
26-
QObject* parent)
25+
const ConfigKey kLastSelectedFeatureConfigKey = ConfigKey("[Library]", "last_selected_feature");
26+
const ConfigKey kLastSelectedChildConfigKey = ConfigKey("[Library]", "last_selected_child");
27+
28+
SidebarModel::SidebarModel(UserSettingsPointer pConfig, QObject* parent)
2729
: QAbstractItemModel(parent),
2830
m_iDefaultSelectedIndex(0),
29-
m_pressedUntilClickedTimer(new QTimer(this)) {
31+
m_pConfig(pConfig),
32+
m_pressedUntilClickedTimer(new QTimer(this)),
33+
m_saveTimer(new QTimer(this)) {
3034
m_pressedUntilClickedTimer->setSingleShot(true);
3135
connect(m_pressedUntilClickedTimer,
3236
&QTimer::timeout,
3337
this,
3438
&SidebarModel::slotPressedUntilClickedTimeout);
39+
40+
m_saveTimer->setSingleShot(true);
41+
connect(m_saveTimer,
42+
&QTimer::timeout,
43+
this,
44+
&SidebarModel::performSave);
3545
}
3646

3747
void SidebarModel::addLibraryFeature(LibraryFeature* pFeature) {
@@ -351,19 +361,35 @@ void SidebarModel::clicked(const QModelIndex& index) {
351361
// When triggered by a mouse event pressed() has been
352362
// invoked immediately before. That doesn't matter,
353363
// because we stop any running timer before handling
354-
// this event.
364+
// the click.
355365
stopPressedUntilClickedTimer();
356-
if (index.isValid()) {
357-
if (index.internalPointer() == this) {
358-
m_sFeatures[index.row()]->activate();
359-
} else {
360-
TreeItem* pTreeItem = static_cast<TreeItem*>(index.internalPointer());
361-
if (pTreeItem) {
362-
LibraryFeature* pFeature = pTreeItem->feature();
363-
DEBUG_ASSERT(pFeature);
364-
pFeature->activateChild(index);
365-
}
366-
}
366+
367+
if (!index.isValid()) {
368+
return;
369+
}
370+
371+
// Schedule save for this selection change
372+
scheduleSelectionSave(index);
373+
374+
TreeItem* pTreeItem = static_cast<TreeItem*>(index.internalPointer());
375+
VERIFY_OR_DEBUG_ASSERT(pTreeItem) {
376+
return;
377+
}
378+
379+
LibraryFeature* pFeature = pTreeItem->getFeature();
380+
VERIFY_OR_DEBUG_ASSERT(pFeature) {
381+
return;
382+
}
383+
384+
if (kDebug) {
385+
kLogger.debug() << "Activating feature:"
386+
<< pFeature->title();
387+
}
388+
389+
if (index.parent().isValid()) {
390+
pFeature->activateChild(index);
391+
} else {
392+
pFeature->activate();
367393
}
368394
}
369395

@@ -614,3 +640,108 @@ void SidebarModel::slotFeatureSelect(LibraryFeature* pFeature,
614640
}
615641
emit selectIndex(ind, scrollTo);
616642
}
643+
644+
void SidebarModel::scheduleSelectionSave(const QModelIndex& index) {
645+
if (!index.isValid()) {
646+
return;
647+
}
648+
m_pendingSelection = index;
649+
m_saveTimer->stop();
650+
m_saveTimer->start(3000);
651+
}
652+
653+
void SidebarModel::performSave() {
654+
if (m_pendingSelection.isValid()) {
655+
saveSelectionToConfig(m_pendingSelection);
656+
emit saveScrollPosition();
657+
}
658+
}
659+
660+
void SidebarModel::saveSelectionToConfig(const QModelIndex& index) {
661+
if (!index.isValid() || !m_pConfig) {
662+
return;
663+
}
664+
665+
TreeItem* pTreeItem = static_cast<TreeItem*>(index.internalPointer());
666+
VERIFY_OR_DEBUG_ASSERT(pTreeItem) {
667+
return;
668+
}
669+
670+
LibraryFeature* pFeature = pTreeItem->getFeature();
671+
VERIFY_OR_DEBUG_ASSERT(pFeature) {
672+
return;
673+
}
674+
675+
// Save feature icon name for robust matching
676+
QString featureIconName = pFeature->getIconName();
677+
m_pConfig->setValue(kLastSelectedFeatureConfigKey, featureIconName);
678+
679+
// Save child data if it's a child item
680+
if (index.parent().isValid()) {
681+
QVariant childData = index.data(DataRole);
682+
if (childData.isValid()) {
683+
m_pConfig->setValue(kLastSelectedChildConfigKey, childData);
684+
} else {
685+
m_pConfig->setValue(kLastSelectedChildConfigKey, QVariant());
686+
}
687+
} else {
688+
// Root feature selected - clear child data
689+
m_pConfig->setValue(kLastSelectedChildConfigKey, QVariant());
690+
}
691+
}
692+
693+
void SidebarModel::restoreLastSelection() {
694+
if (!m_pConfig) {
695+
return;
696+
}
697+
698+
QString savedFeatureIcon = m_pConfig->getValueString(kLastSelectedFeatureConfigKey);
699+
if (savedFeatureIcon.isEmpty()) {
700+
return;
701+
}
702+
703+
QVariant savedChildData = m_pConfig->getValue(kLastSelectedChildConfigKey);
704+
705+
// Find the feature by icon name
706+
LibraryFeature* pTargetFeature = nullptr;
707+
int featureIndex = -1;
708+
for (int i = 0; i < m_sFeatures.size(); ++i) {
709+
if (m_sFeatures[i]->getIconName() == savedFeatureIcon) {
710+
pTargetFeature = m_sFeatures[i];
711+
featureIndex = i;
712+
break;
713+
}
714+
}
715+
716+
if (!pTargetFeature) {
717+
return;
718+
}
719+
720+
QModelIndex targetIndex = index(featureIndex, 0);
721+
722+
// If we have child data, try to find the matching child
723+
if (savedChildData.isValid() && pTargetFeature->getChildModel()) {
724+
QAbstractItemModel* pChildModel = pTargetFeature->getChildModel();
725+
const QModelIndexList matches = pChildModel->match(
726+
pChildModel->index(0, 0),
727+
DataRole,
728+
savedChildData,
729+
1,
730+
Qt::MatchExactly);
731+
732+
VERIFY_OR_DEBUG_ASSERT(!matches.isEmpty() && matches.first().isValid()) {
733+
// Child not found, select feature root
734+
emit selectIndex(targetIndex, true);
735+
return;
736+
}
737+
738+
// Translate child index to sidebar index
739+
QModelIndex childIndex = matches.first();
740+
TreeItem* pTreeItem = static_cast<TreeItem*>(childIndex.internalPointer());
741+
if (pTreeItem) {
742+
targetIndex = createIndex(childIndex.row(), childIndex.column(), pTreeItem);
743+
}
744+
}
745+
746+
emit selectIndex(targetIndex, true);
747+
}

src/library/sidebarmodel.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
#include <QModelIndex>
66
#include <QVariant>
77

8+
#include "preferences/usersettings.h"
9+
810
class LibraryFeature;
911
class QTimer;
1012

@@ -22,13 +24,17 @@ class SidebarModel : public QAbstractItemModel {
2224
Q_ENUM(Roles);
2325

2426
explicit SidebarModel(
27+
UserSettingsPointer pConfig,
2528
QObject* parent = nullptr);
2629
~SidebarModel() override = default;
2730

2831
void addLibraryFeature(LibraryFeature* feature);
2932
QModelIndex getDefaultSelection();
3033
void setDefaultSelection(unsigned int index);
3134
void activateDefaultSelection();
35+
36+
void restoreLastSelection();
37+
void scheduleSelectionSave(const QModelIndex& index);
3238

3339
// Required for QAbstractItemModel
3440
QModelIndex index(int row, int column,
@@ -82,9 +88,11 @@ class SidebarModel : public QAbstractItemModel {
8288

8389
signals:
8490
void selectIndex(const QModelIndex& index, bool scrollTo);
91+
void saveScrollPosition();
8592

8693
private slots:
8794
void slotPressedUntilClickedTimeout();
95+
void performSave();
8896

8997
protected:
9098
QList<LibraryFeature*> m_sFeatures;
@@ -93,10 +101,14 @@ class SidebarModel : public QAbstractItemModel {
93101
QModelIndex translateSourceIndex(const QModelIndex& parent);
94102
QModelIndex translateIndex(const QModelIndex& index, const QAbstractItemModel* model);
95103
void featureRenamed(LibraryFeature*);
104+
void saveSelectionToConfig(const QModelIndex& index);
96105
unsigned int m_iDefaultSelectedIndex; /** Index of the item in the sidebar model to select at startup. */
97106

107+
UserSettingsPointer m_pConfig;
98108
QTimer* const m_pressedUntilClickedTimer;
109+
QTimer* m_saveTimer;
99110
QModelIndex m_pressedIndex;
111+
QModelIndex m_pendingSelection;
100112

101113
void startPressedUntilClickedTimer(const QModelIndex& pressedIndex);
102114
void stopPressedUntilClickedTimer();

src/widget/wlibrarysidebar.cpp

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
#include "widget/wlibrarysidebar.h"
22

33
#include <QHeaderView>
4+
#include <QScrollBar>
5+
#include <QTimer>
46
#include <QUrl>
57
#include <QtDebug>
68

79
#include "library/sidebarmodel.h"
810
#include "moc_wlibrarysidebar.cpp"
11+
#include "preferences/usersettings.h"
912
#include "util/defs.h"
1013
#include "util/dnd.h"
1114

@@ -16,10 +19,8 @@ WLibrarySidebar::WLibrarySidebar(QWidget* parent)
1619
WBaseWidget(this),
1720
m_lastDragMoveAccepted(false) {
1821
qRegisterMetaType<FocusWidget>("FocusWidget");
19-
//Set some properties
2022
setHeaderHidden(true);
2123
setSelectionMode(QAbstractItemView::SingleSelection);
22-
//Drag and drop setup
2324
setDragEnabled(false);
2425
setDragDropMode(QAbstractItemView::DragDrop);
2526
setDropIndicatorShown(true);
@@ -31,6 +32,31 @@ WLibrarySidebar::WLibrarySidebar(QWidget* parent)
3132
header()->setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel);
3233
}
3334

35+
WLibrarySidebar::~WLibrarySidebar() {
36+
saveScrollPosition();
37+
}
38+
39+
void WLibrarySidebar::setup(UserSettingsPointer pConfig) {
40+
m_pConfig = pConfig;
41+
}
42+
43+
void WLibrarySidebar::saveScrollPosition() {
44+
if (m_pConfig) {
45+
m_pConfig->setValue(
46+
ConfigKey(QStringLiteral("[Library]"), QStringLiteral("SidebarScrollPosition")),
47+
verticalScrollBar()->value());
48+
}
49+
}
50+
51+
void WLibrarySidebar::restoreScrollPosition() {
52+
if (m_pConfig) {
53+
int scrollPos = m_pConfig->getValue(
54+
ConfigKey(QStringLiteral("[Library]"), QStringLiteral("SidebarScrollPosition")),
55+
0);
56+
verticalScrollBar()->setValue(scrollPos);
57+
}
58+
}
59+
3460
void WLibrarySidebar::contextMenuEvent(QContextMenuEvent* pEvent) {
3561
// if (pEvent->state() & Qt::RightButton) { //Dis shiz don werk on windowze
3662
QModelIndex clickedIndex = indexAt(pEvent->pos());

src/widget/wlibrarysidebar.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include <QTreeView>
66

77
#include "library/library_decl.h"
8+
#include "preferences/usersettings.h"
89
#include "widget/wbasewidget.h"
910

1011
class LibraryFeature;
@@ -14,7 +15,9 @@ class WLibrarySidebar : public QTreeView, public WBaseWidget {
1415
Q_OBJECT
1516
public:
1617
explicit WLibrarySidebar(QWidget* parent = nullptr);
18+
~WLibrarySidebar() override;
1719

20+
void setup(UserSettingsPointer pConfig);
1821
void contextMenuEvent(QContextMenuEvent* pEvent) override;
1922
void dragMoveEvent(QDragMoveEvent* pEvent) override;
2023
void dragEnterEvent(QDragEnterEvent* pEvent) override;
@@ -34,6 +37,8 @@ class WLibrarySidebar : public QTreeView, public WBaseWidget {
3437
void selectIndex(const QModelIndex& index, bool scrollToIndex = true);
3538
void selectChildIndex(const QModelIndex&, bool selectItem = true);
3639
void slotSetFont(const QFont& font);
40+
void saveScrollPosition();
41+
void restoreScrollPosition();
3742

3843
signals:
3944
void rightClicked(const QPoint&, const QModelIndex&);
@@ -55,4 +60,5 @@ class WLibrarySidebar : public QTreeView, public WBaseWidget {
5560
QBasicTimer m_expandTimer;
5661
QModelIndex m_hoverIndex;
5762
bool m_lastDragMoveAccepted;
63+
UserSettingsPointer m_pConfig;
5864
};

0 commit comments

Comments
 (0)