// SPDX-License-Identifier: LGPL-2.1-or-later /*************************************************************************** * Copyright (c) 2005 Jürgen Riegel * * * * 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 "SoFCSelection.h" #include "MainWindow.h" #include "SoFCSelectionAction.h" #include "SoFCUnifiedSelection.h" #include "ViewParams.h" using namespace Gui; namespace Gui { void printPreselectionInfo( const char* documentName, const char* objectName, const char* subElementName, float x, float y, float z, double precision ); } SoFullPath* Gui::SoFCSelection::currenthighlight = nullptr; // ************************************************************************* SO_NODE_SOURCE(SoFCSelection) /*! Constructor. */ SoFCSelection::SoFCSelection() { SO_NODE_CONSTRUCTOR(SoFCSelection); SO_NODE_ADD_FIELD(colorHighlight, (SbColor(0.8f, 0.1f, 0.1f))); SO_NODE_ADD_FIELD(colorSelection, (SbColor(0.1f, 0.8f, 0.1f))); SO_NODE_ADD_FIELD(style, (EMISSIVE)); SO_NODE_ADD_FIELD(preselectionMode, (AUTO)); SO_NODE_ADD_FIELD(selectionMode, (SEL_ON)); SO_NODE_ADD_FIELD(selected, (NOTSELECTED)); SO_NODE_ADD_FIELD(documentName, ("")); SO_NODE_ADD_FIELD(objectName, ("")); SO_NODE_ADD_FIELD(subElementName, ("")); SO_NODE_ADD_FIELD(useNewSelection, (true)); SO_NODE_DEFINE_ENUM_VALUE(Styles, EMISSIVE); SO_NODE_DEFINE_ENUM_VALUE(Styles, EMISSIVE_DIFFUSE); SO_NODE_DEFINE_ENUM_VALUE(Styles, BOX); SO_NODE_SET_SF_ENUM_TYPE(style, Styles); SO_NODE_DEFINE_ENUM_VALUE(PreselectionModes, AUTO); SO_NODE_DEFINE_ENUM_VALUE(PreselectionModes, ON); SO_NODE_DEFINE_ENUM_VALUE(PreselectionModes, OFF); SO_NODE_SET_SF_ENUM_TYPE(preselectionMode, PreselectionModes); SO_NODE_DEFINE_ENUM_VALUE(SelectionModes, SEL_ON); SO_NODE_DEFINE_ENUM_VALUE(SelectionModes, SEL_OFF); SO_NODE_SET_SF_ENUM_TYPE(selectionMode, SelectionModes); SO_NODE_DEFINE_ENUM_VALUE(Selected, NOTSELECTED); SO_NODE_DEFINE_ENUM_VALUE(Selected, SELECTED); SO_NODE_SET_SF_ENUM_TYPE(selected, Selected); highlighted = false; bShift = false; bCtrl = false; selected = NOTSELECTED; useNewSelection = ViewParams::instance()->getUseNewSelection(); selContext = std::make_shared(); selContext2 = std::make_shared(); } /*! Destructor. */ SoFCSelection::~SoFCSelection() { // If we're being deleted and we're the current highlight, // NULL out that variable if (currenthighlight && (!currenthighlight->getTail()->isOfType(SoFCSelection::getClassTypeId()))) { currenthighlight->unref(); currenthighlight = nullptr; } // delete THIS; } // doc from parent void SoFCSelection::initClass() { SO_NODE_INIT_CLASS(SoFCSelection, SoGroup, "Group"); } void SoFCSelection::finish() { atexit_cleanup(); } /*! Static method that can be used to turn off the current highlight. */ void SoFCSelection::turnOffCurrentHighlight(SoGLRenderAction* action) { SoFCSelection::turnoffcurrent(action); } void SoFCSelection::doAction(SoAction* action) { if (useNewSelection.getValue() && action->getCurPathCode() != SoAction::OFF_PATH) { if (action->getTypeId() == Gui::SoHighlightElementAction::getClassTypeId()) { auto hlaction = static_cast(action); if (!hlaction->isHighlighted()) { auto ctx = Gui::SoFCSelectionRoot::getActionContext(action, this, selContext, false); if (ctx && ctx->isHighlighted()) { ctx->highlightIndex = -1; touch(); } } else { auto ctx = Gui::SoFCSelectionRoot::getActionContext(action, this, selContext); if (ctx) { ctx->highlightColor = hlaction->getColor(); if (!ctx->isHighlighted()) { ctx->highlightIndex = 0; touch(); } } } return; } else if (action->getTypeId() == Gui::SoSelectionElementAction::getClassTypeId()) { auto selaction = static_cast(action); if (selaction->getType() == Gui::SoSelectionElementAction::All || selaction->getType() == Gui::SoSelectionElementAction::Append) { SelContextPtr ctx = Gui::SoFCSelectionRoot::getActionContext(action, this, selContext); if (ctx) { ctx->selectionColor = selaction->getColor(); if (!ctx->isSelectAll()) { ctx->selectAll(); this->touch(); } } } else if (selaction->getType() == Gui::SoSelectionElementAction::None || selaction->getType() == Gui::SoSelectionElementAction::Remove) { SelContextPtr ctx = Gui::SoFCSelectionRoot::getActionContext(action, this, selContext, false); if (ctx && ctx->isSelected()) { ctx->selectionIndex.clear(); this->touch(); } } return; } } if (action->getTypeId() == SoFCDocumentAction::getClassTypeId()) { auto docaction = static_cast(action); this->documentName = docaction->documentName; } if (action->getTypeId() == SoFCDocumentObjectAction::getClassTypeId()) { auto objaction = static_cast(action); objaction->documentName = this->documentName.getValue(); objaction->objectName = this->objectName.getValue(); objaction->componentName = this->subElementName.getValue(); objaction->setHandled(); } if (!useNewSelection.getValue()) { if (action->getTypeId() == SoFCEnablePreselectionAction::getClassTypeId()) { auto preaction = static_cast(action); if (preaction->enabled) { this->preselectionMode = SoFCSelection::AUTO; } else { this->preselectionMode = SoFCSelection::OFF; } } if (action->getTypeId() == SoFCEnableSelectionAction::getClassTypeId()) { auto selaction = static_cast(action); if (selaction->enabled) { this->selectionMode = SoFCSelection::SEL_ON; } else { this->selectionMode = SoFCSelection::SEL_OFF; if (selected.getValue() == SELECTED) { this->selected = NOTSELECTED; } } } if (action->getTypeId() == SoFCSelectionColorAction::getClassTypeId()) { auto colaction = static_cast(action); this->colorSelection = colaction->selectionColor; } if (action->getTypeId() == SoFCHighlightColorAction::getClassTypeId()) { auto colaction = static_cast(action); this->colorHighlight = colaction->highlightColor; } if (selectionMode.getValue() == SEL_ON && action->getTypeId() == SoFCSelectionAction::getClassTypeId()) { auto selaction = static_cast(action); if (selaction->SelChange.Type == SelectionChanges::AddSelection || selaction->SelChange.Type == SelectionChanges::RmvSelection) { if (documentName.getValue() == selaction->SelChange.pDocName && objectName.getValue() == selaction->SelChange.pObjectName && (subElementName.getValue() == selaction->SelChange.pSubName || *(selaction->SelChange.pSubName) == '\0')) { if (selaction->SelChange.Type == SelectionChanges::AddSelection) { if (selected.getValue() == NOTSELECTED) { selected = SELECTED; } } else { if (selected.getValue() == SELECTED) { selected = NOTSELECTED; } } return; } } else if (selaction->SelChange.Type == SelectionChanges::ClrSelection) { if (documentName.getValue() == selaction->SelChange.pDocName || strcmp(selaction->SelChange.pDocName, "") == 0) { if (selected.getValue() == SELECTED) { selected = NOTSELECTED; } } } else if (selaction->SelChange.Type == SelectionChanges::SetSelection) { bool sel = Selection().isSelected( documentName.getValue().getString(), objectName.getValue().getString() /*, subElementName.getValue().getString()*/ ); if (sel) { if (selected.getValue() == NOTSELECTED) { selected = SELECTED; } } else { if (selected.getValue() == SELECTED) { selected = NOTSELECTED; } } } } } inherited::doAction(action); } int SoFCSelection::getPriority(const SoPickedPoint* p) { const SoDetail* detail = p->getDetail(); if (!detail) { return 0; } if (detail->isOfType(SoFaceDetail::getClassTypeId())) { return 1; } if (detail->isOfType(SoLineDetail::getClassTypeId())) { return 2; } if (detail->isOfType(SoPointDetail::getClassTypeId())) { return 3; } return 0; } const SoPickedPoint* SoFCSelection::getPickedPoint(SoHandleEventAction* action) const { // To identify the picking of lines in a concave area we have to // get all intersection points. If we have two or more intersection // points where the first is of a face and the second of a line with // almost similar coordinates we use the second point, instead. const SoPickedPointList& points = action->getPickedPointList(); if (points.getLength() == 0) { return nullptr; } else if (points.getLength() == 1) { return points[0]; } const SoPickedPoint* picked = points[0]; int picked_prio = getPriority(picked); const SbVec3f& picked_pt = picked->getPoint(); for (int i = 1; i < points.getLength(); i++) { const SoPickedPoint* cur = points[i]; int cur_prio = getPriority(cur); const SbVec3f& cur_pt = cur->getPoint(); if ((cur_prio > picked_prio) && picked_pt.equals(cur_pt, 0.01f)) { picked = cur; picked_prio = cur_prio; } } return picked; } // doc from parent void SoFCSelection::handleEvent(SoHandleEventAction* action) { if (useNewSelection.getValue()) { inherited::handleEvent(action); return; } static char buf[513]; auto mymode = static_cast(this->preselectionMode.getValue()); const SoEvent* event = action->getEvent(); // mouse move events for preselection if (event->isOfType(SoLocation2Event::getClassTypeId())) { // NOTE: If preselection is off then we do not check for a picked point because otherwise // this search may slow down extremely the system on really big data sets. In this case we // just check for a picked point if the data set has been selected. if (mymode == AUTO || mymode == ON) { const SoPickedPoint* pp = this->getPickedPoint(action); if (pp && pp->getPath()->containsPath(action->getCurPath())) { if (!highlighted) { if (Gui::Selection().setPreselect( documentName.getValue().getString(), objectName.getValue().getString(), subElementName.getValue().getString(), pp->getPoint()[0], pp->getPoint()[1], pp->getPoint()[2] )) { SoFCSelection::turnoffcurrent(action); SoFCSelection::currenthighlight = static_cast( action->getCurPath()->copy() ); SoFCSelection::currenthighlight->ref(); highlighted = true; this->touch(); // force scene redraw this->redrawHighlighted(action, true); } } const auto& pt = pp->getPoint(); printPreselectionInfo( documentName.getValue().getString(), objectName.getValue().getString(), subElementName.getValue().getString(), pt[0], pt[1], pt[2], 1e-7 ); } else { // picked point if (highlighted) { if (mymode == AUTO) { SoFCSelection::turnoffcurrent(action); } // FIXME: I think we should set 'highlighted' to false whenever no point is // picked else highlighted = false; Gui::Selection().rmvPreselect(); } } } } // key press events else if (event->isOfType(SoKeyboardEvent ::getClassTypeId())) { auto const e = static_cast(event); if (SoKeyboardEvent::isKeyPressEvent(e, SoKeyboardEvent::LEFT_SHIFT) || SoKeyboardEvent::isKeyPressEvent(e, SoKeyboardEvent::RIGHT_SHIFT)) { bShift = true; } if (SoKeyboardEvent::isKeyReleaseEvent(e, SoKeyboardEvent::LEFT_SHIFT) || SoKeyboardEvent::isKeyReleaseEvent(e, SoKeyboardEvent::RIGHT_SHIFT)) { bShift = false; } if (SoKeyboardEvent::isKeyPressEvent(e, SoKeyboardEvent::LEFT_CONTROL) || SoKeyboardEvent::isKeyPressEvent(e, SoKeyboardEvent::RIGHT_CONTROL)) { bCtrl = true; } if (SoKeyboardEvent::isKeyReleaseEvent(e, SoKeyboardEvent::LEFT_CONTROL) || SoKeyboardEvent::isKeyReleaseEvent(e, SoKeyboardEvent::RIGHT_CONTROL)) { bCtrl = false; } } // mouse press events for (de)selection else if (event->isOfType(SoMouseButtonEvent::getClassTypeId())) { auto const e = static_cast(event); if (SoMouseButtonEvent::isButtonReleaseEvent(e, SoMouseButtonEvent::BUTTON1)) { // FIXME: Shouldn't we remove the preselection for newly selected objects? // Otherwise the tree signals that an object is preselected even though it is // hidden. (Werner) const SoPickedPoint* pp = this->getPickedPoint(action); if (pp && pp->getPath()->containsPath(action->getCurPath())) { const auto& pt = pp->getPoint(); if (bCtrl) { if (Gui::Selection().isSelected( documentName.getValue().getString(), objectName.getValue().getString(), subElementName.getValue().getString() )) { Gui::Selection().rmvSelection( documentName.getValue().getString(), objectName.getValue().getString(), subElementName.getValue().getString() ); } else { Gui::Selection().addSelection( documentName.getValue().getString(), objectName.getValue().getString(), subElementName.getValue().getString(), pt[0], pt[1], pt[2] ); if (mymode == OFF) { snprintf( buf, 512, "Selected: %s.%s.%s (%g, %g, %g)", documentName.getValue().getString(), objectName.getValue().getString(), subElementName.getValue().getString(), fabs(pt[0]) > 1e-7 ? pt[0] : 0.0, fabs(pt[1]) > 1e-7 ? pt[1] : 0.0, fabs(pt[2]) > 1e-7 ? pt[2] : 0.0 ); getMainWindow()->showMessage(QString::fromLatin1(buf)); } } } else { // Ctrl if (!Gui::Selection().isSelected( documentName.getValue().getString(), objectName.getValue().getString(), subElementName.getValue().getString() )) { Gui::Selection().clearSelection(documentName.getValue().getString()); Gui::Selection().addSelection( documentName.getValue().getString(), objectName.getValue().getString(), subElementName.getValue().getString(), pt[0], pt[1], pt[2] ); } else { Gui::Selection().clearSelection(documentName.getValue().getString()); Gui::Selection().addSelection( documentName.getValue().getString(), objectName.getValue().getString(), nullptr, pt[0], pt[1], pt[2] ); } if (mymode == OFF) { snprintf( buf, 512, "Selected: %s.%s.%s (%g, %g, %g)", documentName.getValue().getString(), objectName.getValue().getString(), subElementName.getValue().getString(), fabs(pt[0]) > 1e-7 ? pt[0] : 0.0, fabs(pt[1]) > 1e-7 ? pt[1] : 0.0, fabs(pt[2]) > 1e-7 ? pt[2] : 0.0 ); getMainWindow()->showMessage(QString::fromLatin1(buf)); } } action->setHandled(); } // picked point } // mouse release } inherited::handleEvent(action); } // doc from parent void SoFCSelection::GLRenderBelowPath(SoGLRenderAction* action) { SoState* state = action->getState(); SelContextPtr ctx = Gui::SoFCSelectionRoot::getRenderContext(this, selContext); if (selContext2->checkGlobal(ctx)) { ctx = selContext2; } if (!useNewSelection.getValue() && selContext == ctx) { ctx->selectionColor = this->colorSelection.getValue(); ctx->highlightColor = this->colorHighlight.getValue(); if (this->selected.getValue() == SELECTED) { ctx->selectAll(); } else { ctx->selectionIndex.clear(); } ctx->highlightIndex = this->highlighted ? 0 : -1; } // check if preselection is active if (this->setOverride(action, ctx)) { inherited::GLRenderBelowPath(action); state->pop(); } else { inherited::GLRenderBelowPath(action); } } void SoFCSelection::GLRender(SoGLRenderAction* action) { SoState* state = action->getState(); SelContextPtr ctx = Gui::SoFCSelectionRoot::getRenderContext(this, selContext); if (selContext2->checkGlobal(ctx)) { ctx = selContext2; } if (!useNewSelection.getValue() && selContext == ctx) { ctx->selectionColor = this->colorSelection.getValue(); ctx->highlightColor = this->colorHighlight.getValue(); if (this->selected.getValue() == SELECTED) { ctx->selectAll(); } else { ctx->selectionIndex.clear(); } ctx->highlightIndex = this->highlighted ? 0 : -1; } // check if preselection is active if (this->setOverride(action, ctx)) { inherited::GLRender(action); state->pop(); } else { inherited::GLRender(action); } } // doc from parent void SoFCSelection::GLRenderInPath(SoGLRenderAction* action) { SelContextPtr ctx = Gui::SoFCSelectionRoot::getRenderContext(this, selContext); if (selContext2->checkGlobal(ctx)) { ctx = selContext2; } if (!useNewSelection.getValue() && selContext == ctx) { ctx->selectionColor = this->colorSelection.getValue(); ctx->highlightColor = this->colorHighlight.getValue(); if (this->selected.getValue() == SELECTED) { ctx->selectAll(); } else { ctx->selectionIndex.clear(); } ctx->highlightIndex = this->highlighted ? 0 : -1; } // check if preselection is active SoState* state = action->getState(); if (this->setOverride(action, ctx)) { inherited::GLRenderInPath(action); state->pop(); } else { inherited::GLRenderInPath(action); } } SbBool SoFCSelection::preRender(SoGLRenderAction* action, GLint& oldDepthFunc) // //////////////////////////////////////////////////////////////////////// { // If not performing locate highlighting, just return. if (preselectionMode.getValue() == OFF) { return false; } SoState* state = action->getState(); // ??? prevent caching at this level - for some reason the // ??? SoWindowElement::copyMatchInfo() method get called, which should // ??? never be called. We are not caching this node correctly yet.... // SoCacheElement::invalidate(state); SbBool drawHighlighted = (preselectionMode.getValue() == ON || isHighlighted(action) || selected.getValue() == SELECTED); if (drawHighlighted) { // prevent diffuse & emissive color from leaking out... state->push(); SbColor col; if (selected.getValue() == SELECTED) { col = colorSelection.getValue(); } else { col = colorHighlight.getValue(); } // Emissive Color SoLazyElement::setEmissive(state, &col); SoOverrideElement::setEmissiveColorOverride(state, this, true); // Diffuse Color if (style.getValue() == EMISSIVE_DIFFUSE) { SoLazyElement::setDiffuse(state, this, 1, &col, &colorpacker); SoOverrideElement::setDiffuseColorOverride(state, this, true); } } // Draw on top of other things at same z-buffer depth if: // [a] we're highlighted // [b] this is the highlighting pass. This occurs when changing from // non-hilit to lit OR VICE VERSA. // Otherwise, leave it alone... if (drawHighlighted || highlighted) { glGetIntegerv(GL_DEPTH_FUNC, &oldDepthFunc); if (oldDepthFunc != GL_LEQUAL) { glDepthFunc(GL_LEQUAL); } } return drawHighlighted; } /*! Empty method in Coin. Can be used by subclasses to be told when status change. */ void SoFCSelection::redrawHighlighted(SoAction* action, SbBool doHighlight) { Q_UNUSED(action); Q_UNUSED(doHighlight); } SbBool SoFCSelection::readInstance(SoInput* in, unsigned short flags) { // Note: The read in document name can be false, so the caller must ensure pointing to the // correct document SbBool ret = inherited::readInstance(in, flags); return ret; } // // update override state before rendering // bool SoFCSelection::setOverride(SoGLRenderAction* action, SelContextPtr ctx) { auto mymode = static_cast(this->preselectionMode.getValue()); bool preselected = ctx && ctx->isHighlighted() && (useNewSelection.getValue() || mymode == AUTO); if (!preselected && mymode != ON && (!ctx || !ctx->isSelected())) { return false; } // uniqueId is returned by SoNode::getNodeId(). It is used to notify change // and for render cache update. In order to update cache on selection state // change, We manually change the id here by using a combined hash of the // original id and context pointer. auto oldId = this->uniqueId; this->uniqueId ^= std::hash()(ctx.get()) + 0x9e3779b9 + (oldId << 6) + (oldId >> 2); auto mystyle = static_cast(this->style.getValue()); if (mystyle == SoFCSelection::BOX) { if (ctx) { SoFCSelectionRoot::renderBBox( action, this, preselected ? ctx->highlightColor : ctx->selectionColor ); } this->uniqueId = oldId; return false; } SoState* state = action->getState(); state->push(); SoMaterialBindingElement::set(state, SoMaterialBindingElement::OVERALL); SoOverrideElement::setMaterialBindingOverride(state, this, true); if (!preselected && ctx) { SoLazyElement::setEmissive(state, &ctx->selectionColor); } else if (ctx) { SoLazyElement::setEmissive(state, &ctx->highlightColor); } SoOverrideElement::setEmissiveColorOverride(state, this, true); if (SoLazyElement::getLightModel(state) == SoLazyElement::BASE_COLOR || mystyle == SoFCSelection::EMISSIVE_DIFFUSE) { if (!preselected && ctx) { SoLazyElement::setDiffuse(state, this, 1, &ctx->selectionColor, &colorpacker); } else if (ctx) { SoLazyElement::setDiffuse(state, this, 1, &ctx->highlightColor, &colorpacker); } SoOverrideElement::setDiffuseColorOverride(state, this, true); } this->uniqueId = oldId; return true; } // private convenience method void SoFCSelection::turnoffcurrent(SoAction* action) { if (SoFCSelection::currenthighlight && SoFCSelection::currenthighlight->getLength()) { SoNode* tail = SoFCSelection::currenthighlight->getTail(); if (tail->isOfType(SoFCSelection::getClassTypeId())) { static_cast(tail)->highlighted = false; static_cast(tail)->touch(); // force scene redraw if (action) { static_cast(tail)->redrawHighlighted(action, false); } } } if (SoFCSelection::currenthighlight) { SoFCSelection::currenthighlight->unref(); SoFCSelection::currenthighlight = nullptr; } } SbBool SoFCSelection::isHighlighted(SoAction* action) // //////////////////////////////////////////////////////////////////////// { auto actionPath = static_cast(action->getCurPath()); return ( currenthighlight && currenthighlight->getTail() == actionPath->getTail() && // nested SoHL! *currenthighlight == *actionPath ); } void SoFCSelection::applySettings() { // TODO Some view providers got copy of this code: make them use this (2015-09-03, Fat-Zer) // Note: SoFCUnifiedSelection got the same code, keep in sync or think about a way to share it float transparency; ParameterGrp::handle hGrp = Gui::WindowParameter::getDefaultParameter()->GetGroup("View"); bool enablePre = hGrp->GetBool("EnablePreselection", true); bool enableSel = hGrp->GetBool("EnableSelection", true); if (!enablePre) { this->preselectionMode = Gui::SoFCSelection::OFF; } else { // Search for a user defined value with the current color as default SbColor highlightColor = this->colorHighlight.getValue(); auto highlight = (unsigned long)(highlightColor.getPackedValue()); highlight = hGrp->GetUnsigned("HighlightColor", highlight); highlightColor.setPackedValue((uint32_t)highlight, transparency); this->colorHighlight.setValue(highlightColor); } if (!enableSel) { this->selectionMode = Gui::SoFCSelection::SEL_OFF; } else { // Do the same with the selection color SbColor selectionColor = this->colorSelection.getValue(); auto selection = (unsigned long)(selectionColor.getPackedValue()); selection = hGrp->GetUnsigned("SelectionColor", selection); selectionColor.setPackedValue((uint32_t)selection, transparency); this->colorSelection.setValue(selectionColor); } }