|
2 | 2 |
|
3 | 3 | #include <QTimer> |
4 | 4 | #include <QUrl> |
| 5 | +#include <functional> |
5 | 6 |
|
6 | 7 | #include "library/libraryfeature.h" |
7 | 8 | #include "library/treeitem.h" |
@@ -94,6 +95,17 @@ void SidebarModel::setDefaultSelection(unsigned int index) { |
94 | 95 | } |
95 | 96 |
|
96 | 97 | void SidebarModel::activateDefaultSelection() { |
| 98 | + // try to restore previously selected item |
| 99 | + QModelIndex savedIndex = restoreSavedSelection(); |
| 100 | + if (savedIndex.isValid()) { |
| 101 | + // enable smart scrolling - only if item below viewport |
| 102 | + emit selectIndex(savedIndex, true /* scrollTo */); |
| 103 | + // reuse clicked() method to activate the restored selection |
| 104 | + clicked(savedIndex); |
| 105 | + return; |
| 106 | + } |
| 107 | + |
| 108 | + // fallback to default selection if restoration failed |
97 | 109 | if (m_iDefaultSelectedIndex < |
98 | 110 | static_cast<unsigned int>(m_sFeatures.size())) { |
99 | 111 | emit selectIndex(getDefaultSelection(), true /* scrollTo */); |
@@ -354,6 +366,9 @@ void SidebarModel::clicked(const QModelIndex& index) { |
354 | 366 | // this event. |
355 | 367 | stopPressedUntilClickedTimer(); |
356 | 368 | if (index.isValid()) { |
| 369 | + // save the selection for restoration on restart |
| 370 | + saveCurrentSelection(index); |
| 371 | + |
357 | 372 | if (index.internalPointer() == this) { |
358 | 373 | m_sFeatures[index.row()]->activate(); |
359 | 374 | } else { |
@@ -520,7 +535,16 @@ QModelIndex SidebarModel::translateIndex( |
520 | 535 | void SidebarModel::slotDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight) { |
521 | 536 | // qDebug() << "slotDataChanged topLeft:" << topLeft << "bottomRight:" << bottomRight; |
522 | 537 | QModelIndex topLeftTranslated = translateSourceIndex(topLeft); |
523 | | - QModelIndex bottomRightTranslated = translateSourceIndex(bottomRight); |
| 538 | + QModelIndex bottomRightTranslated; |
| 539 | + |
| 540 | + // if bottomRight is invalid in the source, keep it invalid in the translation |
| 541 | + if (bottomRight.isValid()) { |
| 542 | + bottomRightTranslated = translateSourceIndex(bottomRight); |
| 543 | + } else { |
| 544 | + // per Qt convention, invalid bottomRight means same as topLeft |
| 545 | + bottomRightTranslated = topLeftTranslated; |
| 546 | + } |
| 547 | + |
524 | 548 | emit dataChanged(topLeftTranslated, bottomRightTranslated); |
525 | 549 | } |
526 | 550 |
|
@@ -610,3 +634,131 @@ void SidebarModel::slotFeatureSelect(LibraryFeature* pFeature, |
610 | 634 | } |
611 | 635 | emit selectIndex(ind, scrollTo); |
612 | 636 | } |
| 637 | + |
| 638 | +void SidebarModel::saveCurrentSelection(const QModelIndex& index) { |
| 639 | + if (!m_pConfig || !index.isValid()) { |
| 640 | + return; |
| 641 | + } |
| 642 | + |
| 643 | + QString featureName; |
| 644 | + QString childName; |
| 645 | + |
| 646 | + if (index.internalPointer() == this) { |
| 647 | + // root feature selected |
| 648 | + if (index.row() >= 0 && index.row() < m_sFeatures.size()) { |
| 649 | + featureName = m_sFeatures[index.row()]->iconName(); |
| 650 | + } |
| 651 | + } else { |
| 652 | + // child item selected |
| 653 | + TreeItem* pTreeItem = static_cast<TreeItem*>(index.internalPointer()); |
| 654 | + if (pTreeItem) { |
| 655 | + LibraryFeature* pFeature = pTreeItem->feature(); |
| 656 | + if (pFeature) { |
| 657 | + featureName = pFeature->iconName(); |
| 658 | + childName = pTreeItem->getData().toString(); |
| 659 | + } |
| 660 | + } |
| 661 | + } |
| 662 | + |
| 663 | + if (!featureName.isEmpty()) { |
| 664 | + m_pConfig->setValue( |
| 665 | + ConfigKey(QStringLiteral("[Library]"), QStringLiteral("LastSelectedFeature")), |
| 666 | + featureName); |
| 667 | + m_pConfig->setValue( |
| 668 | + ConfigKey(QStringLiteral("[Library]"), QStringLiteral("LastSelectedChild")), |
| 669 | + childName); |
| 670 | + } |
| 671 | +} |
| 672 | + |
| 673 | +QModelIndex SidebarModel::restoreSavedSelection() { |
| 674 | + if (!m_pConfig || m_sFeatures.isEmpty()) { |
| 675 | + return QModelIndex(); |
| 676 | + } |
| 677 | + |
| 678 | + const QString savedFeatureName = m_pConfig->getValueString( |
| 679 | + ConfigKey(QStringLiteral("[Library]"), QStringLiteral("LastSelectedFeature"))); |
| 680 | + |
| 681 | + if (savedFeatureName.isEmpty()) { |
| 682 | + return QModelIndex(); |
| 683 | + } |
| 684 | + |
| 685 | + int featureRow = -1; |
| 686 | + // find the feature by icon name (non-translated, programmatic identifier) |
| 687 | + for (int i = 0; i < m_sFeatures.size(); ++i) { |
| 688 | + if (m_sFeatures[i]->iconName() == savedFeatureName) { |
| 689 | + featureRow = i; |
| 690 | + break; |
| 691 | + } |
| 692 | + } |
| 693 | + |
| 694 | + const QString savedChildName = m_pConfig->getValueString( |
| 695 | + ConfigKey(QStringLiteral("[Library]"), QStringLiteral("LastSelectedChild"))); |
| 696 | + // if no child data, return the feature root. |
| 697 | + // returns invalid index if feature was not found. |
| 698 | + if (savedChildName.isEmpty()) { |
| 699 | + return index(featureRow, 0); |
| 700 | + } |
| 701 | + |
| 702 | + // feature not found, can't search for child |
| 703 | + if (featureRow < 0) { |
| 704 | + return {}; |
| 705 | + } |
| 706 | + |
| 707 | + // search for matching child by data using QAbstractItemModel::match() |
| 708 | + QAbstractItemModel* pChildModel = m_sFeatures[featureRow]->sidebarModel(); |
| 709 | + if (pChildModel && pChildModel->rowCount() > 0) { |
| 710 | + // the model items' data type may be int (playlist, crate) or |
| 711 | + // QString (Missing, Quick Links). |
| 712 | + // get the model's data type, then create the appropriate QVariant. |
| 713 | + // note: assumes the model uses only one data type! |
| 714 | + const QVariant dataVar = pChildModel->data( |
| 715 | + pChildModel->index(0, 0), TreeItemModel::kDataRole); |
| 716 | + auto dataType = dataVar.metaType(); |
| 717 | + QVariant childData; |
| 718 | + switch (dataType.id()) { |
| 719 | + case QMetaType::QString: { |
| 720 | + childData = QVariant::fromValue(savedChildName); |
| 721 | + break; |
| 722 | + } |
| 723 | + case QMetaType::Int: { |
| 724 | + bool convertedToInt = false; |
| 725 | + int intValue = savedChildName.toInt(&convertedToInt); |
| 726 | + if (convertedToInt) { |
| 727 | + childData = QVariant::fromValue(intValue); |
| 728 | + } else { |
| 729 | + qWarning() << "SidebarModel::restoreSavedSelection: could not " |
| 730 | + "convert stored child data to int"; |
| 731 | + } |
| 732 | + break; |
| 733 | + } |
| 734 | + default: |
| 735 | + qWarning() << "SidebarModel::restoreSavedSelection: " |
| 736 | + "model uses unexpected data type" |
| 737 | + << dataType.name(); |
| 738 | + qWarning() << "select feature root"; |
| 739 | + } |
| 740 | + |
| 741 | + if (!childData.isValid()) { |
| 742 | + return {}; |
| 743 | + } |
| 744 | + |
| 745 | + const QModelIndexList matches = pChildModel->match( |
| 746 | + pChildModel->index(0, 0), |
| 747 | + TreeItemModel::kDataRole, |
| 748 | + childData, |
| 749 | + 1, // stop at first match |
| 750 | + Qt::MatchExactly | Qt::MatchRecursive); |
| 751 | + |
| 752 | + if (!matches.isEmpty() && matches.first().isValid()) { |
| 753 | + // translate child model index to sidebar model index |
| 754 | + const QModelIndex childIndex = matches.first(); |
| 755 | + TreeItem* pTreeItem = static_cast<TreeItem*>(childIndex.internalPointer()); |
| 756 | + if (pTreeItem) { |
| 757 | + return createIndex(childIndex.row(), childIndex.column(), pTreeItem); |
| 758 | + } |
| 759 | + } |
| 760 | + } |
| 761 | + |
| 762 | + // child not found, return feature root or invalid index if feature was not found. |
| 763 | + return index(featureRow, 0); |
| 764 | +} |
0 commit comments