630 lines
19 KiB
C++
630 lines
19 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 <QMenu>
|
|
#include <QMessageBox>
|
|
#include <QTreeView>
|
|
#endif
|
|
|
|
#include <Gui/MainWindow.h>
|
|
|
|
#include <Mod/Material/App/MaterialLibrary.h>
|
|
|
|
#include "MaterialSave.h"
|
|
#include "ui_MaterialSave.h"
|
|
|
|
using namespace MatGui;
|
|
|
|
/* TRANSLATOR MatGui::MaterialsEditor */
|
|
|
|
MaterialSave::MaterialSave(const std::shared_ptr<Materials::Material>& material, QWidget* parent)
|
|
: QDialog(parent)
|
|
, ui(new Ui_MaterialSave)
|
|
, _material(material)
|
|
, _saveInherited(true)
|
|
, _selectedPath(QString::fromStdString("/"))
|
|
, _selectedFull(QString::fromStdString("/"))
|
|
, _selectedUUID()
|
|
, _deleteAction(this)
|
|
{
|
|
ui->setupUi(this);
|
|
|
|
setLibraries();
|
|
createModelTree();
|
|
showSelectedTree();
|
|
|
|
if (_material->getName().length() > 0) {
|
|
ui->editFilename->setText(_material->getName() + QString::fromStdString(".FCMat"));
|
|
}
|
|
else {
|
|
ui->editFilename->setText(QString::fromStdString("NewMaterial.FCMat"));
|
|
}
|
|
_filename = QString(ui->editFilename->text()); // No filename by default
|
|
|
|
ui->checkDerived->setChecked(_saveInherited);
|
|
connect(ui->checkDerived, &QCheckBox::stateChanged, this, &MaterialSave::onInherited);
|
|
|
|
connect(ui->standardButtons->button(QDialogButtonBox::Ok),
|
|
&QPushButton::clicked,
|
|
this,
|
|
&MaterialSave::onOk);
|
|
connect(ui->standardButtons->button(QDialogButtonBox::Cancel),
|
|
&QPushButton::clicked,
|
|
this,
|
|
&MaterialSave::onCancel);
|
|
|
|
connect(ui->comboLibrary,
|
|
&QComboBox::currentTextChanged,
|
|
this,
|
|
&MaterialSave::currentTextChanged);
|
|
connect(ui->buttonNewFolder, &QPushButton::clicked, this, &MaterialSave::onNewFolder);
|
|
connect(ui->editFilename, &QLineEdit::textEdited, this, &MaterialSave::onFilename);
|
|
|
|
ui->treeMaterials->setContextMenuPolicy(Qt::CustomContextMenu);
|
|
connect(ui->treeMaterials,
|
|
&QWidget::customContextMenuRequested,
|
|
this,
|
|
&MaterialSave::onContextMenu);
|
|
|
|
_deleteAction.setText(tr("Delete"));
|
|
_deleteAction.setShortcut(QKeySequence::Delete);
|
|
connect(&_deleteAction, &QAction::triggered, this, &MaterialSave::onDelete);
|
|
ui->treeMaterials->addAction(&_deleteAction);
|
|
|
|
QItemSelectionModel* selectionModel = ui->treeMaterials->selectionModel();
|
|
connect(selectionModel,
|
|
&QItemSelectionModel::selectionChanged,
|
|
this,
|
|
&MaterialSave::onSelectModel);
|
|
|
|
auto model = static_cast<QStandardItemModel*>(ui->treeMaterials->model());
|
|
connect(model, &QStandardItemModel::itemChanged, this, &MaterialSave::onItemChanged);
|
|
}
|
|
|
|
/*
|
|
* Destroys the object and frees any allocated resources
|
|
*/
|
|
MaterialSave::~MaterialSave()
|
|
{
|
|
// no need to delete child widgets, Qt does it all for us
|
|
}
|
|
|
|
void MaterialSave::onInherited(int state)
|
|
{
|
|
Q_UNUSED(state)
|
|
|
|
_saveInherited = ui->checkDerived->isChecked();
|
|
}
|
|
|
|
void MaterialSave::onOk(bool checked)
|
|
{
|
|
Q_UNUSED(checked)
|
|
|
|
QString name = _filename.remove(QString::fromStdString(".FCMat"), Qt::CaseInsensitive);
|
|
if (name != _material->getName()) {
|
|
_material->setName(name);
|
|
_material->setEditStateAlter(); // ? Does a name change count?
|
|
}
|
|
|
|
auto variant = ui->comboLibrary->currentData();
|
|
auto library = variant.value<std::shared_ptr<Materials::MaterialLibrary>>();
|
|
QFileInfo filepath(_selectedPath + QString::fromStdString("/") + name
|
|
+ QString::fromStdString(".FCMat"));
|
|
|
|
if (library->fileExists(filepath.filePath())) {
|
|
// confirm overwrite
|
|
auto res = confirmOverwrite(_filename);
|
|
if (res == QMessageBox::Cancel) {
|
|
return;
|
|
}
|
|
|
|
_manager.saveMaterial(library, _material, filepath.filePath(), true, false, _saveInherited);
|
|
accept();
|
|
return;
|
|
}
|
|
|
|
bool saveAsCopy = false;
|
|
if (_manager.exists(_material->getUUID())) {
|
|
// Does it already exist in this library?
|
|
if (_manager.exists(library, _material->getUUID())) {
|
|
// Confirm saving a new material
|
|
auto res = confirmNewMaterial();
|
|
if (res == QMessageBox::Cancel) {
|
|
return;
|
|
}
|
|
// saveAsCopy = false = already done
|
|
}
|
|
else {
|
|
// Copy or new
|
|
auto res = confirmCopy();
|
|
if (res == QMessageBox::Cancel) {
|
|
return;
|
|
}
|
|
if (res == QMessageBox::Save) {
|
|
// QMessageBox::Save saves as normal, a duplicate
|
|
saveAsCopy = true;
|
|
}
|
|
// QMessageBox::Ok saves a new material
|
|
}
|
|
}
|
|
|
|
_manager
|
|
.saveMaterial(library, _material, filepath.filePath(), false, saveAsCopy, _saveInherited);
|
|
|
|
accept();
|
|
}
|
|
|
|
int MaterialSave::confirmOverwrite(const QString& filename)
|
|
{
|
|
QMessageBox box(this);
|
|
box.setIcon(QMessageBox::Question);
|
|
box.setWindowTitle(QObject::tr("Confirm Overwrite"));
|
|
|
|
QFileInfo info(_selectedFull);
|
|
QString prompt = tr("Are you sure you want to save over '%1'?").arg(filename);
|
|
box.setText(prompt);
|
|
|
|
box.setInformativeText(tr("Saving over the original file may cause other documents to break. "
|
|
"This is not recommended."));
|
|
|
|
box.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel);
|
|
box.setDefaultButton(QMessageBox::Cancel);
|
|
box.setEscapeButton(QMessageBox::Cancel);
|
|
|
|
int res = QMessageBox::Cancel;
|
|
box.adjustSize(); // Silence warnings from Qt on Windows
|
|
switch (box.exec()) {
|
|
case QMessageBox::Ok:
|
|
res = QMessageBox::Ok;
|
|
break;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
int MaterialSave::confirmNewMaterial()
|
|
{
|
|
QMessageBox box(this);
|
|
box.setIcon(QMessageBox::Question);
|
|
box.setWindowTitle(tr("Confirm Save As New Material"));
|
|
|
|
QString prompt = tr("Save as new material");
|
|
box.setText(prompt);
|
|
|
|
box.setInformativeText(tr(
|
|
"This material already exists in this library. Would you like to save as a new material?"));
|
|
|
|
box.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel);
|
|
box.setDefaultButton(QMessageBox::Cancel);
|
|
box.setEscapeButton(QMessageBox::Cancel);
|
|
|
|
int res = QMessageBox::Cancel;
|
|
box.adjustSize(); // Silence warnings from Qt on Windows
|
|
switch (box.exec()) {
|
|
case QMessageBox::Ok:
|
|
res = QMessageBox::Ok;
|
|
break;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
int MaterialSave::confirmCopy()
|
|
{
|
|
QMessageBox box(this);
|
|
box.setIcon(QMessageBox::Question);
|
|
box.setWindowTitle(tr("Confirm Save As Copy"));
|
|
|
|
QString prompt = tr("Save as Copy");
|
|
box.setText(prompt);
|
|
|
|
box.setInformativeText(tr("Saving a copy is not recommended as it can break other documents. "
|
|
"We recommend you save as a new material."));
|
|
|
|
QPushButton* duplicateButton = box.addButton(tr("Save Copy"), QMessageBox::AcceptRole);
|
|
QPushButton* newButton = box.addButton(tr("Save As New"), QMessageBox::ActionRole);
|
|
QPushButton* cancelButton = box.addButton(QMessageBox::Cancel);
|
|
|
|
box.setDefaultButton(cancelButton);
|
|
box.setEscapeButton(cancelButton);
|
|
|
|
box.adjustSize(); // Silence warnings from Qt on Windows
|
|
box.exec();
|
|
|
|
int res = QMessageBox::Cancel;
|
|
if (box.clickedButton() == duplicateButton) {
|
|
res = QMessageBox::Save;
|
|
}
|
|
else if (box.clickedButton() == newButton) {
|
|
res = QMessageBox::Ok;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
void MaterialSave::onCancel(bool checked)
|
|
{
|
|
Q_UNUSED(checked)
|
|
|
|
reject();
|
|
}
|
|
|
|
void MaterialSave::accept()
|
|
{
|
|
QDialog::accept();
|
|
}
|
|
|
|
void MaterialSave::reject()
|
|
{
|
|
QDialog::reject();
|
|
}
|
|
|
|
void MaterialSave::setLibraries()
|
|
{
|
|
auto libraries = _manager.getMaterialLibraries();
|
|
for (auto& library : *libraries) {
|
|
if (!library->isReadOnly()) {
|
|
QVariant libraryVariant;
|
|
libraryVariant.setValue(library);
|
|
ui->comboLibrary->addItem(library->getName(), libraryVariant);
|
|
}
|
|
}
|
|
}
|
|
|
|
void MaterialSave::createModelTree()
|
|
{
|
|
auto tree = ui->treeMaterials;
|
|
auto model = new QStandardItemModel();
|
|
tree->setModel(model);
|
|
tree->setHeaderHidden(true);
|
|
}
|
|
|
|
void MaterialSave::addExpanded(QTreeView* tree, QStandardItem* parent, QStandardItem* child)
|
|
{
|
|
parent->appendRow(child);
|
|
tree->setExpanded(child->index(), true);
|
|
}
|
|
|
|
void MaterialSave::addExpanded(QTreeView* tree, QStandardItemModel* parent, QStandardItem* child)
|
|
{
|
|
parent->appendRow(child);
|
|
tree->setExpanded(child->index(), true);
|
|
}
|
|
|
|
void MaterialSave::addMaterials(
|
|
QStandardItem& parent,
|
|
const std::shared_ptr<std::map<QString, std::shared_ptr<Materials::MaterialTreeNode>>>
|
|
modelTree,
|
|
const QIcon& folderIcon,
|
|
const QIcon& icon)
|
|
{
|
|
auto tree = ui->treeMaterials;
|
|
for (auto& mat : *modelTree) {
|
|
std::shared_ptr<Materials::MaterialTreeNode> nodePtr = mat.second;
|
|
if (nodePtr->getType() == Materials::MaterialTreeNode::DataNode) {
|
|
std::shared_ptr<Materials::Material> material = nodePtr->getData();
|
|
QString uuid = material->getUUID();
|
|
|
|
auto card = new QStandardItem(icon, mat.first);
|
|
card->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled
|
|
| Qt::ItemIsDropEnabled);
|
|
card->setData(QVariant(uuid), Qt::UserRole);
|
|
|
|
addExpanded(tree, &parent, card);
|
|
}
|
|
else {
|
|
auto node = new QStandardItem(folderIcon, mat.first);
|
|
node->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled
|
|
| Qt::ItemIsDropEnabled | Qt::ItemIsEditable);
|
|
addExpanded(tree, &parent, node);
|
|
auto treeMap = nodePtr->getFolder();
|
|
addMaterials(*node, treeMap, folderIcon, icon);
|
|
}
|
|
}
|
|
}
|
|
|
|
void MaterialSave::showSelectedTree()
|
|
{
|
|
auto tree = ui->treeMaterials;
|
|
auto model = static_cast<QStandardItemModel*>(tree->model());
|
|
model->clear();
|
|
|
|
if (ui->comboLibrary->count() > 0) {
|
|
auto variant = ui->comboLibrary->currentData();
|
|
auto library = variant.value<std::shared_ptr<Materials::MaterialLibrary>>();
|
|
QIcon icon(library->getIconPath());
|
|
QIcon folderIcon(QString::fromStdString(":/icons/folder.svg"));
|
|
_libraryName = library->getName();
|
|
_selectedPath = QString::fromStdString("/") + _libraryName;
|
|
_selectedFull = _selectedPath;
|
|
|
|
auto lib = new QStandardItem(library->getName());
|
|
lib->setFlags(Qt::ItemIsEnabled | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled);
|
|
addExpanded(tree, model, lib);
|
|
|
|
auto modelTree = _manager.getMaterialTree(library);
|
|
addMaterials(*lib, modelTree, folderIcon, icon);
|
|
}
|
|
else {
|
|
QMessageBox::warning(Gui::getMainWindow(),
|
|
QObject::tr("No writeable library"),
|
|
QObject::tr("No writeable library"));
|
|
}
|
|
}
|
|
|
|
QString MaterialSave::getPath(const QStandardItem* item) const
|
|
{
|
|
QString path = QString::fromStdString("/");
|
|
if (item) {
|
|
path = path + item->text();
|
|
if (item->parent()) {
|
|
return getPath(item->parent()) + path;
|
|
}
|
|
}
|
|
|
|
return path;
|
|
}
|
|
|
|
void MaterialSave::onSelectModel(const QItemSelection& selected, const QItemSelection& deselected)
|
|
{
|
|
// Q_UNUSED(selected);
|
|
Q_UNUSED(deselected);
|
|
|
|
_filename = QString(ui->editFilename->text()); // No filename by default
|
|
auto model = static_cast<QStandardItemModel*>(ui->treeMaterials->model());
|
|
QModelIndexList indexes = selected.indexes();
|
|
if (indexes.count() == 0) {
|
|
_selectedPath = QString::fromStdString("/") + _libraryName;
|
|
_selectedFull = _selectedPath;
|
|
_selectedUUID = QString();
|
|
return;
|
|
}
|
|
for (auto it = indexes.begin(); it != indexes.end(); it++) {
|
|
QStandardItem* item = model->itemFromIndex(*it);
|
|
if (item) {
|
|
auto _selected = item->data(Qt::UserRole);
|
|
if (_selected.isValid()) {
|
|
_selectedPath = getPath(item->parent());
|
|
_selectedFull = getPath(item);
|
|
_selectedUUID = _selected.toString();
|
|
_filename = item->text();
|
|
}
|
|
else {
|
|
_selectedPath = getPath(item);
|
|
_selectedFull = _selectedPath;
|
|
_selectedUUID = QString();
|
|
}
|
|
}
|
|
}
|
|
if (_filename.length() > 0) {
|
|
ui->editFilename->setText(_filename);
|
|
}
|
|
}
|
|
|
|
void MaterialSave::currentTextChanged(const QString& value)
|
|
{
|
|
Q_UNUSED(value)
|
|
|
|
showSelectedTree();
|
|
}
|
|
|
|
std::shared_ptr<Materials::MaterialLibrary> MaterialSave::currentLibrary()
|
|
{
|
|
auto variant = ui->comboLibrary->currentData();
|
|
return variant.value<std::shared_ptr<Materials::MaterialLibrary>>();
|
|
}
|
|
|
|
void MaterialSave::createFolder(const QString& path)
|
|
{
|
|
auto library = currentLibrary();
|
|
|
|
_manager.createFolder(library, path);
|
|
}
|
|
|
|
void MaterialSave::renameFolder(const QString& oldPath, const QString& newPath)
|
|
{
|
|
auto library = currentLibrary();
|
|
|
|
_manager.renameFolder(library, oldPath, newPath);
|
|
}
|
|
|
|
void MaterialSave::deleteRecursive(const QString& path)
|
|
{
|
|
// This will delete files, folders, and any children
|
|
auto library = currentLibrary();
|
|
|
|
_manager.deleteRecursive(library, path);
|
|
}
|
|
|
|
void MaterialSave::onNewFolder(bool checked)
|
|
{
|
|
Q_UNUSED(checked)
|
|
|
|
auto tree = ui->treeMaterials;
|
|
auto model = static_cast<QStandardItemModel*>(tree->model());
|
|
auto current = tree->currentIndex();
|
|
if (!current.isValid()) {
|
|
current = model->index(0, 0);
|
|
}
|
|
auto item = model->itemFromIndex(current);
|
|
|
|
// Check for existing folders starting "New Folder" to prevent duplicates
|
|
int newCount = 0;
|
|
if (item->hasChildren()) {
|
|
for (auto i = 0; i < item->rowCount(); i++) {
|
|
auto child = item->child(i);
|
|
if (child->text().startsWith(tr("New Folder"))) {
|
|
newCount++;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Folders have no associated data
|
|
if (item->data(Qt::UserRole).isNull()) {
|
|
QIcon folderIcon(QString::fromStdString(":/icons/folder.svg"));
|
|
|
|
QString folderName = tr("New Folder");
|
|
if (newCount > 0) {
|
|
folderName += QString::number(newCount);
|
|
}
|
|
auto node = new QStandardItem(folderIcon, folderName);
|
|
node->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled
|
|
| Qt::ItemIsDropEnabled | Qt::ItemIsEditable);
|
|
addExpanded(tree, item, node);
|
|
|
|
QItemSelectionModel* selectionModel = ui->treeMaterials->selectionModel();
|
|
selectionModel->select(node->index(),
|
|
QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Current);
|
|
|
|
createFolder(getPath(node));
|
|
}
|
|
}
|
|
|
|
void MaterialSave::onItemChanged(QStandardItem* item)
|
|
{
|
|
QString oldPath = _selectedPath;
|
|
_selectedPath = getPath(item);
|
|
_selectedFull = _selectedPath;
|
|
|
|
renameFolder(oldPath, _selectedPath);
|
|
}
|
|
|
|
void MaterialSave::onFilename(const QString& text)
|
|
{
|
|
_filename = text;
|
|
}
|
|
|
|
QString MaterialSave::pathFromIndex(const QModelIndex& index) const
|
|
{
|
|
auto model = dynamic_cast<const QStandardItemModel*>(index.model());
|
|
auto item = model->itemFromIndex(index);
|
|
return getPath(item);
|
|
}
|
|
|
|
void MaterialSave::onContextMenu(const QPoint& pos)
|
|
{
|
|
QMenu contextMenu(tr("Context menu"), this);
|
|
|
|
contextMenu.addAction(&_deleteAction);
|
|
|
|
contextMenu.exec(ui->treeMaterials->mapToGlobal(pos));
|
|
}
|
|
|
|
void MaterialSave::onDelete(bool checked)
|
|
{
|
|
Q_UNUSED(checked)
|
|
|
|
QItemSelectionModel* selectionModel = ui->treeMaterials->selectionModel();
|
|
if (!selectionModel->hasSelection()) {
|
|
return;
|
|
}
|
|
|
|
confirmDelete(this);
|
|
}
|
|
|
|
int MaterialSave::confirmDelete(QWidget* parent)
|
|
{
|
|
auto library = currentLibrary();
|
|
|
|
if (library->isRoot(_selectedFull)) {
|
|
return QMessageBox::Cancel;
|
|
}
|
|
|
|
QMessageBox box(parent ? parent : this);
|
|
box.setIcon(QMessageBox::Question);
|
|
box.setWindowTitle(QObject::tr("Confirm Delete"));
|
|
|
|
QFileInfo info(_selectedFull);
|
|
QString prompt = QObject::tr("Are you sure you want to delete '%1'?").arg(info.fileName());
|
|
box.setText(prompt);
|
|
|
|
if (selectedHasChildren()) {
|
|
box.setInformativeText(QObject::tr("Removing this will also remove all contents."));
|
|
}
|
|
box.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel);
|
|
box.setDefaultButton(QMessageBox::Cancel);
|
|
box.setEscapeButton(QMessageBox::Cancel);
|
|
|
|
int res = QMessageBox::Cancel;
|
|
box.adjustSize(); // Silence warnings from Qt on Windows
|
|
switch (box.exec()) {
|
|
case QMessageBox::Ok:
|
|
deleteSelected();
|
|
res = QMessageBox::Ok;
|
|
break;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
bool MaterialSave::selectedHasChildren()
|
|
{
|
|
auto tree = ui->treeMaterials;
|
|
auto model = static_cast<QStandardItemModel*>(tree->model());
|
|
auto current = tree->currentIndex();
|
|
if (!current.isValid()) {
|
|
current = model->index(0, 0);
|
|
}
|
|
auto item = model->itemFromIndex(current);
|
|
|
|
return item->hasChildren();
|
|
}
|
|
|
|
void MaterialSave::deleteSelected()
|
|
{
|
|
auto library = currentLibrary();
|
|
|
|
if (!library->isRoot(_selectedFull)) {
|
|
_manager.deleteRecursive(library, _selectedFull);
|
|
removeSelectedFromTree();
|
|
}
|
|
}
|
|
|
|
void MaterialSave::removeChildren(QStandardItem* item)
|
|
{
|
|
while (item->rowCount() > 0) {
|
|
auto child = item->child(0);
|
|
removeChildren(child);
|
|
item->removeRow(0);
|
|
}
|
|
}
|
|
|
|
void MaterialSave::removeSelectedFromTree()
|
|
{
|
|
auto tree = ui->treeMaterials;
|
|
auto model = static_cast<QStandardItemModel*>(tree->model());
|
|
auto current = tree->currentIndex();
|
|
if (current.row() >= 0) {
|
|
auto item = model->itemFromIndex(current);
|
|
|
|
// Remove the children
|
|
removeChildren(item);
|
|
item->parent()->removeRow(item->row());
|
|
}
|
|
|
|
QItemSelectionModel* selectionModel = ui->treeMaterials->selectionModel();
|
|
selectionModel->clear();
|
|
}
|
|
|
|
#include "moc_MaterialSave.cpp"
|