Files
create/src/Gui/ViewProviderDocumentObject.cpp
2025-11-11 13:49:01 +01:00

792 lines
25 KiB
C++

/***************************************************************************
* Copyright (c) 2004 Jürgen Riegel <juergen.riegel@web.de> *
* *
* 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 <QAction>
#include <QMenu>
#include <Inventor/SoFullPath.h>
#include <Inventor/SoPickedPoint.h>
#include <Inventor/actions/SoSearchAction.h>
#include <Inventor/details/SoDetail.h>
#include <Inventor/misc/SoChildList.h>
#include <Inventor/nodes/SoSeparator.h>
#include <App/Document.h>
#include <App/Origin.h>
#include <Base/Tools.h>
#include "ViewProviderDocumentObjectPy.h"
#include "ActionFunction.h"
#include "Application.h"
#include "Command.h"
#include "Document.h"
#include "MDIView.h"
#include "SoFCUnifiedSelection.h"
#include "Tree.h"
#include "ViewProviderDocumentObject.h"
#include "ViewProviderExtension.h"
#include "TaskView/TaskAppearance.h"
FC_LOG_LEVEL_INIT("Gui", true, true)
using namespace Gui;
PROPERTY_SOURCE(Gui::ViewProviderDocumentObject, Gui::ViewProvider)
ViewProviderDocumentObject::ViewProviderDocumentObject()
{
static const char* dogroup = "Display Options";
static const char* sgroup = "Selection";
ADD_PROPERTY_TYPE(DisplayMode, ((long)0), dogroup, App::Prop_None, "Set the display mode");
ADD_PROPERTY_TYPE(Visibility, (true), dogroup, App::Prop_None, "Show the object in the 3d view");
ADD_PROPERTY_TYPE(ShowInTree, (true), dogroup, App::Prop_None, "Show the object in the tree view");
ADD_PROPERTY_TYPE(SelectionStyle, ((long)0), sgroup, App::Prop_None, "Set the object selection style");
static const char* SelectionStyleEnum[] = {"Shape", "BoundBox", nullptr};
SelectionStyle.setEnums(SelectionStyleEnum);
static const char* OnTopEnum[] = {"Disabled", "Enabled", "Object", "Element", nullptr};
ADD_PROPERTY_TYPE(
OnTopWhenSelected,
((long int)0),
sgroup,
App::Prop_None,
"Enabled: Display the object on top of any other object when selected\n"
"Object: On top only if the whole object is selected\n"
"Element: On top only if some sub-element of the object is selected"
);
OnTopWhenSelected.setEnums(OnTopEnum);
sPixmap = "Feature";
}
ViewProviderDocumentObject::~ViewProviderDocumentObject()
{
// Make sure that the property class does not destruct our string list
DisplayMode.setContainer(nullptr);
DisplayMode.setEnums(nullptr);
}
void ViewProviderDocumentObject::getTaskViewContent(std::vector<Gui::TaskView::TaskContent*>& vec) const
{
vec.push_back(new Gui::TaskView::TaskAppearance());
}
void ViewProviderDocumentObject::startRestoring()
{
hide();
auto vector = getExtensionsDerivedFromType<Gui::ViewProviderExtension>();
for (Gui::ViewProviderExtension* ext : vector) {
ext->extensionStartRestoring();
}
}
void ViewProviderDocumentObject::finishRestoring()
{
auto vector = getExtensionsDerivedFromType<Gui::ViewProviderExtension>();
for (Gui::ViewProviderExtension* ext : vector) {
ext->extensionFinishRestoring();
}
}
bool ViewProviderDocumentObject::isAttachedToDocument() const
{
return (!testStatus(Detach));
}
const char* ViewProviderDocumentObject::detachFromDocument()
{
// here we can return an empty string since the object
// name comes from the document object
setStatus(Detach, true);
return "";
}
bool ViewProviderDocumentObject::removeDynamicProperty(const char* name)
{
App::Property* prop = getDynamicPropertyByName(name);
if (!prop || prop->testStatus(App::Property::LockDynamic)) {
return false;
}
// transactions of view providers are also managed in App::Document.
App::DocumentObject* docobject = getObject();
App::Document* document = docobject ? docobject->getDocument() : nullptr;
if (document) {
document->addOrRemovePropertyOfObject(this, prop, false);
}
return ViewProvider::removeDynamicProperty(name);
}
App::Property* ViewProviderDocumentObject::addDynamicProperty(
const char* type,
const char* name,
const char* group,
const char* doc,
short attr,
bool ro,
bool hidden
)
{
auto prop = ViewProvider::addDynamicProperty(type, name, group, doc, attr, ro, hidden);
if (prop) {
// transactions of view providers are also managed in App::Document.
App::DocumentObject* docobject = getObject();
App::Document* document = docobject ? docobject->getDocument() : nullptr;
if (document) {
document->addOrRemovePropertyOfObject(this, prop, true);
}
}
return prop;
}
void ViewProviderDocumentObject::onBeforeChange(const App::Property* prop)
{
if (isAttachedToDocument()) {
App::DocumentObject* obj = getObject();
App::Document* doc = obj ? obj->getDocument() : nullptr;
if (doc) {
onBeforeChangeProperty(doc, prop);
}
}
ViewProvider::onBeforeChange(prop);
}
void ViewProviderDocumentObject::onChanged(const App::Property* prop)
{
if (prop == &DisplayMode) {
setActiveMode();
}
else if (prop == &Visibility) {
// use this bit to check whether show() or hide() must be called
if (!Visibility.testStatus(App::Property::User2)) {
Visibility.setStatus(App::Property::User2, true);
Visibility.getValue() ? show() : hide();
Visibility.setStatus(App::Property::User2, false);
}
if (!Visibility.testStatus(App::Property::User1) && getObject()
&& getObject()->Visibility.getValue() != Visibility.getValue()) {
// Changing the visibility of a document object will automatically set
// the document modified but if the 'TouchDocument' flag is not set then
// this is undesired behaviour. So, if this change marks the document as
// modified then it must be be reversed.
if (!testStatus(Gui::ViewStatus::TouchDocument)) {
// Note: reverting document modified status like that is not
// appropriate because we can't tell if there is any other
// property being changed due to the change of Visibility here.
// Temporary setting the Visibility property as 'NoModify' is
// the proper way.
Base::ObjectStatusLocker<App::Property::Status, App::Property> guard(
App::Property::NoModify,
&Visibility
);
// bool mod = false;
// if (pcDocument)
// mod = pcDocument->isModified();
getObject()->Visibility.setValue(Visibility.getValue());
// if (pcDocument)
// pcDocument->setModified(mod);
}
else {
getObject()->Visibility.setValue(Visibility.getValue());
}
}
}
else if (prop == &SelectionStyle) {
if (getRoot()->isOfType(SoFCSelectionRoot::getClassTypeId())) {
static_cast<SoFCSelectionRoot*>(getRoot())->selectionStyle = SelectionStyle.getValue()
? SoFCSelectionRoot::Box
: SoFCSelectionRoot::Full;
}
}
if (prop && !prop->testStatus(App::Property::NoModify) && pcDocument
&& !pcDocument->isModified() && testStatus(Gui::ViewStatus::TouchDocument)) {
if (prop) {
FC_LOG(prop->getFullName() << " changed");
}
pcDocument->setModified(true);
}
ViewProvider::onChanged(prop);
}
void ViewProviderDocumentObject::hide()
{
ViewProvider::hide();
// use this bit to check whether 'Visibility' must be adjusted
if (!Visibility.testStatus(App::Property::User2)) {
Visibility.setStatus(App::Property::User2, true);
Visibility.setValue(false);
Visibility.setStatus(App::Property::User2, false);
}
}
bool ViewProviderDocumentObject::isShowable() const
{
return _Showable;
}
void ViewProviderDocumentObject::setShowable(bool enable)
{
if (_Showable == enable) {
return;
}
_Showable = enable;
int which = getModeSwitch()->whichChild.getValue();
if (_Showable && which == -1 && Visibility.getValue()) {
setModeSwitch();
}
else if (!_Showable) {
if (which >= 0) {
ViewProvider::hide();
}
}
}
void ViewProviderDocumentObject::startDefaultEditMode()
{
QString text = QObject::tr("Edit %1").arg(QString::fromUtf8(getObject()->Label.getValue()));
Gui::Command::openCommand(text.toUtf8());
Gui::Document* document = this->getDocument();
if (document) {
document->setEdit(this, ViewProvider::Default);
}
}
void ViewProviderDocumentObject::addDefaultAction(QMenu* menu, const QString& text)
{
QAction* act = menu->addAction(text);
act->setData(QVariant((int)ViewProvider::Default));
auto func = new Gui::ActionFunction(menu);
func->trigger(act, [this]() { this->startDefaultEditMode(); });
}
void ViewProviderDocumentObject::setModeSwitch()
{
if (isShowable()) {
ViewProvider::setModeSwitch();
}
}
void ViewProviderDocumentObject::show()
{
if (TreeWidget::isObjectShowable(getObject())) {
ViewProvider::show();
}
else {
Visibility.setValue(false);
if (getObject()) {
getObject()->Visibility.setValue(false);
}
return;
}
// use this bit to check whether 'Visibility' must be adjusted
if (!Visibility.testStatus(App::Property::User2)) {
Visibility.setStatus(App::Property::User2, true);
Visibility.setValue(true);
Visibility.setStatus(App::Property::User2, false);
}
}
const char* ViewProviderDocumentObject::getTransactionText() const
{
return QT_TRANSLATE_NOOP("Command", "Edit");
}
void ViewProviderDocumentObject::updateView()
{
if (!pcObject || testStatus(ViewStatus::UpdatingView)) {
return;
}
Base::ObjectStatusLocker<ViewStatus, ViewProviderDocumentObject> lock(ViewStatus::UpdatingView, this);
// Disable object visibility syncing
Base::ObjectStatusLocker<App::Property::Status, App::Property> lock2(
App::Property::User1,
&Visibility
);
std::map<std::string, App::Property*> Map;
pcObject->getPropertyMap(Map);
// Hide the object temporarily to speed up the update
bool vis = ViewProvider::isShow();
if (vis) {
ViewProvider::hide();
}
for (const auto& it : Map) {
updateData(it.second);
}
if (vis && Visibility.getValue()) {
ViewProvider::show();
}
}
void ViewProviderDocumentObject::attach(App::DocumentObject* pcObj)
{
// save Object pointer
pcObject = pcObj;
if (pcObj && pcObj->isAttachedToDocument()
&& Visibility.getValue() != pcObj->Visibility.getValue()) {
pcObj->Visibility.setValue(Visibility.getValue());
}
// Retrieve the supported display modes of the view provider
aDisplayModesArray = this->getDisplayModes();
if (aDisplayModesArray.empty()) {
aDisplayModesArray.emplace_back("");
}
// We must collect the const char* of the strings and give it to PropertyEnumeration,
// but we are still responsible for them, i.e. the property class must not delete the literals.
// for (auto it = aDisplayModesArray.begin(); it != aDisplayModesArray.end(); ++it) {
for (const auto& it : aDisplayModesArray) {
aDisplayEnumsArray.push_back(it.c_str());
}
aDisplayEnumsArray.push_back(nullptr); // null termination
DisplayMode.setEnums(&(aDisplayEnumsArray[0]));
if (!isRestoring()) {
// set the active mode
const char* defmode = this->getDefaultDisplayMode();
if (defmode) {
DisplayMode.setValue(defmode);
}
}
// attach the extensions
auto vector = getExtensionsDerivedFromType<Gui::ViewProviderExtension>();
for (Gui::ViewProviderExtension* ext : vector) {
ext->extensionAttach(pcObj);
}
}
void ViewProviderDocumentObject::reattach(App::DocumentObject* pcObj)
{
auto vector = getExtensionsDerivedFromType<Gui::ViewProviderExtension>();
for (Gui::ViewProviderExtension* ext : vector) {
ext->extensionReattach(pcObj);
}
}
void ViewProviderDocumentObject::update(const App::Property* prop)
{
// bypass view provider update to always allow changing visibility from
// document object
if (prop == &getObject()->Visibility) {
if (!isRestoring() && Visibility.getValue() != getObject()->Visibility.getValue()) {
Visibility.setValue(!Visibility.getValue());
}
}
else {
// Disable object visibility syncing
Base::ObjectStatusLocker<App::Property::Status, App::Property> guard(
App::Property::User1,
&Visibility
);
ViewProvider::update(prop);
}
}
Gui::Document* ViewProviderDocumentObject::getDocument() const
{
if (!pcObject) {
throw Base::RuntimeError("View provider detached");
}
if (pcDocument) {
return pcDocument;
}
else {
App::Document* pAppDoc = pcObject->getDocument();
return Gui::Application::Instance->getDocument(pAppDoc);
}
}
Gui::MDIView* ViewProviderDocumentObject::getActiveView() const
{
if (!pcObject) {
throw Base::RuntimeError("View provider detached");
}
if (!pcObject->isAttachedToDocument()) {
// Check if view provider is attached to a document as an annotation
for (auto doc : App::GetApplication().getDocuments()) {
auto guiDoc = Gui::Application::Instance->getDocument(doc);
if (guiDoc->isAnnotationViewProvider(this)) {
return guiDoc->getActiveView();
}
}
return nullptr;
}
App::Document* pAppDoc = pcObject->getDocument();
Gui::Document* pGuiDoc = Gui::Application::Instance->getDocument(pAppDoc);
return pGuiDoc->getActiveView();
}
Gui::MDIView* ViewProviderDocumentObject::getEditingView() const
{
if (!pcObject) {
throw Base::RuntimeError("View provider detached");
}
App::Document* pAppDoc = pcObject->getDocument();
Gui::Document* pGuiDoc = Gui::Application::Instance->getDocument(pAppDoc);
return pGuiDoc->getEditingViewOfViewProvider(const_cast<ViewProviderDocumentObject*>(this));
}
Gui::MDIView* ViewProviderDocumentObject::getInventorView() const
{
if (!pcObject) {
throw Base::RuntimeError("View provider detached");
}
App::Document* pAppDoc = pcObject->getDocument();
Gui::Document* pGuiDoc = Gui::Application::Instance->getDocument(pAppDoc);
Gui::MDIView* mdi = pGuiDoc->getEditingViewOfViewProvider(
const_cast<ViewProviderDocumentObject*>(this)
);
if (!mdi) {
mdi = pGuiDoc->getViewOfViewProvider(const_cast<ViewProviderDocumentObject*>(this));
}
return mdi;
}
Gui::MDIView* ViewProviderDocumentObject::getViewOfNode(SoNode* node) const
{
if (!pcObject) {
throw Base::RuntimeError("View provider detached");
}
App::Document* pAppDoc = pcObject->getDocument();
Gui::Document* pGuiDoc = Gui::Application::Instance->getDocument(pAppDoc);
return pGuiDoc->getViewOfNode(node);
}
SoNode* ViewProviderDocumentObject::findFrontRootOfType(const SoType& type) const
{
if (!pcObject) {
return nullptr;
}
// first get the document this object is part of and get its GUI counterpart
App::Document* pAppDoc = pcObject->getDocument();
Gui::Document* pGuiDoc = Gui::Application::Instance->getDocument(pAppDoc);
SoSearchAction searchAction;
searchAction.setType(type);
searchAction.setInterest(SoSearchAction::FIRST);
// search in all view providers for the node type
std::vector<App::DocumentObject*> obj = pAppDoc->getObjects();
for (auto& it : obj) {
const ViewProvider* vp = pGuiDoc->getViewProvider(it);
// Ignore 'this' view provider. It could also happen that vp is 0, e.g. when
// several objects have been added to the App::Document before notifying the
// Gui::Document
if (!vp || vp == this) {
continue;
}
SoSeparator* front = vp->getFrontRoot();
// if (front && front->getTypeId() == type)
// return front;
if (front) {
searchAction.apply(front);
SoPath* path = searchAction.getPath();
if (path) {
return path->getTail();
}
}
}
return nullptr;
}
void ViewProviderDocumentObject::setActiveMode()
{
if (DisplayMode.isValid()) {
const char* mode = DisplayMode.getValueAsString();
if (mode) {
setDisplayMode(mode);
}
}
if (!Visibility.getValue()) {
ViewProvider::hide();
}
}
bool ViewProviderDocumentObject::canDelete(App::DocumentObject* obj) const
{
Q_UNUSED(obj);
auto* o = getObject();
return o->hasExtension(App::GroupExtension::getExtensionClassTypeId())
|| o->isDerivedFrom<App::Origin>();
}
PyObject* ViewProviderDocumentObject::getPyObject()
{
if (!pyViewObject) {
pyViewObject = new ViewProviderDocumentObjectPy(this);
}
pyViewObject->IncRef();
return pyViewObject;
}
bool ViewProviderDocumentObject::canDropObjectEx(
App::DocumentObject* obj,
App::DocumentObject* owner,
const char* subname,
const std::vector<std::string>& elements
) const
{
auto vector = getExtensionsDerivedFromType<Gui::ViewProviderExtension>();
for (Gui::ViewProviderExtension* ext : vector) {
if (ext->extensionCanDropObjectEx(obj, owner, subname, elements)) {
return true;
}
}
if (obj && obj->getDocument() != getObject()->getDocument()) {
return false;
}
return canDropObject(obj);
}
int ViewProviderDocumentObject::replaceObject(App::DocumentObject* oldObj, App::DocumentObject* newObj)
{
if (!oldObj || !oldObj->isAttachedToDocument() || !newObj || !newObj->isAttachedToDocument()) {
FC_THROWM(Base::RuntimeError, "Invalid object");
}
auto obj = getObject();
if (!obj || !obj->isAttachedToDocument()) {
FC_THROWM(Base::RuntimeError, "View provider not attached");
}
int res = ViewProvider::replaceObject(oldObj, newObj);
if (res >= 0) {
return res;
}
std::vector<std::pair<App::DocumentObjectT, std::unique_ptr<App::Property>>> propChanges;
std::vector<App::Property*> props;
obj->getPropertyList(props);
for (auto prop : props) {
auto linkProp = freecad_cast<App::PropertyLinkBase*>(prop);
if (!linkProp) {
continue;
}
std::unique_ptr<App::Property> copy(linkProp->CopyOnLinkReplace(obj, oldObj, newObj));
if (!copy) {
continue;
}
propChanges.emplace_back(prop, std::move(copy));
}
if (propChanges.empty()) {
return 0;
}
// Global search for affected links
for (auto doc : App::GetApplication().getDocuments()) {
for (auto o : doc->getObjects()) {
if (o == obj) {
continue;
}
std::vector<App::Property*> props;
o->getPropertyList(props);
for (auto prop : props) {
auto linkProp = freecad_cast<App::PropertyLinkBase*>(prop);
if (!linkProp) {
continue;
}
std::unique_ptr<App::Property> copy(linkProp->CopyOnLinkReplace(obj, oldObj, newObj));
if (!copy) {
continue;
}
propChanges.emplace_back(App::DocumentObjectT(prop), std::move(copy));
}
}
}
for (auto& v : propChanges) {
auto prop = v.first.getProperty();
if (prop) {
prop->Paste(*v.second.get());
}
}
return 1;
}
bool ViewProviderDocumentObject::showInTree() const
{
return ShowInTree.getValue();
}
bool ViewProviderDocumentObject::getElementPicked(const SoPickedPoint* pp, std::string& subname) const
{
auto vector = getExtensionsDerivedFromType<Gui::ViewProviderExtension>();
for (Gui::ViewProviderExtension* ext : vector) {
if (ext->extensionGetElementPicked(pp, subname)) {
return true;
}
}
auto childRoot = getChildRoot();
int idx;
if (!childRoot || (idx = pcModeSwitch->whichChild.getValue()) < 0
|| pcModeSwitch->getChild(idx) != childRoot) {
return ViewProvider::getElementPicked(pp, subname);
}
SoPath* path = pp->getPath();
idx = path->findNode(childRoot);
if (idx < 0 || idx + 1 >= path->getLength()) {
return false;
}
auto vp = getDocument()->getViewProvider(path->getNode(idx + 1));
if (!vp) {
return false;
}
auto obj = vp->getObject();
if (!obj || !obj->isAttachedToDocument()) {
return false;
}
std::ostringstream str;
str << obj->getNameInDocument() << '.';
if (vp->getElementPicked(pp, subname)) {
str << subname;
}
subname = str.str();
return true;
}
bool ViewProviderDocumentObject::getDetailPath(
const char* subname,
SoFullPath* path,
bool append,
SoDetail*& det
) const
{
auto len = path->getLength();
if (!append && len >= 2) {
len -= 2;
}
if (ViewProvider::getDetailPath(subname, path, append, det)) {
if (det || !subname || !*subname) {
return true;
}
}
if (det) {
delete det;
det = nullptr;
}
const char* dot = strchr(subname, '.');
if (!dot) {
return false;
}
auto obj = getObject();
if (!obj || !obj->isAttachedToDocument()) {
return false;
}
auto sobj = obj->getSubObject(std::string(subname, dot - subname + 1).c_str());
if (!sobj) {
return false;
}
auto vp = Application::Instance->getViewProvider(sobj);
if (!vp) {
return false;
}
auto childRoot = getChildRoot();
if (!childRoot) {
path->truncate(len);
}
else {
auto idx = pcModeSwitch->whichChild.getValue();
if (idx < 0 || pcModeSwitch->getChild(idx) != childRoot) {
return false;
}
path->append(childRoot);
}
bool ret = false;
if (path->getLength()) {
SoNode* tail = path->getTail();
const SoChildList* children = tail->getChildren();
if (children && children->find(vp->getRoot()) >= 0) {
ret = vp->getDetailPath(dot + 1, path, true, det);
}
}
return ret;
}
void ViewProviderDocumentObject::onPropertyStatusChanged(const App::Property& prop, unsigned long oldStatus)
{
(void)oldStatus;
if (!App::Document::isAnyRestoring() && pcObject && pcObject->getDocument()) {
pcObject->getDocument()->signalChangePropertyEditor(*pcObject->getDocument(), prop);
}
}
ViewProviderDocumentObject* ViewProviderDocumentObject::getLinkedViewProvider(
std::string* subname,
bool recursive
) const
{
(void)subname;
auto self = const_cast<ViewProviderDocumentObject*>(this);
if (!pcObject || !pcObject->isAttachedToDocument()) {
return self;
}
auto linked = pcObject->getLinkedObject(recursive);
if (!linked || linked == pcObject) {
return self;
}
auto res = freecad_cast<ViewProviderDocumentObject*>(
Application::Instance->getViewProvider(linked)
);
if (!res) {
res = self;
}
return res;
}
std::string ViewProviderDocumentObject::getFullName() const
{
if (pcObject) {
return pcObject->getFullName() + ".ViewObject";
}
return std::string("?");
}