Files
create/src/Gui/DlgPropertyLink.cpp
wwmayer 7b0a23337c Gui: Prepare for clang-format (#16051)
* Gui: Prepare for clang-format

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-09-02 17:48:55 +02:00

1233 lines
36 KiB
C++

/***************************************************************************
* Copyright (c) 2014 Werner Mayer <wmayer[at]users.sourceforge.net> *
* *
* 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"
#ifndef _PreComp_
#include <algorithm>
#include <sstream>
#include <QStyledItemDelegate>
#include <QTreeWidgetItem>
#endif
#include <App/Document.h>
#include <App/DocumentObject.h>
#include <App/GeoFeature.h>
#include <App/ObjectIdentifier.h>
#include <App/PropertyPythonObject.h>
#include <Base/Interpreter.h>
#include <Base/Tools.h>
#include "DlgPropertyLink.h"
#include "ui_DlgPropertyLink.h"
#include "Application.h"
#include "Document.h"
#include "BitmapFactory.h"
#include "PropertyView.h"
#include "Selection.h"
#include "Tree.h"
#include "TreeParams.h"
#include "View3DInventor.h"
#include "ViewProviderDocumentObject.h"
using namespace Gui::Dialog;
class ItemDelegate: public QStyledItemDelegate
{
public:
explicit ItemDelegate(QObject* parent = nullptr)
: QStyledItemDelegate(parent)
{}
QWidget* createEditor(QWidget* parent,
const QStyleOptionViewItem& option,
const QModelIndex& index) const override
{
if (index.column() != 1) {
return nullptr;
}
return QStyledItemDelegate::createEditor(parent, option, index);
}
};
/* TRANSLATOR Gui::Dialog::DlgPropertyLink */
DlgPropertyLink::DlgPropertyLink(QWidget* parent)
: QDialog(parent)
, SelectionObserver(false, ResolveMode::NoResolve)
, ui(new Ui_DlgPropertyLink)
{
// clang-format off
ui->setupUi(this);
connect(ui->checkObjectType, &QCheckBox::toggled,
this, &DlgPropertyLink::onObjectTypeToggled);
connect(ui->typeTree, &QTreeWidget::itemSelectionChanged,
this, &DlgPropertyLink::onTypeTreeItemSelectionChanged);
connect(ui->searchBox, &ExpressionLineEdit::textChanged,
this, &DlgPropertyLink::onSearchBoxTextChanged);
ui->typeTree->hide();
ui->searchBox->installEventFilter(this);
ui->searchBox->setNoProperty(true);
ui->searchBox->setExactMatch(Gui::ExpressionParameter::instance()->isExactMatch());
timer = new QTimer(this);
timer->setSingleShot(true);
connect(timer, &QTimer::timeout, this, &DlgPropertyLink::onTimer);
ui->treeWidget->setEditTriggers(QAbstractItemView::DoubleClicked);
ui->treeWidget->setItemDelegate(new ItemDelegate(this));
ui->treeWidget->setMouseTracking(true);
connect(ui->treeWidget, &QTreeWidget::itemEntered,
this, &DlgPropertyLink::onItemEntered);
connect(ui->treeWidget, &QTreeWidget::itemExpanded,
this, &DlgPropertyLink::onItemExpanded);
connect(ui->treeWidget, &QTreeWidget::itemSelectionChanged, this, &DlgPropertyLink::onItemSelectionChanged);
connect(ui->searchBox, &QLineEdit::returnPressed, this, &DlgPropertyLink::onItemSearch);
connect(ui->buttonBox, &QDialogButtonBox::clicked, this, &DlgPropertyLink::onClicked);
refreshButton = ui->buttonBox->addButton(tr("Reset"), QDialogButtonBox::ActionRole);
resetButton = ui->buttonBox->addButton(tr("Clear"), QDialogButtonBox::ResetRole);
// clang-format on
}
/**
* Destroys the object and frees any allocated resources
*/
DlgPropertyLink::~DlgPropertyLink()
{
detachObserver();
// no need to delete child widgets, Qt does it all for us
delete ui;
}
QList<App::SubObjectT> DlgPropertyLink::getLinksFromProperty(const App::PropertyLinkBase* prop)
{
QList<App::SubObjectT> res;
if (!prop) {
return res;
}
std::vector<App::DocumentObject*> objs;
std::vector<std::string> subs;
prop->getLinks(objs, true, &subs, false);
if (subs.empty()) {
for (auto obj : objs) {
res.push_back(App::SubObjectT(obj, nullptr));
}
}
else if (objs.size() == 1) {
for (auto& sub : subs) {
res.push_back(App::SubObjectT(objs.front(), sub.c_str()));
}
}
else {
int i = 0;
for (auto obj : objs) {
res.push_back(App::SubObjectT(obj, subs[i++].c_str()));
}
}
return res;
}
QString
DlgPropertyLink::formatObject(App::Document* ownerDoc, App::DocumentObject* obj, const char* sub)
{
if (!obj || !obj->isAttachedToDocument()) {
return QLatin1String("?");
}
const char* objName = obj->getNameInDocument();
std::string _objName;
if (ownerDoc && ownerDoc != obj->getDocument()) {
_objName = obj->getFullName();
objName = _objName.c_str();
}
if (!sub || !sub[0]) {
if (obj->Label.getStrValue() == obj->getNameInDocument()) {
return QLatin1String(objName);
}
return QString::fromLatin1("%1 (%2)").arg(QLatin1String(objName),
QString::fromUtf8(obj->Label.getValue()));
}
auto sobj = obj->getSubObject(sub);
if (!sobj || sobj->Label.getStrValue() == sobj->getNameInDocument()) {
return QString::fromLatin1("%1.%2").arg(QLatin1String(objName), QString::fromUtf8(sub));
}
return QString::fromLatin1("%1.%2 (%3)")
.arg(QLatin1String(objName),
QString::fromUtf8(sub),
QString::fromUtf8(sobj->Label.getValue()));
}
static inline bool isLinkSub(const QList<App::SubObjectT>& links)
{
for (const auto& link : links) {
if (&link == &links.front()) {
continue;
}
if (link.getDocumentName() != links.front().getDocumentName()
|| link.getObjectName() != links.front().getObjectName()) {
return false;
}
}
return true;
}
QString DlgPropertyLink::formatLinks(App::Document* ownerDoc, QList<App::SubObjectT> links)
{
if (!ownerDoc || links.empty()) {
return {};
}
auto obj = links.front().getObject();
if (!obj) {
return QLatin1String("?");
}
if (links.size() == 1 && links.front().getSubName().empty()) {
return formatObject(ownerDoc, links.front());
}
QStringList list;
if (isLinkSub(links)) {
int i = 0;
for (auto& link : links) {
list << QString::fromUtf8(link.getSubName().c_str());
if (++i >= 3) {
break;
}
}
return QString::fromLatin1("%1 [%2%3]")
.arg(formatObject(ownerDoc, obj, nullptr),
list.join(QLatin1String(", ")),
QLatin1String(links.size() > 3 ? " ..." : ""));
}
int i = 0;
for (auto& link : links) {
list << formatObject(ownerDoc, link);
if (++i >= 3) {
break;
}
}
return QString::fromLatin1("[%1%2]").arg(list.join(QLatin1String(", ")),
QLatin1String(links.size() > 3 ? " ..." : ""));
}
void DlgPropertyLink::init(const App::DocumentObjectT& prop, bool tryFilter)
{
ui->treeWidget->blockSignals(true);
ui->treeWidget->clear();
ui->treeWidget->blockSignals(false);
ui->typeTree->blockSignals(true);
ui->typeTree->clear();
ui->typeTree->blockSignals(false);
oldLinks.clear();
docItems.clear();
typeItems.clear();
itemMap.clear();
inList.clear();
selectedTypes.clear();
currentObj = nullptr;
searchItem = nullptr;
subSelections.clear();
selections.clear();
objProp = prop;
auto owner = objProp.getObject();
if (!owner || !owner->isAttachedToDocument()) {
return;
}
ui->searchBox->setDocumentObject(owner);
auto propLink = Base::freecad_dynamic_cast<App::PropertyLinkBase>(objProp.getProperty());
if (!propLink) {
return;
}
oldLinks = getLinksFromProperty(propLink);
if (propLink->getScope() != App::LinkScope::Hidden) {
// populate inList to filter out any objects that contains the owner object
// of the editing link property
inList = owner->getInListEx(true);
inList.insert(owner);
}
std::vector<App::Document*> docs;
singleSelect = false;
if (propLink->isDerivedFrom(App::PropertyXLinkSub::getClassTypeId())
|| propLink->isDerivedFrom(App::PropertyLinkSub::getClassTypeId())) {
allowSubObject = true;
singleParent = true;
}
else if (propLink->isDerivedFrom(App::PropertyLink::getClassTypeId())) {
singleSelect = true;
}
else if (propLink->isDerivedFrom(App::PropertyLinkSubList::getClassTypeId())) {
allowSubObject = true;
}
if (App::PropertyXLink::supportXLink(propLink)) {
allowSubObject = true;
docs = App::GetApplication().getDocuments();
}
else {
docs.push_back(owner->getDocument());
}
bool isLinkList = false;
if (propLink->isDerivedFrom(App::PropertyXLinkList::getClassTypeId())
|| propLink->isDerivedFrom(App::PropertyLinkList::getClassTypeId())) {
isLinkList = true;
allowSubObject = false;
}
if (singleSelect) {
singleParent = true;
ui->treeWidget->setSelectionMode(QAbstractItemView::SingleSelection);
}
else {
ui->treeWidget->setSelectionMode(QAbstractItemView::MultiSelection);
}
ui->checkSubObject->setVisible(allowSubObject);
if (!allowSubObject) {
ui->treeWidget->setColumnCount(1);
}
else {
ui->treeWidget->setColumnCount(2);
// make sure to show a horizontal scrollbar if needed
ui->treeWidget->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents);
}
std::set<App::Document*> expandDocs;
if (oldLinks.empty()) {
expandDocs.insert(owner->getDocument());
}
else {
for (auto& link : oldLinks) {
auto doc = link.getDocument();
if (doc) {
expandDocs.insert(doc);
}
}
}
QPixmap docIcon(Gui::BitmapFactory().pixmap("Document"));
for (auto d : docs) {
auto item = new QTreeWidgetItem(ui->treeWidget);
item->setIcon(0, docIcon);
item->setText(0, QString::fromUtf8(d->Label.getValue()));
item->setData(0, Qt::UserRole, QByteArray(""));
item->setData(0, Qt::UserRole + 1, QByteArray(d->getName()));
item->setFlags(Qt::ItemIsEnabled);
item->setChildIndicatorPolicy(QTreeWidgetItem::ShowIndicator);
if (expandDocs.count(d)) {
item->setExpanded(true);
}
docItems[d] = item;
}
if (allowSubObject) {
if (propLink->testFlag(App::PropertyLinkBase::LinkSyncSubObject)) {
ui->checkSubObject->setChecked(true);
}
else {
for (auto& link : oldLinks) {
auto sobj = link.getSubObject();
if (sobj && sobj != link.getObject()) {
ui->checkSubObject->setChecked(true);
break;
}
}
}
}
if (oldLinks.isEmpty()) {
return;
}
// Try to select items corresponding to the current links inside the
// property
ui->treeWidget->blockSignals(true);
for (auto& link : oldLinks) {
onSelectionChanged(Gui::SelectionChanges(SelectionChanges::AddSelection,
link.getDocumentName(),
link.getObjectName(),
link.getSubName()));
}
ui->treeWidget->blockSignals(false);
// For link list type property, try to auto filter type
if (tryFilter && isLinkList) {
Base::Type objType;
for (const auto& link : std::as_const(oldLinks)) {
auto obj = link.getSubObject();
if (!obj) {
continue;
}
if (objType.isBad()) {
objType = obj->getTypeId();
continue;
}
for (; objType != App::DocumentObject::getClassTypeId();
objType = objType.getParent()) {
if (obj->isDerivedFrom(objType)) {
break;
}
}
}
Base::Type baseType;
// get only geometric types
if (objType.isDerivedFrom(App::GeoFeature::getClassTypeId())) {
baseType = App::GeoFeature::getClassTypeId();
}
else {
baseType = App::DocumentObject::getClassTypeId();
}
// get the direct base class of App::DocumentObject which 'obj' is derived from
while (!objType.isBad()) {
Base::Type parType = objType.getParent();
if (parType == baseType) {
baseType = objType;
break;
}
objType = parType;
}
if (!baseType.isBad()) {
const char* name = baseType.getName();
auto it = typeItems.find(QByteArray::fromRawData(name, strlen(name) + 1));
if (it != typeItems.end()) {
it->second->setSelected(true);
}
ui->checkObjectType->setChecked(true);
}
}
}
void DlgPropertyLink::onClicked(QAbstractButton* button)
{
if (button == resetButton) {
ui->treeWidget->blockSignals(true);
ui->treeWidget->selectionModel()->clearSelection();
for (auto item : subSelections) {
item->setText(1, QString());
}
ui->treeWidget->blockSignals(false);
subSelections.clear();
Gui::Selection().clearSelection();
}
else if (button == refreshButton) {
init(objProp);
}
}
void DlgPropertyLink::hideEvent(QHideEvent* ev)
{
detachObserver();
QDialog::hideEvent(ev);
}
void DlgPropertyLink::closeEvent(QCloseEvent* ev)
{
detachObserver();
QDialog::closeEvent(ev);
}
void DlgPropertyLink::attachObserver()
{
if (isSelectionAttached()) {
return;
}
Gui::Selection().selStackPush();
attachSelection();
if (!parentView) {
for (auto p = parent(); p; p = p->parent()) {
auto view = qobject_cast<Gui::PropertyView*>(p);
if (view) {
parentView = view;
for (auto& sel : Gui::Selection().getCompleteSelection(ResolveMode::NoResolve)) {
savedSelections.emplace_back(sel.DocName, sel.FeatName, sel.SubName);
}
break;
}
}
}
auto view = qobject_cast<Gui::PropertyView*>(parentView.data());
if (view) {
view->blockSelection(true);
}
}
void DlgPropertyLink::showEvent(QShowEvent* ev)
{
attachObserver();
QDialog::showEvent(ev);
}
void DlgPropertyLink::onItemEntered(QTreeWidgetItem*)
{
int timeout = Gui::TreeParams::getPreSelectionDelay() / 2;
if (timeout < 0) {
timeout = 1;
}
timer->start(timeout);
Gui::Selection().rmvPreselect();
}
void DlgPropertyLink::leaveEvent(QEvent* ev)
{
Gui::Selection().rmvPreselect();
QDialog::leaveEvent(ev);
}
void DlgPropertyLink::detachObserver()
{
if (isSelectionAttached()) {
detachSelection();
}
auto view = qobject_cast<Gui::PropertyView*>(parentView.data());
if (view && !savedSelections.empty()) {
try {
Gui::Selection().clearSelection();
}
catch (Py::Exception& e) {
e.clear();
}
for (auto& sel : savedSelections) {
if (sel.getSubObject()) {
Gui::Selection().addSelection(sel.getDocumentName().c_str(),
sel.getObjectName().c_str(),
sel.getSubName().c_str());
}
}
savedSelections.clear();
}
if (view) {
view->blockSelection(false);
}
parentView = nullptr;
}
void DlgPropertyLink::onItemSelectionChanged()
{
auto newSelections = ui->treeWidget->selectedItems();
if (newSelections.isEmpty() || selections.contains(newSelections.back())) {
selections = newSelections;
if (newSelections.isEmpty()) {
currentObj = nullptr;
}
return;
}
selections = newSelections;
auto sobjs = getLinkFromItem(newSelections.back());
App::DocumentObject* obj = !sobjs.empty() ? sobjs.front().getObject() : nullptr;
if (!obj) {
Gui::Selection().clearSelection();
return;
}
bool focus = false;
// Do auto view switch if tree view does not do it
if (!TreeParams::getSyncView()) {
focus = ui->treeWidget->hasFocus();
auto doc = Gui::Application::Instance->getDocument(sobjs.front().getDocumentName().c_str());
if (doc) {
auto vp = Base::freecad_dynamic_cast<Gui::ViewProviderDocumentObject>(
doc->getViewProvider(obj));
if (vp) {
// If the view provider uses a special window for rendering, switch to it
MDIView* view = vp->getMDIView();
if (view) {
doc->setActiveWindow(view);
}
else {
doc->setActiveView(vp, Gui::View3DInventor::getClassTypeId());
}
}
}
}
// Sync 3d view selection. To give a better visual feedback, we
// only keep the latest selection.
bool blocked = blockSelection(true);
Gui::Selection().clearSelection();
for (auto& sobj : sobjs) {
Gui::Selection().addSelection(sobj.getDocumentName().c_str(),
sobj.getObjectName().c_str(),
sobj.getSubName().c_str());
}
blockSelection(blocked);
// Enforce single parent
if (singleParent && currentObj && currentObj != obj) {
ui->treeWidget->blockSignals(true);
const auto items = ui->treeWidget->selectedItems();
for (auto item : items) {
if (item != selections.back()) {
item->setSelected(false);
}
}
auto last = selections.back();
selections.clear();
selections.append(last);
ui->treeWidget->blockSignals(false);
}
currentObj = obj;
if (focus) {
// FIXME: does not work, why?
ui->treeWidget->setFocus();
}
}
QTreeWidgetItem*
DlgPropertyLink::findItem(App::DocumentObject* obj, const char* subname, bool* pfound)
{
if (pfound) {
*pfound = false;
}
if (!obj || !obj->isAttachedToDocument()) {
return nullptr;
}
std::vector<App::DocumentObject*> sobjs;
if (subname && subname[0]) {
if (!allowSubObject) {
obj = obj->getSubObject(subname);
if (!obj) {
return nullptr;
}
}
else {
sobjs = obj->getSubObjectList(subname);
}
}
auto itDoc = docItems.find(obj->getDocument());
if (itDoc == docItems.end()) {
return nullptr;
}
onItemExpanded(itDoc->second);
auto it = itemMap.find(obj);
if (it == itemMap.end() || it->second->isHidden()) {
return nullptr;
}
if (!allowSubObject) {
if (pfound) {
*pfound = true;
}
return it->second;
}
QTreeWidgetItem* item = it->second;
bool first = true;
for (auto o : sobjs) {
if (first) {
first = false;
continue;
}
onItemExpanded(item);
bool found = false;
for (int i = 0, count = item->childCount(); i < count; ++i) {
auto child = item->child(i);
if (strcmp(o->getNameInDocument(),
child->data(0, Qt::UserRole).toByteArray().constData())
== 0) {
item = child;
found = true;
break;
}
}
if (!found) {
return item;
}
}
if (pfound) {
*pfound = true;
}
return item;
}
void DlgPropertyLink::onSelectionChanged(const Gui::SelectionChanges& msg)
{
if (msg.Type != SelectionChanges::AddSelection) {
return;
}
bool found = false;
auto selObj = msg.Object.getObject();
App::ElementNamePair elementName;
const char* subname = msg.pSubName;
if (!ui->checkSubObject->isChecked()) {
selObj = App::GeoFeature::resolveElement(selObj, subname, elementName);
if (!selObj) {
return;
}
subname = elementName.oldName.c_str();
}
auto item = findItem(selObj, msg.pSubName, &found);
if (!item) {
return;
}
if (!item->isSelected()) {
ui->treeWidget->blockSignals(true);
if (singleSelect || (singleParent && currentObj && currentObj != selObj)) {
ui->treeWidget->selectionModel()->clearSelection();
}
currentObj = selObj;
item->setSelected(true);
selections.append(item);
ui->treeWidget->blockSignals(false);
}
ui->treeWidget->scrollToItem(item);
if (allowSubObject) {
QString element = QString::fromLatin1(msg.Object.getOldElementName().c_str());
if (element.size()) {
QStringList list;
QString text = item->text(1);
if (text.size()) {
list = text.split(QLatin1Char(','));
}
if (list.indexOf(element) < 0) {
list << element;
item->setText(1, list.join(QLatin1String(",")));
subSelections.insert(item);
}
}
else if (subSelections.erase(item)) {
item->setText(1, QString());
}
}
}
void DlgPropertyLink::accept()
{
QDialog::accept();
}
static QTreeWidgetItem*
_getLinkFromItem(std::ostringstream& ss, QTreeWidgetItem* item, const char* objName)
{
auto parent = item->parent();
assert(parent);
QByteArray nextName = parent->data(0, Qt::UserRole).toByteArray();
if (nextName.isEmpty()) {
return item;
}
item = _getLinkFromItem(ss, parent, nextName);
ss << objName << '.';
return item;
}
QList<App::SubObjectT> DlgPropertyLink::getLinkFromItem(QTreeWidgetItem* item,
bool needSubName) const
{
QList<App::SubObjectT> res;
auto parent = item->parent();
if (!parent) {
return res;
}
std::ostringstream ss;
auto parentItem =
_getLinkFromItem(ss, item, item->data(0, Qt::UserRole).toByteArray().constData());
App::SubObjectT sobj(parentItem->data(0, Qt::UserRole + 1).toByteArray().constData(),
parentItem->data(0, Qt::UserRole).toByteArray().constData(),
ss.str().c_str());
QString elements;
if (needSubName && allowSubObject) {
elements = item->text(1);
}
if (elements.isEmpty()) {
res.append(App::SubObjectT());
res.last() = std::move(sobj);
return res;
}
const auto split = elements.split(QLatin1Char(','));
for (const QString& element : split) {
res.append(App::SubObjectT());
res.last() = App::SubObjectT(sobj.getDocumentName().c_str(),
sobj.getObjectName().c_str(),
(sobj.getSubName() + element.toLatin1().constData()).c_str());
}
return res;
}
void DlgPropertyLink::onTimer()
{
auto item = ui->treeWidget->itemAt(ui->treeWidget->viewport()->mapFromGlobal(QCursor::pos()));
if (!item) {
return;
}
auto sobjs = getLinkFromItem(item);
if (sobjs.isEmpty()) {
return;
}
const auto& sobj = sobjs.front();
Gui::Selection().setPreselect(sobj.getDocumentName().c_str(),
sobj.getObjectName().c_str(),
sobj.getSubName().c_str(),
0,
0,
0,
Gui::SelectionChanges::MsgSource::TreeView);
}
QList<App::SubObjectT> DlgPropertyLink::currentLinks() const
{
auto items = ui->treeWidget->selectedItems();
QList<App::SubObjectT> res;
for (auto item : items) {
res.append(getLinkFromItem(item));
}
return res;
}
QList<App::SubObjectT> DlgPropertyLink::originalLinks() const
{
return oldLinks;
}
QString DlgPropertyLink::linksToPython(const QList<App::SubObjectT>& links)
{
if (links.isEmpty()) {
return QLatin1String("None");
}
if (links.size() == 1) {
return QString::fromLatin1(links.front().getSubObjectPython(false).c_str());
}
std::ostringstream ss;
if (isLinkSub(links)) {
ss << '(' << links.front().getObjectPython() << ", [";
for (const auto& link : links) {
const auto& sub = link.getSubName();
if (!sub.empty()) {
ss << "u'" << Base::Tools::escapedUnicodeFromUtf8(sub.c_str()) << "',";
}
}
ss << "])";
}
else {
ss << '[';
for (const auto& link : links) {
ss << link.getSubObjectPython(false) << ',';
}
ss << ']';
}
return QString::fromLatin1(ss.str().c_str());
}
void DlgPropertyLink::filterObjects()
{
for (int i = 0, count = ui->treeWidget->topLevelItemCount(); i < count; ++i) {
auto item = ui->treeWidget->topLevelItem(i);
for (int j = 0, c = item->childCount(); j < c; ++j) {
filterItem(item->child(j));
}
}
}
void DlgPropertyLink::filterItem(QTreeWidgetItem* item)
{
if (filterType(item)) {
item->setHidden(true);
return;
}
item->setHidden(false);
for (int i = 0, count = item->childCount(); i < count; ++i) {
filterItem(item->child(i));
}
}
bool DlgPropertyLink::eventFilter(QObject* obj, QEvent* e)
{
if (obj == ui->searchBox && e->type() == QEvent::KeyPress
&& static_cast<QKeyEvent*>(e)->key() == Qt::Key_Escape) {
ui->searchBox->setText(QString());
return true;
}
return QDialog::eventFilter(obj, e);
}
void DlgPropertyLink::onItemSearch()
{
itemSearch(ui->searchBox->text(), true);
}
void DlgPropertyLink::keyPressEvent(QKeyEvent* ev)
{
if (ev->key() == Qt::Key_Enter || ev->key() == Qt::Key_Return) {
if (ui->searchBox->hasFocus()) {
return;
}
}
QDialog::keyPressEvent(ev);
}
void DlgPropertyLink::itemSearch(const QString& text, bool select)
{
if (searchItem) {
searchItem->setBackground(0, bgBrush);
}
auto owner = objProp.getObject();
if (!owner) {
return;
}
std::string txt(text.toUtf8().constData());
try {
if (txt.empty()) {
return;
}
if (txt.find("<<") == std::string::npos) {
auto pos = txt.find('.');
if (pos == std::string::npos) {
txt += '.';
}
else if (pos != txt.size() - 1) {
txt.insert(pos + 1, "<<");
if (txt.back() != '.') {
txt += '.';
}
txt += ">>.";
}
}
else if (txt.back() != '.') {
txt += '.';
}
txt += "_self";
auto path = App::ObjectIdentifier::parse(owner, txt);
if (path.getPropertyName() != "_self") {
return;
}
App::DocumentObject* obj = path.getDocumentObject();
if (!obj) {
return;
}
bool found;
const char* subname = path.getSubObjectName().c_str();
QTreeWidgetItem* item = findItem(obj, subname, &found);
if (!item) {
return;
}
if (select) {
if (!found) {
return;
}
Gui::Selection().addSelection(obj->getDocument()->getName(),
obj->getNameInDocument(),
subname);
}
else {
Selection().setPreselect(obj->getDocument()->getName(),
obj->getNameInDocument(),
subname,
0,
0,
0,
Gui::SelectionChanges::MsgSource::TreeView);
searchItem = item;
ui->treeWidget->scrollToItem(searchItem);
bgBrush = searchItem->background(0);
searchItem->setBackground(0, QColor(255, 255, 0, 100));
}
}
catch (...) {
}
}
QTreeWidgetItem* DlgPropertyLink::createItem(App::DocumentObject* obj, QTreeWidgetItem* parent)
{
if (!obj || !obj->isAttachedToDocument()) {
return nullptr;
}
if (inList.find(obj) != inList.end()) {
return nullptr;
}
auto vp = Base::freecad_dynamic_cast<ViewProviderDocumentObject>(
Application::Instance->getViewProvider(obj));
if (!vp) {
return nullptr;
}
QTreeWidgetItem* item;
if (parent) {
item = new QTreeWidgetItem(parent);
}
else {
item = new QTreeWidgetItem(ui->treeWidget);
}
item->setIcon(0, vp->getIcon());
item->setText(0, QString::fromUtf8((obj)->Label.getValue()));
item->setData(0, Qt::UserRole, QByteArray(obj->getNameInDocument()));
item->setData(0, Qt::UserRole + 1, QByteArray(obj->getDocument()->getName()));
if (allowSubObject) {
item->setChildIndicatorPolicy(!obj->getLinkedObject(true)->getOutList().empty()
? QTreeWidgetItem::ShowIndicator
: QTreeWidgetItem::DontShowIndicator);
item->setFlags(item->flags() | Qt::ItemIsEditable | Qt::ItemIsUserCheckable);
}
const char* typeName = obj->getTypeId().getName();
QByteArray typeData = QByteArray::fromRawData(typeName, strlen(typeName) + 1);
item->setData(0, Qt::UserRole + 2, typeData);
QByteArray proxyType;
auto prop =
Base::freecad_dynamic_cast<App::PropertyPythonObject>(obj->getPropertyByName("Proxy"));
if (prop) {
Base::PyGILStateLocker lock;
Py::Object proxy = prop->getValue();
if (!proxy.isNone() && !proxy.isString()) {
const char* name = nullptr;
if (proxy.hasAttr("__class__")) {
proxyType = QByteArray(proxy.getAttr("__class__").as_string().c_str());
}
else {
name = proxy.ptr()->ob_type->tp_name;
proxyType = QByteArray::fromRawData(name, strlen(name) + 1);
}
auto it = typeItems.find(proxyType);
if (it != typeItems.end()) {
proxyType = it->first;
}
else if (name) {
proxyType = QByteArray(name, proxyType.size());
}
}
}
item->setData(0, Qt::UserRole + 3, proxyType);
filterItem(item);
return item;
}
QTreeWidgetItem* DlgPropertyLink::createTypeItem(Base::Type type)
{
if (type.isBad()) {
return nullptr;
}
QTreeWidgetItem* item = nullptr;
if (!type.isBad() && type != App::DocumentObject::getClassTypeId()) {
Base::Type parentType = type.getParent();
if (!parentType.isBad()) {
const char* name = parentType.getName();
auto typeData = QByteArray::fromRawData(name, strlen(name) + 1);
auto& typeItem = typeItems[typeData];
if (!typeItem) {
typeItem = createTypeItem(parentType);
typeItem->setData(0, Qt::UserRole, typeData);
}
item = typeItem;
}
}
if (!item) {
item = new QTreeWidgetItem(ui->typeTree);
}
else {
item = new QTreeWidgetItem(item);
}
item->setExpanded(true);
item->setText(0, QString::fromLatin1(type.getName()));
if (type == App::DocumentObject::getClassTypeId()) {
item->setFlags(Qt::ItemIsEnabled);
}
return item;
}
bool DlgPropertyLink::filterType(QTreeWidgetItem* item)
{
auto proxyType = item->data(0, Qt::UserRole + 3).toByteArray();
QTreeWidgetItem* proxyItem = nullptr;
if (proxyType.size()) {
auto& pitem = typeItems[proxyType];
if (!pitem) {
pitem = new QTreeWidgetItem(ui->typeTree);
pitem->setText(0, QString::fromLatin1(proxyType));
pitem->setIcon(0, item->icon(0));
pitem->setData(0, Qt::UserRole, proxyType);
}
proxyItem = pitem;
}
auto typeData = item->data(0, Qt::UserRole + 2).toByteArray();
Base::Type type = Base::Type::fromName(typeData.constData());
if (type.isBad()) {
return false;
}
QTreeWidgetItem*& typeItem = typeItems[typeData];
if (!typeItem) {
typeItem = createTypeItem(type);
typeItem->setData(0, Qt::UserRole, typeData);
}
if (!proxyType.size()) {
QIcon icon = typeItem->icon(0);
if (icon.isNull()) {
typeItem->setIcon(0, item->icon(0));
}
}
if (!ui->checkObjectType->isChecked() || selectedTypes.empty()) {
return false;
}
if (proxyItem && selectedTypes.count(proxyType)) {
return false;
}
for (auto t = type; !t.isBad() && t != App::DocumentObject::getClassTypeId();
t = t.getParent()) {
const char* name = t.getName();
if (selectedTypes.count(QByteArray::fromRawData(name, strlen(name) + 1))) {
return false;
}
}
return true;
}
void DlgPropertyLink::onItemExpanded(QTreeWidgetItem* item)
{
if (item->childCount()) {
return;
}
QByteArray docName = item->data(0, Qt::UserRole + 1).toByteArray();
auto doc = App::GetApplication().getDocument(docName);
if (!doc) {
return;
}
QByteArray objName = item->data(0, Qt::UserRole).toByteArray();
if (objName.isEmpty()) {
for (auto obj : doc->getObjects()) {
auto newItem = createItem(obj, item);
if (newItem) {
itemMap[obj] = newItem;
}
}
}
else if (allowSubObject) {
auto obj = doc->getObject(objName);
if (!obj) {
return;
}
std::set<App::DocumentObject*> childSet;
std::string sub;
for (auto child : obj->getLinkedObject(true)->getOutList()) {
if (!childSet.insert(child).second) {
continue;
}
sub = child->getNameInDocument();
sub += ".";
if (obj->getSubObject(sub.c_str())) {
createItem(child, item);
}
}
}
}
void DlgPropertyLink::onObjectTypeToggled(bool on)
{
ui->typeTree->setVisible(on);
filterObjects();
}
void DlgPropertyLink::onTypeTreeItemSelectionChanged()
{
selectedTypes.clear();
const auto items = ui->typeTree->selectedItems();
for (auto item : items) {
selectedTypes.insert(item->data(0, Qt::UserRole).toByteArray());
}
if (ui->checkObjectType->isChecked()) {
filterObjects();
}
}
void DlgPropertyLink::onSearchBoxTextChanged(const QString& text)
{
itemSearch(text, false);
}
#include "moc_DlgPropertyLink.cpp"