Files
create/src/Gui/PropertyView.cpp
Markus Reitböck 6ef07bb358 Gui: use CMake to generate precompiled headers on all platforms
"Professional CMake" book suggest the following:

"Targets should build successfully with or without compiler support for precompiled headers. It
should be considered an optimization, not a requirement. In particular, do not explicitly include a
precompile header (e.g. stdafx.h) in the source code, let CMake force-include an automatically
generated precompile header on the compiler command line instead. This is more portable across
the major compilers and is likely to be easier to maintain. It will also avoid warnings being
generated from certain code checking tools like iwyu (include what you use)."

Therefore, removed the "#include <PreCompiled.h>" from sources, also
there is no need for the "#ifdef _PreComp_" anymore
2025-09-14 09:47:03 +02:00

595 lines
22 KiB
C++

/***************************************************************************
* Copyright (c) 2002 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 <QEvent>
# include <QGridLayout>
# include <QTimer>
#include <App/Document.h>
#include <App/DocumentObject.h>
#include <Base/Console.h>
#include <Base/Parameter.h>
#include <Base/Tools.h>
#include "PropertyView.h"
#include "Application.h"
#include "Document.h"
#include "MainWindow.h"
#include "SelectionObject.h"
#include "Tree.h"
#include "ViewParams.h"
#include "ViewProvider.h"
#include "ViewProviderDocumentObject.h"
#include "propertyeditor/PropertyEditor.h"
using namespace std;
using namespace Gui;
using namespace Gui::DockWnd;
using namespace Gui::PropertyEditor;
namespace sp = std::placeholders;
static ParameterGrp::handle _GetParam() {
static ParameterGrp::handle hGrp;
if(!hGrp) {
hGrp = App::GetApplication().GetParameterGroupByPath(
"User parameter:BaseApp/Preferences/PropertyView");
}
return hGrp;
}
/* TRANSLATOR Gui::PropertyView */
/*! Property Editor Widget
*
* Provides two Gui::PropertyEditor::PropertyEditor widgets, for "View" and "Data",
* in two tabs.
*/
PropertyView::PropertyView(QWidget *parent)
: QWidget(parent), SelectionObserver(false, ResolveMode::NoResolve)
{
auto pLayout = new QGridLayout( this );
pLayout->setSpacing(0);
pLayout->setContentsMargins(0, 0, 0, 0);
timer = new QTimer(this);
timer->setSingleShot(true);
connect(timer, &QTimer::timeout, this, &PropertyView::onTimer);
tabs = new QTabWidget (this);
tabs->setObjectName(QStringLiteral("propertyTab"));
tabs->setTabPosition(QTabWidget::South);
pLayout->addWidget(tabs, 0, 0);
propertyEditorView = new Gui::PropertyEditor::PropertyEditor();
propertyEditorView->setObjectName(QStringLiteral("propertyEditorView"));
propertyEditorView->setAutomaticDocumentUpdate(_GetParam()->GetBool("AutoTransactionView", false));
propertyEditorView->setAutomaticExpand(_GetParam()->GetBool("AutoExpandView", false));
tabs->addTab(propertyEditorView, tr("View"));
propertyEditorData = new Gui::PropertyEditor::PropertyEditor();
propertyEditorData->setObjectName(QStringLiteral("propertyEditorData"));
propertyEditorData->setAutomaticDocumentUpdate(_GetParam()->GetBool("AutoTransactionData", true));
propertyEditorData->setAutomaticExpand(_GetParam()->GetBool("AutoExpandData", false));
tabs->addTab(propertyEditorData, tr("Data"));
int preferredTab = _GetParam()->GetInt("LastTabIndex", 1);
if ( preferredTab > 0 && preferredTab < tabs->count() )
tabs->setCurrentIndex(preferredTab);
// connect after adding all tabs, so adding doesn't thrash the parameter
connect(tabs, &QTabWidget::currentChanged, this, &PropertyView::tabChanged);
//NOLINTBEGIN
this->connectPropData =
App::GetApplication().signalChangedObject.connect(std::bind
(&PropertyView::slotChangePropertyData, this, sp::_2));
this->connectPropView =
Gui::Application::Instance->signalChangedObject.connect(std::bind
(&PropertyView::slotChangePropertyView, this, sp::_1, sp::_2));
this->connectPropAppend =
App::GetApplication().signalAppendDynamicProperty.connect(std::bind
(&PropertyView::slotAppendDynamicProperty, this, sp::_1));
this->connectPropRemove =
App::GetApplication().signalRemoveDynamicProperty.connect(std::bind
(&PropertyView::slotRemoveDynamicProperty, this, sp::_1));
this->connectPropRename =
App::GetApplication().signalRenameDynamicProperty.connect(std::bind
(&PropertyView::slotRenameDynamicProperty, this, sp::_1, sp::_2));
this->connectPropChange =
App::GetApplication().signalChangePropertyEditor.connect(std::bind
(&PropertyView::slotChangePropertyEditor, this, sp::_1, sp::_2));
this->connectUndoDocument =
App::GetApplication().signalUndoDocument.connect(std::bind
(&PropertyView::slotRollback, this));
this->connectRedoDocument =
App::GetApplication().signalRedoDocument.connect(std::bind
(&PropertyView::slotRollback, this));
this->connectActiveDoc =
Application::Instance->signalActiveDocument.connect(std::bind
(&PropertyView::slotActiveDocument, this, sp::_1));
this->connectDelDocument =
Application::Instance->signalDeleteDocument.connect(
std::bind(&PropertyView::slotDeleteDocument, this, sp::_1));
this->connectDelViewObject =
Application::Instance->signalDeletedObject.connect(
std::bind(&PropertyView::slotDeletedViewObject, this, sp::_1));
this->connectDelObject =
App::GetApplication().signalDeletedObject.connect(
std::bind(&PropertyView::slotDeletedObject, this, sp::_1));
this->connectChangedDocument = App::GetApplication().signalChangedDocument.connect(
std::bind(&PropertyView::slotChangePropertyData, this, sp::_2));
//NOLINTEND
}
PropertyView::~PropertyView()
{
this->connectPropData.disconnect();
this->connectPropView.disconnect();
this->connectPropAppend.disconnect();
this->connectPropRemove.disconnect();
this->connectPropChange.disconnect();
this->connectUndoDocument.disconnect();
this->connectRedoDocument.disconnect();
this->connectActiveDoc.disconnect();
this->connectDelDocument.disconnect();
this->connectDelObject.disconnect();
this->connectDelViewObject.disconnect();
this->connectChangedDocument.disconnect();
}
static bool _ShowAll;
bool PropertyView::showAll() {
return _ShowAll;
}
void PropertyView::setShowAll(bool enable) {
if(_ShowAll != enable) {
_ShowAll = enable;
const auto views = getMainWindow()->findChildren<PropertyView*>();
for(auto view : views) {
if(view->isVisible()) {
view->propertyEditorData->buildUp();
view->propertyEditorView->buildUp();
view->onTimer();
}
}
}
}
void PropertyView::hideEvent(QHideEvent *ev) {
this->timer->stop();
this->detachSelection();
// clear the properties before hiding.
propertyEditorData->buildUp();
propertyEditorView->buildUp();
clearPropertyItemSelection();
QWidget::hideEvent(ev);
}
void PropertyView::showEvent(QShowEvent *ev) {
this->attachSelection();
this->timer->start(ViewParams::instance()->getPropertyViewTimer());
QWidget::showEvent(ev);
}
void PropertyView::clearPropertyItemSelection() {
QModelIndex index;
propertyEditorData->clearSelection();
propertyEditorData->setCurrentIndex(index);
propertyEditorView->clearSelection();
propertyEditorView->setCurrentIndex(index);
}
void PropertyView::slotRollback() {
// PropertyItemDelegate will setup application active transaction on
// entering edit mode, and close active transaction when exit editing. But,
// when the user clicks undo/redo button while editing some property, the
// current active transaction will be closed by design, which cause further
// editing to be not recorded. Hence, we force unselect any property item on
// undo/redo
clearPropertyItemSelection();
}
void PropertyView::slotChangePropertyData(const App::Property& prop)
{
if (propertyEditorData->propOwners.contains(prop.getContainer())) {
propertyEditorData->updateProperty(prop);
timer->start(ViewParams::instance()->getPropertyViewTimer());
}
}
void PropertyView::slotChangePropertyView(const Gui::ViewProvider&, const App::Property& prop)
{
if (propertyEditorView->propOwners.contains(prop.getContainer())) {
propertyEditorView->updateProperty(prop);
timer->start(ViewParams::instance()->getPropertyViewTimer());
}
}
bool PropertyView::isPropertyHidden(const App::Property *prop) {
return prop && !showAll() &&
((prop->getType() & App::Prop_Hidden) || prop->testStatus(App::Property::Hidden));
}
void PropertyView::slotAppendDynamicProperty(const App::Property& prop)
{
if (isPropertyHidden(&prop))
return;
App::PropertyContainer* parent = prop.getContainer();
if (propertyEditorData->propOwners.contains(parent)
|| propertyEditorView->propOwners.contains(parent))
{
timer->start(ViewParams::instance()->getPropertyViewTimer());
}
}
void PropertyView::slotRemoveDynamicProperty(const App::Property& prop)
{
App::PropertyContainer* parent = prop.getContainer();
if(propertyEditorData->propOwners.contains(parent))
propertyEditorData->removeProperty(prop);
else if(propertyEditorView->propOwners.contains(parent))
propertyEditorView->removeProperty(prop);
else
return;
timer->start(ViewParams::instance()->getPropertyViewTimer());
}
void PropertyView::slotRenameDynamicProperty(const App::Property& prop,
const char* /*oldName*/)
{
App::PropertyContainer* parent = prop.getContainer();
if (propertyEditorData->propOwners.contains(parent)) {
propertyEditorData->renameProperty(prop);
}
else if (propertyEditorView->propOwners.contains(parent)) {
propertyEditorView->renameProperty(prop);
}
else {
return;
}
clearPropertyItemSelection();
timer->start(ViewParams::instance()->getPropertyViewTimer());
}
void PropertyView::slotChangePropertyEditor(const App::Document &, const App::Property& prop)
{
App::PropertyContainer* parent = prop.getContainer();
if (propertyEditorData->propOwners.contains(parent)
|| propertyEditorView->propOwners.contains(parent))
timer->start(ViewParams::instance()->getPropertyViewTimer());
}
void PropertyView::slotDeleteDocument(const Gui::Document &doc) {
if(propertyEditorData->propOwners.contains(doc.getDocument())) {
propertyEditorView->buildUp();
propertyEditorData->buildUp();
clearPropertyItemSelection();
timer->start(ViewParams::instance()->getPropertyViewTimer());
}
}
void PropertyView::slotDeletedViewObject(const Gui::ViewProvider &vp) {
if(propertyEditorView->propOwners.contains(&vp)) {
propertyEditorView->buildUp();
propertyEditorData->buildUp();
clearPropertyItemSelection();
timer->start(ViewParams::instance()->getPropertyViewTimer());
}
}
void PropertyView::slotDeletedObject(const App::DocumentObject &obj) {
if(propertyEditorData->propOwners.contains(&obj)) {
propertyEditorView->buildUp();
propertyEditorData->buildUp();
clearPropertyItemSelection();
timer->start(ViewParams::instance()->getPropertyViewTimer());
}
}
void PropertyView::slotActiveDocument(const Gui::Document &doc)
{
checkEnable(doc.getDocument()->getName());
}
void PropertyView::checkEnable(const char *doc) {
if(ViewParams::instance()->getEnablePropertyViewForInactiveDocument()) {
setEnabled(true);
return;
}
// check if at least one selected object is part of the active document
setEnabled(!Selection().hasSelection()
|| Selection().hasSelection(doc, ResolveMode::NoResolve));
}
struct PropertyView::PropInfo
{
std::string propName;
int propId;
std::vector<App::Property*> propList;
};
struct PropertyView::PropFind {
const PropInfo& item;
explicit PropFind(const PropInfo& item) : item(item) {}
bool operator () (const PropInfo& elem) const
{
return (elem.propId == item.propId) &&
(elem.propName == item.propName);
}
};
void PropertyView::onSelectionChanged(const SelectionChanges& msg)
{
if (msg.Type != SelectionChanges::AddSelection &&
msg.Type != SelectionChanges::RmvSelection &&
msg.Type != SelectionChanges::SetSelection &&
msg.Type != SelectionChanges::ClrSelection)
return;
// clear the properties.
timer->start(ViewParams::instance()->getPropertyViewTimer());
}
void PropertyView::onTimer()
{
// See https://forum.freecad.org/viewtopic.php?f=8&t=72526
if (this->updating) {
Base::Console().log("Ignore recursive call of PropertyView::onTimer()\n");
return;
}
Base::StateLocker guard(this->updating);
timer->stop();
if(!this->isSelectionAttached()) {
propertyEditorData->buildUp();
propertyEditorView->buildUp();
clearPropertyItemSelection();
return;
}
if(!Gui::Selection().hasSelection()) {
auto gdoc = TreeWidget::selectedDocument();
if(!gdoc || !gdoc->getDocument()) {
propertyEditorData->buildUp();
propertyEditorView->buildUp();
clearPropertyItemSelection();
return;
}
PropertyModel::PropertyList docProps;
auto doc = gdoc->getDocument();
std::map<std::string,App::Property*> props;
doc->getPropertyMap(props);
for(auto &v : props)
docProps.emplace_back(v.first,
std::vector<App::Property*>(1,v.second));
propertyEditorData->buildUp(std::move(docProps));
tabs->setCurrentIndex(1);
return;
}
std::set<App::DocumentObject *> objSet;
// group the properties by <name,id>
std::vector<PropInfo> propDataMap;
std::vector<PropInfo> propViewMap;
bool checkLink = true;
ViewProviderDocumentObject *vpLast = nullptr;
auto sels = Gui::Selection().getSelectionEx("*");
for(auto &sel : sels) {
App::DocumentObject *ob = sel.getObject();
if(!ob) continue;
// Do not process an object more than once
if(!objSet.insert(ob).second)
continue;
std::vector<App::Property*> dataList;
std::map<std::string, App::Property*> viewList;
auto vp = Application::Instance->getViewProvider(ob);
if(!vp) {
checkLink = false;
ob->getPropertyList(dataList);
continue;
}
if(vp->isDerivedFrom<ViewProviderDocumentObject>()) {
auto cvp = static_cast<ViewProviderDocumentObject*>(vp);
if(vpLast && cvp!=vpLast)
checkLink = false;
vpLast = cvp;
}
ob->getPropertyList(dataList);
// get the properties as map here because it doesn't matter to have them sorted alphabetically
vp->getPropertyMap(viewList);
// store the properties with <name,id> as key in a map
{
for (auto prop : dataList) {
if (isPropertyHidden(prop))
continue;
PropInfo nameType;
nameType.propName = prop->getName();
nameType.propId = prop->getTypeId().getKey();
auto pi = std::find_if(propDataMap.begin(), propDataMap.end(), PropFind(nameType));
if (pi != propDataMap.end()) {
pi->propList.push_back(prop);
}
else {
nameType.propList.push_back(prop);
propDataMap.push_back(nameType);
}
}
}
// the same for the view properties
{
std::map<std::string, App::Property*>::iterator pt;
for (pt = viewList.begin(); pt != viewList.end(); ++pt) {
if (isPropertyHidden(pt->second))
continue;
PropInfo nameType;
nameType.propName = pt->first;
nameType.propId = pt->second->getTypeId().getKey();
auto pi = std::find_if(propViewMap.begin(), propViewMap.end(), PropFind(nameType));
if (pi != propViewMap.end()) {
pi->propList.push_back(pt->second);
}
else {
nameType.propList.push_back(pt->second);
propViewMap.push_back(nameType);
}
}
}
}
// the property must be part of each selected object, i.e. the number
// of selected objects is equal to the number of properties with same
// name and id
std::vector<PropInfo>::const_iterator it;
PropertyModel::PropertyList dataProps;
std::map<std::string, std::vector<App::Property*> > dataPropsMap;
PropertyModel::PropertyList viewProps;
if(checkLink && vpLast) {
// In case the only selected object is a link, insert the link's own
// property before the linked object
App::DocumentObject *obj = vpLast->getObject();
auto linked = obj;
if(obj && obj->canLinkProperties() && (linked=obj->getLinkedObject(true))!=obj && linked) {
std::vector<App::Property*> dataList;
std::map<std::string, App::Property*> propMap;
obj->getPropertyMap(propMap);
linked->getPropertyList(dataList);
for(auto prop : dataList) {
if(isPropertyHidden(prop))
continue;
std::string name(prop->getName());
auto it = propMap.find(name);
if(it!=propMap.end() && !isPropertyHidden(it->second))
continue;
std::vector<App::Property*> items(1,prop);
if(prop->testStatus(App::Property::PropDynamic))
dataPropsMap.emplace(name+"*",std::move(items));
else
dataProps.emplace_back(name+"*", std::move(items));
}
auto vpLinked = Application::Instance->getViewProvider(linked);
if(vpLinked) {
propMap.clear();
vpLast->getPropertyMap(propMap);
dataList.clear();
vpLinked->getPropertyList(dataList);
for(auto prop : dataList) {
if(isPropertyHidden(prop))
continue;
std::string name(prop->getName());
auto it = propMap.find(name);
if(it!=propMap.end() && !isPropertyHidden(it->second))
continue;
std::vector<App::Property*> items(1,prop);
viewProps.emplace_back(name+"*", std::move(items));
}
}
}
}
for(auto &v : dataPropsMap)
dataProps.emplace_back(v.first,std::move(v.second));
dataPropsMap.clear();
for (it = propDataMap.begin(); it != propDataMap.end(); ++it) {
if (it->propList.size() == sels.size()) {
if(it->propList[0]->testStatus(App::Property::PropDynamic))
dataPropsMap.emplace(it->propName, std::move(it->propList));
else
dataProps.emplace_back(it->propName, std::move(it->propList));
}
}
for(auto &v : dataPropsMap)
dataProps.emplace_back(v.first,std::move(v.second));
propertyEditorData->buildUp(std::move(dataProps),true);
for (it = propViewMap.begin(); it != propViewMap.end(); ++it) {
if (it->propList.size() == sels.size())
viewProps.emplace_back(it->propName, std::move(it->propList));
}
propertyEditorView->buildUp(std::move(viewProps));
// make sure the editors are enabled/disabled properly
checkEnable();
}
void PropertyView::tabChanged(int index)
{
_GetParam()->SetInt("LastTabIndex",index);
}
void PropertyView::changeEvent(QEvent *e)
{
if (e->type() == QEvent::LanguageChange) {
tabs->setTabText(0, tr("View"));
tabs->setTabText(1, tr("Data"));
}
QWidget::changeEvent(e);
}
/* TRANSLATOR Gui::DockWnd::PropertyDockView */
PropertyDockView::PropertyDockView(Gui::Document* pcDocument, QWidget *parent)
: DockWindow(pcDocument,parent)
{
setWindowTitle(tr("Property View"));
auto view = new PropertyView(this);
auto pLayout = new QGridLayout(this);
pLayout->setSpacing(0);
pLayout->setContentsMargins(0, 0, 0, 0);
pLayout->addWidget(view, 0, 0);
resize( 200, 400 );
}
PropertyDockView::~PropertyDockView() = default;
#include "moc_PropertyView.cpp"