Merge pull request #11018 from kadet1090/rework-preferences-navigation

[Gui] Rework preferences navigation as TreeView - Part 1
This commit is contained in:
Chris Hennes
2023-11-20 10:44:47 -06:00
committed by GitHub
6 changed files with 593 additions and 366 deletions

View File

@@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>570</width>
<height>454</height>
<width>800</width>
<height>600</height>
</rect>
</property>
<property name="windowTitle">
@@ -20,80 +20,233 @@
<bool>true</bool>
</property>
<layout class="QGridLayout">
<property name="margin">
<number>9</number>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<property name="spacing">
<number>6</number>
</property>
<item row="0" column="0">
<layout class="QHBoxLayout">
<item row="1" column="0">
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="spacing">
<number>6</number>
</property>
<property name="margin">
<number>0</number>
</property>
<item>
<widget class="QListWidget" name="listBox">
<widget class="QFrame" name="sidebar">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Expanding">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>120</width>
<width>180</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>128</width>
<width>240</width>
<height>16777215</height>
</size>
</property>
<property name="autoFillBackground">
<bool>false</bool>
</property>
<property name="styleSheet">
<string notr="true">QFrame { background-color: rgba(0, 0, 0, 25); }
QFrame::item { padding: 6px 8px };</string>
</property>
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
<enum>QFrame::NoFrame</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Sunken</enum>
<enum>QFrame::Raised</enum>
</property>
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="iconSize">
<size>
<width>96</width>
<height>96</height>
</size>
</property>
<property name="spacing">
<number>12</number>
</property>
<property name="viewMode">
<enum>QListView::IconMode</enum>
<property name="lineWidth">
<number>0</number>
</property>
<layout class="QVBoxLayout" name="verticalLayout_4">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>12</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QTreeView" name="groupsTreeView">
<property name="contextMenuPolicy">
<enum>Qt::NoContextMenu</enum>
</property>
<property name="autoFillBackground">
<bool>false</bool>
</property>
<property name="styleSheet">
<string notr="true">background-color: transparent;</string>
</property>
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="lineWidth">
<number>0</number>
</property>
<property name="autoScroll">
<bool>false</bool>
</property>
<property name="iconSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<attribute name="headerVisible">
<bool>false</bool>
</attribute>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="actions">
<property name="spacing">
<number>8</number>
</property>
<property name="leftMargin">
<number>16</number>
</property>
<property name="topMargin">
<number>16</number>
</property>
<property name="rightMargin">
<number>16</number>
</property>
<property name="bottomMargin">
<number>16</number>
</property>
<item>
<widget class="QPushButton" name="buttonReset">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>1</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Reset</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QStackedWidget" name="tabWidgetStack"/>
<widget class="QFrame" name="verticalFrame">
<layout class="QVBoxLayout" name="content">
<property name="spacing">
<number>12</number>
</property>
<property name="leftMargin">
<number>16</number>
</property>
<property name="topMargin">
<number>16</number>
</property>
<property name="rightMargin">
<number>16</number>
</property>
<property name="bottomMargin">
<number>16</number>
</property>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<property name="sizeConstraint">
<enum>QLayout::SetDefaultConstraint</enum>
</property>
<item>
<widget class="QLabel" name="headerLabel">
<property name="font">
<font>
<pointsize>18</pointsize>
</font>
</property>
<property name="text">
<string>Header</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<widget class="QStackedWidget" name="groupWidgetStack">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>1</horstretch>
<verstretch>1</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="autoFillBackground">
<bool>false</bool>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Help|QDialogButtonBox::Ok</set>
</property>
<property name="centerButtons">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</item>
<item row="1" column="0">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
<set>QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Help|QDialogButtonBox::Ok|QDialogButtonBox::Reset</set>
</property>
</widget>
</item>
</layout>
</widget>
<layoutdefault spacing="6" margin="11"/>
<tabstops>
<tabstop>listBox</tabstop>
<tabstop>groupsTreeView</tabstop>
</tabstops>
<resources/>
<connections>

View File

