// SPDX-License-Identifier: LGPL-2.1-or-later /*************************************************************************** * Copyright (c) 2017 Werner Mayer * * * * This file is part of the FreeCAD CAx development system. * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Library General Public * * License as published by the Free Software Foundation; either * * version 2 of the License, or (at your option) any later version. * * * * This library 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 Library General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this library; see the file COPYING.LIB. If not, * * write to the Free Software Foundation, Inc., 59 Temple Place, * * Suite 330, Boston, MA 02111-1307, USA * * * ***************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "TaskSections.h" #include "ui_TaskSections.h" using namespace SurfaceGui; PROPERTY_SOURCE(SurfaceGui::ViewProviderSections, PartGui::ViewProviderSpline) namespace SurfaceGui { void ViewProviderSections::setupContextMenu(QMenu* menu, QObject* receiver, const char* member) { QAction* act; act = menu->addAction(QObject::tr("Edit Sections"), receiver, member); act->setData(QVariant((int)ViewProvider::Default)); PartGui::ViewProviderSpline::setupContextMenu(menu, receiver, member); } bool ViewProviderSections::setEdit(int ModNum) { if (ModNum == ViewProvider::Default) { // When double-clicking on the item for this sketch the // object unsets and sets its edit mode without closing // the task panel Surface::Sections* obj = this->getObject(); Gui::TaskView::TaskDialog* dlg = Gui::Control().activeDialog(); // start the edit dialog if (dlg) { TaskSections* tDlg = qobject_cast(dlg); if (tDlg) { tDlg->setEditedObject(obj); } Gui::Control().showDialog(dlg); } else { Gui::Control().showDialog(new TaskSections(this, obj)); } return true; } else { return ViewProviderSpline::setEdit(ModNum); } } void ViewProviderSections::unsetEdit(int ModNum) { if (ModNum == ViewProvider::Default) { // when pressing ESC make sure to close the dialog QTimer::singleShot(0, &Gui::Control(), &Gui::ControlSingleton::closeDialog); } else { PartGui::ViewProviderSpline::unsetEdit(ModNum); } } QIcon ViewProviderSections::getIcon() const { return Gui::BitmapFactory().pixmap("Surface_Sections"); } void ViewProviderSections::highlightReferences(ShapeType type, const References& refs, bool on) { for (const auto& it : refs) { Part::Feature* base = dynamic_cast(it.first); if (base) { PartGui::ViewProviderPartExt* svp = dynamic_cast( Gui::Application::Instance->getViewProvider(base) ); if (svp) { switch (type) { case ViewProviderSections::Vertex: if (on) { std::vector colors; TopTools_IndexedMapOfShape vMap; TopExp::MapShapes(base->Shape.getValue(), TopAbs_VERTEX, vMap); colors.resize(vMap.Extent(), svp->PointColor.getValue()); for (const auto& jt : it.second) { // check again that the index is in range because it's possible that // the sub-names are invalid std::size_t idx = static_cast(std::stoi(jt.substr(6)) - 1); if (idx < colors.size()) { colors[idx] = Base::Color(1.0, 0.0, 1.0); // magenta } } svp->setHighlightedPoints(colors); } else { svp->unsetHighlightedPoints(); } break; case ViewProviderSections::Edge: if (on) { std::vector colors; TopTools_IndexedMapOfShape eMap; TopExp::MapShapes(base->Shape.getValue(), TopAbs_EDGE, eMap); colors.resize(eMap.Extent(), svp->LineColor.getValue()); for (const auto& jt : it.second) { std::size_t idx = static_cast(std::stoi(jt.substr(4)) - 1); // check again that the index is in range because it's possible that // the sub-names are invalid if (idx < colors.size()) { colors[idx] = Base::Color(1.0, 0.0, 1.0); // magenta } } svp->setHighlightedEdges(colors); } else { svp->unsetHighlightedEdges(); } break; case ViewProviderSections::Face: if (on) { std::vector materials; TopTools_IndexedMapOfShape fMap; TopExp::MapShapes(base->Shape.getValue(), TopAbs_FACE, fMap); materials.resize(fMap.Extent(), svp->ShapeAppearance[0]); for (const auto& jt : it.second) { std::size_t idx = static_cast(std::stoi(jt.substr(4)) - 1); // check again that the index is in range because it's possible that // the sub-names are invalid if (idx < materials.size()) { // magenta materials[idx].diffuseColor = Base::Color(1.0, 0.0, 1.0); } } svp->setHighlightedFaces(materials); } else { svp->unsetHighlightedFaces(); } break; } } } } } // ---------------------------------------------------------------------------- class SectionsPanel::ShapeSelection: public Gui::SelectionFilterGate { public: ShapeSelection(SectionsPanel::SelectionMode& mode, Surface::Sections* editedObject) : Gui::SelectionFilterGate(nullPointer()) , mode(mode) , editedObject(editedObject) {} ~ShapeSelection() override { mode = SectionsPanel::None; } /** * Allow the user to pick only edges. */ bool allow(App::Document*, App::DocumentObject* pObj, const char* sSubName) override { // don't allow references to itself if (pObj == editedObject) { return false; } if (!pObj->isDerivedFrom()) { return false; } if (Base::Tools::isNullOrEmpty(sSubName)) { return false; } switch (mode) { case SectionsPanel::AppendEdge: return allowEdge(true, pObj, sSubName); case SectionsPanel::RemoveEdge: return allowEdge(false, pObj, sSubName); default: return false; } } private: bool allowEdge(bool appendEdges, App::DocumentObject* pObj, const char* sSubName) { std::string element(sSubName); if (element.substr(0, 4) != "Edge") { return false; } auto links = editedObject->NSections.getSubListValues(); for (const auto& it : links) { if (it.first == pObj) { for (const auto& jt : it.second) { if (jt == sSubName) { return !appendEdges; } } } } return appendEdges; } private: SectionsPanel::SelectionMode& mode; Surface::Sections* editedObject; }; // ---------------------------------------------------------------------------- SectionsPanel::SectionsPanel(ViewProviderSections* vp, Surface::Sections* obj) : ui(new Ui_Sections()) { ui->setupUi(this); setupConnections(); ui->statusLabel->clear(); selectionMode = None; this->vp = vp; checkCommand = true; setEditedObject(obj); // Set up button group buttonGroup = new Gui::ButtonGroup(this); buttonGroup->setExclusive(true); buttonGroup->addButton(ui->buttonEdgeAdd, (int)SelectionMode::AppendEdge); buttonGroup->addButton(ui->buttonEdgeRemove, (int)SelectionMode::RemoveEdge); // Create context menu QAction* action = new QAction(tr("Remove"), this); action->setShortcut(Gui::QtTools::deleteKeySequence()); ui->listSections->addAction(action); connect(action, &QAction::triggered, this, &SectionsPanel::onDeleteEdge); ui->listSections->setContextMenuPolicy(Qt::ActionsContextMenu); connect(ui->listSections->model(), &QAbstractItemModel::rowsMoved, this, &SectionsPanel::onIndexesMoved); } /* * Destroys the object and frees any allocated resources */ SectionsPanel::~SectionsPanel() = default; void SectionsPanel::setupConnections() { connect(ui->buttonEdgeAdd, &QToolButton::toggled, this, &SectionsPanel::onButtonEdgeAddToggled); connect(ui->buttonEdgeRemove, &QToolButton::toggled, this, &SectionsPanel::onButtonEdgeRemoveToggled); } // stores object pointer, its old fill type and adjusts radio buttons according to it. void SectionsPanel::setEditedObject(Surface::Sections* fea) { editedObject = fea; // get the section edges auto objects = editedObject->NSections.getValues(); auto edges = editedObject->NSections.getSubValues(); auto count = objects.size(); App::Document* doc = editedObject->getDocument(); for (std::size_t i = 0; i < count; i++) { App::DocumentObject* obj = objects[i]; std::string edge = edges[i]; QListWidgetItem* item = new QListWidgetItem(ui->listSections); ui->listSections->addItem(item); QString text = QStringLiteral("%1.%2").arg( QString::fromUtf8(obj->Label.getValue()), QString::fromStdString(edge) ); item->setText(text); // The user data field of a list widget item // is a list of five elementa: // 1. document name // 2. object name // 3. sub-element name of the edge QList data; data << QByteArray(doc->getName()); data << QByteArray(obj->getNameInDocument()); data << QByteArray(edge.c_str()); item->setData(Qt::UserRole, data); } // attach this document observer attachDocument(Gui::Application::Instance->getDocument(doc)); } void SectionsPanel::changeEvent(QEvent* e) { if (e->type() == QEvent::LanguageChange) { ui->retranslateUi(this); } else { QWidget::changeEvent(e); } } void SectionsPanel::open() { checkOpenCommand(); // highlight the boundary edges this->vp->highlightReferences( ViewProviderSections::Edge, editedObject->NSections.getSubListValues(), true ); Gui::Selection().clearSelection(); // if the surface is not yet created then automatically start "AppendEdge" mode if (editedObject->Shape.getShape().isNull()) { ui->buttonEdgeAdd->setChecked(true); } } void SectionsPanel::clearSelection() { Gui::Selection().clearSelection(); } void SectionsPanel::checkOpenCommand() { if (checkCommand && !Gui::Command::hasPendingCommand()) { std::string Msg("Edit "); Msg += editedObject->Label.getValue(); Gui::Command::openCommand(Msg.c_str()); checkCommand = false; } } void SectionsPanel::slotUndoDocument(const Gui::Document&) { checkCommand = true; } void SectionsPanel::slotRedoDocument(const Gui::Document&) { checkCommand = true; } void SectionsPanel::slotDeletedObject(const Gui::ViewProviderDocumentObject& Obj) { // If this view provider is being deleted then reset the colors of // referenced part objects. The dialog will be deleted later. if (this->vp == &Obj) { this->vp->highlightReferences( ViewProviderSections::Edge, editedObject->NSections.getSubListValues(), false ); } } bool SectionsPanel::accept() { selectionMode = None; Gui::Selection().rmvSelectionGate(); if (editedObject->mustExecute()) { editedObject->recomputeFeature(); } if (!editedObject->isValid()) { QMessageBox::warning( this, tr("Invalid object"), QString::fromLatin1(editedObject->getStatusString()) ); return false; } this->vp->highlightReferences( ViewProviderSections::Edge, editedObject->NSections.getSubListValues(), false ); return true; } bool SectionsPanel::reject() { this->vp->highlightReferences( ViewProviderSections::Edge, editedObject->NSections.getSubListValues(), false ); selectionMode = None; Gui::Selection().rmvSelectionGate(); return true; } void SectionsPanel::onButtonEdgeAddToggled(bool checked) { if (checked) { selectionMode = AppendEdge; // 'selectionMode' is passed by reference and changed when the filter is deleted Gui::Selection().addSelectionGate(new ShapeSelection(selectionMode, editedObject)); } else if (selectionMode == AppendEdge) { exitSelectionMode(); } } void SectionsPanel::onButtonEdgeRemoveToggled(bool checked) { if (checked) { selectionMode = RemoveEdge; // 'selectionMode' is passed by reference and changed when the filter is deleted Gui::Selection().addSelectionGate(new ShapeSelection(selectionMode, editedObject)); } else if (selectionMode == RemoveEdge) { exitSelectionMode(); } } void SectionsPanel::onSelectionChanged(const Gui::SelectionChanges& msg) { if (selectionMode == None) { return; } if (msg.Type == Gui::SelectionChanges::AddSelection) { checkOpenCommand(); if (selectionMode == AppendEdge) { QListWidgetItem* item = new QListWidgetItem(ui->listSections); ui->listSections->addItem(item); Gui::SelectionObject sel(msg); QString text = QStringLiteral("%1.%2").arg( QString::fromUtf8(sel.getObject()->Label.getValue()), QString::fromLatin1(msg.pSubName) ); item->setText(text); QList data; data << QByteArray(msg.pDocName); data << QByteArray(msg.pObjectName); data << QByteArray(msg.pSubName); item->setData(Qt::UserRole, data); appendCurve(sel.getObject(), msg.pSubName); } else if (selectionMode == RemoveEdge) { Gui::SelectionObject sel(msg); QList data; data << QByteArray(msg.pDocName); data << QByteArray(msg.pObjectName); data << QByteArray(msg.pSubName); // only the three first elements must match for (int i = 0; i < ui->listSections->count(); i++) { QListWidgetItem* item = ui->listSections->item(i); QList userdata = item->data(Qt::UserRole).toList(); if (userdata.mid(0, 3) == data) { ui->listSections->takeItem(i); delete item; break; } } removeCurve(sel.getObject(), msg.pSubName); } editedObject->recomputeFeature(); QTimer::singleShot(50, this, &SectionsPanel::clearSelection); } } void SectionsPanel::onDeleteEdge() { int row = ui->listSections->currentRow(); QListWidgetItem* item = ui->listSections->takeItem(row); if (item) { checkOpenCommand(); QList data; data = item->data(Qt::UserRole).toList(); delete item; App::Document* doc = App::GetApplication().getDocument(data[0].toByteArray()); App::DocumentObject* obj = doc ? doc->getObject(data[1].toByteArray()) : nullptr; std::string sub = data[2].toByteArray().constData(); removeCurve(obj, sub); editedObject->recomputeFeature(); } } void SectionsPanel::onIndexesMoved() { QAbstractItemModel* model = qobject_cast(sender()); if (!model) { return; } std::vector objects; std::vector element; int rows = model->rowCount(); for (int i = 0; i < rows; i++) { QModelIndex index = model->index(i, 0); QList data; data = index.data(Qt::UserRole).toList(); App::Document* doc = App::GetApplication().getDocument(data[0].toByteArray()); App::DocumentObject* obj = doc ? doc->getObject(data[1].toByteArray()) : nullptr; std::string sub = data[2].toByteArray().constData(); objects.push_back(obj); element.push_back(sub); } editedObject->NSections.setValues(objects, element); editedObject->recomputeFeature(); } void SectionsPanel::appendCurve(App::DocumentObject* obj, const std::string& subname) { auto objects = editedObject->NSections.getValues(); objects.push_back(obj); auto element = editedObject->NSections.getSubValues(); element.push_back(subname); editedObject->NSections.setValues(objects, element); this->vp->highlightReferences( ViewProviderSections::Edge, editedObject->NSections.getSubListValues(), true ); } void SectionsPanel::removeCurve(App::DocumentObject* obj, const std::string& subname) { this->vp->highlightReferences( ViewProviderSections::Edge, editedObject->NSections.getSubListValues(), false ); auto objects = editedObject->NSections.getValues(); auto element = editedObject->NSections.getSubValues(); auto it = objects.begin(); auto jt = element.begin(); for (; it != objects.end() && jt != element.end(); ++it, ++jt) { if (*it == obj && *jt == subname) { objects.erase(it); element.erase(jt); editedObject->NSections.setValues(objects, element); break; } } this->vp->highlightReferences( ViewProviderSections::Edge, editedObject->NSections.getSubListValues(), true ); } void SectionsPanel::exitSelectionMode() { // 'selectionMode' is passed by reference to the filter and changed when the filter is deleted Gui::Selection().clearSelection(); Gui::Selection().rmvSelectionGate(); } // ---------------------------------------------------------------------------- TaskSections::TaskSections(ViewProviderSections* vp, Surface::Sections* obj) { // first task box widget1 = new SectionsPanel(vp, obj); addTaskBox(Gui::BitmapFactory().pixmap("Surface_Sections"), widget1); } void TaskSections::setEditedObject(Surface::Sections* obj) { widget1->setEditedObject(obj); } void TaskSections::open() { widget1->open(); } bool TaskSections::accept() { bool ok = widget1->accept(); if (ok) { Gui::Command::commitCommand(); Gui::Command::doCommand(Gui::Command::Gui, "Gui.ActiveDocument.resetEdit()"); Gui::Command::updateActive(); } return ok; } bool TaskSections::reject() { bool ok = widget1->reject(); if (ok) { Gui::Command::abortCommand(); Gui::Command::doCommand(Gui::Command::Gui, "Gui.ActiveDocument.resetEdit()"); Gui::Command::updateActive(); } return ok; } } // namespace SurfaceGui #include "moc_TaskSections.cpp"