This adds ability to read and write Style Parameters from YAML files. This will allow to move certain tokens out of the User Parameter system to ensure that they can be update without user needing to reapply the theme.
774 lines
23 KiB
C++
774 lines
23 KiB
C++
// SPDX-License-Identifier: LGPL-2.1-or-later
|
|
/****************************************************************************
|
|
* *
|
|
* Copyright (c) 2025 Kacper Donat <kacper@kadet.net> *
|
|
* *
|
|
* This file is part of FreeCAD. *
|
|
* *
|
|
* FreeCAD is free software: you can redistribute it and/or modify it *
|
|
* under the terms of the GNU Lesser General Public License as *
|
|
* published by the Free Software Foundation, either version 2.1 of the *
|
|
* License, or (at your option) any later version. *
|
|
* *
|
|
* FreeCAD is distributed in the hope that it will be useful, but *
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of *
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
|
|
* Lesser General Public License for more details. *
|
|
* *
|
|
* You should have received a copy of the GNU Lesser General Public *
|
|
* License along with FreeCAD. If not, see *
|
|
* <https://www.gnu.org/licenses/>. *
|
|
* *
|
|
***************************************************************************/
|
|
|
|
#include "PreCompiled.h"
|
|
|
|
#include "Tools.h"
|
|
#include "Application.h"
|
|
#include "OverlayManager.h"
|
|
#include "DlgThemeEditor.h"
|
|
#include "ui_DlgThemeEditor.h"
|
|
#include "BitmapFactory.h"
|
|
|
|
#include <Utilities.h>
|
|
#include <Base/ServiceProvider.h>
|
|
#include <Base/Tools.h>
|
|
|
|
#ifndef _PreComp_
|
|
# include <ranges>
|
|
# include <QImageReader>
|
|
# include <QPainter>
|
|
# include <QStyledItemDelegate>
|
|
# include <QTimer>
|
|
#endif
|
|
|
|
QPixmap colorPreview(const QColor& color)
|
|
{
|
|
constexpr qsizetype size = 16;
|
|
|
|
QPixmap preview = Gui::BitmapFactory().empty({ size, size });
|
|
|
|
QPainter painter(&preview);
|
|
painter.setRenderHint(QPainter::Antialiasing);
|
|
painter.setPen(Qt::NoPen);
|
|
painter.setBrush(color);
|
|
painter.drawEllipse(QRect { 0, 0, size, size });
|
|
|
|
return preview;
|
|
}
|
|
|
|
QString typeOfTokenValue(const Gui::StyleParameters::Value& value)
|
|
{
|
|
// clang-format off
|
|
return std::visit(
|
|
Base::Overloads {
|
|
[](const std::string&) {
|
|
return QWidget::tr("Generic");
|
|
},
|
|
[](const Gui::StyleParameters::Numeric&) {
|
|
return QWidget::tr("Numeric");
|
|
},
|
|
[](const Base::Color&) {
|
|
return QWidget::tr("Color");
|
|
}
|
|
},
|
|
value
|
|
);
|
|
// clang-format on
|
|
}
|
|
|
|
namespace Gui
|
|
{
|
|
struct StyleParametersModel::Item
|
|
{
|
|
Item() = default;
|
|
virtual ~Item() = default;
|
|
|
|
FC_DEFAULT_COPY_MOVE(Item);
|
|
|
|
virtual bool isHeader() const = 0;
|
|
};
|
|
|
|
struct StyleParametersModel::GroupItem: Item
|
|
{
|
|
explicit GroupItem(QString title, ParameterSource* source)
|
|
: title(std::move(title))
|
|
, canAddNewParameters(source && source->metadata.options.testFlag(UserEditable))
|
|
, source(source)
|
|
{}
|
|
|
|
bool isHeader() const override
|
|
{
|
|
return true;
|
|
}
|
|
|
|
QString title;
|
|
bool canAddNewParameters {false};
|
|
ParameterSource* source;
|
|
std::set<std::string> deleted {};
|
|
};
|
|
|
|
struct StyleParametersModel::ParameterItem: Item
|
|
{
|
|
ParameterItem(QString name, StyleParameters::Parameter token)
|
|
: name(std::move(name))
|
|
, token(std::move(token))
|
|
{}
|
|
|
|
bool isHeader() const override
|
|
{
|
|
return false;
|
|
}
|
|
|
|
QString name;
|
|
StyleParameters::Parameter token;
|
|
QFlags<Qt::ItemFlag> flags = Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable;
|
|
};
|
|
|
|
class StyleParametersModel::Node
|
|
{
|
|
public:
|
|
explicit Node(std::unique_ptr<Item> data, Node* parent = nullptr)
|
|
: _parent(parent)
|
|
, _data(std::move(data))
|
|
{}
|
|
|
|
void appendChild(std::unique_ptr<Node> child)
|
|
{
|
|
child->_parent = this;
|
|
_children.push_back(std::move(child));
|
|
}
|
|
|
|
void removeChild(const int row)
|
|
{
|
|
if (row >= 0 && row < static_cast<int>(_children.size())) {
|
|
_children.erase(_children.begin() + row);
|
|
}
|
|
}
|
|
|
|
Node* child(const int row) const
|
|
{
|
|
if (row < 0 || row >= static_cast<int>(_children.size())) {
|
|
if (!_empty) {
|
|
_empty = std::make_unique<Node>(nullptr, const_cast<Node*>(this));
|
|
}
|
|
|
|
return _empty.get();
|
|
}
|
|
|
|
return _children[row].get();
|
|
}
|
|
|
|
int childCount() const
|
|
{
|
|
return static_cast<int>(_children.size());
|
|
}
|
|
|
|
int row() const
|
|
{
|
|
if (!_parent) {
|
|
return 0;
|
|
}
|
|
|
|
const auto& siblings = _parent->_children;
|
|
for (size_t i = 0; i < siblings.size(); ++i) {
|
|
if (siblings[i].get() == this) {
|
|
return static_cast<int>(i);
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
Item* data() const
|
|
{
|
|
return _data.get();
|
|
}
|
|
|
|
template<class T>
|
|
T* data() const
|
|
{
|
|
return dynamic_cast<T*>(_data.get());
|
|
}
|
|
|
|
Node* parent() const
|
|
{
|
|
return _parent;
|
|
}
|
|
|
|
private:
|
|
Node* _parent;
|
|
std::vector<std::unique_ptr<Node>> _children {};
|
|
|
|
mutable std::unique_ptr<Node> _empty {};
|
|
std::unique_ptr<Item> _data {};
|
|
};
|
|
|
|
class DlgThemeEditor::Delegate: public QStyledItemDelegate
|
|
{
|
|
Q_OBJECT
|
|
|
|
QRegularExpression validNameRegExp { QStringLiteral("^[A-Z][a-zA-Z0-9]*$") };
|
|
QRegularExpressionValidator* nameValidator;
|
|
|
|
public:
|
|
explicit Delegate(QObject* parent = nullptr)
|
|
: QStyledItemDelegate(parent)
|
|
, nameValidator(new QRegularExpressionValidator(validNameRegExp, this))
|
|
{}
|
|
|
|
QWidget* createEditor(QWidget* parent,
|
|
[[maybe_unused]] const QStyleOptionViewItem& option,
|
|
const QModelIndex& index) const override
|
|
{
|
|
auto model = dynamic_cast<const StyleParametersModel*>(index.model());
|
|
if (!model) {
|
|
return nullptr;
|
|
}
|
|
|
|
if (model->item<StyleParametersModel::ParameterItem>(index)
|
|
&& index.column() == StyleParametersModel::ParameterExpression) {
|
|
return new QLineEdit(parent);
|
|
}
|
|
|
|
if (index.column() == StyleParametersModel::ParameterName) {
|
|
auto editor = new QLineEdit(parent);
|
|
editor->setValidator(nameValidator);
|
|
return editor;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void setEditorData(QWidget* editor, const QModelIndex& index) const override
|
|
{
|
|
if (auto* lineEdit = qobject_cast<QLineEdit*>(editor)) {
|
|
lineEdit->setText(index.data(Qt::DisplayRole).toString());
|
|
}
|
|
}
|
|
|
|
void setModelData(QWidget* editor,
|
|
QAbstractItemModel* model,
|
|
const QModelIndex& index) const override
|
|
{
|
|
if (auto* lineEdit = qobject_cast<QLineEdit*>(editor)) {
|
|
model->setData(index, lineEdit->text(), Qt::EditRole);
|
|
}
|
|
}
|
|
|
|
void updateEditorGeometry(QWidget* editor,
|
|
const QStyleOptionViewItem& option,
|
|
[[maybe_unused]] const QModelIndex& index) const override
|
|
{
|
|
editor->setGeometry(option.rect);
|
|
}
|
|
|
|
QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const override
|
|
{
|
|
constexpr int height = 36;
|
|
|
|
QSize base = QStyledItemDelegate::sizeHint(option, index);
|
|
return {base.width(), std::max(base.height(), height)};
|
|
}
|
|
|
|
void paintAddPlaceholder(QPainter* painter, const QStyleOptionViewItem& option) const
|
|
{
|
|
QStyle* style = option.widget ? option.widget->style() : QApplication::style();
|
|
QRect rect = style->subElementRect(QStyle::SE_ItemViewItemText, &option, option.widget);
|
|
|
|
QFont font = option.font;
|
|
font.setItalic(true);
|
|
|
|
painter->setFont(font);
|
|
painter->drawText(rect, Qt::AlignLeft | Qt::AlignVCenter, tr("New parameter..."));
|
|
}
|
|
|
|
void paint(QPainter* painter,
|
|
const QStyleOptionViewItem& option,
|
|
const QModelIndex& index) const override
|
|
{
|
|
auto model = dynamic_cast<const StyleParametersModel*>(index.model());
|
|
|
|
painter->save();
|
|
|
|
QStyleOptionViewItem opt(option);
|
|
initStyleOption(&opt, index);
|
|
|
|
if (model->isAddPlaceholder(index)) {
|
|
if (index.column() == StyleParametersModel::ParameterName) {
|
|
paintAddPlaceholder(painter, opt);
|
|
}
|
|
}
|
|
else if (model->item<StyleParametersModel::GroupItem>(index)) {
|
|
constexpr int headerContrast = 120;
|
|
|
|
const bool isLightTheme = option.palette.color(QPalette::Text).lightness() < 128;
|
|
|
|
const QColor headerBackgroundColor = QtTools::valueOr(
|
|
option.widget->property("headerBackgroundColor"),
|
|
isLightTheme
|
|
? option.palette.color(QPalette::AlternateBase).darker(headerContrast)
|
|
: option.palette.color(QPalette::AlternateBase).lighter(headerContrast)
|
|
);
|
|
|
|
painter->fillRect(option.rect, headerBackgroundColor);
|
|
QStyledItemDelegate::paint(painter, option, index);
|
|
}
|
|
else {
|
|
QStyledItemDelegate::paint(painter, option, index);
|
|
}
|
|
|
|
painter->restore();
|
|
}
|
|
};
|
|
|
|
void TokenTreeView::keyPressEvent(QKeyEvent* event)
|
|
{
|
|
static constexpr auto expressionEditKeys = { Qt::Key_Return, Qt::Key_Enter, Qt::Key_Space };
|
|
static constexpr auto nameEditKeys = { Qt::Key_F2 };
|
|
static constexpr auto deleteKeys = { Qt::Key_Delete };
|
|
|
|
const auto isCorrectKey = [&event](auto key) { return event->key() == key; };
|
|
|
|
if (QModelIndex index = currentIndex(); index.isValid()) {
|
|
if (std::ranges::any_of(expressionEditKeys, isCorrectKey)) {
|
|
edit(index.siblingAtColumn(StyleParametersModel::ParameterExpression));
|
|
return;
|
|
}
|
|
|
|
if (std::ranges::any_of(nameEditKeys, isCorrectKey)) {
|
|
edit(index.siblingAtColumn(StyleParametersModel::ParameterName));
|
|
return;
|
|
}
|
|
|
|
if (std::ranges::any_of(deleteKeys, isCorrectKey)) {
|
|
requestRemove(currentIndex());
|
|
return;
|
|
}
|
|
}
|
|
|
|
QTreeView::keyPressEvent(event);
|
|
}
|
|
|
|
StyleParametersModel::StyleParametersModel(
|
|
const std::list<StyleParameters::ParameterSource*>& sources,
|
|
QObject* parent)
|
|
: QAbstractItemModel(parent)
|
|
, ParameterSource({ .name = QT_TR_NOOP("All Theme Editor Parameters") })
|
|
, sources(sources)
|
|
, manager(new StyleParameters::ParameterManager())
|
|
{
|
|
// The parameter model serves as the source, so the manager can compute all necessary things
|
|
manager->addSource(this);
|
|
|
|
reset();
|
|
}
|
|
|
|
StyleParametersModel::~StyleParametersModel() = default;
|
|
|
|
std::list<StyleParameters::Parameter> StyleParametersModel::all() const
|
|
{
|
|
std::map<std::string, StyleParameters::Parameter> result;
|
|
|
|
QtTools::walkTreeModel(this, [this, &result](const QModelIndex& index) {
|
|
if (auto parameterItem = item<ParameterItem>(index)) {
|
|
if (result.contains(parameterItem->token.name)) {
|
|
return;
|
|
}
|
|
|
|
result[parameterItem->token.name] = parameterItem->token;
|
|
}
|
|
});
|
|
|
|
const auto values = result | std::ranges::views::values;
|
|
return std::list<StyleParameters::Parameter>(values.begin(), values.end());
|
|
}
|
|
|
|
std::optional<StyleParameters::Parameter> StyleParametersModel::get(const std::string& name) const
|
|
{
|
|
std::optional<StyleParameters::Parameter> result = std::nullopt;
|
|
|
|
QtTools::walkTreeModel(this, [this, &name, &result](const QModelIndex& index) {
|
|
if (auto parameterItem = item<ParameterItem>(index)) {
|
|
if (parameterItem->token.name == name) {
|
|
result = parameterItem->token;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
});
|
|
|
|
return result;
|
|
}
|
|
|
|
void StyleParametersModel::removeItem(const QModelIndex& index)
|
|
{
|
|
if (auto parameterItem = item<ParameterItem>(index)) {
|
|
auto groupItem = item<GroupItem>(index.parent());
|
|
|
|
if (!groupItem->source->metadata.options.testFlag(UserEditable)) {
|
|
return;
|
|
}
|
|
|
|
groupItem->deleted.insert(parameterItem->token.name);
|
|
|
|
beginRemoveRows(index.parent(), index.row(), index.row());
|
|
node(index.parent())->removeChild(index.row());
|
|
endRemoveRows();
|
|
}
|
|
}
|
|
|
|
void StyleParametersModel::reset()
|
|
{
|
|
using enum StyleParameters::ParameterSourceOption;
|
|
|
|
beginResetModel();
|
|
root = std::make_unique<Node>(std::make_unique<GroupItem>(tr("Root"), nullptr));
|
|
|
|
for (auto* source : sources) {
|
|
auto groupNode = std::make_unique<Node>(
|
|
std::make_unique<GroupItem>(tr(source->metadata.name.c_str()), source));
|
|
|
|
for (const auto& parameter : source->all()) {
|
|
auto item = std::make_unique<Node>(
|
|
std::make_unique<ParameterItem>(QString::fromStdString(parameter.name), parameter));
|
|
|
|
if (source->metadata.options.testFlag(ReadOnly)) {
|
|
item->data<ParameterItem>()->flags = Qt::ItemIsEnabled | Qt::ItemIsSelectable;
|
|
}
|
|
|
|
groupNode->appendChild(std::move(item));
|
|
}
|
|
|
|
root->appendChild(std::move(groupNode));
|
|
}
|
|
|
|
endResetModel();
|
|
}
|
|
|
|
void StyleParametersModel::flush()
|
|
{
|
|
QtTools::walkTreeModel(this, [this](const QModelIndex& index) {
|
|
if (const auto& groupItem = item<GroupItem>(index)) {
|
|
for (const auto& parameter : groupItem->deleted) {
|
|
groupItem->source->remove(parameter);
|
|
}
|
|
|
|
groupItem->deleted.clear();
|
|
}
|
|
|
|
if (const auto& parameterItem = item<ParameterItem>(index)) {
|
|
const auto& groupItem = item<GroupItem>(index.parent());
|
|
|
|
groupItem->source->define(parameterItem->token);
|
|
}
|
|
});
|
|
|
|
for (auto* source : sources) {
|
|
source->flush();
|
|
}
|
|
|
|
reset();
|
|
}
|
|
|
|
int StyleParametersModel::rowCount(const QModelIndex& index) const
|
|
{
|
|
if (index.column() > 0) {
|
|
return 0;
|
|
}
|
|
|
|
int childCount = node(index)->childCount();
|
|
|
|
if (const auto& groupItem = item<GroupItem>(index)) {
|
|
return childCount + (groupItem->canAddNewParameters ? 1 : 0);
|
|
}
|
|
|
|
return childCount;
|
|
}
|
|
|
|
int StyleParametersModel::columnCount([[maybe_unused]] const QModelIndex& index) const
|
|
{
|
|
return ColumnCount;
|
|
}
|
|
|
|
QVariant StyleParametersModel::headerData(int section, Qt::Orientation orientation, int role) const
|
|
{
|
|
if (orientation == Qt::Horizontal && role == Qt::DisplayRole) {
|
|
switch (section) {
|
|
case ParameterName:
|
|
return tr("Name");
|
|
case ParameterExpression:
|
|
return tr("Expression");
|
|
case ParameterPreview:
|
|
return tr("Preview");
|
|
case ParameterType:
|
|
return tr("Type");
|
|
default:
|
|
return {};
|
|
}
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
QVariant StyleParametersModel::data(const QModelIndex& index, int role) const
|
|
{
|
|
if (auto parameterItem = item<ParameterItem>(index)) {
|
|
const auto& [name, token, _] = *parameterItem;
|
|
const auto& value = manager->resolve(name.toStdString());
|
|
|
|
if (!value) {
|
|
return {};
|
|
}
|
|
|
|
if (role == Qt::DisplayRole) {
|
|
if (index.column() == ParameterName) {
|
|
return name;
|
|
}
|
|
if (index.column() == ParameterExpression) {
|
|
return QString::fromStdString(token.value);
|
|
}
|
|
if (index.column() == ParameterType) {
|
|
return typeOfTokenValue(*value);
|
|
}
|
|
if (index.column() == ParameterPreview) {
|
|
return QString::fromStdString(value->toString());
|
|
}
|
|
}
|
|
|
|
if (role == Qt::DecorationRole) {
|
|
if (index.column() == ParameterPreview && std::holds_alternative<Base::Color>(*value)) {
|
|
return colorPreview(std::get<Base::Color>(*value).asValue<QColor>());
|
|
}
|
|
}
|
|
}
|
|
|
|
if (auto groupItem = item<GroupItem>(index)) {
|
|
if (role == Qt::DisplayRole && index.column() == ParameterName) {
|
|
return groupItem->title;
|
|
}
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
bool StyleParametersModel::setData(const QModelIndex& index,
|
|
const QVariant& value,
|
|
[[maybe_unused]] int role)
|
|
{
|
|
if (auto parameterItem = item<ParameterItem>(index)) {
|
|
auto groupItem = item<GroupItem>(index.parent());
|
|
|
|
if (index.column() == ParameterName) {
|
|
QString newName = value.toString();
|
|
|
|
StyleParameters::Parameter newToken = parameterItem->token;
|
|
newToken.name = newName.toStdString();
|
|
|
|
// there is no rename operation, so we need to mark the previous token as deleted
|
|
groupItem->deleted.insert(parameterItem->token.name);
|
|
|
|
parameterItem->name = newName;
|
|
parameterItem->token = newToken;
|
|
}
|
|
|
|
if (index.column() == ParameterExpression) {
|
|
QString newValue = value.toString();
|
|
|
|
StyleParameters::Parameter newToken = parameterItem->token;
|
|
newToken.value = newValue.toStdString();
|
|
|
|
parameterItem->token = newToken;
|
|
}
|
|
}
|
|
|
|
if (isAddPlaceholder(index)) {
|
|
if (index.column() == ParameterName) {
|
|
QString newName = value.toString();
|
|
|
|
if (newName.isEmpty()) {
|
|
return false;
|
|
}
|
|
|
|
StyleParameters::Parameter token { .name = newName.toStdString(), .value = "" };
|
|
|
|
int start = rowCount(index.parent());
|
|
|
|
beginInsertRows(index.parent(), start, start + 1);
|
|
auto item = std::make_unique<Node>(
|
|
std::make_unique<ParameterItem>(newName, token));
|
|
node(index.parent())->appendChild(std::move(item));
|
|
endInsertRows();
|
|
|
|
// this must be queued to basically next frame so widget has a chance to update
|
|
QTimer::singleShot(0, [this, index]() {
|
|
this->newParameterAdded(index);
|
|
});
|
|
}
|
|
}
|
|
|
|
this->manager->reload();
|
|
|
|
QtTools::walkTreeModel(this, [this](const QModelIndex& index) {
|
|
const QModelIndex previewColumnIndex = index.siblingAtColumn(ParameterPreview);
|
|
|
|
Q_EMIT dataChanged(previewColumnIndex, previewColumnIndex);
|
|
});
|
|
|
|
return true;
|
|
}
|
|
|
|
Qt::ItemFlags StyleParametersModel::flags(const QModelIndex& index) const
|
|
{
|
|
if (auto parameterItem = item<ParameterItem>(index)) {
|
|
if (index.column() == ParameterName || index.column() == ParameterExpression) {
|
|
return parameterItem->flags | QAbstractItemModel::flags(index);
|
|
}
|
|
}
|
|
|
|
if (isAddPlaceholder(index)) {
|
|
if (index.column() == ParameterName) {
|
|
return Qt::ItemIsEnabled | Qt::ItemIsEditable | QAbstractItemModel::flags(index);
|
|
}
|
|
}
|
|
|
|
return QAbstractItemModel::flags(index);
|
|
}
|
|
|
|
QModelIndex StyleParametersModel::index(int row, int col, const QModelIndex& parent) const
|
|
{
|
|
if (!hasIndex(row, col, parent)) {
|
|
return {};
|
|
}
|
|
|
|
if (auto child = node(parent)->child(row)) {
|
|
return createIndex(row, col, child);
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
QModelIndex StyleParametersModel::parent(const QModelIndex& index) const
|
|
{
|
|
if (!index.isValid()) {
|
|
return {};
|
|
}
|
|
|
|
auto node = static_cast<Node*>(index.internalPointer());
|
|
auto parent = node->parent();
|
|
|
|
if (!parent || parent == root.get()) {
|
|
return {};
|
|
}
|
|
|
|
return createIndex(parent->row(), 0, parent);
|
|
}
|
|
|
|
bool StyleParametersModel::isAddPlaceholder(const QModelIndex& index) const
|
|
{
|
|
return item(index) == nullptr;
|
|
}
|
|
|
|
StyleParametersModel::Node* StyleParametersModel::node(const QModelIndex& index) const
|
|
{
|
|
return index.isValid() ? static_cast<Node*>(index.internalPointer()) : root.get();
|
|
}
|
|
|
|
StyleParametersModel::Item* StyleParametersModel::item(const QModelIndex& index) const
|
|
{
|
|
return node(index)->data();
|
|
}
|
|
|
|
DlgThemeEditor::DlgThemeEditor(QWidget* parent)
|
|
: QDialog(parent)
|
|
, ui(new Ui::DlgThemeEditor)
|
|
, model(std::make_unique<StyleParametersModel>(
|
|
Base::provideServiceImplementations<StyleParameters::ParameterSource>(),
|
|
this))
|
|
{
|
|
ui->setupUi(this);
|
|
|
|
ui->tokensTreeView->setMouseTracking(true);
|
|
ui->tokensTreeView->setItemDelegate(new Delegate(ui->tokensTreeView));
|
|
ui->tokensTreeView->setModel(model.get());
|
|
|
|
constexpr int typeColumnWidth = 80;
|
|
constexpr int nameColumnWidth = 200;
|
|
|
|
struct ColumnDefinition // NOLINT(*-pro-type-member-init)
|
|
{
|
|
StyleParametersModel::Column column;
|
|
QHeaderView::ResizeMode mode;
|
|
qsizetype defaultWidth = 0;
|
|
};
|
|
|
|
static constexpr std::initializer_list<ColumnDefinition> columnSizingDefinitions = {
|
|
{StyleParametersModel::ParameterName, QHeaderView::ResizeMode::ResizeToContents},
|
|
{StyleParametersModel::ParameterExpression, QHeaderView::ResizeMode::Stretch},
|
|
{StyleParametersModel::ParameterPreview, QHeaderView::ResizeMode::Stretch},
|
|
{StyleParametersModel::ParameterType, QHeaderView::ResizeMode::Fixed, typeColumnWidth},
|
|
};
|
|
|
|
for (const auto& [column, mode, defaultWidth] : columnSizingDefinitions) {
|
|
ui->tokensTreeView->header()->setSectionResizeMode(column, mode);
|
|
|
|
if (defaultWidth > 0) {
|
|
ui->tokensTreeView->header()->setDefaultSectionSize(defaultWidth);
|
|
}
|
|
}
|
|
|
|
ui->tokensTreeView->setColumnWidth(StyleParametersModel::ParameterName, nameColumnWidth);
|
|
ui->tokensTreeView->expandAll();
|
|
|
|
connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
|
|
connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
|
|
|
connect(ui->buttonBox, &QDialogButtonBox::clicked, this, &DlgThemeEditor::handleButtonClick);
|
|
|
|
connect(ui->tokensTreeView,
|
|
&TokenTreeView::requestRemove,
|
|
model.get(),
|
|
qOverload<const QModelIndex&>(&StyleParametersModel::removeItem));
|
|
|
|
connect(model.get(), &StyleParametersModel::modelReset, ui->tokensTreeView, [this] {
|
|
ui->tokensTreeView->expandAll();
|
|
});
|
|
connect(model.get(),
|
|
&StyleParametersModel::newParameterAdded,
|
|
this,
|
|
[this](const QModelIndex& index) {
|
|
const auto newParameterExpressionIndex =
|
|
index.siblingAtColumn(StyleParametersModel::ParameterExpression);
|
|
|
|
ui->tokensTreeView->scrollTo(newParameterExpressionIndex);
|
|
ui->tokensTreeView->setCurrentIndex(newParameterExpressionIndex);
|
|
ui->tokensTreeView->edit(newParameterExpressionIndex);
|
|
});
|
|
}
|
|
|
|
DlgThemeEditor::~DlgThemeEditor() = default;
|
|
|
|
void DlgThemeEditor::handleButtonClick(QAbstractButton* button)
|
|
{
|
|
auto role = ui->buttonBox->buttonRole(button);
|
|
|
|
switch (role) {
|
|
case QDialogButtonBox::ApplyRole:
|
|
case QDialogButtonBox::AcceptRole:
|
|
model->flush();
|
|
Application::Instance->reloadStyleSheet();
|
|
break;
|
|
case QDialogButtonBox::ResetRole:
|
|
model->reset();
|
|
break;
|
|
default:
|
|
// no-op
|
|
break;
|
|
}
|
|
}
|
|
|
|
} // namespace Gui
|
|
|
|
#include "DlgThemeEditor.moc"
|