510 lines
17 KiB
C++
510 lines
17 KiB
C++
/***************************************************************************
|
|
* Copyright (c) 2023 David Carter <dcarter@david.carter.ca> *
|
|
* *
|
|
* 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"
|
|
#ifndef _PreComp_
|
|
#include <QColorDialog>
|
|
#include <QDesktopServices>
|
|
#include <QIODevice>
|
|
#include <QItemSelectionModel>
|
|
#include <QMessageBox>
|
|
#include <QPainter>
|
|
#include <QString>
|
|
#include <QStringList>
|
|
#include <QSvgRenderer>
|
|
#include <QTextStream>
|
|
#include <QVariant>
|
|
#include <limits>
|
|
#endif
|
|
|
|
#include <App/Application.h>
|
|
#include <Base/Interpreter.h>
|
|
#include <Base/Quantity.h>
|
|
#include <Gui/Application.h>
|
|
#include <Gui/Command.h>
|
|
#include <Gui/InputField.h>
|
|
#include <Gui/PrefWidgets.h>
|
|
#include <Gui/SpinBox.h>
|
|
#include <Gui/WaitCursor.h>
|
|
|
|
#include <Mod/Material/App/Exceptions.h>
|
|
#include <Mod/Material/App/ModelManager.h>
|
|
|
|
#include "Array2D.h"
|
|
#include "Array3D.h"
|
|
#include "ImageEdit.h"
|
|
#include "ListEdit.h"
|
|
#include "MaterialDelegate.h"
|
|
#include "MaterialSave.h"
|
|
#include "TextEdit.h"
|
|
|
|
|
|
using namespace MatGui;
|
|
|
|
MaterialDelegate::MaterialDelegate(QObject* parent)
|
|
: BaseDelegate(parent)
|
|
{}
|
|
|
|
bool MaterialDelegate::newRow(const QAbstractItemModel* model, const QModelIndex& index) const
|
|
{
|
|
Q_UNUSED(model)
|
|
Q_UNUSED(index)
|
|
|
|
// New rows are for lists and arrays
|
|
return false;
|
|
}
|
|
|
|
Materials::MaterialValue::ValueType MaterialDelegate::getType(const QModelIndex& index) const
|
|
{
|
|
auto treeModel = dynamic_cast<const QStandardItemModel*>(index.model());
|
|
auto item = treeModel->itemFromIndex(index);
|
|
auto group = item->parent();
|
|
if (!group) {
|
|
return {};
|
|
}
|
|
|
|
int row = index.row();
|
|
QString propertyType;
|
|
if (group->child(row, 1)) {
|
|
propertyType = group->child(row, 2)->text();
|
|
}
|
|
|
|
return Materials::MaterialValue::mapType(propertyType);
|
|
}
|
|
|
|
QString MaterialDelegate::getUnits(const QModelIndex& index) const
|
|
{
|
|
auto treeModel = dynamic_cast<const QStandardItemModel*>(index.model());
|
|
auto item = treeModel->itemFromIndex(index);
|
|
auto group = item->parent();
|
|
if (!group) {
|
|
return {};
|
|
}
|
|
|
|
int row = index.row();
|
|
QString propertyUnits;
|
|
if (group->child(row, 1)) {
|
|
propertyUnits = group->child(row, 3)->text();
|
|
}
|
|
return propertyUnits;
|
|
}
|
|
|
|
QVariant MaterialDelegate::getValue(const QModelIndex& index) const
|
|
{
|
|
auto treeModel = dynamic_cast<const QStandardItemModel*>(index.model());
|
|
auto item = treeModel->itemFromIndex(index);
|
|
auto group = item->parent();
|
|
if (!group) {
|
|
return {};
|
|
}
|
|
|
|
int row = index.row();
|
|
QVariant propertyValue;
|
|
if (group->child(row, 1)) {
|
|
auto material = group->child(row, 1)->data().value<std::shared_ptr<Materials::Material>>();
|
|
// auto propertyName = group->child(row, 0)->text();
|
|
auto propertyName = group->child(row, 0)->data().toString();
|
|
propertyValue = material->getProperty(propertyName)->getValue();
|
|
}
|
|
return propertyValue;
|
|
}
|
|
|
|
void MaterialDelegate::setValue(QAbstractItemModel* model,
|
|
const QModelIndex& index,
|
|
const QVariant& value) const
|
|
{
|
|
auto matModel = dynamic_cast<const QStandardItemModel*>(model);
|
|
auto item = matModel->itemFromIndex(index);
|
|
auto group = item->parent();
|
|
if (!group) {
|
|
return;
|
|
}
|
|
|
|
int row = index.row();
|
|
if (group->child(row, 1)) {
|
|
auto material = group->child(row, 1)->data().value<std::shared_ptr<Materials::Material>>();
|
|
auto propertyName = group->child(row, 0)->data().toString();
|
|
std::string _name = propertyName.toStdString();
|
|
auto property = material->getProperty(propertyName);
|
|
|
|
try {
|
|
property->setValue(value);
|
|
}
|
|
catch (const Base::ValueError&) {
|
|
// Units mismatch
|
|
auto quantity = value.value<Base::Quantity>();
|
|
Base::Console().log("Units mismatch '%s' = '%s', "
|
|
"setting to default property units '%s'\n",
|
|
propertyName.toStdString().c_str(),
|
|
quantity.getUserString().c_str(),
|
|
property->getUnits().toStdString().c_str());
|
|
|
|
QMessageBox msgBox;
|
|
msgBox.setWindowTitle(QStringLiteral("Property Units Mismatch"));
|
|
msgBox.setText(QStringLiteral("Units mismatch '%1' = '%2', "
|
|
"setting to default property units '%3'\n")
|
|
.arg(propertyName)
|
|
.arg(QString::fromStdString(quantity.getUserString()))
|
|
.arg(property->getUnits()));
|
|
msgBox.exec();
|
|
|
|
property->setQuantity(
|
|
Base::Quantity(quantity.getValue(), property->getUnits().toStdString()));
|
|
}
|
|
|
|
group->child(row, 1)->setText(property->getString());
|
|
}
|
|
|
|
notifyChanged(model, index);
|
|
}
|
|
|
|
void MaterialDelegate::notifyChanged(const QAbstractItemModel* model,
|
|
const QModelIndex& index) const
|
|
{
|
|
auto treeModel = dynamic_cast<const QStandardItemModel*>(model);
|
|
auto item = treeModel->itemFromIndex(index);
|
|
auto group = item->parent();
|
|
if (!group) {
|
|
return;
|
|
}
|
|
|
|
int row = index.row();
|
|
if (group->child(row, 1)) {
|
|
auto material = group->child(row, 1)->data().value<std::shared_ptr<Materials::Material>>();
|
|
// auto propertyName = group->child(row, 0)->text();
|
|
auto propertyName = group->child(row, 0)->data().toString();
|
|
auto propertyValue = material->getProperty(propertyName)->getValue();
|
|
material->setEditStateAlter();
|
|
|
|
Q_EMIT const_cast<MaterialDelegate*>(this)->propertyChange(propertyName, propertyValue);
|
|
}
|
|
}
|
|
|
|
bool MaterialDelegate::editorEvent(QEvent* event,
|
|
QAbstractItemModel* model,
|
|
const QStyleOptionViewItem& option,
|
|
const QModelIndex& index)
|
|
{
|
|
if (index.column() == 1) {
|
|
if (event->type() == QEvent::MouseButtonDblClick) {
|
|
auto treeModel = dynamic_cast<const QStandardItemModel*>(index.model());
|
|
|
|
// Check we're not the material model root. This is also used to access the entry
|
|
// columns
|
|
auto item = treeModel->itemFromIndex(index);
|
|
auto group = item->parent();
|
|
if (!group) {
|
|
return QStyledItemDelegate::editorEvent(event, model, option, index);
|
|
}
|
|
|
|
int row = index.row();
|
|
|
|
// QString propertyName = group->child(row, 0)->text();
|
|
QString propertyName = group->child(row, 0)->data().toString();
|
|
|
|
auto type = getType(index);
|
|
if (type == Materials::MaterialValue::Color) {
|
|
showColorModal(propertyName, item);
|
|
// Mark as handled
|
|
return true;
|
|
}
|
|
if (type == Materials::MaterialValue::MultiLineString) {
|
|
showMultiLineStringModal(propertyName, item);
|
|
// Mark as handled
|
|
return true;
|
|
}
|
|
if (type == Materials::MaterialValue::List || type == Materials::MaterialValue::FileList
|
|
|| type == Materials::MaterialValue::ImageList) {
|
|
showListModal(propertyName, item);
|
|
// Mark as handled
|
|
return true;
|
|
}
|
|
if (type == Materials::MaterialValue::Array2D) {
|
|
showArray2DModal(propertyName, item);
|
|
// Mark as handled
|
|
return true;
|
|
}
|
|
if (type == Materials::MaterialValue::Array3D) {
|
|
showArray3DModal(propertyName, item);
|
|
// Mark as handled
|
|
return true;
|
|
}
|
|
if (type == Materials::MaterialValue::Image || type == Materials::MaterialValue::SVG) {
|
|
showImageModal(propertyName, item);
|
|
// Mark as handled
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return QStyledItemDelegate::editorEvent(event, model, option, index);
|
|
}
|
|
|
|
void MaterialDelegate::showColorModal(const QString& propertyName, QStandardItem* item)
|
|
{
|
|
QColor currentColor; // = d->col;
|
|
currentColor.setRgba(parseColor(item->text()));
|
|
auto dlg = new QColorDialog(currentColor);
|
|
|
|
dlg->setAttribute(Qt::WA_DeleteOnClose);
|
|
if (Gui::DialogOptions::dontUseNativeColorDialog()) {
|
|
dlg->setOptions(QColorDialog::DontUseNativeDialog);
|
|
}
|
|
dlg->setOption(QColorDialog::ColorDialogOption::ShowAlphaChannel, false);
|
|
|
|
dlg->setCurrentColor(currentColor);
|
|
dlg->adjustSize();
|
|
|
|
connect(dlg, &QColorDialog::finished, this, [&](int result) {
|
|
if (result == QDialog::Accepted) {
|
|
QColor color = dlg->selectedColor();
|
|
if (color.isValid()) {
|
|
QString colorText = QString(QStringLiteral("(%1,%2,%3,%4)"))
|
|
.arg(color.red() / 255.0)
|
|
.arg(color.green() / 255.0)
|
|
.arg(color.blue() / 255.0)
|
|
.arg(color.alpha() / 255.0);
|
|
item->setText(colorText);
|
|
Q_EMIT const_cast<MaterialDelegate*>(this)->propertyChange(propertyName,
|
|
item->text());
|
|
}
|
|
}
|
|
});
|
|
|
|
dlg->exec();
|
|
}
|
|
|
|
void MaterialDelegate::showImageModal(const QString& propertyName, QStandardItem* item)
|
|
{
|
|
auto material = item->data().value<std::shared_ptr<Materials::Material>>();
|
|
auto dlg = new ImageEdit(propertyName, material);
|
|
|
|
dlg->setAttribute(Qt::WA_DeleteOnClose);
|
|
|
|
dlg->adjustSize();
|
|
|
|
// connect(dlg, &QDialog::finished, this, [&](int result) {});
|
|
|
|
dlg->exec();
|
|
}
|
|
|
|
void MaterialDelegate::showListModal(const QString& propertyName, QStandardItem* item)
|
|
{
|
|
auto material = item->data().value<std::shared_ptr<Materials::Material>>();
|
|
auto dlg = new ListEdit(propertyName, material);
|
|
|
|
dlg->setAttribute(Qt::WA_DeleteOnClose);
|
|
|
|
dlg->adjustSize();
|
|
|
|
// connect(dlg, &QDialog::finished, this, [&](int result) {});
|
|
|
|
dlg->exec();
|
|
}
|
|
|
|
void MaterialDelegate::showMultiLineStringModal(const QString& propertyName, QStandardItem* item)
|
|
{
|
|
auto material = item->data().value<std::shared_ptr<Materials::Material>>();
|
|
auto dlg = new TextEdit(propertyName, material);
|
|
|
|
dlg->setAttribute(Qt::WA_DeleteOnClose);
|
|
|
|
dlg->adjustSize();
|
|
|
|
// connect(dlg, &QDialog::finished, this, [&](int result) {});
|
|
|
|
dlg->exec();
|
|
}
|
|
|
|
|
|
void MaterialDelegate::showArray2DModal(const QString& propertyName, QStandardItem* item)
|
|
{
|
|
auto material = item->data().value<std::shared_ptr<Materials::Material>>();
|
|
auto dlg = new Array2D(propertyName, material);
|
|
|
|
dlg->setAttribute(Qt::WA_DeleteOnClose);
|
|
|
|
dlg->adjustSize();
|
|
|
|
// connect(dlg, &QDialog::finished, this, [&](int result) {});
|
|
|
|
dlg->exec();
|
|
}
|
|
|
|
void MaterialDelegate::showArray3DModal(const QString& propertyName, QStandardItem* item)
|
|
{
|
|
auto material = item->data().value<std::shared_ptr<Materials::Material>>();
|
|
auto dlg = new Array3D(propertyName, material);
|
|
|
|
dlg->setAttribute(Qt::WA_DeleteOnClose);
|
|
|
|
dlg->adjustSize();
|
|
|
|
// connect(dlg, &QDialog::finished, this, [&](int result) {});
|
|
|
|
dlg->exec();
|
|
}
|
|
|
|
void MaterialDelegate::paint(QPainter* painter,
|
|
const QStyleOptionViewItem& option,
|
|
const QModelIndex& index) const
|
|
{
|
|
if (index.column() != 1) {
|
|
QStyledItemDelegate::paint(painter, option, index);
|
|
return;
|
|
}
|
|
|
|
auto treeModel = dynamic_cast<const QStandardItemModel*>(index.model());
|
|
|
|
// Check we're not the material model root. This is also used to access the entry columns
|
|
auto item = treeModel->itemFromIndex(index);
|
|
auto group = item->parent();
|
|
if (!group) {
|
|
QStyledItemDelegate::paint(painter, option, index);
|
|
return;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
QWidget* MaterialDelegate::createEditor(QWidget* parent,
|
|
const QStyleOptionViewItem& styleOption,
|
|
const QModelIndex& index) const
|
|
{
|
|
Q_UNUSED(styleOption)
|
|
|
|
if (index.column() != 1) {
|
|
return nullptr;
|
|
}
|
|
|
|
auto treeModel = dynamic_cast<const QStandardItemModel*>(index.model());
|
|
|
|
// Check we're not the material model root. This is also used to access the entry columns
|
|
auto item = treeModel->itemFromIndex(index);
|
|
auto group = item->parent();
|
|
if (!group) {
|
|
return nullptr;
|
|
}
|
|
|
|
QVariant value = treeModel->data(index);
|
|
QWidget* editor = createWidget(parent, value, index);
|
|
|
|
return editor;
|
|
}
|
|
|
|
QWidget* MaterialDelegate::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::IntSpinBox(parent);
|
|
spinner->setMinimum(0);
|
|
spinner->setMaximum(std::numeric_limits<int>::max());
|
|
spinner->setValue(item.toInt());
|
|
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<double>::min());
|
|
spinner->setMaximum(std::numeric_limits<double>::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::InputField(parent);
|
|
auto input = new Gui::QuantitySpinBox(parent);
|
|
input->setMinimum(std::numeric_limits<double>::min());
|
|
input->setMaximum(std::numeric_limits<double>::max());
|
|
input->setUnitText(getUnits(index));
|
|
// input->setPrecision(6);
|
|
input->setValue(item.value<Base::Quantity>());
|
|
|
|
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 {
|
|
// Default editor
|
|
auto lineEdit = new Gui::PrefLineEdit(parent);
|
|
lineEdit->setText(item.toString());
|
|
widget = lineEdit;
|
|
}
|
|
|
|
return widget;
|
|
}
|
|
|
|
#include "moc_MaterialDelegate.cpp"
|