// SPDX-License-Identifier: LGPL-2.1-or-later /*************************************************************************** * Copyright (c) 2009 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 * * * ***************************************************************************/ #ifdef __GNUC__ # include #endif #include #include #include #include #include #include #include "Selection.h" #include "SelectionFilter.h" #include "SelectionFilterPy.h" #include "SelectionObject.h" using namespace Gui; // suppress annoying warnings from generated source files #ifdef _MSC_VER # pragma warning(disable : 4003) # pragma warning(disable : 4018) # pragma warning(disable : 4065) # pragma warning(disable : 4335) // disable MAC file format warning on VC #endif SelectionFilterGate::SelectionFilterGate(const char* filter) { Filter = new SelectionFilter(filter); } SelectionFilterGate::SelectionFilterGate(SelectionFilter* filter) { Filter = filter; } SelectionFilterGate::SelectionFilterGate() { Filter = nullptr; } SelectionFilterGate::~SelectionFilterGate() { delete Filter; } bool SelectionFilterGate::allow(App::Document* /*pDoc*/, App::DocumentObject* pObj, const char* sSubName) { return Filter->test(pObj, sSubName); } // ---------------------------------------------------------------------------- SelectionGatePython::SelectionGatePython(const Py::Object& obj) : gate(obj) {} SelectionGatePython::~SelectionGatePython() = default; bool SelectionGatePython::allow(App::Document* doc, App::DocumentObject* obj, const char* sub) { Base::PyGILStateLocker lock; try { if (this->gate.hasAttr(std::string("allow"))) { Py::Callable method(this->gate.getAttr(std::string("allow"))); Py::Object pyDoc = Py::asObject(doc->getPyObject()); Py::Object pyObj = Py::asObject(obj->getPyObject()); Py::Object pySub = Py::None(); if (sub) { pySub = Py::String(sub); } Py::Tuple args(3); args.setItem(0, pyDoc); args.setItem(1, pyObj); args.setItem(2, pySub); Py::Boolean ok(method.apply(args)); return (bool)ok; } } catch (Py::Exception&) { Base::PyException e; // extract the Python error text e.reportException(); } return true; } // ---------------------------------------------------------------------------- SelectionFilterGatePython::SelectionFilterGatePython(SelectionFilterPy* obj) : filter(obj) { Base::PyGILStateLocker lock; Py_INCREF(filter); } SelectionFilterGatePython::~SelectionFilterGatePython() { Base::PyGILStateLocker lock; Py_DECREF(filter); } bool SelectionFilterGatePython::allow(App::Document*, App::DocumentObject* obj, const char* sub) { return filter->filter.test(obj, sub); } // ---------------------------------------------------------------------------- SelectionFilter::SelectionFilter(const char* filter, App::DocumentObject* container) : container(container) { setFilter(filter); } SelectionFilter::SelectionFilter(const std::string& filter, App::DocumentObject* container) : container(container) { setFilter(filter.c_str()); } void SelectionFilter::setFilter(const char* filter) { if (Base::Tools::isNullOrEmpty(filter)) { Ast.reset(); Filter.clear(); } else { Filter = filter; if (!parse()) { throw Base::ParserError(Errors.c_str()); } } } SelectionFilter::~SelectionFilter() = default; bool SelectionFilter::match() { if (!Ast) { return false; } Result.clear(); for (const auto& it : Ast->Objects) { std::size_t min = 1; std::size_t max = 1; if (it->Slice) { min = it->Slice->Min; max = it->Slice->Max; } std::vector temp = container ? Gui::Selection().getSelectionIn(container, it->ObjectType) : Gui::Selection().getSelectionEx(nullptr, it->ObjectType); // test if subnames present if (it->SubName.empty()) { // if no subnames the count of the object get tested if (temp.size() < min || temp.size() > max) { return false; } } else { // if subnames present count all subs over the selected object of type std::size_t subCount = 0; for (const auto& it2 : temp) { const std::vector& subNames = it2.getSubNames(); if (subNames.empty()) { return false; } for (const auto& subName : subNames) { if (subName.find(it->SubName) != 0) { return false; } } subCount += subNames.size(); } if (subCount < min || subCount > max) { return false; } } Result.push_back(temp); } return true; } bool SelectionFilter::test(App::DocumentObject* pObj, const char* sSubName) { if (!Ast) { return false; } for (const auto& it : Ast->Objects) { if (pObj->isDerivedFrom(it->ObjectType)) { if (!sSubName) { return true; } if (it->SubName.empty()) { return true; } if (std::string(sSubName).find(it->SubName) == 0) { return true; } } } return false; } void SelectionFilter::addError(const char* e) { Errors += e; Errors += '\n'; } // === Parser & Scanner stuff =============================================== // include the Scanner and the Parser for the filter language SelectionFilter* ActFilter = nullptr; Node_Block* TopBlock = nullptr; // error func void yyerror(const char* errorinfo) { ActFilter->addError(errorinfo); } // for VC9 (isatty and fileno not supported anymore) #ifdef _MSC_VER int isatty(int i) { return _isatty(i); } int fileno(FILE* stream) { return _fileno(stream); } #endif namespace SelectionParser { /*! * \brief The StringFactory class * Helper class to record the created strings used by the parser. */ class StringFactory { std::list> data; std::size_t max_elements = 20; public: static StringFactory* instance() { static auto inst = new StringFactory(); return inst; } std::string* make(const std::string& str) { data.push_back(std::make_unique(str)); return data.back().get(); } static std::string* New(const std::string& str) { return StringFactory::instance()->make(str); } void clear() { if (data.size() > max_elements) { data.clear(); } } }; // show the parser the lexer method #define yylex SelectionFilterlex int SelectionFilterlex(); // Parser, defined in SelectionFilter.y #include "SelectionFilter.tab.c" #ifndef DOXYGEN_SHOULD_SKIP_THIS // Scanner, defined in SelectionFilter.l # if defined(__clang__) # pragma clang diagnostic push # pragma clang diagnostic ignored "-Wsign-compare" # pragma clang diagnostic ignored "-Wunneeded-internal-declaration" # elif defined(__GNUC__) # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wsign-compare" # endif # include "SelectionFilter.lex.cpp" # if defined(__clang__) # pragma clang diagnostic pop # elif defined(__GNUC__) # pragma GCC diagnostic pop # endif #endif // DOXYGEN_SHOULD_SKIP_THIS class StringBufferCleaner { public: explicit StringBufferCleaner(YY_BUFFER_STATE buffer) : my_string_buffer {buffer} {} ~StringBufferCleaner() { // free the scan buffer yy_delete_buffer(my_string_buffer); } StringBufferCleaner(const StringBufferCleaner&) = delete; StringBufferCleaner(StringBufferCleaner&&) = delete; StringBufferCleaner& operator=(const StringBufferCleaner&) = delete; StringBufferCleaner& operator=(StringBufferCleaner&&) = delete; private: YY_BUFFER_STATE my_string_buffer; }; } // namespace SelectionParser bool SelectionFilter::parse() { Errors = ""; SelectionParser::YY_BUFFER_STATE my_string_buffer = SelectionParser::SelectionFilter_scan_string( Filter.c_str() ); SelectionParser::StringBufferCleaner cleaner(my_string_buffer); // be aware that this parser is not reentrant! Don't use with Threats!!! assert(!ActFilter); ActFilter = this; /*int my_parse_result =*/SelectionParser::yyparse(); ActFilter = nullptr; Ast.reset(TopBlock); TopBlock = nullptr; SelectionParser::StringFactory::instance()->clear(); if (Errors.empty()) { return true; } else { return false; } }