Files
create/src/Gui/DlgPropertyLink.cpp
Benjamin Nauck 05c61e349a Gui: Add support for selecting multiple objects without keyboard
Adds checkboxes to the document tree which makes it possible to
select multiple document objects on devices without physical keyboard.

These checkboxes are opt-in and can be enabled/disabled from:
Edit -> Preferences -> Display -> Navigation -> Selection

Forum thread for feature the request:
https://forum.freecadweb.org/viewtopic.php?f=34&t=53065

We need to iterate the tree to add/remove the selection boxes when
the enable-setting has been changed.

The size for the first column in the tree is adjusted to fit both
name and optional checkbox.
2021-02-03 21:51:56 +01:00

1064 lines
33 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 <QTreeWidgetItem>
# include <QMessageBox>
# include <QPushButton>
#endif
#include <QStyledItemDelegate>
#include <Base/Tools.h>
#include <App/Application.h>
#include <App/Document.h>
#include <App/DocumentObject.h>
#include <App/GeoFeature.h>
#include <App/DocumentObserver.h>
#include "Document.h"
#include "View3DInventor.h"
#include "Tree.h"
#include "Selection.h"
#include "PropertyView.h"
#include "BitmapFactory.h"
#include "DlgPropertyLink.h"
#include "Application.h"
#include "ViewProviderDocumentObject.h"
#include "MetaTypes.h"
#include "ui_DlgPropertyLink.h"
using namespace Gui::Dialog;
class ItemDelegate: public QStyledItemDelegate {
public:
ItemDelegate(QObject* parent=0): QStyledItemDelegate(parent) {}
virtual QWidget* createEditor(QWidget *parent,
const QStyleOptionViewItem &option, const QModelIndex &index) const
{
if(index.column() != 1)
return nullptr;
return QStyledItemDelegate::createEditor(parent, option, index);
}
};
/* TRANSLATOR Gui::Dialog::DlgPropertyLink */
DlgPropertyLink::DlgPropertyLink(QWidget* parent)
: QDialog(parent), SelectionObserver(false,0)
, ui(new Ui_DlgPropertyLink)
{
ui->setupUi(this);
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, SIGNAL(timeout()), this, SLOT(onTimer()));
ui->treeWidget->setEditTriggers(QAbstractItemView::DoubleClicked);
ui->treeWidget->setItemDelegate(new ItemDelegate(this));
ui->treeWidget->setMouseTracking(true);
connect(ui->treeWidget, SIGNAL(itemEntered(QTreeWidgetItem*, int)),
this, SLOT(onItemEntered(QTreeWidgetItem*)));
connect(ui->treeWidget, SIGNAL(itemExpanded(QTreeWidgetItem*)),
this, SLOT(onItemExpanded(QTreeWidgetItem*)));
connect(ui->treeWidget, SIGNAL(itemSelectionChanged()), this, SLOT(onItemSelectionChanged()));
connect(ui->searchBox, SIGNAL(returnPressed()), this, SLOT(onItemSearch()));
connect(ui->buttonBox, SIGNAL(clicked(QAbstractButton*)), this, SLOT(onClicked(QAbstractButton*)));
refreshButton = ui->buttonBox->addButton(tr("Reset"), QDialogButtonBox::ActionRole);
resetButton = ui->buttonBox->addButton(tr("Clear"), QDialogButtonBox::ResetRole);
}
/**
* 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,0));
} 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->getNameInDocument())
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(QList<App::SubObjectT> links)
{
for(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 QString();
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,0),
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->getNameInDocument())
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
#if QT_VERSION >= 0x050000
ui->treeWidget->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents);
#else
ui->treeWidget->header()->setResizeMode(0, QHeaderView::ResizeToContents);
#endif
}
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(oldLinks.isEmpty())
return;
if(allowSubObject) {
for(auto &link : oldLinks) {
auto sobj = link.getSubObject();
if(sobj && sobj!=link.getObject()) {
ui->checkSubObject->setChecked(true);
break;
}
}
}
// 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(auto link : 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(isConnectionAttached())
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(0))
savedSelections.emplace_back(sel.DocName, sel.FeatName, sel.SubName);
break;
}
}
}
auto view = qobject_cast<Gui::PropertyView*>(parentView.data());
if(view)
view->blockConnection(true);
}
void DlgPropertyLink::showEvent(QShowEvent *ev) {
attachObserver();
QDialog::showEvent(ev);
}
void DlgPropertyLink::onItemEntered(QTreeWidgetItem *) {
int timeout = Gui::TreeParams::Instance()->PreSelectionDelay()/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(isConnectionAttached())
detachSelection();
auto view = qobject_cast<Gui::PropertyView*>(parentView.data());
if(view && savedSelections.size()) {
Gui::Selection().clearSelection();
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->blockConnection(false);
parentView = nullptr;
}
void DlgPropertyLink::onItemSelectionChanged()
{
auto newSelections = ui->treeWidget->selectedItems();
if(newSelections.isEmpty() || selections.contains(newSelections.back())) {
selections = newSelections;
if(newSelections.isEmpty())
currentObj = 0;
return;
}
selections = newSelections;
auto sobjs = getLinkFromItem(newSelections.back());
App::DocumentObject *obj = sobjs.size()?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::Instance()->SyncView()) {
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) {
doc->setActiveView(vp, Gui::View3DInventor::getClassTypeId());
}
}
}
// Sync 3d view selection. To give a better visual feedback, we
// only keep the latest selection.
bool blocked = blockConnection(true);
Gui::Selection().clearSelection();
for(auto &sobj : sobjs)
Gui::Selection().addSelection(sobj.getDocumentName().c_str(),
sobj.getObjectName().c_str(),
sobj.getSubName().c_str());
blockConnection(blocked);
// Enforce single parent
if(singleParent && currentObj && currentObj!=obj) {
ui->treeWidget->blockSignals(true);
for(auto item : ui->treeWidget->selectedItems()) {
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->getNameInDocument())
return 0;
std::vector<App::DocumentObject *> sobjs;
if(subname && subname[0]) {
if(!allowSubObject) {
obj = obj->getSubObject(subname);
if(!obj)
return 0;
} else {
sobjs = obj->getSubObjectList(subname);
}
}
auto itDoc = docItems.find(obj->getDocument());
if(itDoc == docItems.end())
return 0;
onItemExpanded(itDoc->second);
auto it = itemMap.find(obj);
if(it == itemMap.end() || it->second->isHidden())
return 0;
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();
std::pair<std::string,std::string> elementName;
const char *subname = msg.pSubName;
if(!ui->checkSubObject->isChecked()) {
selObj = App::GeoFeature::resolveElement(selObj,subname,elementName);
if(!selObj)
return;
subname = elementName.second.c_str();
}
auto item = findItem(selObj, msg.pSubName, &found);
if(!item || !found)
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);
const char *nextName = parent->data(0, Qt::UserRole).toByteArray().constData();
if(!nextName[0])
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;
}
for(const QString &element : elements.split(QLatin1Char(','))) {
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,2);
}
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(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(auto link : links) {
const auto &sub = link.getSubName();
if(sub.size())
ss << "u'" << Base::Tools::escapedUnicodeFromUtf8(sub.c_str()) << "',";
}
ss << "])";
} else {
ss << '[';
for(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,2);
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->getNameInDocument())
return 0;
if(inList.find(obj)!=inList.end())
return 0;
auto vp = Base::freecad_dynamic_cast<ViewProviderDocumentObject>(
Application::Instance->getViewProvider(obj));
if(!vp)
return 0;
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().size()?
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 = 0;
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 0;
QTreeWidgetItem *item = 0;
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 = 0;
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;
const char *docName = item->data(0, Qt::UserRole+1).toByteArray().constData();
auto doc = App::GetApplication().getDocument(docName);
if(!doc)
return;
const char *objName = item->data(0, Qt::UserRole).toByteArray().constData();
if(!objName[0]) {
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::on_checkObjectType_toggled(bool on)
{
ui->typeTree->setVisible(on);
filterObjects();
}
void DlgPropertyLink::on_typeTree_itemSelectionChanged() {
selectedTypes.clear();
for(auto item : ui->typeTree->selectedItems())
selectedTypes.insert(item->data(0, Qt::UserRole).toByteArray());
if(ui->checkObjectType->isChecked())
filterObjects();
}
void DlgPropertyLink::on_searchBox_textChanged(const QString& text)
{
itemSearch(text,false);
}
#include "moc_DlgPropertyLink.cpp"