/*************************************************************************** * Copyright (c) 2025 Kindred Systems * * * * 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 "PreCompiled.h" #include #include #include #include #include #include #include "FileOriginPython.h" #include "OriginManager.h" #include "BitmapFactory.h" namespace Gui { std::vector FileOriginPython::_instances; void FileOriginPython::addOrigin(const Py::Object& obj) { // Check if already registered if (findOrigin(obj)) { Base::Console().warning("FileOriginPython: Origin already registered\n"); return; } auto* origin = new FileOriginPython(obj); // Cache the ID immediately for registration origin->_cachedId = origin->callStringMethod("id"); if (origin->_cachedId.empty()) { Base::Console().error("FileOriginPython: Origin must have non-empty id()\n"); delete origin; return; } _instances.push_back(origin); // Register with OriginManager if (!OriginManager::instance()->registerOrigin(origin)) { // Registration failed - remove from our instances list // (registerOrigin already deleted the origin) _instances.pop_back(); } } void FileOriginPython::removeOrigin(const Py::Object& obj) { FileOriginPython* origin = findOrigin(obj); if (!origin) { return; } std::string originId = origin->_cachedId; // Remove from instances list auto it = std::find(_instances.begin(), _instances.end(), origin); if (it != _instances.end()) { _instances.erase(it); } // Unregister from OriginManager (this will delete the origin) OriginManager::instance()->unregisterOrigin(originId); } FileOriginPython* FileOriginPython::findOrigin(const Py::Object& obj) { for (auto* instance : _instances) { if (instance->_inst == obj) { return instance; } } return nullptr; } FileOriginPython::FileOriginPython(const Py::Object& obj) : _inst(obj) { } FileOriginPython::~FileOriginPython() = default; Py::Object FileOriginPython::callMethod(const char* method) const { return callMethod(method, Py::Tuple()); } Py::Object FileOriginPython::callMethod(const char* method, const Py::Tuple& args) const { Base::PyGILStateLocker lock; try { if (_inst.hasAttr(method)) { Py::Callable func(_inst.getAttr(method)); return func.apply(args); } } catch (Py::Exception&) { Base::PyException e; e.reportException(); } return Py::None(); } bool FileOriginPython::callBoolMethod(const char* method, bool defaultValue) const { Base::PyGILStateLocker lock; try { if (_inst.hasAttr(method)) { Py::Callable func(_inst.getAttr(method)); Py::Object result = func.apply(Py::Tuple()); if (result.isBoolean()) { return Py::Boolean(result); } if (result.isNumeric()) { return Py::Long(result) != 0; } } } catch (Py::Exception&) { Base::PyException e; e.reportException(); } return defaultValue; } std::string FileOriginPython::callStringMethod(const char* method, const std::string& defaultValue) const { Base::PyGILStateLocker lock; try { if (_inst.hasAttr(method)) { Py::Callable func(_inst.getAttr(method)); Py::Object result = func.apply(Py::Tuple()); if (result.isString()) { return Py::String(result).as_std_string(); } } } catch (Py::Exception&) { Base::PyException e; e.reportException(); } return defaultValue; } Py::Object FileOriginPython::getDocPyObject(App::Document* doc) const { if (!doc) { return Py::None(); } return Py::asObject(doc->getPyObject()); } // Identity methods std::string FileOriginPython::id() const { return _cachedId.empty() ? callStringMethod("id") : _cachedId; } std::string FileOriginPython::name() const { return callStringMethod("name", id()); } std::string FileOriginPython::nickname() const { return callStringMethod("nickname", name()); } QIcon FileOriginPython::icon() const { std::string iconName = callStringMethod("icon", "document-new"); return BitmapFactory().iconFromTheme(iconName.c_str()); } OriginType FileOriginPython::type() const { Base::PyGILStateLocker lock; try { if (_inst.hasAttr("type")) { Py::Callable func(_inst.getAttr("type")); Py::Object result = func.apply(Py::Tuple()); if (result.isNumeric()) { int t = static_cast(Py::Long(result)); if (t >= 0 && t <= static_cast(OriginType::Custom)) { return static_cast(t); } } } } catch (Py::Exception&) { Base::PyException e; e.reportException(); } return OriginType::Custom; } // Workflow characteristics bool FileOriginPython::tracksExternally() const { return callBoolMethod("tracksExternally", false); } bool FileOriginPython::requiresAuthentication() const { return callBoolMethod("requiresAuthentication", false); } // Capability queries bool FileOriginPython::supportsRevisions() const { return callBoolMethod("supportsRevisions", false); } bool FileOriginPython::supportsBOM() const { return callBoolMethod("supportsBOM", false); } bool FileOriginPython::supportsPartNumbers() const { return callBoolMethod("supportsPartNumbers", false); } bool FileOriginPython::supportsAssemblies() const { return callBoolMethod("supportsAssemblies", false); } // Connection state ConnectionState FileOriginPython::connectionState() const { Base::PyGILStateLocker lock; try { if (_inst.hasAttr("connectionState")) { Py::Callable func(_inst.getAttr("connectionState")); Py::Object result = func.apply(Py::Tuple()); if (result.isNumeric()) { int s = static_cast(Py::Long(result)); if (s >= 0 && s <= static_cast(ConnectionState::Error)) { return static_cast(s); } } } } catch (Py::Exception&) { Base::PyException e; e.reportException(); } return ConnectionState::Connected; } bool FileOriginPython::connect() { return callBoolMethod("connect", true); } void FileOriginPython::disconnect() { callMethod("disconnect"); } // Document identity std::string FileOriginPython::documentIdentity(App::Document* doc) const { Base::PyGILStateLocker lock; try { if (_inst.hasAttr("documentIdentity")) { Py::Callable func(_inst.getAttr("documentIdentity")); Py::Tuple args(1); args.setItem(0, getDocPyObject(doc)); Py::Object result = func.apply(args); if (result.isString()) { return Py::String(result).as_std_string(); } } } catch (Py::Exception&) { Base::PyException e; e.reportException(); } return {}; } std::string FileOriginPython::documentDisplayId(App::Document* doc) const { Base::PyGILStateLocker lock; try { if (_inst.hasAttr("documentDisplayId")) { Py::Callable func(_inst.getAttr("documentDisplayId")); Py::Tuple args(1); args.setItem(0, getDocPyObject(doc)); Py::Object result = func.apply(args); if (result.isString()) { return Py::String(result).as_std_string(); } } } catch (Py::Exception&) { Base::PyException e; e.reportException(); } return documentIdentity(doc); } bool FileOriginPython::ownsDocument(App::Document* doc) const { Base::PyGILStateLocker lock; try { if (_inst.hasAttr("ownsDocument")) { Py::Callable func(_inst.getAttr("ownsDocument")); Py::Tuple args(1); args.setItem(0, getDocPyObject(doc)); Py::Object result = func.apply(args); if (result.isBoolean()) { return Py::Boolean(result); } if (result.isNumeric()) { return Py::Long(result) != 0; } } } catch (Py::Exception&) { Base::PyException e; e.reportException(); } return false; } bool FileOriginPython::syncProperties(App::Document* doc) { Base::PyGILStateLocker lock; try { if (_inst.hasAttr("syncProperties")) { Py::Callable func(_inst.getAttr("syncProperties")); Py::Tuple args(1); args.setItem(0, getDocPyObject(doc)); Py::Object result = func.apply(args); if (result.isBoolean()) { return Py::Boolean(result); } if (result.isNumeric()) { return Py::Long(result) != 0; } } } catch (Py::Exception&) { Base::PyException e; e.reportException(); } return true; } // Core document operations App::Document* FileOriginPython::newDocument(const std::string& name) { Base::PyGILStateLocker lock; try { if (_inst.hasAttr("newDocument")) { Py::Callable func(_inst.getAttr("newDocument")); Py::Tuple args(1); args.setItem(0, Py::String(name)); Py::Object result = func.apply(args); if (!result.isNone()) { // Extract App::Document* from Python object if (PyObject_TypeCheck(result.ptr(), &(App::DocumentPy::Type))) { return static_cast(result.ptr())->getDocumentPtr(); } } } } catch (Py::Exception&) { Base::PyException e; e.reportException(); } return nullptr; } App::Document* FileOriginPython::openDocument(const std::string& identity) { Base::PyGILStateLocker lock; try { if (_inst.hasAttr("openDocument")) { Py::Callable func(_inst.getAttr("openDocument")); Py::Tuple args(1); args.setItem(0, Py::String(identity)); Py::Object result = func.apply(args); if (!result.isNone()) { if (PyObject_TypeCheck(result.ptr(), &(App::DocumentPy::Type))) { return static_cast(result.ptr())->getDocumentPtr(); } } } } catch (Py::Exception&) { Base::PyException e; e.reportException(); } return nullptr; } bool FileOriginPython::saveDocument(App::Document* doc) { Base::PyGILStateLocker lock; try { if (_inst.hasAttr("saveDocument")) { Py::Callable func(_inst.getAttr("saveDocument")); Py::Tuple args(1); args.setItem(0, getDocPyObject(doc)); Py::Object result = func.apply(args); if (result.isBoolean()) { return Py::Boolean(result); } if (result.isNumeric()) { return Py::Long(result) != 0; } } } catch (Py::Exception&) { Base::PyException e; e.reportException(); } return false; } bool FileOriginPython::saveDocumentAs(App::Document* doc, const std::string& newIdentity) { Base::PyGILStateLocker lock; try { if (_inst.hasAttr("saveDocumentAs")) { Py::Callable func(_inst.getAttr("saveDocumentAs")); Py::Tuple args(2); args.setItem(0, getDocPyObject(doc)); args.setItem(1, Py::String(newIdentity)); Py::Object result = func.apply(args); if (result.isBoolean()) { return Py::Boolean(result); } if (result.isNumeric()) { return Py::Long(result) != 0; } } } catch (Py::Exception&) { Base::PyException e; e.reportException(); } return false; } // Extended operations bool FileOriginPython::commitDocument(App::Document* doc) { Base::PyGILStateLocker lock; try { if (_inst.hasAttr("commitDocument")) { Py::Callable func(_inst.getAttr("commitDocument")); Py::Tuple args(1); args.setItem(0, getDocPyObject(doc)); Py::Object result = func.apply(args); if (result.isBoolean()) { return Py::Boolean(result); } if (result.isNumeric()) { return Py::Long(result) != 0; } } } catch (Py::Exception&) { Base::PyException e; e.reportException(); } return false; } bool FileOriginPython::pullDocument(App::Document* doc) { Base::PyGILStateLocker lock; try { if (_inst.hasAttr("pullDocument")) { Py::Callable func(_inst.getAttr("pullDocument")); Py::Tuple args(1); args.setItem(0, getDocPyObject(doc)); Py::Object result = func.apply(args); if (result.isBoolean()) { return Py::Boolean(result); } if (result.isNumeric()) { return Py::Long(result) != 0; } } } catch (Py::Exception&) { Base::PyException e; e.reportException(); } return false; } bool FileOriginPython::pushDocument(App::Document* doc) { Base::PyGILStateLocker lock; try { if (_inst.hasAttr("pushDocument")) { Py::Callable func(_inst.getAttr("pushDocument")); Py::Tuple args(1); args.setItem(0, getDocPyObject(doc)); Py::Object result = func.apply(args); if (result.isBoolean()) { return Py::Boolean(result); } if (result.isNumeric()) { return Py::Long(result) != 0; } } } catch (Py::Exception&) { Base::PyException e; e.reportException(); } return false; } void FileOriginPython::showInfo(App::Document* doc) { Base::PyGILStateLocker lock; try { if (_inst.hasAttr("showInfo")) { Py::Callable func(_inst.getAttr("showInfo")); Py::Tuple args(1); args.setItem(0, getDocPyObject(doc)); func.apply(args); } } catch (Py::Exception&) { Base::PyException e; e.reportException(); } } void FileOriginPython::showBOM(App::Document* doc) { Base::PyGILStateLocker lock; try { if (_inst.hasAttr("showBOM")) { Py::Callable func(_inst.getAttr("showBOM")); Py::Tuple args(1); args.setItem(0, getDocPyObject(doc)); func.apply(args); } } catch (Py::Exception&) { Base::PyException e; e.reportException(); } } App::Document* FileOriginPython::openDocumentInteractive() { Base::PyGILStateLocker lock; try { if (_inst.hasAttr("openDocumentInteractive")) { Py::Callable func(_inst.getAttr("openDocumentInteractive")); Py::Object result = func.apply(Py::Tuple()); if (!result.isNone()) { if (PyObject_TypeCheck(result.ptr(), &(App::DocumentPy::Type))) { return static_cast(result.ptr())->getDocumentPtr(); } } } } catch (Py::Exception&) { Base::PyException e; e.reportException(); } return nullptr; } bool FileOriginPython::saveDocumentAsInteractive(App::Document* doc) { Base::PyGILStateLocker lock; try { if (_inst.hasAttr("saveDocumentAsInteractive")) { Py::Callable func(_inst.getAttr("saveDocumentAsInteractive")); Py::Tuple args(1); args.setItem(0, getDocPyObject(doc)); Py::Object result = func.apply(args); if (result.isBoolean()) { return Py::Boolean(result); } if (result.isNumeric()) { return Py::Long(result) != 0; } } } catch (Py::Exception&) { Base::PyException e; e.reportException(); } return false; } } // namespace Gui