@@ -56,7 +56,24 @@
using namespace Gui::Dialog;
const int DlgPreferencesImp::GroupNameRole = Qt::UserRole;
QWidget* PreferencesPageItem::getWidget() const {
return _widget;
}
void PreferencesPageItem::setWidget(QWidget* widget)
{
if (_widget) {
_widget->setProperty(PropertyName, QVariant::fromValue<PreferencesPageItem*>(nullptr));
}
_widget = widget;
_widget->setProperty(PropertyName, QVariant::fromValue(this));
}
Q_DECLARE_METATYPE(PreferencesPageItem*);
const int DlgPreferencesImp::GroupNameRole = Qt::UserRole + 1;
const int DlgPreferencesImp::PageNameRole = Qt::UserRole + 2;
/* TRANSLATOR Gui::Dialog::DlgPreferencesImp */
@@ -78,16 +95,28 @@ DlgPreferencesImp::DlgPreferencesImp(QWidget* parent, Qt::WindowFlags fl)
{
ui->setupUi(this);
QFontMetrics fm(font());
int length = QtTools::horizontalAdvance(fm, longestGroupName());
ui->listBox->setFixedWidth(Base::clamp<int>(length + 20, 108, 120));
ui->listBox->setGridSize(QSize(Base::clamp<int>(length + 20, 108, 120), 75));
// remove unused help button
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
setupConnections();
connect(ui->buttonBox,
&QDialogButtonBox::clicked,
this,
&DlgPreferencesImp::onButtonBoxClicked);
connect(ui->buttonBox,
&QDialogButtonBox::helpRequested,
getMainWindow(),
&MainWindow::whatsThis);
connect(ui->groupsTreeView,
&QTreeView::clicked,
this,
&DlgPreferencesImp::onPageSelected);
connect(ui->buttonReset,
&QPushButton::clicked,
this,
&DlgPreferencesImp::showResetOptions);
ui->groupsTreeView->setModel(&_model);
setupPages();
// Maintain a static pointer to the current active dialog (if there is one) so that
@@ -106,110 +135,127 @@ DlgPreferencesImp::~DlgPreferencesImp()
}
}
void DlgPreferencesImp::setupConnections()
{
connect(ui->buttonBox, &QDialogButtonBox::clicked,
this, &DlgPreferencesImp::onButtonBoxClicked);
connect(ui->buttonBox, &QDialogButtonBox::helpRequested,
getMainWindow(), &MainWindow::whatsThis);
connect(ui->listBox, &QListWidget::currentItemChanged,
this, &DlgPreferencesImp::changeGroup);
if (auto reset = ui->buttonBox->button(QDialogButtonBox::Reset)) {
QString text = reset->text();
text.append(QLatin1String("..."));
reset->setText(text);
}
}
void DlgPreferencesImp::setupPages()
{
// make sure that pages are ready to create
GetWidgetFactorySupplier();
for (const auto &group : _pages) {
QTabWidget* groupTab = createTabForGroup(group.first);
for (const auto &page : group.second) {
createPageInGroup(groupTab, page);
for (const auto &[name, pages] : _pages) {
auto* group = createGroup(name);
for (const auto &page : pages) {
createPageInGroup(group, page);
}
}
// show the first group
ui->listBox->setCurrentRow(0);
updatePageDependentLabels();
}
QString DlgPreferencesImp::longestGroupName() const
QPixmap DlgPreferencesImp::loadIconForGroup(const std::string &name) const
{
std::string name;
for (const auto &group : _pages) {
if (group.first.size() > name.size())
name = group.first;
std::string fileName = name;
// normalize file name
for (auto& ch : fileName) {
ch = ch == ' ' ? '_' : tolower(ch);
}
return QString::fromStdString(name);
}
/**
* Create the necessary widgets for a new group named \a groupName. Returns a
* pointer to the group's QTabWidget: that widget's lifetime is managed by the
* tabWidgetStack, do not manually deallocate.
*/
QTabWidget* DlgPreferencesImp::createTabForGroup(const std::string &groupName)
{
QString groupNameQString = QString::fromStdString(groupName);
std::string fileName = groupName;
QString tooltip;
getGroupData(groupName, fileName, tooltip);
auto tabWidget = new QTabWidget;
ui->tabWidgetStack->addWidget(tabWidget);
tabWidget->setProperty("GroupName", QVariant(groupNameQString));
auto item = new QListWidgetItem(ui->listBox);
item->setData(GroupNameRole, QVariant(groupNameQString));
item->setText(QObject::tr(groupNameQString.toLatin1()));
item->setToolTip(tooltip);
for (auto &ch : fileName) {
if (ch == ' ')
ch = '_';
else
ch = tolower(ch);
}
fileName = std::string("preferences-") + fileName;
QPixmap icon = Gui::BitmapFactory().pixmapFromSvg(fileName.c_str(), QSize(48, 48));
QPixmap icon = Gui::BitmapFactory().pixmapFromSvg(fileName.c_str(), QSize(24, 24));
if (icon.isNull()) {
icon = Gui::BitmapFactory().pixmap(fileName.c_str());
if (icon.isNull()) {
qWarning() << "No group icon found for " << fileName.c_str();
}
else if (icon.size() != QSize(48, 48)) {
icon = icon.scaled(48, 48, Qt::KeepAspectRatio, Qt::SmoothTransformation);
qWarning() << "Group icon for " << fileName.c_str() << " is not of size 48x48, so it was scaled";
else if (icon.size() != QSize(24, 24)) {
icon = icon.scaled(24, 24, Qt::KeepAspectRatio, Qt::SmoothTransformation);
}
}
item->setIcon(icon);
item->setTextAlignment(Qt::AlignHCenter);
item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
return tabWidget;
return icon;
}
/**
* Create a new preference page called \a pageName on the group tab \a tabWidget.
* Create the necessary widgets for a new group named \a groupName. Returns a
* pointer to the group's SettingsPageItem: that widget's lifetime is managed by the
* QStandardItemModel, do not manually deallocate.
*/
void DlgPreferencesImp::createPageInGroup(QTabWidget *tabWidget, const std::string &pageName)
PreferencesPageItem* DlgPreferencesImp::createGroup(const std::string &groupName)
{
QString groupNameQString = QString::fromStdString(groupName);
std::string iconName;
QString tooltip;
getGroupData(groupName, iconName, tooltip);
auto groupPages = new QStackedWidget;
groupPages->setProperty(GroupNameProperty, QVariant(groupNameQString));
ui->groupWidgetStack->addWidget(groupPages);
auto item = new PreferencesPageItem;
item->setData(QVariant(groupNameQString), GroupNameRole);
item->setText(QObject::tr(groupNameQString.toLatin1()));
item->setToolTip(tooltip);
item->setIcon(loadIconForGroup(iconName));
item->setTextAlignment(Qt::AlignLeft | Qt::AlignVCenter);
item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
item->setWidget(groupPages);
_model.invisibleRootItem()->appendRow(item);
return item;
}
PreferencePage* DlgPreferencesImp::createPreferencePage(const std::string& pageName, const std::string& groupName)
{
PreferencePage* page = WidgetFactory().createPreferencePage(pageName.c_str());
if (!page) {
return nullptr;
}
// settings layout already takes care for margins, we need to reset everything to 0
page->setContentsMargins(0, 0, 0, 0);
page->layout()->setContentsMargins(0, 0, 0, 0);
page->setProperty(GroupNameProperty, QString::fromStdString(groupName));
page->setProperty(PageNameProperty, QString::fromStdString(pageName));
return page;
}
/**
* Create a new preference page called \a pageName in the group \a groupItem.
*/
void DlgPreferencesImp::createPageInGroup(PreferencesPageItem *groupItem, const std::string &pageName)
{
try {
PreferencePage* page = WidgetFactory().createPreferencePage(pageName.c_str());
if (page) {
tabWidget->addTab(page, page->windowTitle());
page->loadSettings();
page->setProperty("GroupName", tabWidget->property("GroupName"));
page->setProperty("PageName", QVariant(QString::fromStdString(pageName)));
}
else {
PreferencePage* page = createPreferencePage(pageName, groupItem->data(GroupNameRole).toString().toStdString());
if (!page) {
Base::Console().Warning("%s is not a preference page\n", pageName.c_str());
return;
}
auto pageItem = new PreferencesPageItem;
pageItem->setText(page->windowTitle());
pageItem->setEditable(false);
pageItem->setData(groupItem->data(GroupNameRole), GroupNameRole);
pageItem->setData(QString::fromStdString(pageName), PageNameRole);
pageItem->setWidget(page);
groupItem->appendRow(pageItem);
page->loadSettings();
auto pages = qobject_cast<QStackedWidget*>(groupItem->getWidget());
pages->addWidget(page);
}
catch (const Base::Exception& e) {
Base::Console().Error("Base exception thrown for '%s'\n", pageName.c_str());
@@ -220,11 +266,11 @@ void DlgPreferencesImp::createPageInGroup(QTabWidget *tabWidget, const std::stri
}
}
void DlgPreferencesImp::changeGroup(QListWidgetItem *current, QListWidgetItem *previous)
void DlgPreferencesImp::updatePageDependentLabels()
{
if (!current)
current = previous;
ui->tabWidgetStack->setCurrentIndex(ui->listBox->row(current));
auto currentPageItem = getCurrentPage();
ui->headerLabel->setText(currentPageItem->text());
}
/**
@@ -236,7 +282,7 @@ void DlgPreferencesImp::changeGroup(QListWidgetItem *current, QListWidgetItem *p
*/
void DlgPreferencesImp::addPage(const std::string& className, const std::string& group)
{
std::list<TGroupPages>::iterator groupToAddTo = _pages.end();
auto groupToAddTo = _pages.end();
for (auto it = _pages.begin(); it != _pages.end(); ++it) {
if (it->first == group) {
groupToAddTo = it;
@@ -263,7 +309,7 @@ void DlgPreferencesImp::addPage(const std::string& className, const std::string&
void DlgPreferencesImp::removePage(const std::string& className, const std::string& group)
{
for (std::list<TGroupPages>::iterator it = _pages.begin(); it != _pages.end(); ++it) {
for (auto it = _pages.begin(); it != _pages.end(); ++it) {
if (it->first == group) {
if (className.empty()) {
_pages.erase(it);
@@ -274,8 +320,9 @@ void DlgPreferencesImp::removePage(const std::string& className, const std::stri
for (auto jt = p.begin(); jt != p.end(); ++jt) {
if (*jt == className) {
p.erase(jt);
if (p.empty())
if (p.empty()) {
_pages.erase(it);
}
return;
}
}
@@ -302,16 +349,19 @@ void DlgPreferencesImp::setGroupData(const std::string& name, const std::string&
void DlgPreferencesImp::getGroupData(const std::string& group, std::string& icon, QString& tip)
{
auto it = _groupMap.find(group);
if (it != _groupMap.end()) {
icon = it->second.iconName;
tip = it->second.tooltip;
}
if (icon.empty())
if (icon.empty()) {
icon = group;
}
if (tip.isEmpty())
if (tip.isEmpty()) {
tip = QObject::tr(group.c_str());
}
}
/**
@@ -319,16 +369,20 @@ void DlgPreferencesImp::getGroupData(const std::string& group, std::string& icon
*/
void DlgPreferencesImp::activateGroupPage(const QString& group, int index)
{
int ct = ui->listBox->count();
for (int i=0; i<ct; i++) {
QListWidgetItem* item = ui->listBox->item(i);
if (item->data(GroupNameRole).toString() == group) {
ui->listBox->setCurrentItem(item);
auto tabWidget = dynamic_cast<QTabWidget*>(ui->tabWidgetStack->widget(i));
if (tabWidget) {
tabWidget->setCurrentIndex(index);
break;
}
for (int i = 0; i < ui->groupWidgetStack->count(); i++) {
auto* pageStackWidget = qobject_cast<QStackedWidget*>(ui->groupWidgetStack->widget(i));
if (!pageStackWidget) {
continue;
}
if (pageStackWidget->property(GroupNameProperty).toString() == group) {
ui->groupWidgetStack->setCurrentWidget(pageStackWidget);
pageStackWidget->setCurrentIndex(index);
updatePageDependentLabels();
return;
}
}
}
@@ -338,20 +392,20 @@ void DlgPreferencesImp::activateGroupPage(const QString& group, int index)
*/
void DlgPreferencesImp::activeGroupPage(QString& group, int& index) const
{
int row = ui->listBox->currentRow();
auto item = ui->listBox->item(row);
auto tabWidget = dynamic_cast<QTabWidget*>(ui->tabWidgetStack->widget(row));
auto groupWidget = qobject_cast<QStackedWidget*>(ui->groupWidgetStack->currentWidget());
if (item && tabWidget) {
group = item->data(GroupNameRole).toString();
index = tabWidget->currentIndex();
if (groupWidget) {
group = groupWidget->property(GroupNameProperty).toString();
index = groupWidget->currentIndex();
}
}
void DlgPreferencesImp::accept()
{
this->invalidParameter = false;
applyChanges();
if (!this->invalidParameter) {
QDialog::accept();
restartIfRequired();
@@ -364,48 +418,48 @@ void DlgPreferencesImp::reject()
restartIfRequired();
}
void DlgPreferencesImp::onButtonBoxClicked(QAbstractButton* btn)
void DlgPreferencesImp::onButtonBoxClicked(QAbstractButton* btn)
{
if (ui->buttonBox->standardButton(btn) == QDialogButtonBox::Apply) {
applyChanges();
}
else if (ui->buttonBox->standardButton(btn) == QDialogButtonBox::Reset) {
showResetOptions();
}
}
void DlgPreferencesImp::showResetOptions()
{
// clang-format off
QMenu menu(this);
// Reset per tab
auto tabWidget = static_cast<QTabWidget*>(ui->tabWidgetStack->currentWidget());
int tabIndex = tabWidget->currentIndex();
QString tabText = tabWidget->tabText(tabIndex);
QAction* tabAction = menu.addAction(tr("Reset tab '%1'...").arg(tabText), this,
&DlgPreferencesImp::onButtonResetTabClicked);
tabAction->setToolTip(tr("Resets the user settings for the tab '%1'").arg(tabText));
auto currentPageItem = getCurrentPage();
auto currentGroupItem = static_cast<PreferencesPageItem*>(currentPageItem->parent());
auto pageText = currentPageItem->text();
auto groupText = currentGroupItem->text();
// Reset per page
QAction* pageAction = menu.addAction(tr("Reset page '%1'...").arg(pageText),
this,
[&] { restorePageDefaults(currentPageItem); });
pageAction->setToolTip(tr("Resets the user settings for the page '%1'").arg(pageText));
// Reset per group
int groupIndex = ui->listBox->currentRow();
QString group = ui->listBox->item(groupIndex)->text();
QAction* grpAction = menu.addAction(tr("Reset group '%1'...").arg(group), this,
&DlgPreferencesImp::onButtonResetGroupClicked);
grpAction->setToolTip(tr("Resets the user settings for the group '%1'").arg(group));
QAction* groupAction = menu.addAction(tr("Reset group '%1'...").arg(groupText),
this,
[&] { restorePageDefaults(static_cast<PreferencesPageItem*>(currentPageItem->parent())); });
groupAction->setToolTip(tr("Resets the user settings for the group '%1'").arg(groupText));
// Reset all
QAction* allAction = menu.addAction(tr("Reset all..."), this,
QAction* allAction = menu.addAction(tr("Reset all..."),
this,
&DlgPreferencesImp::restoreDefaults);
allAction->setToolTip(tr("Resets the user settings entirely"));
connect(&menu, &QMenu::hovered, [&menu](QAction* hover){
connect(&menu, &QMenu::hovered, [&menu](QAction* hover) {
QPoint pos = menu.pos();
pos.rx() += menu.width() + 10;
QToolTip::showText(pos, hover->toolTip());
});
menu.exec(QCursor::pos());
// clang-format on
}
void DlgPreferencesImp::restoreDefaults()
@@ -432,89 +486,6 @@ void DlgPreferencesImp::restoreDefaults()
reject();
}
}
void DlgPreferencesImp::onButtonResetTabClicked()
{
auto tabWidget = static_cast<QTabWidget*>(ui->tabWidgetStack->widget(ui->listBox->currentRow()));
QMessageBox box(this);
box.setIcon(QMessageBox::Question);
box.setWindowTitle(tr("Reset Tab Settings"));
box.setText(tr("All the settings for the tab '%1' will be deleted.").arg(tabWidget->tabText(tabWidget->currentIndex())));
box.setInformativeText(tr("Do you want to continue?"));
box.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
box.setDefaultButton(QMessageBox::No);
if (box.exec() == QMessageBox::Yes) {
int pageIndex = tabWidget->currentIndex();
QString pageText = tabWidget->tabText(pageIndex);
PreferencePage* page = qobject_cast<PreferencePage*>(tabWidget->widget(pageIndex));
restorePageDefaults(&page);
page->setProperty("GroupName", tabWidget->property("GroupName"));
tabWidget->removeTab(pageIndex);
tabWidget->insertTab(pageIndex, page, pageText);
tabWidget->setCurrentIndex(pageIndex);
applyChanges();
}
}
void DlgPreferencesImp::onButtonResetGroupClicked()
{
QMessageBox box(this);
box.setIcon(QMessageBox::Question);
box.setWindowTitle(tr("Reset Group Settings"));
box.setText(tr("All the settings for the group '%1' will be deleted.").arg(ui->listBox->currentItem()->text()));
box.setInformativeText(tr("Do you want to continue?"));
box.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
box.setDefaultButton(QMessageBox::No);
if (box.exec() == QMessageBox::Yes) {
auto tabWidget = static_cast<QTabWidget*>(ui->tabWidgetStack->widget(ui->listBox->currentRow()));
int pageIndex = tabWidget->currentIndex();
for (int i = 0; i < tabWidget->count(); i++) {
QString pageText = tabWidget->tabText(i);
PreferencePage* page = qobject_cast<PreferencePage*>(tabWidget->widget(i));
restorePageDefaults(&page);
page->setProperty("GroupName", tabWidget->property("GroupName"));
tabWidget->removeTab(i);
tabWidget->insertTab(i, page, pageText);
}
tabWidget->setCurrentIndex(pageIndex);
applyChanges();
}
}
void DlgPreferencesImp::restorePageDefaults(PreferencePage** page)
{
QList<QObject*> prefs = (*page)->findChildren<QObject*>();
for (const auto & pref : prefs) {
if (!pref->property("prefPath").isNull() && !pref->property("prefEntry").isNull()) {
std::string path = pref->property("prefPath").toString().toStdString();
std::string entry = pref->property("prefEntry").toString().toStdString();
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(std::string("User parameter:BaseApp/Preferences/" + path).c_str());
for (const auto & pn : hGrp->GetParameterNames(entry.c_str())){
hGrp->RemoveAttribute(pn.first, pn.second.c_str());
}
}
}
std::string pageName = (*page)->property("PageName").toString().toStdString();
(*page) = WidgetFactory().createPreferencePage(pageName.c_str());
(*page)->loadSettings();
(*page)->setProperty("PageName", QVariant(QString::fromStdString(pageName)));
}
/**
* If the dialog is currently showing and the static variable _pages changed, this function
* will rescan that list of pages and add any that are new to the current dialog. It will not
@@ -526,33 +497,39 @@ void DlgPreferencesImp::reloadPages()
// Make sure that pages are ready to create
GetWidgetFactorySupplier();
for (const auto &group : _pages) {
QString groupName = QString::fromStdString(group.first);
for (const auto &[ group, pages ] : _pages) {
QString groupName = QString::fromStdString(group);
// First, does this group already exist?
QTabWidget* tabWidget = nullptr;
for (int tabNumber = 0; tabNumber < ui->tabWidgetStack->count(); ++tabNumber) {
auto thisTabWidget = qobject_cast<QTabWidget*>(ui->tabWidgetStack->widget(tabNumber));
if (thisTabWidget->property("GroupName").toString() == groupName) {
tabWidget = thisTabWidget;
PreferencesPageItem* groupItem = nullptr;
auto root = _model.invisibleRootItem();
for (int i = 0; i < root->rowCount(); i++) {
auto currentGroupItem = static_cast<PreferencesPageItem*>(root->child(i));
auto currentGroupName = currentGroupItem->data(GroupNameRole).toString();
if (currentGroupName == groupName) {
groupItem = currentGroupItem;
break;
}
}
// This is a new tab that wasn't there when we started this instance of the dialog:
if (!tabWidget) {
tabWidget = createTabForGroup(group.first);
// This is a new group that wasn't there when we started this instance of the dialog:
if (!groupItem) {
groupItem = createGroup(group);
}
// Move on to the pages in the group to see if we need to add any
for (const auto& page : group.second) {
for (const auto& page : pages) {
// Does this page already exist?
QString pageName = QString::fromStdString(page);
bool pageExists = false;
for (int pageNumber = 0; pageNumber < tabWidget->count(); ++pageNumber) {
auto prefPage = qobject_cast<PreferencePage*>(tabWidget->widget(pageNumber));
if (prefPage && prefPage->property("PageName").toString() == pageName) {
for (int i = 0; i < groupItem->rowCount(); i++) {
auto currentPageItem = static_cast<PreferencesPageItem*>(groupItem->child(i));
if (currentPageItem->data(PageNameRole).toString() == pageName) {
pageExists = true;
break;
}
@@ -560,7 +537,7 @@ void DlgPreferencesImp::reloadPages()
// This is a new page that wasn't there when we started this instance of the dialog:
if (!pageExists) {
createPageInGroup(tabWidget, page);
createPageInGroup(groupItem, page);
}
}
}
@@ -573,36 +550,44 @@ void DlgPreferencesImp::applyChanges()
// call it to validate if user input is correct. If something fails (i.e.,
// not correct), shows a messageBox and set this->invalidParameter = true to
// cancel further operation in other methods (like in accept()).
try {
for (int i=0; i<ui->tabWidgetStack->count(); i++) {
auto tabWidget = static_cast<QTabWidget*>(ui->tabWidgetStack->widget(i));
for (int j=0; j<tabWidget->count(); j++) {
QWidget* page = tabWidget->widget(j);
int index = page->metaObject()->indexOfMethod("checkSettings()");
for (int i = 0; i < ui->groupWidgetStack->count(); i++) {
auto pagesStackWidget = qobject_cast<QStackedWidget*>(ui->groupWidgetStack->widget(i));
for (int j = 0; j < pagesStackWidget->count(); j++) {
QWidget* page = pagesStackWidget->widget(j);
int index = page->metaObject()->indexOfMethod("checkSettings()");
if (index >= 0) {
try {
if (index >= 0) {
page->qt_metacall(QMetaObject::InvokeMetaMethod, index, nullptr);
}
page->qt_metacall(QMetaObject::InvokeMetaMethod, index, nullptr);
}
catch (const Base::Exception& e) {
ui->listBox->setCurrentRow(i);
tabWidget->setCurrentIndex(j);
QMessageBox::warning(this, tr("Wrong parameter"), QString::fromLatin1(e.what()));
throw;
ui->groupWidgetStack->setCurrentIndex(i);
pagesStackWidget->setCurrentIndex(j);
QMessageBox::warning(this,
tr("Wrong parameter"),
QString::fromLatin1(e.what()));
this->invalidParameter = true;
// exit early due to found errors
return;
}
}
}
} catch (const Base::Exception&) {
this->invalidParameter = true;
return;
}
// If everything is ok (i.e., no validation problem), call method
// saveSettings() in every subpage (DlgSetting*) object.
for (int i=0; i<ui->tabWidgetStack->count(); i++) {
auto tabWidget = static_cast<QTabWidget*>(ui->tabWidgetStack->widget(i));
for (int j=0; j<tabWidget->count(); j++) {
auto page = qobject_cast<PreferencePage*>(tabWidget->widget(j));
for (int i = 0; i < ui->groupWidgetStack->count(); i++) {
auto pageStackWidget = qobject_cast<QStackedWidget*>(ui->groupWidgetStack->widget(i));
for (int j = 0; j < pageStackWidget->count(); j++) {
auto page = qobject_cast<PreferencePage*>(pageStackWidget->widget(j));
if (page) {
page->saveSettings();
restartRequired = restartRequired || page->isRestartRequired();
@@ -610,8 +595,10 @@ void DlgPreferencesImp::applyChanges()
}
}
bool saveParameter = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/General")->
GetBool("SaveUserParameter", true);
bool saveParameter = App::GetApplication()
.GetParameterGroupByPath("User parameter:BaseApp/Preferences/General")
->GetBool("SaveUserParameter", true);
if (saveParameter) {
ParameterManager* parmgr = App::GetApplication().GetParameterSet("User parameter");
parmgr->SaveDocument(App::Application::Config()["UserParameter"].c_str());
@@ -621,18 +608,19 @@ void DlgPreferencesImp::applyChanges()
void DlgPreferencesImp::restartIfRequired()
{
if (restartRequired) {
QMessageBox* restartBox = new QMessageBox();
restartBox->setIcon(QMessageBox::Warning);
restartBox->setWindowTitle(tr("Restart required"));
restartBox->setText(tr("You must restart FreeCAD for changes to take effect."));
restartBox->setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel);
restartBox->setDefaultButton(QMessageBox::Cancel);
auto okBtn = restartBox->button(QMessageBox::Ok);
auto cancelBtn = restartBox->button(QMessageBox::Cancel);
QMessageBox restartBox;
restartBox.setIcon(QMessageBox::Warning);
restartBox.setWindowTitle(tr("Restart required"));
restartBox.setText(tr("You must restart FreeCAD for changes to take effect."));
restartBox.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel);
restartBox.setDefaultButton(QMessageBox::Cancel);
auto okBtn = restartBox.button(QMessageBox::Ok);
auto cancelBtn = restartBox.button(QMessageBox::Cancel);
okBtn->setText(tr("Restart now"));
cancelBtn->setText(tr("Restart later"));
int exec = restartBox->exec();
int exec = restartBox.exec();
if (exec == QMessageBox::Ok) {
//restart FreeCAD after a delay to give time to this dialog to close
@@ -653,71 +641,54 @@ void DlgPreferencesImp::showEvent(QShowEvent* ev)
QDialog::showEvent(ev);
}
void DlgPreferencesImp::resizeEvent(QResizeEvent* ev)
QModelIndex findRootIndex(const QModelIndex& index)
{
if (canEmbedScrollArea) {
// embed the widget stack into a scroll area if the size is
// bigger than the available desktop
QRect rect = QApplication::primaryScreen()->availableGeometry();
int maxHeight = rect.height() - 60;
int maxWidth = rect.width();
if (height() > maxHeight || width() > maxWidth) {
canEmbedScrollArea = false;
ui->hboxLayout->removeWidget(ui->tabWidgetStack);
auto scrollArea = new QScrollArea(this);
scrollArea->setFrameShape(QFrame::NoFrame);
scrollArea->setWidgetResizable(true);
scrollArea->setWidget(ui->tabWidgetStack);
ui->hboxLayout->addWidget(scrollArea);
auto root = index;
// if possible the minimum width should so that it doesn't show
// a horizontal scroll bar.
auto bar = scrollArea->verticalScrollBar();
if (bar) {
int newWidth = width() + bar->width();
newWidth = std::min<int>(newWidth, maxWidth);
int newHeight = std::min<int>(height(), maxHeight);
QMetaObject::invokeMethod(this, "resizeWindow",
Qt::QueuedConnection,
Q_ARG(int, newWidth),
Q_ARG(int, newHeight));
}
QPoint center = rect.center();
move(center.x() - width() * 0.5, 10);
}
while (root.parent().isValid()) {
root = root.parent();
}
QDialog::resizeEvent(ev);
return root;
}
void DlgPreferencesImp::resizeWindow(int w, int h)
void DlgPreferencesImp::onPageSelected(const QModelIndex& index)
{
resize(w, h);
auto root = findRootIndex(index);
auto* groupItem = static_cast<PreferencesPageItem*>(_model.itemFromIndex(root));
auto* pagesStackWidget = static_cast<QStackedWidget*>(groupItem->getWidget());
ui->groupWidgetStack->setCurrentWidget(groupItem->getWidget());
if (index != root) {
pagesStackWidget->setCurrentIndex(index.row());
}
updatePageDependentLabels();
}
void DlgPreferencesImp::changeEvent(QEvent *e)
{
if (e->type() == QEvent::LanguageChange) {
ui->retranslateUi(this);
// update the widgets' tabs
for (int i=0; i<ui->tabWidgetStack->count(); i++) {
auto tabWidget = static_cast<QTabWidget*>(ui->tabWidgetStack->widget(i));
for (int j=0; j<tabWidget->count(); j++) {
QWidget* page = tabWidget->widget(j);
tabWidget->setTabText(j, page->windowTitle());
auto root = _model.invisibleRootItem();
for (int i = 0; i < root->rowCount(); i++) {
auto groupItem = static_cast<PreferencesPageItem*>(root->child(i));
auto groupName = groupItem->data(GroupNameRole).toString();
groupItem->setText(QObject::tr(groupName.toLatin1()));
for (int j = 0; j < groupItem->rowCount(); j++) {
auto pageModelItem = static_cast<PreferencesPageItem*>(groupItem->child(j));
auto pageModelWidget = static_cast<PreferencePage*>(pageModelItem->getWidget());
pageModelItem->setText(pageModelWidget->windowTitle());
}
}
// update the items' text
for (int i=0; i<ui->listBox->count(); i++) {
QListWidgetItem *item = ui->listBox->item(i);
QByteArray group = item->data(GroupNameRole).toByteArray();
item->setText(QObject::tr(group.constData()));
}
//resizes items list and buttons
QFontMetrics fm(font());
int length = QtTools::horizontalAdvance(fm, longestGroupName());
ui->listBox->setFixedWidth(Base::clamp<int>(length + 20, 108, 120));
ui->listBox->setGridSize(QSize(Base::clamp<int>(length + 20, 108, 120), 75));
updatePageDependentLabels();
} else {
QWidget::changeEvent(e);
}
@@ -725,15 +696,74 @@ void DlgPreferencesImp::changeEvent(QEvent *e)
void DlgPreferencesImp::reload()
{
for (int i = 0; i < ui->tabWidgetStack->count(); i++) {
auto tabWidget = static_cast<QTabWidget*>(ui->tabWidgetStack->widget(i));
for (int j = 0; j < tabWidget->count(); j++) {
auto page = qobject_cast<PreferencePage*>(tabWidget->widget(j));
if (page)
for (int i = 0; i < ui->groupWidgetStack->count(); i++) {
auto pageStackWidget = static_cast<QStackedWidget*>(ui->groupWidgetStack->widget(i));
for (int j = 0; j < pageStackWidget->count(); j++) {
auto page = qobject_cast<PreferencePage*>(pageStackWidget->widget(j));
if (page) {
page->loadSettings();
}
}
}
applyChanges();
}
void DlgPreferencesImp::restorePageDefaults(PreferencesPageItem* item)
{
if (item->hasChildren()) {
// If page has children iterate over them and restore each
for (int i = 0; i < item->rowCount(); i++) {
auto child = static_cast<PreferencesPageItem*>(item->child(i));
restorePageDefaults(child);
}
}
else {
auto* page = qobject_cast<PreferencePage*>(item->getWidget());
auto prefs = page->findChildren<QObject*>();
page->resetSettingsToDefaults();
std::string pageName = page->property(PageNameProperty).toString().toStdString();
std::string groupName = page->property(GroupNameProperty).toString().toStdString();
auto newPage = createPreferencePage(pageName, groupName);
newPage->loadSettings();
auto groupPageStack = qobject_cast<QStackedWidget*>(page->parentWidget());
auto replacedWidgetIndex = groupPageStack->indexOf(page);
auto currentWidgetIndex = groupPageStack->currentIndex();
groupPageStack->removeWidget(page);
groupPageStack->insertWidget(replacedWidgetIndex, newPage);
item->setWidget(newPage);
if (replacedWidgetIndex == currentWidgetIndex) {
groupPageStack->setCurrentIndex(currentWidgetIndex);
}
}
}
PreferencesPageItem* DlgPreferencesImp::getCurrentPage() const
{
auto groupPagesStack = qobject_cast<QStackedWidget*>(ui->groupWidgetStack->currentWidget());
if (!groupPagesStack) {
return nullptr;
}
auto pageWidget = qobject_cast<PreferencePage*>(groupPagesStack->currentWidget());
if (!pageWidget) {
return nullptr;
}
return pageWidget->property(PreferencesPageItem::PropertyName).value<PreferencesPageItem*>();
}
#include "moc_DlgPreferencesImp.cpp"

View File

@@ -27,6 +27,7 @@
#define GUI_DIALOG_DLGPREFERENCESIMP_H
#include <QDialog>
#include <QStandardItemModel>
#include <memory>
#include <FCGlobal.h>
@@ -34,11 +35,22 @@ class QAbstractButton;
class QListWidgetItem;
class QTabWidget;
namespace Gui {
namespace Dialog {
namespace Gui::Dialog {
class PreferencePage;
class Ui_DlgPreferences;
class PreferencesPageItem : public QStandardItem
{
public:
QWidget* getWidget() const;
void setWidget(QWidget* widget);
static constexpr char const* PropertyName = "SettingsPageItem";
private:
QWidget *_widget = nullptr;
};
/**
* This class implements a dialog containing several preference pages.
*
@@ -120,6 +132,8 @@ public:
static void getGroupData(const std::string& group, std::string& icon, QString& tip);
static void reloadSettings();
static PreferencePage* createPreferencePage(const std::string& pageName, const std::string& groupName);
explicit DlgPreferencesImp(QWidget* parent = nullptr, Qt::WindowFlags fl = Qt::WindowFlags());
~DlgPreferencesImp() override;
@@ -130,53 +144,63 @@ public:
void activeGroupPage(QString& group, int& index) const;
protected:
void setupConnections();
void changeEvent(QEvent *e) override;
void showEvent(QShowEvent*) override;
void resizeEvent(QResizeEvent*) override;
void onButtonResetTabClicked();
void onButtonResetGroupClicked();
protected Q_SLOTS:
void changeGroup(QListWidgetItem *current, QListWidgetItem *previous);
void onButtonBoxClicked(QAbstractButton*);
void resizeWindow(int w, int h);
void onPageSelected(const QModelIndex &index);
private:
/** @name for internal use only */
//@{
void setupPages();
void reloadPages();
QTabWidget* createTabForGroup(const std::string& groupName);
void createPageInGroup(QTabWidget* tabWidget, const std::string& pageName);
PreferencesPageItem* getCurrentPage() const;
PreferencesPageItem* createGroup(const std::string& groupName);
void createPageInGroup(PreferencesPageItem* item, const std::string& pageName);
void applyChanges();
void showResetOptions();
void restoreDefaults();
void restorePageDefaults(PreferencePage**);
QString longestGroupName() const;
void restorePageDefaults(PreferencesPageItem* item);
void restartIfRequired();
void updatePageDependentLabels();
QPixmap loadIconForGroup(const std::string& name) const;
//@}
private:
using TGroupPages = std::pair<std::string, std::list<std::string>>;
static std::list<TGroupPages> _pages; /**< Name of all registered preference pages */
QStandardItemModel _model;
struct Group {
std::string iconName;
QString tooltip;
};
static std::map<std::string, Group> _groupMap;
std::unique_ptr<Ui_DlgPreferences> ui;
bool invalidParameter;
bool canEmbedScrollArea;
bool restartRequired;
static const int GroupNameRole; /**< A name for our Qt::UserRole, used when storing user data in a list item */
/**< A name for our Qt::UserRole, used when storing user data in a list item */
static const int GroupNameRole;
static const int PageNameRole;
static constexpr char const* GroupNameProperty = "GroupName";
static constexpr char const* PageNameProperty = "PageName";
static DlgPreferencesImp* _activeDialog; /**< Defaults to the nullptr, points to the current instance if there is one */
};
} // namespace Dialog
} // namespace Gui
#endif // GUI_DIALOG_DLGPREFERENCESIMP_H

View File

@@ -815,7 +815,7 @@ void PrefFontBox::savePreferences()
QFont currFont = currentFont();
QString currName = currFont.family();
getWindowParameter()->SetASCII( entryName() , currName.toUtf8() );
getWindowParameter()->SetASCII(entryName(), currName.toUtf8());
}
#include "moc_PrefWidgets.cpp"

View File

@@ -28,6 +28,7 @@
#endif
#include <Base/Console.h>
#include <App/Application.h>
#include "PropertyPage.h"
#include "PrefWidgets.h"
@@ -209,6 +210,24 @@ void PreferenceUiForm::saveSettings()
savePrefWidgets<Gui::PrefQuantitySpinBox*>();
}
void PreferencePage::resetSettingsToDefaults()
{
auto prefs = this->findChildren<QObject*>();
for (const auto& pref : prefs) {
if (!pref->property("prefPath").isNull() && !pref->property("prefEntry").isNull()) {
std::string path = pref->property("prefPath").toString().toStdString();
std::string entry = pref->property("prefEntry").toString().toStdString();
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
std::string("User parameter:BaseApp/Preferences/" + path).c_str());
for (const auto& pn : hGrp->GetParameterNames(entry.c_str())) {
hGrp->RemoveAttribute(pn.first, pn.second.c_str());
}
}
}
}
// ----------------------------------------------------------------
/** Construction */

View File

@@ -79,6 +79,7 @@ public:
public Q_SLOTS:
virtual void loadSettings()=0;
virtual void saveSettings()=0;
virtual void resetSettingsToDefaults();
protected:
void changeEvent(QEvent* event) override = 0;