// SPDX-License-Identifier: LGPL-2.1-or-later /*************************************************************************** * Copyright (c) 2023 David Carter * * * * 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 * * . * * * **************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "BaseDelegate.h" #include "ListModel.h" #include "MaterialSave.h" using namespace MatGui; BaseDelegate::BaseDelegate(QObject* parent) : QStyledItemDelegate(parent) {} bool BaseDelegate::newRow(const QAbstractItemModel* model, const QModelIndex& index) const { // The model always includes an empty row to allow for additions return (index.row() == (model->rowCount() - 1)); } QString BaseDelegate::getStringValue(const QModelIndex& index) const { QVariant item = getValue(index); auto propertyValue = item.value(); return propertyValue; } Color BaseDelegate::parseColor(const QString& color) const { QString trimmed = color; trimmed.replace(QRegularExpression(QStringLiteral("\\(([^<]*)\\)")), QStringLiteral("\\1")); QStringList parts = trimmed.split(QStringLiteral(",")); if (parts.length() < 3) { return Color(); } return Color(parts.at(0).toDouble(), parts.at(1).toDouble(), parts.at(2).toDouble(), parts.length() > 3 ? parts.at(3).toDouble() : 1.0); } void BaseDelegate::paintQuantity(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const { auto model = index.model(); painter->save(); if (newRow(model, index)) { painter->drawText(option.rect, 0, QString()); } else { QString text; QVariant item = getValue(index); auto quantity = item.value(); if (quantity.isValid()) { text = QString::fromStdString(quantity.getUserString()); } painter->drawText(option.rect, 0, text); } painter->restore(); } void BaseDelegate::paintImage(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const { auto propertyValue = getStringValue(index); painter->save(); QImage img; if (!propertyValue.isEmpty()) { QByteArray by = QByteArray::fromBase64(propertyValue.toUtf8()); img = QImage::fromData(by).scaled(64, 64, Qt::KeepAspectRatio); } QRect target(option.rect); if (target.width() > target.height()) { target.setWidth(target.height()); } else { target.setHeight(target.width()); } painter->drawImage(target, img, img.rect()); painter->restore(); } void BaseDelegate::paintSVG(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const { auto propertyValue = getStringValue(index); painter->save(); if (!propertyValue.isEmpty()) { QSvgRenderer renderer(propertyValue.toUtf8()); renderer.render(painter, QRectF(option.rect)); } painter->restore(); } void BaseDelegate::paintColor(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const { auto propertyValue = getStringValue(index); painter->save(); int left = option.rect.left() + 2; int width = option.rect.width() - 4; if (option.rect.width() > 75) { left += (option.rect.width() - 75) / 2; width = 71; } painter->fillRect(left, option.rect.top() + 2, width, option.rect.height() - 4, QBrush(Qt::black)); left = option.rect.left() + 5; width = option.rect.width() - 10; if (option.rect.width() > 75) { left += (option.rect.width() - 75) / 2; width = 65; } auto color = parseColor(propertyValue).asValue(); painter->fillRect(left, option.rect.top() + 5, width, option.rect.height() - 10, QBrush(color)); painter->restore(); } void BaseDelegate::paintList(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const { Q_UNUSED(index) painter->save(); QImage list(QStringLiteral(":/icons/list.svg")); QRect target(option.rect); if (target.width() > target.height()) { target.setWidth(target.height()); } else { target.setHeight(target.width()); } painter->drawImage(target, list, list.rect()); painter->restore(); } void BaseDelegate::paintMultiLineString(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const { Q_UNUSED(index) painter->save(); QImage table(QStringLiteral(":/icons/multiline.svg")); QRect target(option.rect); if (target.width() > target.height()) { target.setWidth(target.height()); } else { target.setHeight(target.width()); } painter->drawImage(target, table, table.rect()); painter->restore(); } void BaseDelegate::paintArray(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const { Q_UNUSED(index) painter->save(); QImage table(QStringLiteral(":/icons/table.svg")); QRect target(option.rect); if (target.width() > target.height()) { target.setWidth(target.height()); } else { target.setHeight(target.width()); } painter->drawImage(target, table, table.rect()); painter->restore(); } void BaseDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const { auto type = getType(index); if (type == Materials::MaterialValue::Quantity) { paintQuantity(painter, option, index); return; } if (type == Materials::MaterialValue::Image) { paintImage(painter, option, index); return; } if (type == Materials::MaterialValue::SVG) { paintSVG(painter, option, index); return; } if (type == Materials::MaterialValue::Color) { paintColor(painter, option, index); return; } if (type == Materials::MaterialValue::List || type == Materials::MaterialValue::FileList || type == Materials::MaterialValue::ImageList) { paintList(painter, option, index); return; } if (type == Materials::MaterialValue::MultiLineString) { paintMultiLineString(painter, option, index); return; } if (type == Materials::MaterialValue::Array2D || type == Materials::MaterialValue::Array3D) { paintArray(painter, option, index); return; } QStyledItemDelegate::paint(painter, option, index); } QSize BaseDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const { Q_UNUSED(option) Q_UNUSED(index) auto type = getType(index); if (type == Materials::MaterialValue::Color) { return {75, 23}; // Standard QPushButton size } if (type == Materials::MaterialValue::Image || type == Materials::MaterialValue::SVG) { return {64, 64}; } if (type == Materials::MaterialValue::Array2D || type == Materials::MaterialValue::Array3D || type == Materials::MaterialValue::MultiLineString || type == Materials::MaterialValue::List || type == Materials::MaterialValue::FileList || type == Materials::MaterialValue::ImageList) { return {23, 23}; } return QStyledItemDelegate::sizeHint(option, index); } void BaseDelegate::setEditorData(QWidget* editor, const QModelIndex& index) const { if (!editor) { return; } auto item = getValue(index); auto type = getType(index); if (type == Materials::MaterialValue::List) { auto input = dynamic_cast(editor); item = input->text(); return; } if (type == Materials::MaterialValue::FileList || type == Materials::MaterialValue::File) { auto chooser = dynamic_cast(editor); chooser->setFileName(item.toString()); return; } if (type == Materials::MaterialValue::Quantity) { auto input = dynamic_cast(editor); // input->setQuantityString(item.toString()); input->setValue(item.value()); return; } if (type == Materials::MaterialValue::List || type == Materials::MaterialValue::ImageList) { // Handled by dialogs return; } QStyledItemDelegate::setEditorData(editor, index); } void BaseDelegate::setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const { QVariant value; auto type = getType(index); if (type == Materials::MaterialValue::FileList || type == Materials::MaterialValue::File) { auto chooser = dynamic_cast(editor); value = chooser->fileName(); } else if (type == Materials::MaterialValue::Quantity) { auto input = dynamic_cast(editor); // value = input->text(); // return; // auto quantity = Base::Quantity::parse(input->text()); auto quantity = input->value(); value = QVariant::fromValue(quantity); } else if (type == Materials::MaterialValue::Integer) { auto spinner = dynamic_cast(editor); value = spinner->value(); } else if (type == Materials::MaterialValue::Float) { auto spinner = dynamic_cast(editor); value = spinner->value(); } else if (type == Materials::MaterialValue::Boolean) { auto combo = dynamic_cast(editor); value = combo->currentText(); } else if (type == Materials::MaterialValue::Image || type == Materials::MaterialValue::SVG) { // Value was already saved to the property notifyChanged(index.model(), index); return; } else { auto lineEdit = dynamic_cast(editor); value = lineEdit->text(); } setValue(model, index, value); // Q_EMIT model->dataChanged(index, index); } QWidget* BaseDelegate::createEditor(QWidget* parent, const QStyleOptionViewItem& styleOption, const QModelIndex& index) const { Q_UNUSED(styleOption) auto model = index.model(); // If this is a new row then it has to be added before the editor is created. Otherwise // Adding the row while the editor is active can change where the editor is placed. if (newRow(model, index)) { const_cast(model)->insertRows(index.row(), 1); } auto item = getValue(index); QWidget* editor = createWidget(parent, item, index); return editor; } QWidget* BaseDelegate::createWidget(QWidget* parent, const QVariant& item, const QModelIndex& index) const { QWidget* widget = nullptr; auto type = getType(index); if (type == Materials::MaterialValue::Integer) { auto spinner = new Gui::UIntSpinBox(parent); spinner->setMinimum(0); spinner->setMaximum(std::numeric_limits::max()); spinner->setValue(item.toUInt()); widget = spinner; } else if (type == Materials::MaterialValue::Float) { auto spinner = new Gui::DoubleSpinBox(parent); // the magnetic permeability is the parameter for which many decimals matter // the most however, even for this, 6 digits are sufficient spinner->setDecimals(6); // for almost all Float parameters of materials a step of 1 would be too large spinner->setSingleStep(0.1); spinner->setMinimum(std::numeric_limits::min()); spinner->setMaximum(std::numeric_limits::max()); spinner->setValue(item.toDouble()); widget = spinner; } else if (type == Materials::MaterialValue::Boolean) { auto combo = new Gui::PrefComboBox(parent); combo->insertItem(0, QStringLiteral("")); combo->insertItem(1, tr("False")); combo->insertItem(2, tr("True")); combo->setCurrentText(item.toString()); widget = combo; } else if (type == Materials::MaterialValue::Quantity) { auto input = new Gui::QuantitySpinBox(parent); input->setMinimum(std::numeric_limits::min()); input->setMaximum(std::numeric_limits::max()); input->setUnitText(getUnits(index)); input->setValue(item.value()); widget = input; } else if (type == Materials::MaterialValue::File) { auto chooser = new Gui::FileChooser(parent); if (!item.toString().isEmpty()) { chooser->setFileName(item.toString()); } widget = chooser; } else if (type == Materials::MaterialValue::FileList) { auto chooser = new Gui::FileChooser(parent); auto propertyValue = item.toString(); connect(chooser, &Gui::FileChooser::fileNameChanged, [this, chooser, index](const QString&) { setModelData(chooser, const_cast(index.model()), index); }); connect(chooser, &Gui::FileChooser::fileNameSelected, [this, chooser, index](const QString&) { setModelData(chooser, const_cast(index.model()), index); }); widget = chooser; } else { // Default editor auto lineEdit = new Gui::PrefLineEdit(parent); lineEdit->setText(item.toString()); widget = lineEdit; } return widget; } #include "moc_BaseDelegate.cpp"