You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
466 lines
14 KiB
466 lines
14 KiB
#include "settingsdialog.h"
|
|
#include "settingsdialog_p.h"
|
|
#include "ui_settingsdialog.h"
|
|
#include "widgetspresenter_p.h"
|
|
|
|
#include <QtCore/QMetaProperty>
|
|
#include <QtCore/QRegularExpression>
|
|
|
|
#include <QtMvvmCore/CoreApp>
|
|
|
|
#include <QtWidgets/QPushButton>
|
|
#include <QtWidgets/QScrollArea>
|
|
|
|
#include <QtMvvmCore/private/qtmvvm_logging_p.h>
|
|
|
|
using namespace QtMvvm;
|
|
|
|
SettingsDialog::SettingsDialog(ViewModel *viewModel, QWidget *parent) :
|
|
QDialog(parent),
|
|
d(new SettingsDialogPrivate(this, viewModel))
|
|
{
|
|
d->ui->setupUi(this);
|
|
connect(d->ui->buttonBox, &QDialogButtonBox::clicked,
|
|
d, &SettingsDialogPrivate::buttonBoxClicked);
|
|
connect(d->ui->filterLineEdit, &QLineEdit::textChanged,
|
|
d, &SettingsDialogPrivate::filterTextChanged);
|
|
|
|
if(parentWidget()) {
|
|
setWindowModality(Qt::WindowModal);
|
|
setWindowFlags(Qt::Sheet | Qt::WindowCloseButtonHint | Qt::WindowContextHelpButtonHint);
|
|
} else {
|
|
setWindowModality(Qt::ApplicationModal);
|
|
setWindowFlags(Qt::Dialog | Qt::WindowCloseButtonHint | Qt::WindowContextHelpButtonHint);
|
|
}
|
|
|
|
#ifdef Q_OS_OSX
|
|
auto font = d->ui->titleLabel->font();
|
|
font.setPointSize(16);
|
|
d->ui->titleLabel->setFont(font);
|
|
#endif
|
|
|
|
int listSpacing = d->calcSpacing(Qt::Vertical);
|
|
d->delegate = new CategoryItemDelegate(std::bind(&SettingsDialogPrivate::updateWidth, d, std::placeholders::_1),
|
|
d->ui->categoryListWidget->iconSize(),
|
|
qMax(qRound(listSpacing * (2./3.)), 1),
|
|
this);
|
|
d->ui->categoryListWidget->setItemDelegate(d->delegate);
|
|
d->ui->categoryListWidget->setSpacing(qMax(qRound(listSpacing / 3.), 1) - 1);
|
|
|
|
int spacing = d->calcSpacing(Qt::Horizontal);
|
|
d->ui->contentLayout->setSpacing(spacing);
|
|
d->ui->categoryLineSpacer->changeSize(spacing,
|
|
0,
|
|
QSizePolicy::Fixed,
|
|
QSizePolicy::Fixed);
|
|
|
|
connect(d->viewModel, &SettingsViewModel::beginLoadSetup,
|
|
d, &SettingsDialogPrivate::createUi);
|
|
}
|
|
|
|
SettingsDialog::~SettingsDialog() {}
|
|
|
|
QString SettingsDialog::labelFilterStyleSheet() const
|
|
{
|
|
return QStringLiteral("QLabel {"
|
|
" background-color: rgba(19,232,51,0.4);"
|
|
" border: 1px solid rgba(19,196,45,0.8);"
|
|
" border-radius: 4px;"
|
|
"}");
|
|
}
|
|
|
|
QUrl SettingsDialog::iconOverwrite() const
|
|
{
|
|
return QStringLiteral("qrc:/de/skycoder42/qtmvvm/icons/settings.ico");
|
|
}
|
|
|
|
// ------------- Private Implementation -------------
|
|
|
|
const QString SettingsDialogPrivate::TabContentId(QStringLiteral("__qtmvvm_settings_widgets_tab_content"));
|
|
|
|
SettingsDialogPrivate::SettingsDialogPrivate(SettingsDialog *q_ptr, ViewModel *viewModel) :
|
|
QObject(q_ptr),
|
|
q(q_ptr),
|
|
viewModel(static_cast<SettingsViewModel*>(viewModel)),
|
|
ui(new Ui::SettingsDialog()),
|
|
delegate(nullptr),
|
|
maxWidthBase(0),
|
|
entryMap(),
|
|
changedEntries()
|
|
{}
|
|
|
|
SettingsDialogPrivate::~SettingsDialogPrivate() {}
|
|
|
|
void SettingsDialogPrivate::createUi()
|
|
{
|
|
auto icoUrl = q->iconOverwrite();
|
|
if(icoUrl.isValid())
|
|
viewModel->settingsSetupLoader()->changeDefaultIcon(icoUrl);
|
|
auto setup = viewModel->loadSetup(QStringLiteral("widgets"));
|
|
|
|
ui->filterLineEdit->setVisible(setup.allowSearch);
|
|
ui->buttonBox->button(QDialogButtonBox::RestoreDefaults)->setVisible(setup.allowRestore && viewModel->canRestoreDefaults());
|
|
|
|
for(auto category : setup.categories)
|
|
createCategory(category);
|
|
|
|
resetListSize();
|
|
ui->categoryListWidget->setCurrentRow(0);
|
|
}
|
|
|
|
void SettingsDialogPrivate::createCategory(const SettingsElements::Category &category)
|
|
{
|
|
auto item = new QListWidgetItem();
|
|
item->setText(category.title);
|
|
item->setIcon(loadIcon(category.icon));
|
|
item->setToolTip(category.tooltip.isNull() ? category.title : category.tooltip);
|
|
item->setWhatsThis(item->toolTip());
|
|
auto tab = new QTabWidget(ui->contentStackWidget);
|
|
tab->setTabBarAutoHide(true);
|
|
|
|
ui->contentStackWidget->addWidget(tab);
|
|
ui->categoryListWidget->addItem(item);
|
|
|
|
for(auto section : category.sections)
|
|
createSection(section, tab);
|
|
}
|
|
|
|
void SettingsDialogPrivate::createSection(const SettingsElements::Section §ion, QTabWidget *tabWidget)
|
|
{
|
|
auto scrollArea = new QScrollArea(tabWidget);
|
|
scrollArea->setWidgetResizable(true);
|
|
scrollArea->setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContents);
|
|
scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
|
scrollArea->setAutoFillBackground(true);
|
|
auto pal = scrollArea->palette();
|
|
pal.setColor(QPalette::Window, tabWidget->palette().color(QPalette::Base));
|
|
scrollArea->setPalette(pal);
|
|
scrollArea->setFrameShape(QFrame::NoFrame);
|
|
|
|
auto scrollContent = new QWidget(scrollArea);
|
|
scrollContent->setObjectName(TabContentId);
|
|
auto layout = new QFormLayout(scrollContent);
|
|
scrollContent->setLayout(layout);
|
|
scrollArea->setWidget(scrollContent);
|
|
|
|
auto index = tabWidget->addTab(scrollArea, loadIcon(section.icon), section.title);
|
|
auto tooltip = section.tooltip.isNull() ? section.title : section.tooltip;
|
|
tabWidget->tabBar()->setTabToolTip(index, tooltip);
|
|
tabWidget->tabBar()->setTabWhatsThis(index, tooltip);
|
|
|
|
for(auto group : section.groups)
|
|
createGroup(group, scrollContent, layout);
|
|
}
|
|
|
|
void SettingsDialogPrivate::createGroup(const SettingsElements::Group &group, QWidget *contentWidget, QFormLayout *layout)
|
|
{
|
|
QWidget *sectionWidget = nullptr;
|
|
QFormLayout *sectionLayout = nullptr;
|
|
if(group.title.isNull()) {
|
|
sectionWidget = contentWidget;
|
|
sectionLayout = layout;
|
|
} else {
|
|
auto groupBox = new QGroupBox(group.title, contentWidget);
|
|
groupBox->setToolTip(group.tooltip.isNull() ? group.title : group.tooltip);
|
|
layout->addRow(groupBox);
|
|
sectionWidget = groupBox;
|
|
sectionLayout = new QFormLayout(groupBox);
|
|
groupBox->setLayout(sectionLayout);
|
|
}
|
|
|
|
for(auto entry : group.entries)
|
|
createEntry(entry, sectionWidget, sectionLayout);
|
|
}
|
|
|
|
void SettingsDialogPrivate::createEntry(const SettingsElements::Entry &entry, QWidget *sectionWidget, QFormLayout *layout)
|
|
{
|
|
QWidget *content = nullptr;
|
|
if(entry.type == "action") {
|
|
auto btn = new QPushButton(sectionWidget);
|
|
for(auto it = entry.properties.constBegin(); it != entry.properties.constEnd(); it++)
|
|
btn->setProperty(qUtf8Printable(it.key()), it.value());
|
|
auto key = entry.key;
|
|
auto args = entry.properties.value(QStringLiteral("args")).toMap();
|
|
connect(btn, &QPushButton::clicked, this, [this, key, args](){
|
|
viewModel->callAction(key, args);
|
|
});
|
|
content = btn;
|
|
} else {
|
|
try {
|
|
auto widgetFactory = WidgetsPresenterPrivate::currentPresenter()->inputWidgetFactory();
|
|
content = widgetFactory->createInput(entry.type, sectionWidget, entry.properties);
|
|
auto property = content->metaObject()->userProperty();
|
|
property.write(content, viewModel->loadValue(entry.key, entry.defaultValue));
|
|
if(property.hasNotifySignal()) {
|
|
auto changedSlot = metaObject()->method(metaObject()->indexOfSlot("propertyChanged()"));
|
|
connect(content, property.notifySignal(),
|
|
this, changedSlot);
|
|
} else
|
|
changedEntries.insert(content);
|
|
|
|
entryMap.insert(content, {entry, property});
|
|
} catch (PresenterException &e) {
|
|
logWarning() << "Failed to create settings widget for key"
|
|
<< entry.key
|
|
<< "with error:" << e.what();
|
|
content = new QLabel(tr("<i>Failed to load edit view!<i>"), sectionWidget);
|
|
auto pal = content->palette();
|
|
pal.setColor(QPalette::WindowText, Qt::darkRed);
|
|
content->setPalette(pal);
|
|
}
|
|
}
|
|
|
|
auto label = new QLabel(entry.title + tr(":"), sectionWidget);
|
|
label->setBuddy(content);
|
|
label->setToolTip(entry.tooltip.isNull() ? entry.title : entry.tooltip);
|
|
label->setWhatsThis(label->toolTip());
|
|
if(content->toolTip().isNull())
|
|
content->setToolTip(label->toolTip());
|
|
if(content->whatsThis().isNull())
|
|
content->setWhatsThis(label->toolTip());
|
|
|
|
layout->addRow(label, content);
|
|
}
|
|
|
|
void SettingsDialogPrivate::saveValues()
|
|
{
|
|
for(auto it = changedEntries.begin(); it != changedEntries.end();) {
|
|
auto widget = *it;
|
|
auto info = entryMap.value(widget);
|
|
viewModel->saveValue(info.first.key, info.second.read(widget));
|
|
if(info.second.hasNotifySignal())
|
|
it = changedEntries.erase(it);
|
|
else
|
|
it++;
|
|
}
|
|
}
|
|
|
|
void SettingsDialogPrivate::restoreValues()
|
|
{
|
|
for(auto info : entryMap)
|
|
viewModel->resetValue(info.first.key);
|
|
}
|
|
|
|
int SettingsDialogPrivate::calcSpacing(Qt::Orientation orientation)
|
|
{
|
|
auto baseSize = q->style()->pixelMetric(orientation == Qt::Horizontal ?
|
|
QStyle::PM_LayoutHorizontalSpacing :
|
|
QStyle::PM_LayoutVerticalSpacing);
|
|
if(baseSize < 0)
|
|
baseSize = q->style()->layoutSpacing(QSizePolicy::DefaultType, QSizePolicy::DefaultType, orientation);
|
|
if(baseSize < 0) {
|
|
#ifdef Q_OS_OSX
|
|
baseSize = 10;
|
|
#else
|
|
baseSize = 6;
|
|
#endif
|
|
}
|
|
|
|
return baseSize;
|
|
}
|
|
|
|
void SettingsDialogPrivate::updateWidth(int width)
|
|
{
|
|
if(width > maxWidthBase) {
|
|
maxWidthBase = width;
|
|
QStyle *style = ui->categoryListWidget->style();
|
|
width += style->pixelMetric(QStyle::PM_ScrollBarExtent);
|
|
width += style->pixelMetric(QStyle::PM_LayoutHorizontalSpacing);
|
|
ui->categoryListWidget->setFixedWidth(width);
|
|
}
|
|
}
|
|
void SettingsDialogPrivate::resetListSize()
|
|
{
|
|
int max = ui->categoryListWidget->count();
|
|
if(max <= 1) {
|
|
ui->categoryContentWidget->hide();
|
|
q->resize(q->width() - ui->categoryContentWidget->sizeHint().width(), q->height());
|
|
} else {
|
|
auto width = ui->categoryListWidget->sizeHint().width();
|
|
ui->categoryListWidget->setFixedWidth(width);
|
|
maxWidthBase = width;
|
|
}
|
|
}
|
|
|
|
QIcon SettingsDialogPrivate::loadIcon(const QUrl &icon)
|
|
{
|
|
if(icon.scheme() == QStringLiteral("qrc"))
|
|
return QIcon(QLatin1Char(':') + icon.path());
|
|
else
|
|
return QIcon(icon.toLocalFile());
|
|
}
|
|
|
|
void SettingsDialogPrivate::searchInDialog(const QRegularExpression ®ex)
|
|
{
|
|
for(int i = 0, max = ui->categoryListWidget->count(); i < max; ++i) {
|
|
auto item = ui->categoryListWidget->item(i);
|
|
auto tab = qobject_cast<QTabWidget*>(ui->contentStackWidget->widget(i));
|
|
if(!tab)
|
|
continue;
|
|
|
|
if(searchInCategory(regex, tab) ||
|
|
regex.match(item->text()).hasMatch()) {
|
|
item->setHidden(false);
|
|
if(ui->categoryListWidget->currentRow() == -1)
|
|
ui->categoryListWidget->setCurrentRow(i);
|
|
} else {
|
|
item->setHidden(true);
|
|
if(ui->categoryListWidget->currentRow() == i) {
|
|
auto found = false;
|
|
for(int j = 0; j < max; j++) {
|
|
if(!ui->categoryListWidget->item(j)->isHidden()){
|
|
ui->categoryListWidget->setCurrentRow(j);
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if(!found) {
|
|
ui->categoryListWidget->setCurrentRow(-1);
|
|
ui->contentStackWidget->setCurrentIndex(max);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool SettingsDialogPrivate::searchInCategory(const QRegularExpression ®ex, QTabWidget *tab)
|
|
{
|
|
auto someFound = false;
|
|
for(int i = 0, max = tab->count(); i < max; ++i) {
|
|
if(searchInSection(regex, tab->widget(i)->findChild<QWidget*>(TabContentId)) ||
|
|
regex.match(tab->tabText(i)).hasMatch()){
|
|
tab->setTabEnabled(i, true);
|
|
someFound = true;
|
|
} else
|
|
tab->setTabEnabled(i, false);
|
|
}
|
|
|
|
return someFound;
|
|
}
|
|
|
|
bool SettingsDialogPrivate::searchInSection(const QRegularExpression ®ex, QWidget *contentWidget)
|
|
{
|
|
auto layout = qobject_cast<QFormLayout*>(contentWidget->layout());
|
|
if(!layout)
|
|
return false;
|
|
|
|
auto someFound = false;
|
|
for(int i = 0, max = layout->rowCount(); i < max; ++i) {
|
|
auto spanItem = layout->itemAt(i, QFormLayout::SpanningRole);
|
|
if(spanItem) {
|
|
auto group = qobject_cast<QGroupBox*>(spanItem->widget());
|
|
if(!group)
|
|
continue;
|
|
someFound |= searchInGroup(regex, group) ||
|
|
regex.match(group->title()).hasMatch();
|
|
} else {
|
|
auto label = qobject_cast<QLabel*>(layout->itemAt(i, QFormLayout::LabelRole)->widget());
|
|
if(!label)
|
|
continue;
|
|
auto content = layout->itemAt(i, QFormLayout::FieldRole)->widget();
|
|
someFound |= searchInEntry(regex, label, content);
|
|
}
|
|
}
|
|
|
|
return someFound;
|
|
}
|
|
|
|
bool SettingsDialogPrivate::searchInGroup(const QRegularExpression ®ex, QGroupBox *groupWidget)
|
|
{
|
|
auto layout = qobject_cast<QFormLayout*>(groupWidget->layout());
|
|
if(!layout)
|
|
return false;
|
|
|
|
auto someFound = false;
|
|
for(int i = 0, max = layout->rowCount(); i < max; ++i) {
|
|
auto label = qobject_cast<QLabel*>(layout->itemAt(i, QFormLayout::LabelRole)->widget());
|
|
if(!label)
|
|
continue;
|
|
|
|
auto content = layout->itemAt(i, QFormLayout::FieldRole)->widget();
|
|
someFound |= searchInEntry(regex, label, content);
|
|
}
|
|
|
|
return someFound;
|
|
}
|
|
|
|
bool SettingsDialogPrivate::searchInEntry(const QRegularExpression ®ex, QLabel *label, QWidget *content)
|
|
{
|
|
if(regex.pattern().isEmpty()) {
|
|
label->setStyleSheet(QString());
|
|
return false;
|
|
}
|
|
|
|
auto keys = entryMap.value(content).first.searchKeys;
|
|
keys.append(label->text());
|
|
for(auto key : keys) {
|
|
if(regex.match(key).hasMatch()) {
|
|
label->setStyleSheet(q->labelFilterStyleSheet());
|
|
return true;
|
|
}
|
|
}
|
|
|
|
label->setStyleSheet(QString());
|
|
return false;
|
|
}
|
|
|
|
void SettingsDialogPrivate::propertyChanged()
|
|
{
|
|
auto widget = qobject_cast<QWidget*>(sender());
|
|
if(widget)
|
|
changedEntries.insert(widget);
|
|
}
|
|
|
|
void SettingsDialogPrivate::buttonBoxClicked(QAbstractButton *button)
|
|
{
|
|
switch(ui->buttonBox->standardButton(button)) {
|
|
case QDialogButtonBox::Ok:
|
|
saveValues();
|
|
q->accept();
|
|
break;
|
|
case QDialogButtonBox::Cancel:
|
|
q->reject();
|
|
break;
|
|
case QDialogButtonBox::Apply:
|
|
saveValues();
|
|
break;
|
|
case QDialogButtonBox::RestoreDefaults:
|
|
if(viewModel->canRestoreDefaults()) {
|
|
auto result = CoreApp::showDialog(viewModel->restoreConfig());
|
|
connect(result, &MessageResult::dialogDone, this, [this](MessageConfig::StandardButton result) {
|
|
if(result == MessageConfig::Yes) {
|
|
restoreValues();
|
|
q->accept();
|
|
}
|
|
});
|
|
}
|
|
break;
|
|
default:
|
|
Q_UNREACHABLE();
|
|
}
|
|
}
|
|
|
|
void SettingsDialogPrivate::filterTextChanged(const QString &searchText)
|
|
{
|
|
searchInDialog(QRegularExpression {
|
|
searchText,
|
|
QRegularExpression::CaseInsensitiveOption |
|
|
QRegularExpression::DontCaptureOption |
|
|
QRegularExpression::UseUnicodePropertiesOption
|
|
});
|
|
}
|
|
|
|
|
|
|
|
CategoryItemDelegate::CategoryItemDelegate(const std::function<void(int)> &updateFunc, const QSize &iconSize, int layoutSpacing, QObject *parent) :
|
|
QStyledItemDelegate(parent),
|
|
_iconSize(iconSize + QSize(0, layoutSpacing)),
|
|
_updateFunc(updateFunc)
|
|
{}
|
|
|
|
QSize CategoryItemDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
|
|
{
|
|
QSize size = QStyledItemDelegate::sizeHint(option, index);
|
|
_updateFunc(size.width());
|
|
return size.expandedTo(_iconSize);
|
|
}
|
|
|