mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-10-27 10:01:12 -05:00
[Client] Settings: sub-page rows deep-linking into page sections (#7487)
Add AbstractSettingsPage::getSections built from each page's group boxes. Show each multi-group page's sections as child rows in the navigation tree. Section rows show the parent page and scroll to and highlight the group box. Start pages collapsed. Keep single-group pages flat. Reworked to stay in the enlarged modal instead of the settings tab. Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
This commit is contained in:
@@ -54,6 +54,7 @@
|
||||
#include <QStringLiteral>
|
||||
#include <QTreeWidget>
|
||||
#include <QVBoxLayout>
|
||||
#include <QVariant>
|
||||
#include <QWidget>
|
||||
#include <QtGlobal>
|
||||
#include <libcockatrice/settings/personal_settings.h>
|
||||
@@ -88,6 +89,33 @@ static QStringList pageIconResources()
|
||||
QStringLiteral("theme:config/sound"), QStringLiteral("theme:config/shorcuts")};
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
/// Item data role holding a sub-page row's section widget, stored as a WidgetId
|
||||
constexpr int SectionWidgetRole = Qt::UserRole + 1;
|
||||
using WidgetId = quintptr;
|
||||
|
||||
/**
|
||||
* @brief Packs a section widget for storage in a tree item's data role
|
||||
* @param widget The section's group box (owned by its settings page)
|
||||
* @return The widget as an opaque id
|
||||
*/
|
||||
WidgetId toWidgetId(QWidget *widget)
|
||||
{
|
||||
return reinterpret_cast<WidgetId>(widget);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Unpacks a section widget from a tree item's data role
|
||||
* @param id The stored id, or 0 for page rows without a section
|
||||
* @return The section widget, or nullptr when unset
|
||||
*/
|
||||
QWidget *fromWidgetId(WidgetId id)
|
||||
{
|
||||
return reinterpret_cast<QWidget *>(id);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
SettingsView::SettingsView(QWidget *parent) : QWidget(parent), currentTabIndex(0), searchActive(false)
|
||||
{
|
||||
setupUi();
|
||||
@@ -110,9 +138,6 @@ void SettingsView::setupUi()
|
||||
auto *searchLayout = new QHBoxLayout;
|
||||
searchLayout->addWidget(searchEdit);
|
||||
|
||||
// Navigation tree (built in setupNavTree)
|
||||
setupNavTree();
|
||||
|
||||
// Pages stacked widget
|
||||
pagesWidget = new QStackedWidget;
|
||||
|
||||
@@ -145,6 +170,9 @@ void SettingsView::setupUi()
|
||||
|
||||
Q_ASSERT(pages.size() == NumPages);
|
||||
|
||||
// Navigation tree (needs the pages for their sections)
|
||||
setupNavTree();
|
||||
|
||||
// Search results view (hidden by default)
|
||||
searchResultsView = new QListView;
|
||||
searchResultsView->setUniformItemSizes(false);
|
||||
@@ -246,6 +274,12 @@ void SettingsView::setupNavTree()
|
||||
item->setIcon(0, QPixmap(iconResources[page]));
|
||||
item->setData(0, Qt::UserRole, static_cast<int>(page));
|
||||
pageItems[page] = item;
|
||||
// Sub-page rows deep-link into the page's sections (scroll + highlight)
|
||||
for (const SettingsPageSection §ion : pages[page]->getSections()) {
|
||||
auto *sectionItem = new QTreeWidgetItem(item, {section.title});
|
||||
sectionItem->setData(0, Qt::UserRole, static_cast<int>(page));
|
||||
sectionItem->setData(0, SectionWidgetRole, QVariant::fromValue(toWidgetId(section.sectionWidget)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -258,7 +292,12 @@ void SettingsView::setupNavTree()
|
||||
}
|
||||
bool ok = false;
|
||||
int index = current->data(0, Qt::UserRole).toInt(&ok);
|
||||
if (ok) {
|
||||
if (!ok) {
|
||||
return;
|
||||
}
|
||||
if (QWidget *section = fromWidgetId(current->data(0, SectionWidgetRole).value<WidgetId>())) {
|
||||
navigateToSection(index, section);
|
||||
} else {
|
||||
onTabClicked(index);
|
||||
}
|
||||
});
|
||||
@@ -409,19 +448,44 @@ void SettingsView::navigateToSearchResult(const QModelIndex &index)
|
||||
|
||||
// Scroll to the widget, focus it, and flash to highlight it
|
||||
if (entry.widget) {
|
||||
QWidget *widget = entry.widget;
|
||||
while (widget) {
|
||||
if (auto *scrollArea = qobject_cast<QScrollArea *>(widget)) {
|
||||
scrollArea->ensureWidgetVisible(entry.widget);
|
||||
break;
|
||||
}
|
||||
widget = widget->parentWidget();
|
||||
}
|
||||
scrollToAndFlash(entry.widget);
|
||||
entry.widget->setFocus();
|
||||
flashWidget(entry.widget);
|
||||
}
|
||||
}
|
||||
|
||||
void SettingsView::navigateToSection(int pageIndex, QWidget *sectionWidget)
|
||||
{
|
||||
if (pageIndex < 0 || pageIndex >= pages.size() || !sectionWidget) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentTabIndex != pageIndex) {
|
||||
if (searchActive) {
|
||||
switchToTabMode();
|
||||
}
|
||||
setActiveTab(pageIndex);
|
||||
}
|
||||
|
||||
// Focus stays in the navigation tree; only scroll and highlight the section
|
||||
scrollToAndFlash(sectionWidget);
|
||||
}
|
||||
|
||||
void SettingsView::scrollToAndFlash(QWidget *target)
|
||||
{
|
||||
if (!target) {
|
||||
return;
|
||||
}
|
||||
QWidget *widget = target;
|
||||
while (widget) {
|
||||
if (auto *scrollArea = qobject_cast<QScrollArea *>(widget)) {
|
||||
scrollArea->ensureWidgetVisible(target);
|
||||
break;
|
||||
}
|
||||
widget = widget->parentWidget();
|
||||
}
|
||||
flashWidget(target);
|
||||
}
|
||||
|
||||
void SettingsView::setTab(int index)
|
||||
{
|
||||
if (index >= 0 && index < pageItems.size()) {
|
||||
@@ -515,6 +579,18 @@ void SettingsView::retranslateNavNames()
|
||||
pageItems[i]->setText(0, pageNames[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// Sub-page rows mirror the pages' group box titles (already retranslated by the pages)
|
||||
for (int i = 0; i < pageItems.size() && i < pages.size(); ++i) {
|
||||
if (!pageItems[i]) {
|
||||
continue;
|
||||
}
|
||||
const QList<SettingsPageSection> sections = pages[i]->getSections();
|
||||
Q_ASSERT(pageItems[i]->childCount() == sections.size());
|
||||
for (int c = 0; c < pageItems[i]->childCount() && c < sections.size(); ++c) {
|
||||
pageItems[i]->child(c)->setText(0, sections[c].title);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QStringList SettingsView::translatedGroupNames()
|
||||
|
||||
@@ -86,8 +86,10 @@ private:
|
||||
void switchToTabMode();
|
||||
void switchToSearchMode();
|
||||
void navigateToSearchResult(const QModelIndex &index);
|
||||
void navigateToSection(int pageIndex, QWidget *sectionWidget);
|
||||
void setActiveTab(int index);
|
||||
static void flashWidget(QWidget *widget);
|
||||
static void scrollToAndFlash(QWidget *target);
|
||||
static QStringList translatedPageNames();
|
||||
static QStringList translatedGroupNames();
|
||||
|
||||
|
||||
@@ -137,6 +137,30 @@ QList<SettingsSearchEntry> AbstractSettingsPage::getSearchEntries()
|
||||
return autoDetectSearchEntries(this, -1);
|
||||
}
|
||||
|
||||
QList<SettingsPageSection> AbstractSettingsPage::getSections()
|
||||
{
|
||||
QList<SettingsPageSection> sections;
|
||||
|
||||
// Walk the top-level layout in display order (creation order differs on some pages)
|
||||
if (QLayout *mainLayout = layout()) {
|
||||
for (int i = 0; i < mainLayout->count(); ++i) {
|
||||
QLayoutItem *item = mainLayout->itemAt(i);
|
||||
QWidget *widget = item ? item->widget() : nullptr;
|
||||
auto *groupBox = qobject_cast<QGroupBox *>(widget);
|
||||
if (!groupBox || groupBox->title().isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
sections.append({groupBox->title(), groupBox});
|
||||
}
|
||||
}
|
||||
|
||||
// A single group is the page itself; sub-page entries would only duplicate it
|
||||
if (sections.size() < 2) {
|
||||
sections.clear();
|
||||
}
|
||||
return sections;
|
||||
}
|
||||
|
||||
QList<SettingsSearchEntry> AbstractSettingsPage::autoDetectSearchEntries(QWidget *page, int pageIndex)
|
||||
{
|
||||
QList<SettingsSearchEntry> entries;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#define COCKATRICE_ABSTRACT_SETTINGS_PAGE_H
|
||||
|
||||
#include <QList>
|
||||
#include <QString>
|
||||
#include <QWidget>
|
||||
#include <qtmetamacros.h>
|
||||
|
||||
@@ -11,12 +12,28 @@
|
||||
|
||||
struct SettingsSearchEntry;
|
||||
|
||||
/**
|
||||
* @brief A navigable section of a settings page (one group box)
|
||||
*/
|
||||
struct SettingsPageSection
|
||||
{
|
||||
QString title; ///< Translated group title
|
||||
QWidget *sectionWidget = nullptr; ///< Group box to scroll to and highlight
|
||||
};
|
||||
|
||||
class AbstractSettingsPage : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
virtual void retranslateUi() = 0;
|
||||
virtual QList<SettingsSearchEntry> getSearchEntries();
|
||||
/**
|
||||
* @brief Sections of this page for sub-page navigation, in display order
|
||||
*
|
||||
* Scans the page's direct-child group boxes; pages with a single group
|
||||
* report no sections so the nav stays flat for them.
|
||||
*/
|
||||
QList<SettingsPageSection> getSections();
|
||||
|
||||
protected:
|
||||
static QList<SettingsSearchEntry> autoDetectSearchEntries(QWidget *page, int pageIndex);
|
||||
|
||||
Reference in New Issue
Block a user