/*************************************************************************** * Copyright (c) 2012 Jan Rheinländer * * * * * * 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 "TaskDressUpParameters.h" FC_LOG_LEVEL_INIT("PartDesign", true, true) using namespace PartDesignGui; using namespace Gui; /* TRANSLATOR PartDesignGui::TaskDressUpParameters */ TaskDressUpParameters::TaskDressUpParameters( ViewProviderDressUp* DressUpView, bool selectEdges, bool selectFaces, QWidget* parent ) : TaskFeatureParameters(DressUpView, parent, DressUpView->featureIcon(), DressUpView->menuName) , proxy(nullptr) , deleteAction(nullptr) , addAllEdgesAction(nullptr) , allowFaces(selectFaces) , allowEdges(selectEdges) , DressUpView(DressUpView) { // remember initial transaction ID App::GetApplication().getActiveTransaction(&transactionID); selectionMode = none; } TaskDressUpParameters::~TaskDressUpParameters() { // make sure to remove selection gate in all cases Gui::Selection().rmvSelectionGate(); } void TaskDressUpParameters::setupTransaction() { if (DressUpView.expired()) { return; } int tid = 0; App::GetApplication().getActiveTransaction(&tid); if (tid && tid == transactionID) { return; } // open a transaction if none is active std::string n("Edit "); n += DressUpView->getObject()->Label.getValue(); transactionID = App::GetApplication().setActiveTransaction(n.c_str()); } void TaskDressUpParameters::referenceSelected(const Gui::SelectionChanges& msg, QListWidget* widget) { if (strcmp(msg.pDocName, DressUpView->getObject()->getDocument()->getName()) != 0) { return; } Gui::Selection().clearSelection(); PartDesign::DressUp* pcDressUp = DressUpView->getObject(); App::DocumentObject* base = this->getBase(); // TODO: Must we make a copy here instead of assigning to const char* ? const char* fname = base->getNameInDocument(); if (strcmp(msg.pObjectName, fname) != 0) { return; } const std::string subName(msg.pSubName); std::vector refs = pcDressUp->Base.getSubValues(); if (const auto f = std::ranges::find(refs, subName); f != refs.end()) { refs.erase(f); // it's in the list. Remove it removeItemFromListWidget(widget, msg.pSubName); } else { refs.push_back(subName); // not yet in the list so we add it widget->addItem(QString::fromStdString(msg.pSubName)); } updateFeature(pcDressUp, refs); } void TaskDressUpParameters::addAllEdges(QListWidget* widget) { Q_UNUSED(widget) if (DressUpView.expired()) { return; } PartDesign::DressUp* pcDressUp = DressUpView->getObject(); App::DocumentObject* base = pcDressUp->Base.getValue(); if (!base) { return; } int count = Part::Feature::getTopoShape( base, Part::ShapeOption::ResolveLink | Part::ShapeOption::Transform ) .countSubShapes(TopAbs_EDGE); auto subValues = pcDressUp->Base.getSubValues(false); std::size_t len = subValues.size(); for (int i = 0; i < count; ++i) { std::string name = "Edge" + std::to_string(i + 1); if (std::find(subValues.begin(), subValues.begin() + len, name) == subValues.begin() + len) { subValues.push_back(name); } } if (subValues.size() == len) { return; } try { setupTransaction(); pcDressUp->Base.setValue(base, subValues); } catch (Base::Exception& e) { e.reportException(); } } void TaskDressUpParameters::deleteRef(QListWidget* widget) { // delete any selections since the reference(s) being deleted might be highlighted Gui::Selection().clearSelection(); // get the list of items to be deleted QList selectedList = widget->selectedItems(); PartDesign::DressUp* pcDressUp = DressUpView->getObject(); std::vector refs = pcDressUp->Base.getSubValues(); // delete the selection backwards to assure the list index keeps valid for the deletion QSignalBlocker block(widget); for (int i = selectedList.count() - 1; i > -1; i--) { // the ref index is the same as the listWidgetReferences index // so we can erase using the row number of the element to be deleted int rowNumber = widget->row(selectedList.at(i)); refs.erase(refs.begin() + rowNumber); widget->model()->removeRow(rowNumber); } updateFeature(pcDressUp, refs); } void TaskDressUpParameters::updateFeature( PartDesign::DressUp* pcDressUp, const std::vector& refs ) { if (selectionMode == refSel) { DressUpView->highlightReferences(false); } setupTransaction(); pcDressUp->Base.setValue(pcDressUp->Base.getValue(), refs); pcDressUp->recomputeFeature(); if (selectionMode == refSel) { DressUpView->highlightReferences(true); } else { hideOnError(); } } void TaskDressUpParameters::onButtonRefSel(bool checked) { setSelectionMode(checked ? refSel : none); } void TaskDressUpParameters::doubleClicked(QListWidgetItem* item) { // executed when the user double-clicks on any item in the list // shows the fillets as they are -> useful to switch out of selection mode Q_UNUSED(item) wasDoubleClicked = true; // assure we are not in selection mode setSelectionMode(none); // enable next possible single-click event after double-click time passed QTimer::singleShot( QApplication::doubleClickInterval(), this, &TaskDressUpParameters::itemClickedTimeout ); } void TaskDressUpParameters::setSelection(QListWidgetItem* current) { // executed when the user selected an item in the list (but double-clicked it) // highlights the currently selected item if (current == nullptr) { setSelectionMode(none); return; } if (!wasDoubleClicked) { // we treat it as single-click event once the QApplication double-click time is passed QTimer::singleShot( QApplication::doubleClickInterval(), this, &TaskDressUpParameters::itemClickedTimeout ); // name of the item std::string subName = current->text().toStdString(); // get the document name std::string docName = DressUpView->getObject()->getDocument()->getName(); // get the name of the body we are in Part::BodyBase* body = PartDesign::Body::findBodyOf(DressUpView->getObject()); if (body) { std::string objName = body->getNameInDocument(); // Enter selection mode if (selectionMode == none) { setSelectionMode(refSel); } else { Gui::Selection().clearSelection(); } // highlight the selected item bool block = this->blockSelection(true); tryAddSelection(docName, objName, subName); this->blockSelection(block); } } } void TaskDressUpParameters::tryAddSelection( const std::string& doc, const std::string& obj, const std::string& sub ) { try { Gui::Selection().addSelection(doc.c_str(), obj.c_str(), sub.c_str(), 0, 0, 0); } catch (const Base::Exception& e) { e.reportException(); } catch (const Standard_Failure& e) { Base::Console().error("OCC error: %s\n", e.GetMessageString()); } } QString TaskDressUpParameters::startSelectionLabel() { return tr("Select"); } QString TaskDressUpParameters::stopSelectionLabel() { return tr("Confirm Selection"); } void TaskDressUpParameters::itemClickedTimeout() { // executed after double-click time passed wasDoubleClicked = false; } void TaskDressUpParameters::createAddAllEdgesAction(QListWidget* parentList) { // creates a context menu, a shortcut for it and connects it to a slot function addAllEdgesAction = new QAction(tr("Add All Edges"), this); addAllEdgesAction->setShortcut(QKeySequence(QStringLiteral("Ctrl+Shift+A"))); // display shortcut behind the context menu entry addAllEdgesAction->setShortcutVisibleInContextMenu(true); parentList->addAction(addAllEdgesAction); addAllEdgesAction->setStatusTip( tr("Adds all edges to the list box (only when in add selection mode)") ); parentList->setContextMenuPolicy(Qt::ActionsContextMenu); } void TaskDressUpParameters::createDeleteAction(QListWidget* parentList) { // creates a context menu, a shortcut for it and connects it to a slot function deleteAction = new QAction(tr("Remove"), this); deleteAction->setShortcut(Gui::QtTools::deleteKeySequence()); // display shortcut behind the context menu entry deleteAction->setShortcutVisibleInContextMenu(true); parentList->addAction(deleteAction); parentList->setContextMenuPolicy(Qt::ActionsContextMenu); } bool TaskDressUpParameters::event(QEvent* event) { if (event->type() == QEvent::ShortcutOverride) { QKeyEvent* kevent = static_cast(event); // NOLINT if (deleteAction && Gui::QtTools::matches(kevent, deleteAction->shortcut())) { kevent->accept(); return true; } if (addAllEdgesAction && Gui::QtTools::matches(kevent, addAllEdgesAction->shortcut())) { kevent->accept(); return true; } } return TaskBox::event(event); } void TaskDressUpParameters::keyPressEvent(QKeyEvent* ke) { if (deleteAction && deleteAction->isEnabled() && Gui::QtTools::matches(ke, deleteAction->shortcut())) { deleteAction->trigger(); return; } if (addAllEdgesAction && addAllEdgesAction->isEnabled() && Gui::QtTools::matches(ke, addAllEdgesAction->shortcut())) { addAllEdgesAction->trigger(); return; } TaskBox::keyPressEvent(ke); } const std::vector TaskDressUpParameters::getReferences() const { PartDesign::DressUp* pcDressUp = DressUpView->getObject(); std::vector result = pcDressUp->Base.getSubValues(); return result; } // TODO: This code is identical with TaskTransformedParameters::removeItemFromListWidget() void TaskDressUpParameters::removeItemFromListWidget(QListWidget* widget, const char* itemstr) { QList items = widget->findItems(QString::fromLatin1(itemstr), Qt::MatchExactly); if (!items.empty()) { for (auto item : items) { QListWidgetItem* it = widget->takeItem(widget->row(item)); delete it; } } } void TaskDressUpParameters::hideOnError() { App::DocumentObject* dressup = DressUpView->getObject(); DressUpView->setErrorState(dressup->isError()); } ViewProviderDressUp* TaskDressUpParameters::getDressUpView() const { return DressUpView.expired() ? nullptr : DressUpView.get(); } Part::Feature* TaskDressUpParameters::getBase() const { if (ViewProviderDressUp* vp = getDressUpView()) { auto dressUp = vp->getObject(); // Unlikely but this may throw an exception in case we are started to edit an object which // base feature was deleted. This exception will be likely unhandled inside the dialog and // pass upper. But an error message inside the report view is better than a SEGFAULT. // Generally this situation should be prevented in ViewProviderDressUp::setEdit() return dressUp->getBaseObject(); } return nullptr; } void TaskDressUpParameters::setSelectionMode(selectionModes mode) { if (DressUpView.expired()) { return; } selectionMode = mode; setButtons(mode); if (mode == none) { Gui::Selection().rmvSelectionGate(); // remove any highlights and selections DressUpView->highlightReferences(false); if (previouslyShownViewProvider != nullptr) { // restore the previously shown view provider previouslyShownViewProvider->show(); previouslyShownViewProvider = nullptr; } } else { AllowSelectionFlags allow; allow.setFlag(AllowSelection::EDGE, allowEdges); allow.setFlag(AllowSelection::FACE, allowFaces); Gui::Selection().addSelectionGate(new ReferenceSelection(this->getBase(), allow)); DressUpView->highlightReferences(true); // selection must come from the previous feature, we also need to remember the currently // shown so we can restore it later previouslyShownViewProvider = DressUpView->getBodyViewProvider()->getShownViewProvider(); DressUpView->showPreviousFeature(true); } Gui::Selection().clearSelection(); } //************************************************************************** //************************************************************************** // TaskDialog //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ TaskDlgDressUpParameters::TaskDlgDressUpParameters(ViewProviderDressUp* DressUpView) : TaskDlgFeatureParameters(DressUpView) , parameter(nullptr) { assert(DressUpView); auto pcDressUp = DressUpView->getObject(); auto base = pcDressUp->Base.getValue(); std::vector newSubList; bool changed = false; auto& shadowSubs = pcDressUp->Base.getShadowSubs(); for (auto& shadowSub : shadowSubs) { auto displayName = shadowSub.oldName; // If there is a missing tag on the shadow sub, take a guess at a new name. if (boost::starts_with(shadowSub.oldName, Data::MISSING_PREFIX)) { Part::Feature::guessNewLink(displayName, base, shadowSub.newName.c_str()); newSubList.emplace_back(displayName); changed = true; } } if (changed) { pcDressUp->Base.setValue(base, newSubList); } } TaskDlgDressUpParameters::~TaskDlgDressUpParameters() = default; //==== calls from the TaskView =============================================================== bool TaskDlgDressUpParameters::accept() { getViewObject()->highlightReferences(false); std::vector refs = parameter->getReferences(); std::stringstream str; str << Gui::Command::getObjectCmd(getObject()) << ".Base = (" << Gui::Command::getObjectCmd(parameter->getBase()) << ",["; for (const auto& ref : refs) { str << "\"" << ref << "\","; } str << "])"; Gui::Command::runCommand(Gui::Command::Doc, str.str().c_str()); return TaskDlgFeatureParameters::accept(); } bool TaskDlgDressUpParameters::reject() { getViewObject()->highlightReferences(false); return TaskDlgFeatureParameters::reject(); } #include "moc_TaskDressUpParameters.cpp"