Gui: property view related changes

* Display property from linked object, colored green,

* Change DlgPropertyLink to support external linking and sub-object
  selection

* Improve large selection performance by using a timer

* Improve TAB key behavior in property editor

* Add context menu to show hidden properties, change property status,
  set expression on any and property, and add/remove dynamic properties

* Optimize expression completer model construction, as the original
  implementation gets prohibitively slow for moderate number of objects.
This commit is contained in:
Zheng, Lei
2019-07-11 13:02:45 +08:00
committed by wmayer
parent ebf321fc47
commit 8b3ef8faf5
23 changed files with 2292 additions and 564 deletions

View File

@@ -367,6 +367,7 @@ set(Gui_MOC_HDRS
DAGView/DAGModel.h
TaskElementColors.h
DlgObjectSelection.h
DlgAddProperty.h
${FreeCADGui_SDK_MOC_HDRS}
)
@@ -441,6 +442,7 @@ SET(Gui_UIC_SRCS
TaskView/TaskSelectLinkProperty.ui
TaskElementColors.ui
DlgObjectSelection.ui
DlgAddProperty.ui
)
SET(Gui_RES_SRCS
@@ -516,6 +518,7 @@ SET(Dialog_CPP_SRCS
DocumentRecovery.cpp
TaskElementColors.cpp
DlgObjectSelection.cpp
DlgAddProperty.cpp
)
SET(Dialog_HPP_SRCS
@@ -551,6 +554,7 @@ SET(Dialog_HPP_SRCS
DocumentRecovery.h
TaskElementColors.h
DlgObjectSelection.h
DlgAddProperty.h
)
SET(Dialog_SRCS
@@ -564,6 +568,7 @@ SET(Dialog_SRCS
DlgAuthorization.ui
DlgDisplayProperties.ui
DlgInputDialog.ui
DlgAddProperty.ui
DlgLocationAngle.ui
DlgLocationPos.ui
DlgMacroExecute.ui

151
src/Gui/DlgAddProperty.cpp Normal file
View File

@@ -0,0 +1,151 @@
/****************************************************************************
* Copyright (c) 2019 Zheng, Lei (realthunder) <realthunder.dev@gmail.com>*
* *
* 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 <QMessageBox>
#endif
#include "ui_DlgAddProperty.h"
#include <Base/Tools.h>
#include <App/Application.h>
#include <App/Document.h>
#include <App/DocumentObject.h>
#include "MainWindow.h"
#include <ViewProviderDocumentObject.h>
#include "DlgAddProperty.h"
using namespace Gui;
using namespace Gui::Dialog;
DlgAddProperty::DlgAddProperty(QWidget* parent,
std::unordered_set<App::PropertyContainer *> &&c)
: QDialog( parent )
, containers(std::move(c))
, ui(new Ui_DlgAddProperty)
{
ui->setupUi(this);
auto hGrp = App::GetApplication().GetParameterGroupByPath(
"User parameter:BaseApp/Preferences/PropertyView");
auto defType = Base::Type::fromName(
hGrp->GetASCII("NewPropertyType","App::PropertyString").c_str());
if(defType.isBad())
defType = App::PropertyString::getClassTypeId();
std::vector<Base::Type> types;
Base::Type::getAllDerivedFrom(Base::Type::fromName("App::Property"),types);
for(auto type : types) {
ui->comboType->addItem(QString::fromLatin1(type.getName()));
if(type == defType)
ui->comboType->setCurrentIndex(ui->comboType->count()-1);
}
ui->edtGroup->setText(QString::fromLatin1(
hGrp->GetASCII("NewPropertyGroup","Base").c_str()));
ui->chkAppend->setChecked(hGrp->GetBool("NewPropertyAppend",true));
}
/**
* Destroys the object and frees any allocated resources
*/
DlgAddProperty::~DlgAddProperty()
{
// no need to delete child widgets, Qt does it all for us
}
static std::string containerName(const App::PropertyContainer *c) {
auto doc = Base::freecad_dynamic_cast<App::Document>(c);
if(doc)
return doc->getName();
auto obj = Base::freecad_dynamic_cast<App::DocumentObject>(c);
if(obj)
return obj->getFullName();
auto vpd = Base::freecad_dynamic_cast<ViewProviderDocumentObject>(c);
if(vpd)
return vpd->getObject()->getFullName();
return "?";
}
void DlgAddProperty::accept()
{
std::string name = ui->edtName->text().toUtf8().constData();
std::string group = ui->edtGroup->text().toUtf8().constData();
if(name.empty() || group.empty()
|| name != Base::Tools::getIdentifier(name)
|| group != Base::Tools::getIdentifier(group))
{
QMessageBox::critical(getMainWindow(),
QObject::tr("Invalid name"),
QObject::tr("The property name or group name must only contain alpha numericals,\n"
"underscore, and must not start with a digit."));
return;
}
if(ui->chkAppend->isChecked())
name = group + "_" + name;
for(auto c : containers) {
if(c->getPropertyByName(name.c_str())) {
QMessageBox::critical(getMainWindow(),
QObject::tr("Invalid name"),
QObject::tr("The property '%1' already exists in '%2'").arg(
QString::fromLatin1(name.c_str()),
QString::fromLatin1(containerName(c).c_str())));
return;
}
}
std::string type = ui->comboType->currentText().toLatin1().constData();
for(auto it=containers.begin();it!=containers.end();++it) {
try {
(*it)->addDynamicProperty(type.c_str(),name.c_str(),
group.c_str(),ui->edtDoc->toPlainText().toUtf8().constData());
} catch(Base::Exception &e) {
e.ReportException();
for(auto it2=containers.begin();it2!=it;++it2) {
try {
(*it)->removeDynamicProperty(name.c_str());
} catch(Base::Exception &e) {
e.ReportException();
}
}
QMessageBox::critical(getMainWindow(),
QObject::tr("Add property"),
QObject::tr("Failed to add property to '%1': %2").arg(
QString::fromLatin1(containerName(*it).c_str()),
QString::fromUtf8(e.what())));
return;
}
}
auto hGrp = App::GetApplication().GetParameterGroupByPath(
"User parameter:BaseApp/Preferences/PropertyView");
hGrp->SetASCII("NewPropertyType",type.c_str());
hGrp->SetASCII("NewPropertyGroup",group.c_str());
hGrp->SetBool("NewPropertyAppend",ui->chkAppend->isChecked());
QDialog::accept();
}
#include "moc_DlgAddProperty.cpp"

54
src/Gui/DlgAddProperty.h Normal file
View File

@@ -0,0 +1,54 @@
/****************************************************************************
* Copyright (c) 2019 Zheng, Lei (realthunder) <realthunder.dev@gmail.com>*
* *
* 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 *
* *
****************************************************************************/
#ifndef GUI_DIALOG_DLGADDPROPERTY_H
#define GUI_DIALOG_DLGADDPROPERTY_H
#include <unordered_set>
namespace App {
class PropertyContainer;
}
namespace Gui {
namespace Dialog {
class Ui_DlgAddProperty;
class GuiExport DlgAddProperty : public QDialog
{
Q_OBJECT
public:
DlgAddProperty(QWidget *parent, std::unordered_set<App::PropertyContainer*> &&);
~DlgAddProperty();
virtual void accept() override;
private:
std::unordered_set<App::PropertyContainer*> containers;
std::unique_ptr<Ui_DlgAddProperty> ui;
};
} // namespace Dialog
} // namespace Gui
#endif // GUI_DIALOG_DLGADDPROPERTY_H

119
src/Gui/DlgAddProperty.ui Normal file
View File

@@ -0,0 +1,119 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Gui::Dialog::DlgAddProperty</class>
<widget class="QDialog" name="Gui::Dialog::DlgAddProperty">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>354</width>
<height>258</height>
</rect>
</property>
<property name="windowTitle">
<string>Add property</string>
</property>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Type</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="comboType"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Group</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="edtGroup"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Name</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="edtName"/>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Document</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QPlainTextEdit" name="edtDoc"/>
</item>
<item row="4" column="1">
<widget class="QCheckBox" name="chkAppend">
<property name="toolTip">
<string>Append the group name in front of the property name in the form of 'group'_'name' to avoid conflict with existing property. The prefixed group name will be auto trimmed when shown in the property editor.</string>
</property>
<property name="text">
<string>Append group name</string>
</property>
</widget>
</item>
<item row="5" column="0" colspan="2">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<tabstops>
<tabstop>comboType</tabstop>
<tabstop>edtGroup</tabstop>
<tabstop>edtName</tabstop>
</tabstops>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>Gui::Dialog::DlgAddProperty</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>199</x>
<y>99</y>
</hint>
<hint type="destinationlabel">
<x>199</x>
<y>58</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>Gui::Dialog::DlgAddProperty</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>199</x>
<y>99</y>
</hint>
<hint type="destinationlabel">
<x>199</x>
<y>58</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@@ -90,6 +90,11 @@ DlgExpressionInput::DlgExpressionInput(const App::ObjectIdentifier & _path,
ui->horizontalSpacer_3->changeSize(0, 2);
ui->verticalLayout->setContentsMargins(9, 9, 9, 9);
this->adjustSize();
// It is strange that (at least on Linux) DlgExpressionInput will shrink
// to be narrower than ui->expression after calling adjustSize() above.
// Why?
if(this->width() < ui->expression->width() + 18)
this->resize(ui->expression->width()+18,this->height());
}
ui->expression->setFocus();
}
@@ -138,10 +143,12 @@ void DlgExpressionInput::textChanged(const QString &text)
if (n) {
Base::Quantity value = n->getQuantity();
if (!value.getUnit().isEmpty() && value.getUnit() != impliedUnit)
throw Base::UnitsMismatchError("Unit mismatch between result and required unit");
if(!impliedUnit.isEmpty()) {
if (!value.getUnit().isEmpty() && value.getUnit() != impliedUnit)
throw Base::UnitsMismatchError("Unit mismatch between result and required unit");
value.setUnit(impliedUnit);
value.setUnit(impliedUnit);
}
ui->msg->setText(value.getUserString());
}

View File

@@ -25,7 +25,7 @@
#ifndef _PreComp_
# include <algorithm>
# include <sstream>
# include <QListWidgetItem>
# include <QTreeWidgetItem>
# include <QMessageBox>
#endif
@@ -34,24 +34,85 @@
#include <App/DocumentObject.h>
#include <App/GeoFeature.h>
#include "BitmapFactory.h"
#include "DlgPropertyLink.h"
#include "Application.h"
#include "ViewProvider.h"
#include "ViewProviderDocumentObject.h"
#include "ui_DlgPropertyLink.h"
using namespace Gui::Dialog;
/* TRANSLATOR Gui::Dialog::DlgPropertyLink */
DlgPropertyLink::DlgPropertyLink(const QStringList& list, QWidget* parent, Qt::WindowFlags fl)
DlgPropertyLink::DlgPropertyLink(const QStringList& list, QWidget* parent, Qt::WindowFlags fl, bool xlink)
: QDialog(parent, fl), link(list), ui(new Ui_DlgPropertyLink)
{
#ifdef FC_DEBUG
assert(list.size() >= 5);
assert(list.size() >= 4);
#endif
// populate inList to filter out any objects that contains the owner object
// of the editing link property
auto doc = App::GetApplication().getDocument(qPrintable(link[0]));
if(doc) {
auto obj = doc->getObject(qPrintable(link[3]));
if(obj && obj->getNameInDocument()) {
inList = obj->getInListEx(true);
inList.insert(obj);
}
}
ui->setupUi(this);
findObjects(ui->checkObjectType->isChecked(), QString());
ui->typeTree->hide();
if(!xlink)
ui->comboBox->hide();
else {
std::string linkDoc = qPrintable(link[0]);
for(auto doc : App::GetApplication().getDocuments()) {
QString name(QString::fromUtf8(doc->Label.getValue()));
ui->comboBox->addItem(name,QVariant(QString::fromLatin1(doc->getName())));
if(linkDoc == doc->getName())
ui->comboBox->setCurrentIndex(ui->comboBox->count()-1);
}
}
Base::Type baseType;
App::Document *linkedDoc = doc;
if (link.size()>FC_XLINK_VALUE_INDEX)
linkedDoc = App::GetApplication().getDocument(qPrintable(link[FC_XLINK_VALUE_INDEX]));
if(linkedDoc) {
QString objName = link[1]; // linked object name
auto obj = linkedDoc->getObject((const char*)objName.toLatin1());
if (obj && inList.find(obj)==inList.end()) {
Base::Type objType = obj->getTypeId();
// 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()) {
std::string name = objType.getName();
Base::Type parType = objType.getParent();
if (parType == baseType) {
baseType = objType;
break;
}
objType = parType;
}
}
}
if(!baseType.isBad()) {
types.insert(baseType.getName());
ui->checkObjectType->setChecked(true);
}else
findObjects();
connect(ui->treeWidget, SIGNAL(itemExpanded(QTreeWidgetItem*)),
this, SLOT(onItemExpanded(QTreeWidgetItem*)));
}
/**
@@ -65,15 +126,14 @@ DlgPropertyLink::~DlgPropertyLink()
void DlgPropertyLink::setSelectionMode(QAbstractItemView::SelectionMode mode)
{
ui->listWidget->setSelectionMode(mode);
ui->listWidget->clear();
findObjects(ui->checkObjectType->isChecked(), ui->searchBox->text());
ui->treeWidget->setSelectionMode(mode);
findObjects();
}
void DlgPropertyLink::accept()
{
if (ui->listWidget->selectionMode() == QAbstractItemView::SingleSelection) {
QList<QListWidgetItem*> items = ui->listWidget->selectedItems();
if (ui->treeWidget->selectionMode() == QAbstractItemView::SingleSelection) {
QList<QTreeWidgetItem*> items = ui->treeWidget->selectedItems();
if (items.isEmpty()) {
QMessageBox::warning(this, tr("No selection"), tr("Please select an object from the list"));
return;
@@ -83,149 +143,248 @@ void DlgPropertyLink::accept()
QDialog::accept();
}
static QStringList getLinkFromItem(const QStringList &link, QTreeWidgetItem *selItem) {
QStringList list = link;
if(link.size()>FC_XLINK_VALUE_INDEX) {
QString subname;
auto parent = selItem;
for(auto item=parent;;item=parent) {
parent = item->parent();
if(!parent) {
list[1] = item->data(0,Qt::UserRole).toString();
break;
}
subname = QString::fromLatin1("%1.%2").
arg(item->data(0,Qt::UserRole).toString()).arg(subname);
}
list[FC_XLINK_VALUE_INDEX] = subname;
if(subname.size())
list[2] = QString::fromLatin1("%1 (%2.%3)").
arg(selItem->text(0)).arg(list[1]).arg(subname);
else
list[2] = selItem->text(0);
QString docName(selItem->data(0, Qt::UserRole+1).toString());
if(list.size()>FC_XLINK_VALUE_INDEX+1)
list[FC_XLINK_VALUE_INDEX+1] = docName;
else
list << docName;
}else{
list[1] = selItem->data(0,Qt::UserRole).toString();
list[2] = selItem->text(0);
if (list[1].isEmpty())
list[2] = QString::fromUtf8("");
}
return list;
}
QStringList DlgPropertyLink::propertyLink() const
{
QList<QListWidgetItem*> items = ui->listWidget->selectedItems();
auto items = ui->treeWidget->selectedItems();
if (items.isEmpty()) {
return link;
}
else {
QStringList list = link;
list[1] = items[0]->data(Qt::UserRole).toString();
list[2] = items[0]->text();
if (list[1].isEmpty())
list[2] = QString::fromUtf8("");
return list;
}
return getLinkFromItem(link,items[0]);
}
QVariantList DlgPropertyLink::propertyLinkList() const
{
QVariantList varList;
QList<QListWidgetItem*> items = ui->listWidget->selectedItems();
if (items.isEmpty()) {
varList << link;
}
else {
for (QList<QListWidgetItem*>::iterator it = items.begin(); it != items.end(); ++it) {
QStringList list = link;
list[1] = (*it)->data(Qt::UserRole).toString();
list[2] = (*it)->text();
if (list[1].isEmpty())
list[2] = QString::fromUtf8("");
varList << list;
}
}
QList<QTreeWidgetItem*> items = ui->treeWidget->selectedItems();
for (QList<QTreeWidgetItem*>::iterator it = items.begin(); it != items.end(); ++it)
varList << getLinkFromItem(link,*it);
return varList;
}
void DlgPropertyLink::findObjects(bool on, const QString& searchText)
void DlgPropertyLink::findObjects()
{
QString docName = link[0]; // document name
QString objName = link[1]; // internal object name
QString parName = link[3]; // internal object name of the parent of the link property
QString proName = link[4]; // property name
bool filterType = ui->checkObjectType->isChecked();
ui->treeWidget->clear();
bool isSingleSelection = (ui->listWidget->selectionMode() == QAbstractItemView::SingleSelection);
QString docName = link[0]; // document name of the owner object of this editing property
bool isSingleSelection = (ui->treeWidget->selectionMode() == QAbstractItemView::SingleSelection);
App::Document* doc = App::GetApplication().getDocument((const char*)docName.toLatin1());
if (doc) {
Base::Type baseType = App::DocumentObject::getClassTypeId();
if (!on) {
App::DocumentObject* obj = doc->getObject((const char*)objName.toLatin1());
if (obj) {
Base::Type objType = obj->getTypeId();
// get only geometric types
if (objType.isDerivedFrom(App::GeoFeature::getClassTypeId()))
baseType = App::GeoFeature::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;
}
}
}
// build list of objects names already in property so we can mark them as selected later on
std::vector<const char *> selectedNames;
std::set<std::string> selectedNames;
// build ignore list
std::vector<App::DocumentObject*> ignoreList;
App::DocumentObject* par = doc->getObject((const char*)parName.toLatin1());
App::Property* prop = par->getPropertyByName((const char*)proName.toLatin1());
if (prop) {
// for multi-selection we need all objects
if (isSingleSelection) {
ignoreList = par->getOutListOfProperty(prop);
} else {
// Add a "None" entry on top
if (isSingleSelection) {
auto* item = new QTreeWidgetItem(ui->treeWidget);
item->setText(0,tr("None (Remove link)"));
QByteArray ba("");
item->setData(0,Qt::UserRole, ba);
}else {
QString ownerName = link[3];
QString proName = link[4];
auto owner = doc->getObject(ownerName.toLatin1());
if(owner) {
App::Property* prop = owner->getPropertyByName((const char*)proName.toLatin1());
// gather names of objects currently in property
if (prop->getTypeId().isDerivedFrom(App::PropertyLinkList::getClassTypeId())) {
if (prop && prop->getTypeId().isDerivedFrom(App::PropertyLinkList::getClassTypeId())) {
const App::PropertyLinkList* propll = static_cast<const App::PropertyLinkList*>(prop);
std::vector<App::DocumentObject*> links = propll->getValues();
for (std::vector<App::DocumentObject*>::iterator it = links.begin(); it != links.end(); ++it) {
selectedNames.push_back((*it)->getNameInDocument());
}
}
}
// add the inlist to the ignore list to avoid dependency loops
std::vector<App::DocumentObject*> inList = par->getInListRecursive();
ignoreList.insert(ignoreList.end(), inList.begin(), inList.end());
ignoreList.push_back(par);
}
// Add a "None" entry on top
QListWidgetItem* item = new QListWidgetItem(ui->listWidget);
item->setText(tr("None (Remove link)"));
QByteArray ba("");
item->setData(Qt::UserRole, ba);
std::vector<App::DocumentObject*> obj = doc->getObjectsOfType(baseType);
for (std::vector<App::DocumentObject*>::iterator it = obj.begin(); it != obj.end(); ++it) {
Gui::ViewProvider* vp = Gui::Application::Instance->getViewProvider(*it);
bool nameOk = true;
if (!searchText.isEmpty()) {
QString label = QString::fromUtf8((*it)->Label.getValue());
if (!label.contains(searchText,Qt::CaseInsensitive))
nameOk = false;
}
if (vp && nameOk) {
// filter out the objects
if (std::find(ignoreList.begin(), ignoreList.end(), *it) == ignoreList.end()) {
QListWidgetItem* item = new QListWidgetItem(ui->listWidget);
item->setIcon(vp->getIcon());
item->setText(QString::fromUtf8((*it)->Label.getValue()));
QByteArray ba((*it)->getNameInDocument());
item->setData(Qt::UserRole, ba);
// mark items as selected if needed
for (std::vector<const char *>::iterator nit = selectedNames.begin(); nit != selectedNames.end(); ++nit) {
if (strcmp(*nit,(*it)->getNameInDocument()) == 0) {
item->setSelected(true);
break;
}
selectedNames.insert((*it)->getNameInDocument());
}
}
}
}
Base::PyGILStateLocker lock;
std::map<std::string,QIcon> typeInfos;
QTreeWidgetItem *selected = 0;
for(auto obj : doc->getObjects()) {
auto it = types.end();
auto prop = Base::freecad_dynamic_cast<App::PropertyPythonObject>(
obj->getPropertyByName("Proxy"));
bool pass = false;
if(prop && !prop->getValue().isNone()) {
std::string typeName = prop->getValue().type().as_string();
if(refreshTypes) {
QIcon &icon = typeInfos[typeName];
if(icon.isNull()) {
auto vp = Application::Instance->getViewProvider(obj);
if(vp)
icon = vp->getIcon();
}
}
if(filterType)
pass = types.count(typeName);
}
if(refreshTypes) {
auto type = obj->getTypeId();
QIcon &icon = typeInfos[type.getName()];
if(!prop && icon.isNull()) {
auto vp = Application::Instance->getViewProvider(obj);
if(vp)
icon = vp->getIcon();
}
for(type = type.getParent();!type.isBad();type=type.getParent())
typeInfos.emplace(type.getName(),QIcon());
}
if(filterType && !pass && types.size()) {
for(auto type = obj->getTypeId();!type.isBad();type=type.getParent()) {
it = types.find(type.getName());
if(it!=types.end())
break;
}
if(it == types.end())
continue;
}
auto item = createItem(obj,0);
if(item && selectedNames.count(obj->getNameInDocument())) {
if(!selected)
selected = item;
item->setSelected(true);
}
}
if(selected)
ui->treeWidget->scrollToItem(selected);
if(refreshTypes) {
refreshTypes = false;
ui->typeTree->blockSignals(true);
ui->typeTree->clear();
QTreeWidgetItem *selected = 0;
QIcon icon = BitmapFactory().pixmap("px");
for(auto &v : typeInfos) {
auto item = new QTreeWidgetItem(ui->typeTree);
item->setText(0,QString::fromLatin1(v.first.c_str()));
item->setIcon(0,v.second.isNull()?icon:v.second);
if(types.count(v.first)) {
item->setSelected(true);
if(!selected)
selected = item;
}
}
if(selected)
ui->typeTree->scrollToItem(selected);
ui->typeTree->blockSignals(false);
if(types.size() && !selected) {
types.clear();
findObjects();
}
}
}
}
QTreeWidgetItem *DlgPropertyLink::createItem(App::DocumentObject *obj, QTreeWidgetItem *parent) {
if(!obj || !obj->getNameInDocument())
return 0;
if(inList.find(obj)!=inList.end())
return 0;
auto vp = Gui::Application::Instance->getViewProvider(obj);
if(!vp)
return 0;
QString searchText = ui->searchBox->text();
if (!searchText.isEmpty()) {
QString label = QString::fromUtf8((obj)->Label.getValue());
if (!label.contains(searchText,Qt::CaseInsensitive))
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(link.size()>=5) {
item->setChildIndicatorPolicy(obj->hasChildElement()||vp->getChildRoot()?
QTreeWidgetItem::ShowIndicator:QTreeWidgetItem::DontShowIndicator);
}
return item;
}
void DlgPropertyLink::onItemExpanded(QTreeWidgetItem * item) {
if(link.size()<5 || item->childCount())
return;
std::string name(qPrintable(item->data(0, Qt::UserRole).toString()));
std::string docName(qPrintable(item->data(0, Qt::UserRole+1).toString()));
auto doc = App::GetApplication().getDocument(docName.c_str());
if(doc) {
auto obj = doc->getObject(name.c_str());
if(!obj) return;
auto vp = Application::Instance->getViewProvider(obj);
if(!vp) return;
for(auto obj : vp->claimChildren())
createItem(obj,item);
}
}
void DlgPropertyLink::on_checkObjectType_toggled(bool on)
{
ui->listWidget->clear();
findObjects(on, ui->searchBox->text());
ui->typeTree->setVisible(on);
findObjects();
}
void DlgPropertyLink::on_searchBox_textChanged(const QString& search)
{
ui->listWidget->clear();
bool on = ui->checkObjectType->isChecked();
findObjects(on, search);
void DlgPropertyLink::on_typeTree_itemSelectionChanged() {
types.clear();
for(auto item : ui->typeTree->selectedItems())
types.insert(item->text(0).toLatin1().constData());
findObjects();
}
void DlgPropertyLink::on_searchBox_textChanged(const QString& /*search*/)
{
findObjects();
}
void DlgPropertyLink::on_comboBox_currentIndexChanged(int index)
{
link[0] = ui->comboBox->itemData(index).toString();
refreshTypes = true;
findObjects();
}
#include "moc_DlgPropertyLink.cpp"

View File

@@ -27,6 +27,8 @@
#include <QDialog>
#include <QAbstractItemView>
#define FC_XLINK_VALUE_INDEX 5
namespace Gui { namespace Dialog {
class Ui_DlgPropertyLink;
@@ -35,7 +37,7 @@ class DlgPropertyLink : public QDialog
Q_OBJECT
public:
DlgPropertyLink(const QStringList& list, QWidget* parent = 0, Qt::WindowFlags fl = 0);
DlgPropertyLink(const QStringList& list, QWidget* parent = 0, Qt::WindowFlags fl = 0, bool xlink=false);
~DlgPropertyLink();
void setSelectionMode(QAbstractItemView::SelectionMode mode);
@@ -45,14 +47,21 @@ public:
private Q_SLOTS:
void on_checkObjectType_toggled(bool);
void on_typeTree_itemSelectionChanged();
void on_searchBox_textChanged(const QString&);
void on_comboBox_currentIndexChanged(int);
void onItemExpanded(QTreeWidgetItem * item);
private:
void findObjects(bool on, const QString& searchText);
QTreeWidgetItem *createItem(App::DocumentObject *obj, QTreeWidgetItem *parent);
void findObjects();
private:
QStringList link;
Ui_DlgPropertyLink* ui;
std::set<App::DocumentObject*> inList;
std::set<std::string> types;
bool refreshTypes = true;
};
} // namespace Dialog

View File

@@ -6,15 +6,37 @@
<rect>
<x>0</x>
<y>0</y>
<width>257</width>
<height>428</height>
<width>436</width>
<height>438</height>
</rect>
</property>
<property name="windowTitle">
<string>Link</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="6" column="0">
<widget class="QCheckBox" name="checkObjectType">
<property name="text">
<string>Filter by type</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QTreeWidget" name="treeWidget">
<property name="headerHidden">
<bool>true</bool>
</property>
<property name="expandsOnDoubleClick">
<bool>false</bool>
</property>
<column>
<property name="text">
<string notr="true">1</string>
</property>
</column>
</widget>
</item>
<item row="3" column="0">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label">
@@ -32,23 +54,34 @@
</item>
</layout>
</item>
<item row="4" column="0">
<widget class="QCheckBox" name="checkObjectType">
<property name="text">
<string>Show all object types</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QListWidget" name="listWidget"/>
</item>
<item row="5" column="0">
<item row="7" column="0">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QComboBox" name="comboBox"/>
</item>
<item row="2" column="0">
<widget class="QTreeWidget" name="typeTree">
<property name="selectionMode">
<enum>QAbstractItemView::ExtendedSelection</enum>
</property>
<property name="rootIsDecorated">
<bool>false</bool>
</property>
<attribute name="headerVisible">
<bool>false</bool>
</attribute>
<column>
<property name="text">
<string notr="true">1</string>
</property>
</column>
</widget>
</item>
</layout>
</widget>
<resources/>

View File

@@ -30,10 +30,14 @@
#include <App/Expression.h>
#include <App/DocumentObject.h>
#include <Base/Tools.h>
#include <Base/Console.h>
#include <App/ObjectIdentifier.h>
#include <App/Document.h>
#include <App/Application.h>
#include <boost/bind.hpp>
FC_LOG_LEVEL_INIT("Expression",true,true)
using namespace Gui;
using namespace App;
@@ -67,10 +71,22 @@ void Gui::ExpressionBinding::setExpression(boost::shared_ptr<Expression> expr)
}
lastExpression = getExpression();
bool transaction = !App::GetApplication().getActiveTransaction();
if(transaction) {
std::ostringstream ss;
ss << (expr?"Set":"Discard") << " expression " << docObj->Label.getValue();
App::GetApplication().setActiveTransaction(ss.str().c_str());
}
docObj->ExpressionEngine.setValue(path, expr);
if(m_autoApply)
apply();
if(transaction)
App::GetApplication().closeActiveTransaction();
}
void ExpressionBinding::bind(const App::ObjectIdentifier &_path)
@@ -105,17 +121,34 @@ boost::shared_ptr<App::Expression> ExpressionBinding::getExpression() const
return docObj->getExpression(path).expression;
}
std::string ExpressionBinding::getExpressionString() const
std::string ExpressionBinding::getExpressionString(bool no_throw) const
{
if (!getExpression())
throw Base::RuntimeError("No expression found.");
return getExpression()->toString();
try {
if (!getExpression())
throw Base::RuntimeError("No expression found.");
return getExpression()->toString();
} catch (Base::Exception &e) {
if(no_throw)
FC_ERR("failed to get expression string: " << e.what());
else
throw;
} catch (std::exception &e) {
if(no_throw)
FC_ERR("failed to get expression string: " << e.what());
else
throw;
} catch (...) {
if(no_throw)
FC_ERR("failed to get expression string: unknown exception");
else
throw;
}
return std::string();
}
std::string ExpressionBinding::getEscapedExpressionString() const
{
return Base::Tools::escapedUnicodeFromUtf8(getExpressionString().c_str());
return Base::Tools::escapedUnicodeFromUtf8(getExpressionString(false).c_str());
}
QPixmap ExpressionBinding::getIcon(const char* name, const QSize& size) const
@@ -142,11 +175,20 @@ bool ExpressionBinding::apply(const std::string & propName)
if (!docObj)
throw Base::RuntimeError("Document object not found.");
bool transaction = !App::GetApplication().getActiveTransaction();
if(transaction) {
std::ostringstream ss;
ss << "Set expression " << docObj->Label.getValue();
App::GetApplication().setActiveTransaction(ss.str().c_str());
}
Gui::Command::doCommand(Gui::Command::Doc,"App.getDocument('%s').%s.setExpression('%s', u'%s')",
docObj->getDocument()->getName(),
docObj->getNameInDocument(),
path.toEscapedString().c_str(),
getEscapedExpressionString().c_str());
if(transaction)
App::GetApplication().closeActiveTransaction();
return true;
}
else {
@@ -156,11 +198,20 @@ bool ExpressionBinding::apply(const std::string & propName)
if (!docObj)
throw Base::RuntimeError("Document object not found.");
if (lastExpression)
if (lastExpression) {
bool transaction = !App::GetApplication().getActiveTransaction();
if(transaction) {
std::ostringstream ss;
ss << "Discard expression " << docObj->Label.getValue();
App::GetApplication().setActiveTransaction(ss.str().c_str());
}
Gui::Command::doCommand(Gui::Command::Doc,"App.getDocument('%s').%s.setExpression('%s', None)",
docObj->getDocument()->getName(),
docObj->getNameInDocument(),
path.toEscapedString().c_str());
if(transaction)
App::GetApplication().closeActiveTransaction();
}
}
return false;
@@ -183,9 +234,7 @@ bool ExpressionBinding::apply()
if (prop->isReadOnly())
return true;
std::string name = docObj->getNameInDocument();
return apply("App.ActiveDocument." + name + "." + getPath().toEscapedString());
return apply(Gui::Command::getObjectCmd(docObj) + "." + getPath().toEscapedString());
}
void ExpressionBinding::expressionChange(const ObjectIdentifier& id) {

View File

@@ -58,7 +58,7 @@ public:
protected:
const App::ObjectIdentifier & getPath() const { return path; }
boost::shared_ptr<App::Expression> getExpression() const;
std::string getExpressionString() const;
std::string getExpressionString(bool no_throw=true) const;
std::string getEscapedExpressionString() const;
virtual void setExpression(boost::shared_ptr<App::Expression> expr);

View File

@@ -5,21 +5,292 @@
#include <QStandardItemModel>
#include <QLineEdit>
#include <QAbstractItemView>
#include <QTextBlock>
#endif
#include <boost/algorithm/string/predicate.hpp>
#include <Base/Tools.h>
#include <Base/Console.h>
#include <App/Application.h>
#include <App/Document.h>
#include <App/DocumentObject.h>
#include <App/DocumentObserver.h>
#include <App/ObjectIdentifier.h>
#include "ExpressionCompleter.h"
#include <App/Expression.h>
#include <App/PropertyLinks.h>
FC_LOG_LEVEL_INIT("Completer",true,true);
Q_DECLARE_METATYPE(App::ObjectIdentifier);
using namespace App;
using namespace Gui;
class ExpressionCompleterModel: public QAbstractItemModel {
public:
ExpressionCompleterModel(QObject *parent, const App::DocumentObject *obj)
:QAbstractItemModel(parent)
{
if(obj) {
currentDoc = obj->getDocument()->getName();
currentObj = obj->getNameInDocument();
inList = obj->getInListEx(true);
}
}
// This ExpressionCompleter model works without any pysical items.
// Everything item related is stored inside QModelIndex.InternalPointer/InternalId(),
// using the following Info structure.
//
// The Info contains two indices, one for document and the other for object.
// For 32-bit system, the index is 16bit which limits the size to 64K. For
// 64-bit system, the index is 32bit.
//
// The "virtual" items are organized as a tree. The root items are special,
// which consists of three types in the following order,
//
// * Document, even index contains item using document's name, while
// odd index with quoted document label.
// * Objects of the current document, even index with object's internal
// name, and odd index with quoted object label.
// * Properties of the current object.
//
// Document item contains object item as child, and object item contains
// property item.
//
// The QModelIndex of a root item has both the doc field and obj field set
// to -1, and uses the row as the item index. We can figure out the type of
// the item solely based on this row index.
//
// QModelIndex of a non-root object item has doc field as the document
// index, and obj field set to -1.
//
// QModelIndex of a non-root property item has doc field as the document
// index, and obj field as the object index.
union Info {
struct {
qint32 doc;
qint32 obj;
}d;
struct {
qint16 doc;
qint16 obj;
}d32;
void *ptr;
};
static void *infoId(const Info &info) {
if(sizeof(void*) >= sizeof(info))
return info.ptr;
Info info32;
info32.d32.doc = (qint16)info.d.doc;
info32.d32.obj = (qint16)info.d.obj;
return info32.ptr;
};
static Info getInfo(const QModelIndex &index) {
Info info;
info.ptr = index.internalPointer();
if(sizeof(void*) >= sizeof(Info))
return info;
Info res;
res.d.doc = info.d32.doc;
res.d.obj = info.d32.obj;
return res;
}
QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const {
if(role!=Qt::EditRole && role!=Qt::DisplayRole && role!=Qt::UserRole)
return QVariant();
QVariant v;
Info info = getInfo(index);
_data(info,index.row(),&v,0,role==Qt::UserRole);
FC_TRACE(info.d.doc << "," << info.d.obj << "," << index.row()
<< ": " << v.toString().toUtf8().constData());
return v;
}
void _data(const Info &info, int row, QVariant *v, int *count, bool sep=false) const {
int idx;
idx = info.d.doc<0?row:info.d.doc;
const auto &docs = App::GetApplication().getDocuments();
int docSize = (int)docs.size()*2;
int objSize = 0;
int propSize = 0;
std::vector<App::Property*> props;
App::Document *doc = 0;
App::DocumentObject *obj = 0;
App::Property *prop = 0;
if(idx>=0 && idx<docSize)
doc = docs[idx/2];
else {
doc = App::GetApplication().getDocument(currentDoc.c_str());
if(!doc)
return;
idx -= docSize;
if(info.d.doc<0)
row = idx;
const auto &objs = doc->getObjects();
objSize = (int)objs.size()*2;
if(idx>=0 && idx<objSize) {
obj = objs[idx/2];
if(inList.count(obj))
return;
} else {
auto cobj = doc->getObject(currentObj.c_str());
if(cobj) {
idx -= objSize;
if(info.d.doc<0)
row = idx;
cobj->getPropertyList(props);
propSize = (int)props.size();
if(idx >= propSize)
return;
if(idx>=0) {
obj = cobj;
prop = props[idx];
}
}
}
}
if(info.d.doc<0) {
if(count)
*count = docSize + objSize + propSize;
if(idx>=0 && v) {
QString res;
if(prop)
res = QString::fromLatin1(prop->getName());
else if(obj) {
if(idx & 1)
res = QString::fromUtf8(quote(obj->Label.getStrValue()).c_str());
else
res = QString::fromLatin1(obj->getNameInDocument());
if(sep)
res += QLatin1Char('.');
}else {
if(idx & 1)
res = QString::fromUtf8(quote(doc->Label.getStrValue()).c_str());
else
res = QString::fromLatin1(doc->getName());
if(sep)
res += QLatin1Char('#');
}
v->setValue(res);
}
return;
}
if(!obj) {
idx = info.d.obj<0?row:info.d.obj;
const auto &objs = doc->getObjects();
objSize = (int)objs.size()*2;
if(idx<0 || idx>=objSize || inList.count(obj))
return;
obj = objs[idx/2];
if(info.d.obj<0) {
if(count)
*count = objSize;
if(v) {
QString res;
if(idx&1)
res = QString::fromUtf8(quote(obj->Label.getStrValue()).c_str());
else
res = QString::fromLatin1(obj->getNameInDocument());
if(sep)
res += QLatin1Char('.');
v->setValue(res);
}
return;
}
}
if(!prop) {
idx = row;
obj->getPropertyList(props);
propSize = (int)props.size();
if(idx<0 || idx>=propSize)
return;
prop = props[idx];
if(count)
*count = propSize;
}
if(v)
*v = QString::fromLatin1(prop->getName());
return;
}
QModelIndex parent(const QModelIndex & index) const {
if(!index.isValid())
return QModelIndex();
Info info;
Info parentInfo;
info = parentInfo = getInfo(index);
if(info.d.obj>=0) {
parentInfo.d.obj = -1;
return createIndex(info.d.obj,0,infoId(parentInfo));
}
if(info.d.doc>=0) {
parentInfo.d.doc = -1;
return createIndex(info.d.doc,0,infoId(parentInfo));
}
return QModelIndex();
}
QModelIndex index(int row, int column, const QModelIndex & parent = QModelIndex()) const {
if(row<0)
return QModelIndex();
Info info;
if(!parent.isValid()) {
info.d.doc = -1;
info.d.obj = -1;
}else{
info = getInfo(parent);
if(info.d.doc<=0)
info.d.doc = parent.row();
else if(info.d.obj<=0)
info.d.obj = parent.row();
else
return QModelIndex();
}
return createIndex(row,column,infoId(info));
}
int rowCount(const QModelIndex & parent = QModelIndex()) const {
const auto &docs = App::GetApplication().getDocuments();
Info info;
int row = 0;
if(!parent.isValid()) {
info.d.doc = -1;
info.d.obj = -1;
row = -1;
}else{
info = getInfo(parent);
if(info.d.doc<0)
info.d.doc = parent.row();
else if(info.d.obj<0)
info.d.obj = parent.row();
else
return 0;
}
int count = 0;
_data(info,row,0,&count);
FC_TRACE(info.d.doc << "," << info.d.obj << "," << row << " row count " << count);
return count;
}
int columnCount(const QModelIndex &) const {
return 1;
}
private:
std::set<App::DocumentObject*> inList;
std::string currentDoc;
std::string currentObj;
};
/**
* @brief Construct an ExpressionCompleter object.
* @param currentDoc Current document to generate the model from.
@@ -27,208 +298,88 @@ using namespace Gui;
* @param parent Parent object owning the completer.
*/
ExpressionCompleter::ExpressionCompleter(const App::Document * currentDoc, const App::DocumentObject * currentDocObj, QObject *parent)
: QCompleter(parent), prefixStart(0)
ExpressionCompleter::ExpressionCompleter(const App::DocumentObject * currentDocObj, QObject *parent)
: QCompleter(parent), prefixStart(0), currentObj(currentDocObj)
{
QStandardItemModel* model = new QStandardItemModel(this);
std::vector<App::Document*> docs = App::GetApplication().getDocuments();
std::vector<App::Document*>::const_iterator di = docs.begin();
std::vector<DocumentObject*> deps;
if (currentDocObj)
deps = currentDocObj->getInList();
std::set<const DocumentObject*> forbidden;
for (std::vector<DocumentObject*>::const_iterator it = deps.begin(); it != deps.end(); ++it)
forbidden.insert(*it);
/* Create tree with full path to all objects */
while (di != docs.end()) {
QStandardItem* docItem = new QStandardItem(QString::fromLatin1((*di)->getName()));
docItem->setData(QString::fromLatin1((*di)->getName()) + QString::fromLatin1("#"), Qt::UserRole);
createModelForDocument(*di, docItem, forbidden);
model->appendRow(docItem);
++di;
}
/* Create branch with current document object */
if (currentDocObj) {
createModelForDocument(currentDocObj->getDocument(), model->invisibleRootItem(), forbidden);
createModelForDocumentObject(currentDocObj, model->invisibleRootItem());
}
else {
if (currentDoc)
createModelForDocument(currentDoc, model->invisibleRootItem(), forbidden);
}
setModel(model);
setCaseSensitivity(Qt::CaseInsensitive);
}
/**
* @brief Create model node given document, the parent and forbidden node.
* @param doc Document
* @param parent Parent item
* @param forbidden Forbidden document objects; typically those that will create a loop in the DAG if used.
*/
void ExpressionCompleter::init() {
if(model())
return;
void ExpressionCompleter::createModelForDocument(const App::Document * doc, QStandardItem * parent,
const std::set<const DocumentObject*> & forbidden) {
std::vector<App::DocumentObject*> docObjs = doc->getObjects();
std::vector<App::DocumentObject*>::const_iterator doi = docObjs.begin();
while (doi != docObjs.end()) {
std::set<const DocumentObject*>::const_iterator it = forbidden.find(*doi);
// Skip?
if (it != forbidden.end()) {
++doi;
continue;
}
QStandardItem* docObjItem = new QStandardItem(QString::fromLatin1((*doi)->getNameInDocument()));
docObjItem->setData(QString::fromLatin1((*doi)->getNameInDocument()) + QString::fromLatin1("."), Qt::UserRole);
createModelForDocumentObject(*doi, docObjItem);
parent->appendRow(docObjItem);
if (strcmp((*doi)->getNameInDocument(), (*doi)->Label.getValue()) != 0) {
std::string label = (*doi)->Label.getValue();
if (!ExpressionParser::isTokenAnIndentifier(label))
label = quote(label);
docObjItem = new QStandardItem(QString::fromUtf8(label.c_str()));
docObjItem->setData( QString::fromUtf8(label.c_str()) + QString::fromLatin1("."), Qt::UserRole);
createModelForDocumentObject(*doi, docObjItem);
parent->appendRow(docObjItem);
}
++doi;
}
setModel(new ExpressionCompleterModel(this,currentObj.getObject()));
}
/**
* @brief Create model nodes for document object
* @param docObj Document object
* @param parent Parent item
*/
void ExpressionCompleter::createModelForDocumentObject(const DocumentObject * docObj, QStandardItem * parent)
{
std::vector<App::Property*> props;
docObj->getPropertyList(props);
std::vector<App::Property*>::const_iterator pi = props.begin();
while (pi != props.end()) {
// Skip all types of links
if ((*pi)->isDerivedFrom(App::PropertyLink::getClassTypeId()) ||
(*pi)->isDerivedFrom(App::PropertyLinkSub::getClassTypeId())) {
++pi;
continue;
}
createModelForPaths(*pi, parent);
++pi;
}
}
/**
* @brief Create nodes for a property.
* @param prop
* @param docObjItem
*/
void ExpressionCompleter::createModelForPaths(const App::Property * prop, QStandardItem *docObjItem)
{
std::vector<ObjectIdentifier> paths;
std::vector<ObjectIdentifier>::const_iterator ppi;
prop->getPaths(paths);
for (ppi = paths.begin(); ppi != paths.end(); ++ppi) {
QStandardItem* pathItem = new QStandardItem(Base::Tools::fromStdString(ppi->toString()));
QVariant value;
value.setValue(*ppi);
pathItem->setData(value, Qt::UserRole);
docObjItem->appendRow(pathItem);
}
}
QString ExpressionCompleter::pathFromIndex ( const QModelIndex & index ) const
{
QStandardItemModel * m = static_cast<QStandardItemModel*>(model());
if (m->data(index, Qt::UserRole).canConvert<App::ObjectIdentifier>()) {
App::ObjectIdentifier p = m->data(index, Qt::UserRole).value<App::ObjectIdentifier>();
QString pStr = Base::Tools::fromStdString(p.toString());
QString parentStr;
QModelIndex parent = index.parent();
while (parent.isValid()) {
QString thisParentStr = m->data(parent, Qt::UserRole).toString();
parentStr = thisParentStr + parentStr;
parent = parent.parent();
}
return parentStr + pStr;
}
else if (m->data(index, Qt::UserRole).canConvert<QString>()) {
QModelIndex parent = index;
QString parentStr;
while (parent.isValid()) {
QString thisParentStr = m->data(parent, Qt::UserRole).toString();
parentStr = thisParentStr + parentStr;
parent = parent.parent();
}
return parentStr;
}
else
auto m = model();
if(!m || !index.isValid())
return QString();
QString res;
auto parent = index;
do {
res = m->data(parent, Qt::UserRole).toString() + res;
parent = parent.parent();
}while(parent.isValid());
auto info = ExpressionCompleterModel::getInfo(index);
FC_TRACE("join path " << info.d.doc << "," << info.d.obj << "," << index.row()
<< ": " << res.toUtf8().constData());
return res;
}
QStringList ExpressionCompleter::splitPath ( const QString & path ) const
QStringList ExpressionCompleter::splitPath ( const QString & input ) const
{
try {
App::ObjectIdentifier p = ObjectIdentifier::parse(0, path.toUtf8().constData());
QStringList l;
QStringList l;
std::string path = input.toUtf8().constData();
if(path.empty())
return l;
int retry = 0;
std::string trim;
while(1) {
try {
App::ObjectIdentifier p = ObjectIdentifier::parse(
currentObj.getObject(), path);
if (p.getProperty()) {
for (int i = 0; i < p.numComponents(); ++i)
l << Base::Tools::fromStdString(p.getPropertyComponent(i).toString());
return l;
}
else {
std::vector<std::string> sl = p.getStringList();
std::vector<std::string>::const_iterator sli = sl.begin();
if(retry && sl.size())
sl.pop_back();
if(trim.size() && boost::ends_with(sl.back(),trim))
sl.back().resize(sl.back().size()-trim.size());
while (sli != sl.end()) {
l << Base::Tools::fromStdString(*sli);
++sli;
}
FC_TRACE("split path " << path
<< " -> " << l.join(QLatin1String("/")).toUtf8().constData());
return l;
}
}
catch (const Base::Exception &) {
return QStringList() << path;
catch (const Base::Exception &e) {
FC_TRACE("split path " << path << " error: " << e.what());
if(!retry) {
char last = path[path.size()-1];
if(last!='#' && last!='.' && path.find('#')!=std::string::npos) {
path += "._self";
++retry;
continue;
}
}else if(retry==1) {
path.resize(path.size()-6);
char last = path[path.size()-1];
if(last!='.' && last!='<' && path.find("#<<")!=std::string::npos) {
path += ">>._self";
++retry;
trim = ">>";
continue;
}
}
return QStringList() << input;
}
}
}
@@ -292,7 +443,7 @@ void ExpressionCompleter::slotUpdate(const QString & prefix)
ExpressionLineEdit::ExpressionLineEdit(QWidget *parent)
: QLineEdit(parent)
, completer(0)
, block(false)
, block(true)
{
connect(this, SIGNAL(textChanged(const QString&)), this, SLOT(slotTextChanged(const QString&)));
}
@@ -305,7 +456,7 @@ void ExpressionLineEdit::setDocumentObject(const App::DocumentObject * currentDo
}
if (currentDocObj != 0) {
completer = new ExpressionCompleter(currentDocObj->getDocument(), currentDocObj, this);
completer = new ExpressionCompleter(currentDocObj, this);
completer->setWidget(this);
completer->setCaseSensitivity(Qt::CaseInsensitive);
connect(completer, SIGNAL(activated(QString)), this, SLOT(slotCompleteText(QString)));
@@ -338,10 +489,79 @@ void ExpressionLineEdit::slotCompleteText(const QString & completionPrefix)
QString before(text().left(start));
QString after(text().mid(cursorPosition()));
block = true;
Base::FlagToggler<bool> flag(block,false);
setText(before + completionPrefix + after);
setCursorPosition(QString(before + completionPrefix).length());
block = false;
}
void ExpressionLineEdit::keyPressEvent(QKeyEvent *e) {
Base::FlagToggler<bool> flag(block,true);
QLineEdit::keyPressEvent(e);
}
///////////////////////////////////////////////////////////////////////
ExpressionTextEdit::ExpressionTextEdit(QWidget *parent)
: QPlainTextEdit(parent)
, completer(0)
, block(true)
{
connect(this, SIGNAL(textChanged()), this, SLOT(slotTextChanged()));
}
void ExpressionTextEdit::setDocumentObject(const App::DocumentObject * currentDocObj)
{
if (completer) {
delete completer;
completer = 0;
}
if (currentDocObj != 0) {
completer = new ExpressionCompleter(currentDocObj, this);
completer->setWidget(this);
completer->setCaseSensitivity(Qt::CaseInsensitive);
connect(completer, SIGNAL(activated(QString)), this, SLOT(slotCompleteText(QString)));
connect(completer, SIGNAL(highlighted(QString)), this, SLOT(slotCompleteText(QString)));
connect(this, SIGNAL(textChanged2(QString)), completer, SLOT(slotUpdate(QString)));
}
}
bool ExpressionTextEdit::completerActive() const
{
return completer && completer->popup() && completer->popup()->isVisible();
}
void ExpressionTextEdit::hideCompleter()
{
if (completer && completer->popup())
completer->popup()->setVisible(false);
}
void ExpressionTextEdit::slotTextChanged()
{
if (!block) {
QTextCursor cursor = textCursor();
Q_EMIT textChanged2(cursor.block().text().left(cursor.positionInBlock()));
}
}
void ExpressionTextEdit::slotCompleteText(const QString & completionPrefix)
{
QTextCursor cursor = textCursor();
int start = completer->getPrefixStart();
int pos = cursor.positionInBlock();
if(pos>=start) {
Base::FlagToggler<bool> flag(block,false);
if(pos>start)
cursor.movePosition(QTextCursor::PreviousCharacter,QTextCursor::KeepAnchor,pos-start);
cursor.insertText(completionPrefix);
}
}
void ExpressionTextEdit::keyPressEvent(QKeyEvent *e) {
Base::FlagToggler<bool> flag(block,true);
QPlainTextEdit::keyPressEvent(e);
}
#include "moc_ExpressionCompleter.cpp"

View File

@@ -4,7 +4,10 @@
#include <QObject>
#include <QCompleter>
#include <QLineEdit>
#include <QPlainTextEdit>
#include <set>
#include <memory>
#include <App/DocumentObserver.h>
class QStandardItem;
@@ -25,7 +28,7 @@ class GuiExport ExpressionCompleter : public QCompleter
{
Q_OBJECT
public:
ExpressionCompleter(const App::Document * currentDoc, const App::DocumentObject * currentDocObj, QObject *parent = 0);
ExpressionCompleter(const App::DocumentObject * currentDocObj, QObject *parent = 0);
int getPrefixStart() const { return prefixStart; }
@@ -33,14 +36,12 @@ public Q_SLOTS:
void slotUpdate(const QString &prefix);
private:
void createModelForDocument(const App::Document * doc, QStandardItem * parent, const std::set<const App::DocumentObject *> &forbidden);
void createModelForDocumentObject(const App::DocumentObject * docObj, QStandardItem * parent);
void createModelForPaths(const App::Property * prop, QStandardItem *docObjItem);
void init();
virtual QString pathFromIndex ( const QModelIndex & index ) const;
virtual QStringList splitPath ( const QString & path ) const;
int prefixStart;
App::DocumentObjectT currentObj;
};
@@ -56,6 +57,27 @@ Q_SIGNALS:
public Q_SLOTS:
void slotTextChanged(const QString & text);
void slotCompleteText(const QString & completionPrefix);
protected:
void keyPressEvent(QKeyEvent * event);
private:
ExpressionCompleter * completer;
bool block;
};
class GuiExport ExpressionTextEdit : public QPlainTextEdit {
Q_OBJECT
public:
ExpressionTextEdit(QWidget *parent = 0);
void setDocumentObject(const App::DocumentObject *currentDocObj);
bool completerActive() const;
void hideCompleter();
protected:
void keyPressEvent(QKeyEvent * event);
Q_SIGNALS:
void textChanged2(QString text);
public Q_SLOTS:
void slotTextChanged();
void slotCompleteText(const QString & completionPrefix);
private:
ExpressionCompleter * completer;
bool block;

View File

@@ -26,6 +26,7 @@
# include <QGridLayout>
# include <QHeaderView>
# include <QEvent>
# include <QTimer>
#endif
#include <boost/bind.hpp>
@@ -38,12 +39,17 @@
#include <App/PropertyContainer.h>
#include <App/DocumentObject.h>
#include <App/Document.h>
#include <Base/Console.h>
#include "PropertyView.h"
#include "Application.h"
#include "MainWindow.h"
#include "Document.h"
#include "BitmapFactory.h"
#include "ViewProvider.h"
#include "ViewProviderDocumentObject.h"
#include "Tree.h"
#include "ViewParams.h"
#include "propertyeditor/PropertyEditor.h"
@@ -52,6 +58,14 @@ using namespace Gui;
using namespace Gui::DockWnd;
using namespace Gui::PropertyEditor;
static ParameterGrp::handle _GetParam() {
static ParameterGrp::handle hGrp;
if(!hGrp) {
hGrp = App::GetApplication().GetParameterGroupByPath(
"User parameter:BaseApp/Preferences/PropertyView");
}
return hGrp;
}
/* TRANSLATOR Gui::PropertyView */
@@ -61,12 +75,16 @@ using namespace Gui::PropertyEditor;
* in two tabs.
*/
PropertyView::PropertyView(QWidget *parent)
: QWidget(parent)
: QWidget(parent),SelectionObserver(false)
{
QGridLayout* pLayout = new QGridLayout( this );
pLayout->setSpacing(0);
pLayout->setMargin (0);
timer = new QTimer(this);
timer->setSingleShot(true);
connect(timer, SIGNAL(timeout()), this, SLOT(onTimer()));
tabs = new QTabWidget (this);
tabs->setObjectName(QString::fromUtf8("propertyTab"));
tabs->setTabPosition(QTabWidget::South);
@@ -83,14 +101,10 @@ PropertyView::PropertyView(QWidget *parent)
propertyEditorData->setAutomaticDocumentUpdate(true);
tabs->addTab(propertyEditorData, tr("Data"));
ParameterGrp::handle hGrp = App::GetApplication().GetUserParameter().
GetGroup("BaseApp")->GetGroup("Preferences")->GetGroup("PropertyView");
if ( hGrp ) {
int preferredTab = hGrp->GetInt("LastTabIndex", 1);
int preferredTab = _GetParam()->GetInt("LastTabIndex", 1);
if ( preferredTab > 0 && preferredTab < tabs->count() )
tabs->setCurrentIndex(preferredTab);
}
if ( preferredTab > 0 && preferredTab < tabs->count() )
tabs->setCurrentIndex(preferredTab);
// connect after adding all tabs, so adding doesn't thrash the parameter
connect(tabs, SIGNAL(currentChanged(int)), this, SLOT(tabChanged(int)));
@@ -109,10 +123,19 @@ PropertyView::PropertyView(QWidget *parent)
(&PropertyView::slotRemoveDynamicProperty, this, _1));
this->connectPropChange =
App::GetApplication().signalChangePropertyEditor.connect(boost::bind
(&PropertyView::slotChangePropertyEditor, this, _1));
(&PropertyView::slotChangePropertyEditor, this, _1, _2));
this->connectUndoDocument =
App::GetApplication().signalUndoDocument.connect(boost::bind
(&PropertyView::slotRollback, this));
this->connectRedoDocument =
App::GetApplication().signalRedoDocument.connect(boost::bind
(&PropertyView::slotRollback, this));
this->connectActiveDoc =
Application::Instance->signalActiveDocument.connect(boost::bind
(&PropertyView::slotActiveDocument, this, _1));
this->connectDelDocument =
Application::Instance->signalDeleteDocument.connect(
boost::bind(&PropertyView::slotDeleteDocument, this, _1));
}
PropertyView::~PropertyView()
@@ -122,7 +145,60 @@ PropertyView::~PropertyView()
this->connectPropAppend.disconnect();
this->connectPropRemove.disconnect();
this->connectPropChange.disconnect();
this->connectUndoDocument.disconnect();
this->connectRedoDocument.disconnect();
this->connectActiveDoc.disconnect();
this->connectDelDocument.disconnect();
}
static bool _ShowAll;
bool PropertyView::showAll() {
return _ShowAll;
}
void PropertyView::setShowAll(bool enable) {
if(_ShowAll != enable) {
_ShowAll = enable;
for(auto view : getMainWindow()->findChildren<PropertyView*>()) {
if(view->isVisible())
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(100);
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::DocumentObject&, const App::Property& prop)
@@ -135,32 +211,33 @@ void PropertyView::slotChangePropertyView(const Gui::ViewProvider&, const App::P
propertyEditorView->updateProperty(prop);
}
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)
{
App::PropertyContainer* parent = prop.getContainer();
if (parent->isHidden(&prop))
if (isPropertyHidden(&prop))
return;
if (parent->isDerivedFrom(App::DocumentObject::getClassTypeId())) {
propertyEditorData->appendProperty(prop);
}
else if (parent->isDerivedFrom(Gui::ViewProvider::getClassTypeId())) {
propertyEditorView->appendProperty(prop);
if (propertyEditorData->appendProperty(prop)
|| propertyEditorView->appendProperty(prop))
{
timer->start(100);
}
}
void PropertyView::slotRemoveDynamicProperty(const App::Property& prop)
{
App::PropertyContainer* parent = prop.getContainer();
if (parent && parent->isDerivedFrom(App::DocumentObject::getClassTypeId())) {
if(propertyEditorData->propOwners.count(parent))
propertyEditorData->removeProperty(prop);
}
else if (parent && parent->isDerivedFrom(Gui::ViewProvider::getClassTypeId())) {
else if(propertyEditorView->propOwners.count(parent))
propertyEditorView->removeProperty(prop);
}
}
void PropertyView::slotChangePropertyEditor(const App::Property& prop)
void PropertyView::slotChangePropertyEditor(const App::Document &, const App::Property& prop)
{
App::PropertyContainer* parent = prop.getContainer();
if (parent && parent->isDerivedFrom(App::DocumentObject::getClassTypeId())) {
@@ -171,25 +248,27 @@ void PropertyView::slotChangePropertyEditor(const App::Property& prop)
}
}
void PropertyView::slotDeleteDocument(const Gui::Document &doc) {
if(propertyEditorData->propOwners.count(doc.getDocument())) {
propertyEditorView->buildUp();
propertyEditorData->buildUp();
clearPropertyItemSelection();
}
}
void PropertyView::slotActiveDocument(const Gui::Document &doc)
{
// allow to disable the auto-deactivation
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/View");
bool enableEditor = hGrp->GetBool("EnablePropertyViewForInactiveDocument", false);
if (enableEditor) {
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
std::vector<SelectionSingleton::SelObj> array = Gui::Selection().getCompleteSelection();
for (std::vector<SelectionSingleton::SelObj>::const_iterator it = array.begin(); it != array.end(); ++it) {
if (Gui::Application::Instance->getDocument(it->pDoc) == &doc) {
enableEditor = true;
break;
}
}
setEnabled(enableEditor || array.empty());
setEnabled(!Selection().hasSelection()
|| Selection().hasSelection(doc,false));
}
struct PropertyView::PropInfo
@@ -217,53 +296,119 @@ void PropertyView::onSelectionChanged(const SelectionChanges& msg)
msg.Type != SelectionChanges::ClrSelection)
return;
// allow to disable the auto-deactivation
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/View");
bool enableEditor = hGrp->GetBool("EnablePropertyViewForInactiveDocument", false);
Gui::Document *activeDoc = Application::Instance->activeDocument();
// clear the properties.
propertyEditorData->buildUp();
propertyEditorView->buildUp();
clearPropertyItemSelection();
timer->start(100);
}
void PropertyView::onTimer() {
propertyEditorData->buildUp();
propertyEditorView->buildUp();
clearPropertyItemSelection();
timer->stop();
if(!Gui::Selection().hasSelection()) {
auto gdoc = TreeWidget::selectedDocument();
if(!gdoc || !gdoc->getDocument())
return;
PropertyModel::PropertyList docProps;
auto doc = gdoc->getDocument();
std::vector<App::Property*> props;
doc->getPropertyList(props);
for(auto prop : props)
docProps.emplace_back(prop->getName(),
std::vector<App::Property*>(1,prop));
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;
std::vector<SelectionSingleton::SelObj> array = Gui::Selection().getCompleteSelection();
for (std::vector<SelectionSingleton::SelObj>::const_iterator it = array.begin(); it != array.end(); ++it) {
App::DocumentObject *ob=0;
ViewProvider *vp=0;
bool checkLink = true;
ViewProviderDocumentObject *vpLast = 0;
const auto &array = Gui::Selection().getCompleteSelection(false);
for(auto &sel : array) {
if(!sel.pObject) continue;
App::DocumentObject *parent = 0;
App::DocumentObject *ob = sel.pObject->resolve(sel.SubName,&parent);
if(!ob) continue;
// App::Link should be able to handle special case below now, besides, the new
// support of plain group in App::Link breaks because of the code below
#if 0
if(parent) {
auto parentVp = Application::Instance->getViewProvider(parent);
if(parentVp) {
// For special case where the SubName reference can resolve to
// a non-child object (e.g. link array element), the tree view
// will select the parent instead. So we shall show the
// property of the parent as well.
bool found = false;
for(auto child : parentVp->claimChildren()) {
if(ob == child) {
found = true;
break;
}
}
if(!found)
ob = parent;
}
}
#endif
// 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;
if ((*it).pObject) {
(*it).pObject->getPropertyList(dataList);
ob = (*it).pObject;
// get also the properties of the associated view provider
Gui::Document* doc = Gui::Application::Instance->getDocument(it->pDoc);
vp = doc->getViewProvider((*it).pObject);
if(!vp) continue;
// get the properties as map here because it doesn't matter to have them sorted alphabetically
vp->getPropertyMap(viewList);
if (activeDoc == doc) {
enableEditor = true;
}
auto vp = Application::Instance->getViewProvider(ob);
if(!vp) {
checkLink = false;
ob->getPropertyList(dataList);
continue;
}
if(vp->isDerivedFrom(ViewProviderDocumentObject::getClassTypeId())) {
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
std::vector<App::Property*>::iterator pt;
if (ob) {
for (pt = dataList.begin(); pt != dataList.end(); ++pt) {
if (isPropertyHidden(*pt))
continue;
PropInfo nameType;
nameType.propName = ob->getPropertyName(*pt);
nameType.propName = (*pt)->getName();
nameType.propId = (*pt)->getTypeId().getKey();
if (!ob->isHidden(*pt)) {
std::vector<PropInfo>::iterator pi = std::find_if(propDataMap.begin(), propDataMap.end(), PropFind(nameType));
if (pi != propDataMap.end()) {
pi->propList.push_back(*pt);
}
else {
nameType.propList.push_back(*pt);
propDataMap.push_back(nameType);
}
std::vector<PropInfo>::iterator pi = std::find_if(propDataMap.begin(), propDataMap.end(), PropFind(nameType));
if (pi != propDataMap.end()) {
pi->propList.push_back(*pt);
}
else {
nameType.propList.push_back(*pt);
propDataMap.push_back(nameType);
}
}
}
@@ -271,19 +416,20 @@ void PropertyView::onSelectionChanged(const SelectionChanges& msg)
if (vp) {
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();
if (!vp->isHidden(pt->second)) {
std::vector<PropInfo>::iterator 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);
}
std::vector<PropInfo>::iterator 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);
}
}
}
@@ -294,32 +440,69 @@ void PropertyView::onSelectionChanged(const SelectionChanges& msg)
// name and id
std::vector<PropInfo>::const_iterator it;
PropertyModel::PropertyList dataProps;
for (it = propDataMap.begin(); it != propDataMap.end(); ++it) {
if (it->propList.size() == array.size()) {
dataProps.push_back(std::make_pair(it->propName, it->propList));
}
}
propertyEditorData->buildUp(dataProps);
PropertyModel::PropertyList viewProps;
for (it = propViewMap.begin(); it != propViewMap.end(); ++it) {
if (it->propList.size() == array.size()) {
viewProps.push_back(std::make_pair(it->propName, it->propList));
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*> v(1,prop);
dataProps.emplace_back(name+"*", v);
}
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*> v(1,prop);
viewProps.emplace_back(name+"*", v);
}
}
}
}
propertyEditorView->buildUp(viewProps);
for (it = propDataMap.begin(); it != propDataMap.end(); ++it) {
if (it->propList.size() == array.size())
dataProps.push_back(std::make_pair(it->propName, it->propList));
}
propertyEditorData->buildUp(std::move(dataProps));
for (it = propViewMap.begin(); it != propViewMap.end(); ++it) {
if (it->propList.size() == array.size())
viewProps.push_back(std::make_pair(it->propName, it->propList));
}
propertyEditorView->buildUp(std::move(viewProps));
// make sure the editors are enabled/disabled properly
setEnabled(enableEditor || array.empty());
checkEnable();
}
void PropertyView::tabChanged(int index)
{
ParameterGrp::handle hGrp = App::GetApplication().GetUserParameter().
GetGroup("BaseApp")->GetGroup("Preferences")->GetGroup("PropertyView");
if (hGrp) {
hGrp->SetInt("LastTabIndex", index);
}
_GetParam()->SetInt("LastTabIndex",index);
}
void PropertyView::changeEvent(QEvent *e)

View File

@@ -64,13 +64,20 @@ public:
Gui::PropertyEditor::PropertyEditor* propertyEditorView;
Gui::PropertyEditor::PropertyEditor* propertyEditorData;
void clearPropertyItemSelection();
static bool showAll();
static void setShowAll(bool);
static bool isPropertyHidden(const App::Property *);
public Q_SLOTS:
/// Stores a preference for the last tab selected
void tabChanged(int index);
void onTimer();
protected:
void changeEvent(QEvent *e);
void showEvent(QShowEvent *) override;
void hideEvent(QHideEvent *) override;
private:
void onSelectionChanged(const SelectionChanges& msg);
@@ -78,8 +85,12 @@ private:
void slotChangePropertyView(const Gui::ViewProvider&, const App::Property&);
void slotAppendDynamicProperty(const App::Property&);
void slotRemoveDynamicProperty(const App::Property&);
void slotChangePropertyEditor(const App::Property&);
void slotChangePropertyEditor(const App::Document&, const App::Property&);
void slotRollback();
void slotActiveDocument(const Gui::Document&);
void slotDeleteDocument(const Gui::Document&);
void checkEnable(const char *doc = 0);
private:
struct PropInfo;
@@ -90,8 +101,12 @@ private:
Connection connectPropAppend;
Connection connectPropRemove;
Connection connectPropChange;
Connection connectUndoDocument;
Connection connectRedoDocument;
Connection connectActiveDoc;
Connection connectDelDocument;
QTabWidget* tabs;
QTimer* timer;
};
namespace DockWnd {

View File

@@ -38,18 +38,26 @@
# include <QTextBlock>
# include <QTimer>
# include <QToolTip>
# include <QDebug>
#endif
#include <Base/Tools.h>
#include <Base/Exception.h>
#include <Base/Interpreter.h>
#include <App/Expression.h>
#include "Command.h"
#include "Widgets.h"
#include "Application.h"
#include "Action.h"
#include "PrefWidgets.h"
#include "BitmapFactory.h"
#include "DlgExpressionInput.h"
#include "QuantitySpinBox_p.h"
using namespace Gui;
using namespace App;
using namespace Base;
/**
* Constructs an empty command view with parent \a parent.
@@ -1403,4 +1411,173 @@ void LabelEditor::setInputType(InputType t)
this->type = t;
}
// --------------------------------------------------------------------
ExpLineEdit::ExpLineEdit(QWidget* parent, bool expressionOnly)
: QLineEdit(parent), autoClose(expressionOnly)
{
defaultPalette = palette();
/* Icon for f(x) */
QFontMetrics fm(font());
int frameWidth = style()->pixelMetric(QStyle::PM_SpinBoxFrameWidth);
iconHeight = fm.height() - frameWidth;
iconLabel = new ExpressionLabel(this);
iconLabel->setCursor(Qt::ArrowCursor);
QPixmap pixmap = getIcon(":/icons/bound-expression-unset.svg", QSize(iconHeight, iconHeight));
iconLabel->setPixmap(pixmap);
iconLabel->setStyleSheet(QString::fromLatin1("QLabel { border: none; padding: 0px; padding-top: %2px; width: %1px; height: %1px }").arg(iconHeight).arg(frameWidth/2));
iconLabel->hide();
setStyleSheet(QString::fromLatin1("QLineEdit { padding-right: %1px } ").arg(iconHeight+frameWidth));
QObject::connect(iconLabel, SIGNAL(clicked()), this, SLOT(openFormulaDialog()));
if(expressionOnly)
QMetaObject::invokeMethod(this, "openFormulaDialog", Qt::QueuedConnection, QGenericReturnArgument());
}
bool ExpLineEdit::apply(const std::string& propName) {
if (!ExpressionBinding::apply(propName)) {
QString val = QString::fromUtf8(Base::Interpreter().strToPython(text().toUtf8()).c_str());
Gui::Command::doCommand(Gui::Command::Doc, "%s = \"%s\"", propName.c_str(), val.constData());
return true;
}
else
return false;
}
void ExpLineEdit::bind(const ObjectIdentifier& _path) {
ExpressionBinding::bind(_path);
int frameWidth = style()->pixelMetric(QStyle::PM_SpinBoxFrameWidth);
setStyleSheet(QString::fromLatin1("QLineEdit { padding-right: %1px } ").arg(iconLabel->sizeHint().width() + frameWidth + 1));
iconLabel->show();
}
void ExpLineEdit::setExpression(boost::shared_ptr<Expression> expr)
{
Q_ASSERT(isBound());
try {
ExpressionBinding::setExpression(expr);
}
catch (const Base::Exception & e) {
setReadOnly(true);
QPalette p(palette());
p.setColor(QPalette::Active, QPalette::Text, Qt::red);
setPalette(p);
iconLabel->setToolTip(QString::fromLatin1(e.what()));
}
}
void ExpLineEdit::onChange() {
if (getExpression()) {
std::unique_ptr<Expression> result(getExpression()->eval());
if(result->isDerivedFrom(App::StringExpression::getClassTypeId()))
setText(QString::fromUtf8(static_cast<App::StringExpression*>(
result.get())->getText().c_str()));
else
setText(QString::fromUtf8(result->toString().c_str()));
setReadOnly(true);
iconLabel->setPixmap(getIcon(":/icons/bound-expression.svg", QSize(iconHeight, iconHeight)));
QPalette p(palette());
p.setColor(QPalette::Text, Qt::lightGray);
setPalette(p);
setToolTip(Base::Tools::fromStdString(getExpression()->toString()));
}
else {
setReadOnly(false);
iconLabel->setPixmap(getIcon(":/icons/bound-expression-unset.svg", QSize(iconHeight, iconHeight)));
QPalette p(palette());
p.setColor(QPalette::Active, QPalette::Text, defaultPalette.color(QPalette::Text));
setPalette(p);
}
iconLabel->setToolTip(QString());
}
void ExpLineEdit::resizeEvent(QResizeEvent * event)
{
QLineEdit::resizeEvent(event);
int frameWidth = style()->pixelMetric(QStyle::PM_SpinBoxFrameWidth);
QSize sz = iconLabel->sizeHint();
iconLabel->move(rect().right() - frameWidth - sz.width(), 0);
try {
if (isBound() && getExpression()) {
setReadOnly(true);
QPixmap pixmap = getIcon(":/icons/bound-expression.svg", QSize(iconHeight, iconHeight));
iconLabel->setPixmap(pixmap);
QPalette p(palette());
p.setColor(QPalette::Text, Qt::lightGray);
setPalette(p);
setToolTip(Base::Tools::fromStdString(getExpression()->toString()));
}
else {
setReadOnly(false);
QPixmap pixmap = getIcon(":/icons/bound-expression-unset.svg", QSize(iconHeight, iconHeight));
iconLabel->setPixmap(pixmap);
QPalette p(palette());
p.setColor(QPalette::Active, QPalette::Text, defaultPalette.color(QPalette::Text));
setPalette(p);
}
iconLabel->setToolTip(QString());
}
catch (const Base::Exception & e) {
setReadOnly(true);
QPalette p(palette());
p.setColor(QPalette::Active, QPalette::Text, Qt::red);
setPalette(p);
iconLabel->setToolTip(QString::fromLatin1(e.what()));
}
}
void ExpLineEdit::openFormulaDialog()
{
Q_ASSERT(isBound());
Gui::Dialog::DlgExpressionInput* box = new Gui::Dialog::DlgExpressionInput(
getPath(), getExpression(),Unit(), this);
connect(box, SIGNAL(finished(int)), this, SLOT(finishFormulaDialog()));
box->show();
QPoint pos = mapToGlobal(QPoint(0,0));
box->move(pos-box->expressionPosition());
box->setExpressionInputSize(width(), height());
}
void ExpLineEdit::finishFormulaDialog()
{
Gui::Dialog::DlgExpressionInput* box = qobject_cast<Gui::Dialog::DlgExpressionInput*>(sender());
if (!box) {
qWarning() << "Sender is not a Gui::Dialog::DlgExpressionInput";
return;
}
if (box->result() == QDialog::Accepted)
setExpression(box->getExpression());
else if (box->discardedFormula())
setExpression(boost::shared_ptr<Expression>());
box->deleteLater();
if(autoClose)
this->deleteLater();
}
void ExpLineEdit::keyPressEvent(QKeyEvent *event)
{
if (!hasExpression())
QLineEdit::keyPressEvent(event);
}
#include "moc_Widgets.cpp"

View File

@@ -34,6 +34,8 @@
#include <QBasicTimer>
#include <QTime>
#include <QToolButton>
#include <QModelIndex>
#include "ExpressionBinding.h"
namespace Gui {
class PrefCheckBox;
@@ -449,6 +451,33 @@ private:
QPushButton *button;
};
/**
* The ExpLineEdit class provides a lineedit that support expressing binding.
* \author realthunder
*/
class GuiExport ExpLineEdit : public QLineEdit, public ExpressionBinding
{
Q_OBJECT
public:
ExpLineEdit ( QWidget * parent=0, bool expressionOnly=false );
void setExpression(boost::shared_ptr<App::Expression> expr);
void bind(const App::ObjectIdentifier &_path);
bool apply(const std::string &propName);
void keyPressEvent(QKeyEvent *event);
void resizeEvent(QResizeEvent *event);
private Q_SLOTS:
void finishFormulaDialog();
void openFormulaDialog();
virtual void onChange();
private:
bool autoClose;
};
} // namespace Gui
#endif // GUI_WIDGETS_H

View File

@@ -26,24 +26,38 @@
#ifndef _PreComp_
# include <QApplication>
# include <QPainter>
# include <QMenu>
# include <QDebug>
# include <QDialog>
# include <QMessageBox>
# include <QCheckBox>
#endif
#include <Base/Console.h>
#include <Base/Tools.h>
#include <App/Application.h>
#include <App/Document.h>
#include "MainWindow.h"
#include "DlgAddProperty.h"
#include "PropertyEditor.h"
#include "PropertyItemDelegate.h"
#include "PropertyModel.h"
#include "PropertyView.h"
FC_LOG_LEVEL_INIT("PropertyView",true,true);
using namespace Gui::PropertyEditor;
PropertyEditor::PropertyEditor(QWidget *parent)
: QTreeView(parent), autoupdate(false), committing(false), delaybuild(false)
: QTreeView(parent), autoupdate(false), committing(false), delaybuild(false), binding(false)
{
propertyModel = new PropertyModel(this);
setModel(propertyModel);
PropertyItemDelegate* delegate = new PropertyItemDelegate(this);
setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel);
delegate = new PropertyItemDelegate(this);
delegate->setItemEditorFactory(new PropertyItemEditorFactory);
setItemDelegate(delegate);
@@ -54,8 +68,10 @@ PropertyEditor::PropertyEditor(QWidget *parent)
this->background = opt.palette.dark();
this->groupColor = opt.palette.color(QPalette::BrightText);
connect(this, SIGNAL(activated(QModelIndex)), this, SLOT(onItemActivated(QModelIndex)));
connect(this, SIGNAL(clicked(QModelIndex)), this, SLOT(onItemActivated(QModelIndex)));
this->setSelectionMode(QAbstractItemView::ExtendedSelection);
connect(this, SIGNAL(activated(const QModelIndex &)), this, SLOT(onItemActivated(const QModelIndex &)));
connect(this, SIGNAL(clicked(const QModelIndex &)), this, SLOT(onItemActivated(const QModelIndex &)));
}
PropertyEditor::~PropertyEditor()
@@ -137,14 +153,19 @@ void PropertyEditor::commitData (QWidget * editor)
void PropertyEditor::editorDestroyed (QObject * editor)
{
delegate->editorClosed(0,QAbstractItemDelegate::NoHint);
QTreeView::editorDestroyed(editor);
}
void PropertyEditor::currentChanged ( const QModelIndex & current, const QModelIndex & previous )
{
FC_LOG("current changed " << current.row()<<","<<current.column()
<< " " << previous.row()<<","<<previous.column());
QTreeView::currentChanged(current, previous);
if (previous.isValid())
closePersistentEditor(model()->buddy(previous));
// if (previous.isValid())
// closePersistentEditor(model()->buddy(previous));
// DO NOT activate editor here, use onItemActivate() which response to
// signals of activated and clicked.
@@ -153,16 +174,62 @@ void PropertyEditor::currentChanged ( const QModelIndex & current, const QModelI
// openPersistentEditor(model()->buddy(current));
}
void PropertyEditor::setupTransaction(const QModelIndex &index) {
if(!autoupdate)
return;
if(this->state()!=EditingState) {
FC_LOG("editor not editing");
return;
}
auto &app = App::GetApplication();
if(app.getActiveTransaction()) {
FC_LOG("editor already transacting " << app.getActiveTransaction());
return;
}
PropertyItem* item = static_cast<PropertyItem*>(index.internalPointer());
auto items = item->getPropertyData();
for(auto propItem=item->parent();items.empty() && propItem;propItem=propItem->parent())
items = propItem->getPropertyData();
if(items.empty()) {
FC_LOG("editor no item");
return;
}
auto prop = items[0];
auto parent = prop->getContainer();
auto obj = Base::freecad_dynamic_cast<App::DocumentObject>(parent);
if(!obj || !obj->getDocument()) {
FC_LOG("invalid object");
return;
}
if(obj->getDocument()->hasPendingTransaction()) {
FC_LOG("pending transaction");
return;
}
std::ostringstream str;
str << tr("Edit").toUtf8().constData() << ' ';
for(auto prop : items) {
if(prop->getContainer()!=obj) {
obj = 0;
break;
}
}
if(obj && obj->getNameInDocument())
str << obj->getNameInDocument() << '.';
else
str << tr("property").toUtf8().constData() << ' ';
str << prop->getName();
if(items.size()>1)
str << "...";
app.setActiveTransaction(str.str().c_str());
FC_LOG("editor transaction " << app.getActiveTransaction());
}
void PropertyEditor::onItemActivated ( const QModelIndex & index )
{
if (autoupdate) {
PropertyItem* property = static_cast<PropertyItem*>(index.internalPointer());
QString edit = tr("Edit %1").arg(property->propertyName());
App::Document* doc = App::GetApplication().getActiveDocument();
if (doc)
doc->openTransaction(edit.toUtf8());
}
openPersistentEditor(model()->buddy(index));
if(index.column() != 1)
return;
edit(model()->buddy(index),AllEditTriggers,0);
setupTransaction(index);
}
void PropertyEditor::closeEditor (QWidget * editor, QAbstractItemDelegate::EndEditHint hint)
@@ -171,35 +238,38 @@ void PropertyEditor::closeEditor (QWidget * editor, QAbstractItemDelegate::EndEd
App::Document* doc = App::GetApplication().getActiveDocument();
if (doc) {
if (!doc->isTransactionEmpty()) {
doc->commitTransaction();
// Between opening and committing a transaction a recompute
// could already have been done
if (doc->isTouched())
doc->recompute();
}
else {
doc->abortTransaction();
}
}
App::GetApplication().closeActiveTransaction();
}
QModelIndex indexSaved = currentIndex();
FC_LOG("index saved " << indexSaved.row() << ", " << indexSaved.column());
QTreeView::closeEditor(editor, hint);
// If after closing the editor this widget is still in editing state
// then a new editor must have been created. So, a transaction must be
// opened, too.
if (autoupdate && this->state() == EditingState) {
App::Document* doc = App::GetApplication().getActiveDocument();
if (doc) {
QString edit;
QModelIndex index = currentIndex();
if (index.isValid()) {
PropertyItem* property = static_cast<PropertyItem*>(index.internalPointer());
edit = tr("Edit %1").arg(property->propertyName());
}
doc->openTransaction(edit.toUtf8());
QModelIndex lastIndex;
while(this->state()!=EditingState) {
QModelIndex index;
if (hint == QAbstractItemDelegate::EditNextItem) {
index = moveCursor(MoveDown,Qt::NoModifier);
} else if(hint == QAbstractItemDelegate::EditPreviousItem) {
index = moveCursor(MoveUp,Qt::NoModifier);
} else
break;
if(!index.isValid() || index==lastIndex) {
setCurrentIndex(indexSaved);
break;
}
lastIndex = index;
setCurrentIndex(index);
edit(index,AllEditTriggers,0);
}
setupTransaction(currentIndex());
}
void PropertyEditor::reset()
@@ -237,7 +307,7 @@ void PropertyEditor::drawBranches(QPainter *painter, const QRect &rect, const QM
//painter->setPen(savedPen);
}
void PropertyEditor::buildUp(const PropertyModel::PropertyList& props)
void PropertyEditor::buildUp(PropertyModel::PropertyList &&props)
{
if (committing) {
Base::Console().Warning("While committing the data to the property the selection has changed.\n");
@@ -255,7 +325,12 @@ void PropertyEditor::buildUp(const PropertyModel::PropertyList& props)
this->setCurrentIndex(index);
}
propList = props;
propList = std::move(props);
propOwners.clear();
for(auto &v : propList) {
for(auto prop : v.second)
propOwners.insert(prop->getContainer());
}
}
void PropertyEditor::updateProperty(const App::Property& prop)
@@ -271,7 +346,7 @@ void PropertyEditor::setEditorMode(const QModelIndex & parent, int start, int en
for (int i=start; i<=end; i++) {
QModelIndex item = propertyModel->index(i, column, parent);
PropertyItem* propItem = static_cast<PropertyItem*>(item.internalPointer());
if (propItem && propItem->testStatus(App::Property::Hidden)) {
if (!PropertyView::showAll() && propItem && propItem->testStatus(App::Property::Hidden)) {
setRowHidden (i, parent, true);
}
if (propItem && propItem->isSeparator()) {
@@ -285,10 +360,10 @@ void PropertyEditor::updateEditorMode(const App::Property& prop)
{
// check if the parent object is selected
std::string editor = prop.getEditorName();
if (editor.empty())
if (!PropertyView::showAll() && editor.empty())
return;
bool hidden = prop.testStatus(App::Property::Hidden);
bool hidden = PropertyView::isPropertyHidden(&prop);
bool readOnly = prop.testStatus(App::Property::ReadOnly);
int column = 1;
@@ -324,34 +399,8 @@ void PropertyEditor::updateItemEditor(bool enable, int column, const QModelIndex
}
}
void PropertyEditor::appendProperty(const App::Property& prop)
{
// check if the parent object is selected
std::string editor = prop.getEditorName();
if (editor.empty())
return;
App::PropertyContainer* parent = prop.getContainer();
std::string context = prop.getName();
bool canAddProperty = (!propList.empty());
for (PropertyModel::PropertyList::iterator it = propList.begin(); it != propList.end(); ++it) {
if (it->second.empty() || it->second.size() > 1) {
canAddProperty = false;
break;
}
else if (it->second.front()->getContainer() != parent) {
canAddProperty = false;
break;
}
}
if (canAddProperty) {
std::vector<App::Property*> list;
list.push_back(const_cast<App::Property*>(&prop));
std::pair< std::string, std::vector<App::Property*> > pair = std::make_pair(context, list);
propList.push_back(pair);
propertyModel->appendProperty(prop);
}
bool PropertyEditor::appendProperty(const App::Property& prop) {
return !!propOwners.count(prop.getContainer());
}
void PropertyEditor::removeProperty(const App::Property& prop)
@@ -371,4 +420,166 @@ void PropertyEditor::removeProperty(const App::Property& prop)
}
}
enum MenuAction {
MA_ShowAll,
MA_Expression,
MA_RemoveProp,
MA_AddProp,
MA_Transient,
MA_Output,
MA_NoRecompute,
MA_ReadOnly,
MA_Hidden,
MA_Touched,
MA_EvalOnRestore,
};
void PropertyEditor::contextMenuEvent(QContextMenuEvent *) {
QMenu menu;
QAction *showAll = menu.addAction(tr("Show all"));
showAll->setCheckable(true);
showAll->setChecked(PropertyView::showAll());
showAll->setData(QVariant(MA_ShowAll));
auto contextIndex = currentIndex();
std::unordered_set<App::Property*> props;
if(PropertyView::showAll()) {
for(auto index : selectedIndexes()) {
auto item = static_cast<PropertyItem*>(index.internalPointer());
if(item->isSeparator())
continue;
for(auto parent=item;parent;parent=item->parent()) {
const auto &ps = parent->getPropertyData();
if(ps.size()) {
props.insert(ps.begin(),ps.end());
break;
}
}
}
if(props.size())
menu.addAction(tr("Add property"))->setData(QVariant(MA_AddProp));
bool canRemove = !props.empty();
unsigned long propType = 0;
unsigned long propStatus = 0xffffffff;
for(auto prop : props) {
propType |= prop->getType();
propStatus &= prop->getStatus();
if(!prop->testStatus(App::Property::PropDynamic)
|| prop->testStatus(App::Property::LockDynamic))
{
canRemove = false;
}
}
if(canRemove)
menu.addAction(tr("Remove property"))->setData(QVariant(MA_RemoveProp));
if(props.size() == 1) {
auto item = static_cast<PropertyItem*>(contextIndex.internalPointer());
auto prop = *props.begin();
if(item->isBound()
&& !prop->isDerivedFrom(App::PropertyExpressionEngine::getClassTypeId())
&& !prop->isReadOnly()
&& !(prop->getType() & App::Prop_ReadOnly))
{
contextIndex = propertyModel->buddy(contextIndex);
setCurrentIndex(contextIndex);
menu.addSeparator();
menu.addAction(tr("Expression..."))->setData(QVariant(MA_Expression));
}
}
if(props.size()) {
menu.addSeparator();
QAction *action;
QString text;
#define _ACTION_SETUP(_name) do {\
text = tr(#_name);\
action = menu.addAction(text);\
action->setData(QVariant(MA_##_name));\
action->setCheckable(true);\
if(propStatus & (1<<App::Property::_name))\
action->setChecked(true);\
}while(0)
#define ACTION_SETUP(_name) do {\
_ACTION_SETUP(_name);\
if(propType & App::Prop_##_name) {\
action->setText(text + QString::fromLatin1(" *"));\
action->setChecked(true);\
}\
}while(0)
ACTION_SETUP(Hidden);
ACTION_SETUP(Output);
ACTION_SETUP(NoRecompute);
ACTION_SETUP(ReadOnly);
ACTION_SETUP(Transient);
_ACTION_SETUP(Touched);
_ACTION_SETUP(EvalOnRestore);
}
}
auto action = menu.exec(QCursor::pos());
if(!action)
return;
switch(action->data().toInt()) {
case MA_ShowAll:
PropertyView::setShowAll(action->isChecked());
return;
#define ACTION_CHECK(_name) \
case MA_##_name:\
for(auto prop : props) \
prop->setStatus(App::Property::_name,action->isChecked());\
break
ACTION_CHECK(Transient);
ACTION_CHECK(ReadOnly);
ACTION_CHECK(Output);
ACTION_CHECK(Hidden);
ACTION_CHECK(EvalOnRestore);
case MA_Touched:
for(auto prop : props) {
if(action->isChecked())
prop->touch();
else
prop->purgeTouched();
}
break;
case MA_Expression:
if(contextIndex == currentIndex()) {
closePersistentEditor(contextIndex);
Base::FlagToggler<> flag(binding);
edit(contextIndex,AllEditTriggers,0);
}
break;
case MA_AddProp: {
App::AutoTransaction committer("Add property");
std::unordered_set<App::PropertyContainer*> containers;
for(auto prop : props)
containers.insert(prop->getContainer());
Gui::Dialog::DlgAddProperty dlg(
Gui::getMainWindow(),std::move(containers));
dlg.exec();
return;
}
case MA_RemoveProp: {
App::AutoTransaction committer("Remove property");
for(auto prop : props) {
try {
prop->getContainer()->removeDynamicProperty(prop->getName());
}catch(Base::Exception &e) {
e.ReportException();
}
}
break;
}
default:
break;
}
}
#include "moc_PropertyEditor.cpp"

View File

@@ -27,9 +27,11 @@
#include <map>
#include <string>
#include <vector>
#include <unordered_set>
#include <QTreeView>
#include <App/DocumentObserver.h>
#include "PropertyItem.h"
#include "PropertyModel.h"
@@ -38,8 +40,12 @@ class Property;
}
namespace Gui {
class PropertyView;
namespace PropertyEditor {
class PropertyItemDelegate;
class PropertyModel;
/*!
Put this into the .qss file after Gui--PropertyEditor--PropertyEditor
@@ -66,10 +72,10 @@ public:
~PropertyEditor();
/** Builds up the list view with the properties. */
void buildUp(const PropertyModel::PropertyList& props);
void buildUp(PropertyModel::PropertyList &&props = PropertyModel::PropertyList());
void updateProperty(const App::Property&);
void updateEditorMode(const App::Property&);
void appendProperty(const App::Property&);
bool appendProperty(const App::Property&);
void removeProperty(const App::Property&);
void setAutomaticDocumentUpdate(bool);
bool isAutomaticDocumentUpdate(bool) const;
@@ -81,7 +87,9 @@ public:
QColor groupTextColor() const;
void setGroupTextColor(const QColor& c);
public Q_SLOTS:
bool isBinding() const { return binding; }
protected Q_SLOTS:
void onItemActivated(const QModelIndex &index);
protected:
@@ -92,21 +100,29 @@ protected:
virtual void rowsInserted (const QModelIndex & parent, int start, int end);
virtual void drawBranches(QPainter *painter, const QRect &rect, const QModelIndex &index) const;
virtual QStyleOptionViewItem viewOptions() const;
virtual void contextMenuEvent(QContextMenuEvent *event);
virtual bool event(QEvent*);
private:
void setEditorMode(const QModelIndex & parent, int start, int end);
void updateItemEditor(bool enable, int column, const QModelIndex& parent);
void setupTransaction(const QModelIndex &);
private:
PropertyItemDelegate *delegate;
PropertyModel* propertyModel;
QStringList selectedProperty;
PropertyModel::PropertyList propList;
std::unordered_set<App::PropertyContainer*> propOwners;
bool autoupdate;
bool committing;
bool delaybuild;
QColor groupColor;
QBrush background;
bool binding;
friend class Gui::PropertyView;
};
} //namespace PropertyEditor

View File

@@ -48,6 +48,7 @@
#include <App/PropertyUnits.h>
#include <Gui/Application.h>
#include <Gui/Control.h>
#include <Gui/Widgets.h>
#include <Gui/Command.h>
#include <Gui/Document.h>
#include <Gui/Selection.h>
@@ -58,6 +59,7 @@
#include <Gui/QuantitySpinBox.h>
#include "PropertyItem.h"
#include "PropertyView.h"
#include <Gui/SpinBox.h>
using namespace Gui::PropertyEditor;
@@ -84,10 +86,11 @@ PropertyItem* PropertyItemFactory::createPropertyItem (const char* sName) const
}
// ----------------------------------------------------
Q_DECLARE_METATYPE(Py::Object)
PROPERTYITEM_SOURCE(Gui::PropertyEditor::PropertyItem)
PropertyItem::PropertyItem() : parentItem(0), readonly(false), cleared(false)
PropertyItem::PropertyItem() : parentItem(0), readonly(false), cleared(false), linked(false)
{
precision = Base::UnitsApi::getDecimals();
setAutoApply(true);
@@ -262,6 +265,18 @@ bool PropertyItem::isReadOnly() const
return readonly;
}
void PropertyItem::setLinked(bool l)
{
linked = l;
for (QList<PropertyItem*>::iterator it = childItems.begin(); it != childItems.end(); ++it)
(*it)->setLinked(l);
}
bool PropertyItem::isLinked() const
{
return linked;
}
bool PropertyItem::testStatus(App::Property::Status pos) const
{
std::vector<App::Property*>::const_iterator it;
@@ -301,7 +316,47 @@ QVariant PropertyItem::decoration(const QVariant&) const
QVariant PropertyItem::toString(const QVariant& prop) const
{
return prop;
if(prop != QVariant() || propertyItems.size()!=1)
return prop;
Base::PyGILStateLocker lock;
Py::Object pyobj(propertyItems[0]->getPyObject(),true);
std::ostringstream ss;
if(pyobj.isNone())
ss << "<None>";
else if(pyobj.isSequence()) {
ss << '[';
Py::Sequence seq(pyobj);
bool first = true;
size_t i=0;
for(i=0;i<2 && i<seq.size(); ++i) {
if(first)
first = false;
else
ss << ", ";
ss << Py::Object(seq[i]).as_string();
}
if(i<seq.size())
ss << "...";
ss << ']';
}else if(pyobj.isMapping()) {
ss << '{';
Py::Mapping map(pyobj);
bool first = true;
auto it = map.begin();
for(int i=0;i<2 && it!=map.end(); ++it) {
if(first)
first = false;
else
ss << ", ";
const auto &v = *it;
ss << Py::Object(v.first).as_string() << ':' << Py::Object(v.second).as_string();
}
if(it!=map.end())
ss << "...";
ss << '}';
}else
ss << pyobj.as_string();
return QVariant(QString::fromUtf8(ss.str().c_str()));
}
QVariant PropertyItem::value(const App::Property* /*prop*/) const
@@ -316,20 +371,21 @@ void PropertyItem::setValue(const QVariant& /*value*/)
QString PropertyItem::pythonIdentifier(const App::Property* prop) const
{
App::PropertyContainer* parent = prop->getContainer();
QString propPrefix = QString::fromLatin1(parent->getPropertyPrefix());
if (parent->getTypeId() == App::Document::getClassTypeId()) {
App::Document* doc = static_cast<App::Document*>(parent);
QString docName = QString::fromLatin1(App::GetApplication().getDocumentName(doc));
QString propName = QString::fromLatin1(parent->getPropertyName(prop));
return QString::fromLatin1("FreeCAD.getDocument(\"%1\").%2").arg(docName, propName);
QString propName = QString::fromLatin1(prop->getName());
return QString::fromLatin1("FreeCAD.getDocument(\"%1\").%3%2").arg(docName).arg(propName).arg(propPrefix);
}
if (parent->getTypeId().isDerivedFrom(App::DocumentObject::getClassTypeId())) {
App::DocumentObject* obj = static_cast<App::DocumentObject*>(parent);
App::Document* doc = obj->getDocument();
QString docName = QString::fromLatin1(App::GetApplication().getDocumentName(doc));
QString objName = QString::fromLatin1(obj->getNameInDocument());
QString propName = QString::fromLatin1(parent->getPropertyName(prop));
return QString::fromLatin1("FreeCAD.getDocument(\"%1\").getObject(\"%2\").%3")
.arg(docName, objName, propName);
QString propName = QString::fromLatin1(prop->getName());
return QString::fromLatin1("FreeCAD.getDocument(\"%1\").getObject(\"%2\").%4%3")
.arg(docName,objName,propName,propPrefix);
}
auto* vp = dynamic_cast<Gui::ViewProviderDocumentObject*>(parent);
if (vp) {
@@ -337,9 +393,9 @@ QString PropertyItem::pythonIdentifier(const App::Property* prop) const
App::Document* doc = obj->getDocument();
QString docName = QString::fromLatin1(App::GetApplication().getDocumentName(doc));
QString objName = QString::fromLatin1(obj->getNameInDocument());
QString propName = QString::fromLatin1(parent->getPropertyName(prop));
return QString::fromLatin1("FreeCADGui.getDocument(\"%1\").getObject(\"%2\").%3")
.arg(docName, objName, propName);
QString propName = QString::fromLatin1(prop->getName());
return QString::fromLatin1("FreeCADGui.getDocument(\"%1\").getObject(\"%2\").%4%3")
.arg(docName,objName,propName,propPrefix);
}
return QString();
}
@@ -358,6 +414,34 @@ QVariant PropertyItem::editorData(QWidget * /*editor*/) const
return QVariant();
}
QWidget* PropertyItem::createExpressionEditor(QWidget* parent, const QObject* receiver, const char* method) const
{
if(!isBound())
return 0;
ExpLineEdit *le = new ExpLineEdit(parent,true);
le->setFrame(false);
le->setReadOnly(true);
QObject::connect(le, SIGNAL(textChanged(const QString&)), receiver, method);
le->bind(getPath());
le->setAutoApply(autoApply());
return le;
}
void PropertyItem::setExpressionEditorData(QWidget *editor, const QVariant& data) const
{
QLineEdit *le = qobject_cast<QLineEdit*>(editor);
if(le)
le->setText(data.toString());
}
QVariant PropertyItem::expressionEditorData(QWidget *editor) const
{
QLineEdit *le = qobject_cast<QLineEdit*>(editor);
if(le)
return QVariant(le->text());
return QVariant();
}
QString PropertyItem::propertyName() const
{
if (propName.isEmpty())
@@ -417,13 +501,32 @@ QVariant PropertyItem::data(int column, int role) const
{
// property name
if (column == 0) {
if (role == Qt::BackgroundRole) {
if(PropertyView::showAll()
&& propertyItems.size() == 1
&& propertyItems.front()->testStatus(App::Property::PropDynamic)
&& !propertyItems.front()->testStatus(App::Property::LockDynamic))
{
return QBrush(QColor(0xFF,0xFF,0x99));
}
return QVariant();
}
if (role == Qt::DisplayRole)
return displayName();
// no properties set
if (propertyItems.empty())
return QVariant();
else if (role == Qt::ToolTipRole)
return toolTip(propertyItems[0]);
else if (role == Qt::ToolTipRole) {
if(!PropertyView::showAll())
return toolTip(propertyItems[0]);
QString type = QString::fromLatin1("Type: %1").arg(
QString::fromLatin1(propertyItems[0]->getTypeId().getName()));
QString doc = toolTip(propertyItems[0]).toString();
if(doc.size())
return type + QLatin1String("\n\n") + doc;
return type;
} else if (role == Qt::TextColorRole && linked)
return QVariant::fromValue(QColor(0,0x80,0));
else
return QVariant();
}
@@ -456,7 +559,11 @@ QVariant PropertyItem::data(int column, int role) const
return toString(value(propertyItems[0]));
else if (role == Qt::ToolTipRole)
return toolTip(propertyItems[0]);
else
else if( role == Qt::TextColorRole) {
if(hasExpression())
return QVariant::fromValue(QColor(0,0,255.0));
return QVariant();
} else
return QVariant();
}
}
@@ -544,20 +651,27 @@ QVariant PropertyStringItem::value(const App::Property* prop) const
void PropertyStringItem::setValue(const QVariant& value)
{
if (!value.canConvert(QVariant::String))
return;
QString val = value.toString();
val = QString::fromUtf8(Base::Interpreter().strToPython(val.toUtf8()).c_str());
QString data = QString::fromLatin1("\"%1\"").arg(val);
setPropertyValue(data);
if(!hasExpression()) {
if (!value.canConvert(QVariant::String))
return;
QString val = value.toString();
val = QString::fromUtf8(Base::Interpreter().strToPython(val.toUtf8()).c_str());
QString data = QString::fromLatin1("\"%1\"").arg(val);
setPropertyValue(data);
}
}
QWidget* PropertyStringItem::createEditor(QWidget* parent, const QObject* receiver, const char* method) const
{
QLineEdit *le = new QLineEdit(parent);
ExpLineEdit *le = new ExpLineEdit(parent);
le->setFrame(false);
le->setReadOnly(isReadOnly());
QObject::connect(le, SIGNAL(textChanged(const QString&)), receiver, method);
if(isBound()) {
le->bind(getPath());
le->setAutoApply(autoApply());
}
return le;
}
@@ -3381,14 +3495,15 @@ void LinkSelection::select()
{
Gui::Selection().clearSelection();
Gui::Selection().addSelection((const char*)link[0].toLatin1(),
(const char*)link[1].toLatin1());
(const char*)link[1].toLatin1(),
link.size()>=6?(const char*)link[5].toUtf8():0);
this->deleteLater();
}
// ---------------------------------------------------------------
LinkLabel::LinkLabel (QWidget * parent) : QWidget(parent)
{
LinkLabel::LinkLabel (QWidget * parent, bool xlink) : QWidget(parent), isXLink(xlink)
{
QHBoxLayout *layout = new QHBoxLayout(this);
layout->setMargin(0);
layout->setSpacing(1);
@@ -3447,7 +3562,7 @@ void LinkLabel::onLinkActivated (const QString& s)
void LinkLabel::onEditClicked ()
{
Gui::Dialog::DlgPropertyLink dlg(link, this);
Gui::Dialog::DlgPropertyLink dlg(link, this, 0, isXLink);
if (dlg.exec() == QDialog::Accepted) {
setPropertyLink(dlg.propertyLink());
/*emit*/ linkChanged(link);
@@ -3463,7 +3578,7 @@ void LinkLabel::resizeEvent(QResizeEvent* e)
PROPERTYITEM_SOURCE(Gui::PropertyEditor::PropertyLinkItem)
PropertyLinkItem::PropertyLinkItem()
PropertyLinkItem::PropertyLinkItem():isXLink(false)
{
}
@@ -3473,23 +3588,72 @@ QVariant PropertyLinkItem::toString(const QVariant& prop) const
return QVariant(list[2]);
}
QVariant PropertyLinkItem::data(int column, int role) const {
if(propertyItems.size() && column == 1 && role == Qt::TextColorRole) {
auto xlink = Base::freecad_dynamic_cast<const App::PropertyXLink>(propertyItems[0]);
if(xlink && xlink->checkRestore()>1)
return QVariant::fromValue(QColor(0xff,0,0));
}
return PropertyItem::data(column,role);
}
QVariant PropertyLinkItem::value(const App::Property* prop) const
{
assert(prop && prop->getTypeId().isDerivedFrom(App::PropertyLink::getClassTypeId()));
auto xlink = Base::freecad_dynamic_cast<const App::PropertyXLink>(prop);
isXLink = xlink!=0;
const App::PropertyLink* prop_link = static_cast<const App::PropertyLink*>(prop);
App::PropertyContainer* c = prop_link->getContainer();
// the list has five elements:
// [document name, internal name, label, internal name of container, property name]
// The list has five mandatory elements:
//
// document name of the container,
// internal name of the linked object,
// label,
// internal name of container,
// property name
//
// and two additional elements if it is a PropertyXLink
//
// subname
// (optional) document name of linked object if it is different from the container
//
App::DocumentObject* obj = prop_link->getValue();
QStringList list;
if (obj) {
list << QString::fromLatin1(obj->getDocument()->getName());
list << QString::fromLatin1(obj->getNameInDocument());
list << QString::fromUtf8(obj->Label.getValue());
}
else {
std::string _objName;
const char *objName = obj->getNameInDocument();
auto owner = Base::freecad_dynamic_cast<App::DocumentObject>(c);
if(!objName || (owner && owner->getDocument()!=obj->getDocument())) {
_objName = obj->getFullName();
objName = _objName.c_str();
}
if(xlink && xlink->getSubName(false)[0]) {
auto subObj = obj->getSubObject(xlink->getSubName(false));
if(subObj)
list << QString::fromLatin1("%1 (%2.%3)").
arg(QString::fromUtf8(subObj->Label.getValue()),
QString::fromLatin1(objName),
QString::fromUtf8(xlink->getSubName(false)));
else
list << QString::fromLatin1("%1.%2").
arg(QString::fromLatin1(objName),
QString::fromUtf8(xlink->getSubName(false)));
}else if(objName!=obj->Label.getValue()) {
list << QString::fromLatin1("%1 (%2)").
arg(QString::fromUtf8(obj->Label.getValue()),
QString::fromLatin1(objName));
} else {
list << QString::fromUtf8(obj->Label.getValue());
}
} else {
// no object assigned
// the document name
if (c->getTypeId().isDerivedFrom(App::DocumentObject::getClassTypeId())) {
@@ -3502,8 +3666,13 @@ QVariant PropertyLinkItem::value(const App::Property* prop) const
// the internal object name
list << QString::fromLatin1("Null");
// the object label
list << QString::fromLatin1("");
std::string msg;
if(xlink && xlink->checkRestore(&msg)>1)
list << QString::fromUtf8(msg.c_str());
else
list << QString::fromLatin1("");
}
// the name of this object
@@ -3511,11 +3680,18 @@ QVariant PropertyLinkItem::value(const App::Property* prop) const
App::DocumentObject* obj = static_cast<App::DocumentObject*>(c);
list << QString::fromLatin1(obj->getNameInDocument());
}
else {
else
list << QString::fromLatin1("Null");
}
list << QString::fromLatin1(prop->getName());
assert(list.size() == FC_XLINK_VALUE_INDEX);
if(xlink) {
list << QString::fromUtf8(xlink->getSubName(false));
auto cobj = dynamic_cast<App::DocumentObject*>(c);
if(cobj && obj && cobj->getDocument()!=obj->getDocument())
list << QString::fromLatin1(obj->getDocument()->getName());
}
return QVariant(list);
}
@@ -3531,15 +3707,23 @@ void PropertyLinkItem::setValue(const QVariant& value)
QString data;
if ( o.isEmpty() )
data = QString::fromLatin1("None");
else
data = QString::fromLatin1("App.getDocument('%1').getObject('%2')").arg(d, o);
else if(isXLink && items.size()>FC_XLINK_VALUE_INDEX+1) {
QString doc;
if(items.size()>=FC_XLINK_VALUE_INDEX+2)
doc = items[FC_XLINK_VALUE_INDEX+1];
else
doc = d;
data = QString::fromLatin1("(App.getDocument('%1').getObject('%2'),'%3')").
arg(doc,o,items[FC_XLINK_VALUE_INDEX]);
}else
data = QString::fromLatin1("App.getDocument('%1').getObject('%2')").arg(d,o);
setPropertyValue(data);
}
}
QWidget* PropertyLinkItem::createEditor(QWidget* parent, const QObject* receiver, const char* method) const
{
LinkLabel *ll = new LinkLabel(parent);
LinkLabel *ll = new LinkLabel(parent, isXLink);
ll->setAutoFillBackground(true);
ll->setDisabled(isReadOnly());
QObject::connect(ll, SIGNAL(linkChanged(const QStringList&)), receiver, method);
@@ -3718,8 +3902,10 @@ void PropertyLinkListItem::setValue(const QVariant& value)
if (!o.isEmpty())
data << QString::fromLatin1("App.getDocument('%1').getObject('%2')").arg(d, o);
}
setPropertyValue(QString::fromLatin1("[%1]").arg(data.join(QString::fromLatin1(", "))));
if(data.size()==0)
setPropertyValue(QLatin1String("[]"));
else
setPropertyValue(QString::fromLatin1("[%1]").arg(data.join(QString::fromLatin1(", "))));
}
QWidget* PropertyLinkListItem::createEditor(QWidget* parent, const QObject* receiver, const char* method) const

View File

@@ -125,6 +125,10 @@ public:
virtual QVariant editorData(QWidget *editor) const;
virtual bool isSeparator() const { return false; }
QWidget* createExpressionEditor(QWidget* parent, const QObject* receiver, const char* method) const;
void setExpressionEditorData(QWidget *editor, const QVariant& data) const;
QVariant expressionEditorData(QWidget *editor) const;
/**override the bind functions to ensure we issue the propertyBound() call, which is then overloaded by
childs which like to be informed of a binding*/
virtual void bind(const App::Property& prop);
@@ -145,13 +149,16 @@ public:
void setDecimals(int);
int decimals() const;
void setLinked(bool);
bool isLinked() const;
PropertyItem *child(int row);
int childCount() const;
int columnCount() const;
QString propertyName() const;
void setPropertyName(const QString&);
void setPropertyValue(const QString&);
QVariant data(int column, int role) const;
virtual QVariant data(int column, int role) const;
bool setData (const QVariant& value);
Qt::ItemFlags flags(int column) const;
int row() const;
@@ -169,16 +176,16 @@ protected:
virtual void initialize();
QString pythonIdentifier(const App::Property*) const;
private:
protected:
QString propName;
QString displayText;
QVariant propData;
std::vector<App::Property*> propertyItems;
PropertyItem *parentItem;
QList<PropertyItem*> childItems;
bool readonly;
int precision;
bool cleared;
bool linked;
};
/**
@@ -948,7 +955,7 @@ class LinkLabel : public QWidget
Q_OBJECT
public:
LinkLabel (QWidget * parent = 0);
LinkLabel (QWidget * parent = 0, bool xlink = false);
virtual ~LinkLabel();
void setPropertyLink(const QStringList& o);
QStringList propertyLink() const;
@@ -967,6 +974,7 @@ private:
QLabel* label;
QPushButton* editButton;
QStringList link;
bool isXLink;
};
/**
@@ -986,9 +994,13 @@ protected:
virtual QVariant toString(const QVariant&) const;
virtual QVariant value(const App::Property*) const;
virtual void setValue(const QVariant&);
virtual QVariant data(int column, int role) const override;
protected:
PropertyLinkItem();
private:
mutable bool isXLink;
};
class LinkListLabel : public QWidget

View File

@@ -29,14 +29,23 @@
# include <QPainter>
#endif
#include <Base/Console.h>
#include <Base/Tools.h>
#include <App/Application.h>
#include <App/Document.h>
#include <App/DocumentObject.h>
#include "PropertyItemDelegate.h"
#include "PropertyItem.h"
#include "PropertyEditor.h"
FC_LOG_LEVEL_INIT("PropertyView",true,true);
using namespace Gui::PropertyEditor;
PropertyItemDelegate::PropertyItemDelegate(QObject* parent)
: QItemDelegate(parent), pressed(false)
: QItemDelegate(parent), expressionEditor(0)
, pressed(false), changed(false)
{
connect(this, SIGNAL(closeEditor(QWidget*, QAbstractItemDelegate::EndEditHint)),
this, SLOT(editorClosed(QWidget*, QAbstractItemDelegate::EndEditHint)));
@@ -116,9 +125,21 @@ bool PropertyItemDelegate::editorEvent (QEvent * event, QAbstractItemModel* mode
void PropertyItemDelegate::editorClosed(QWidget *editor, QAbstractItemDelegate::EndEditHint hint)
{
#if 0
int id = 0;
auto &app = App::GetApplication();
const char *name = app.getActiveTransaction(&id);
if(id && id==activeTransactionID) {
FC_LOG("editor close transaction " << name);
app.closeActiveTransaction(false,id);
activeTransactionID = 0;
}
FC_LOG("editor close " << editor);
#endif
// don't close the editor when pressing Tab or Shift+Tab
// https://forum.freecadweb.org/viewtopic.php?f=3&t=34627#p290957
if (hint != EditNextItem && hint != EditPreviousItem)
if (editor && hint != EditNextItem && hint != EditPreviousItem)
editor->close();
}
@@ -131,7 +152,16 @@ QWidget * PropertyItemDelegate::createEditor (QWidget * parent, const QStyleOpti
PropertyItem *childItem = static_cast<PropertyItem*>(index.internalPointer());
if (!childItem)
return 0;
QWidget* editor = childItem->createEditor(parent, this, SLOT(valueChanged()));
FC_LOG("create editor " << index.row() << "," << index.column());
PropertyEditor *parentEditor = qobject_cast<PropertyEditor*>(this->parent());
QWidget* editor;
expressionEditor = 0;
if(parentEditor && parentEditor->isBinding())
expressionEditor = editor = childItem->createExpressionEditor(parent, this, SLOT(valueChanged()));
else
editor = childItem->createEditor(parent, this, SLOT(valueChanged()));
if (editor) // Make sure the editor background is painted so the cell content doesn't show through
editor->setAutoFillBackground(true);
if (editor && childItem->isReadOnly())
@@ -144,14 +174,17 @@ QWidget * PropertyItemDelegate::createEditor (QWidget * parent, const QStyleOpti
editor->setFocus();
}
this->pressed = false;
return editor;
}
void PropertyItemDelegate::valueChanged()
{
QWidget* editor = qobject_cast<QWidget*>(sender());
if (editor)
if (editor) {
Base::FlagToggler<> flag(changed);
commitData(editor);
}
}
void PropertyItemDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
@@ -161,17 +194,24 @@ void PropertyItemDelegate::setEditorData(QWidget *editor, const QModelIndex &ind
QVariant data = index.data(Qt::EditRole);
PropertyItem *childItem = static_cast<PropertyItem*>(index.internalPointer());
editor->blockSignals(true);
childItem->setEditorData(editor, data);
if(expressionEditor == editor)
childItem->setExpressionEditorData(editor, data);
else
childItem->setEditorData(editor, data);
editor->blockSignals(false);
return;
}
void PropertyItemDelegate::setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const
{
if (!index.isValid())
if (!index.isValid() || !changed)
return;
PropertyItem *childItem = static_cast<PropertyItem*>(index.internalPointer());
QVariant data = childItem->editorData(editor);
QVariant data;
if(expressionEditor == editor)
data = childItem->expressionEditorData(editor);
else
data = childItem->editorData(editor);
model->setData(index, data, Qt::EditRole);
}

View File

@@ -50,7 +50,9 @@ public Q_SLOTS:
void editorClosed (QWidget * editor, QAbstractItemDelegate::EndEditHint hint);
private:
mutable QWidget *expressionEditor;
mutable bool pressed;
bool changed;
};
} // namespace PropertyEditor

View File

@@ -27,8 +27,11 @@
# include <cfloat>
#endif
#include <boost/algorithm/string/predicate.hpp>
#include "PropertyModel.h"
#include "PropertyItem.h"
#include "PropertyView.h"
using namespace Gui::PropertyEditor;
@@ -206,6 +209,24 @@ QModelIndex PropertyModel::propertyIndexFromPath(const QStringList& path) const
return parent;
}
struct PropItemInfo {
const std::string &name;
const std::vector<App::Property*> &props;
PropItemInfo(const std::string &n, const std::vector<App::Property*> &p)
:name(n),props(p)
{}
};
static void setPropertyItemName(PropertyItem *item, const char *propName, QString groupName) {
QString name = QString::fromLatin1(propName);
if(name.size()>groupName.size()+1
&& name.startsWith(groupName + QLatin1Char('_')))
name = name.right(name.size()-groupName.size()-1);
item->setPropertyName(name);
}
void PropertyModel::buildUp(const PropertyModel::PropertyList& props)
{
beginResetModel();
@@ -214,42 +235,47 @@ void PropertyModel::buildUp(const PropertyModel::PropertyList& props)
rootItem->reset();
// sort the properties into their groups
std::map<std::string, std::vector<std::vector<App::Property*> > > propGroup;
std::map<std::string, std::vector<PropItemInfo> > propGroup;
PropertyModel::PropertyList::const_iterator jt;
for (jt = props.begin(); jt != props.end(); ++jt) {
App::Property* prop = jt->second.front();
const char* group = prop->getGroup();
bool isEmpty = (group == 0 || group[0] == '\0');
std::string grp = isEmpty ? QT_TRANSLATE_NOOP("App::Property", "Base") : group;
propGroup[grp].push_back(jt->second);
propGroup[grp].emplace_back(jt->first,jt->second);
}
std::map<std::string, std::vector<std::vector<App::Property*> > >
::const_iterator kt;
for (kt = propGroup.begin(); kt != propGroup.end(); ++kt) {
for (auto kt = propGroup.begin(); kt != propGroup.end(); ++kt) {
// set group item
PropertyItem* group = static_cast<PropertyItem*>(PropertySeparatorItem::create());
group->setParent(rootItem);
rootItem->appendChild(group);
group->setPropertyName(QString::fromLatin1(kt->first.c_str()));
QString groupName = QString::fromLatin1(kt->first.c_str());
group->setPropertyName(groupName);
// setup the items for the properties
std::vector<std::vector<App::Property*> >::const_iterator it;
for (it = kt->second.begin(); it != kt->second.end(); ++it) {
App::Property* prop = it->front();
QString editor = QString::fromLatin1(prop->getEditorName());
if (!editor.isEmpty()) {
PropertyItem* item = PropertyItemFactory::instance().createPropertyItem(prop->getEditorName());
for (auto it = kt->second.begin(); it != kt->second.end(); ++it) {
const auto &info = *it;
App::Property* prop = info.props.front();
std::string editor(prop->getEditorName());
if(editor.empty() && PropertyView::showAll())
editor = "Gui::PropertyEditor::PropertyItem";
if (!editor.empty()) {
PropertyItem* item = PropertyItemFactory::instance().createPropertyItem(editor.c_str());
if (!item) {
qWarning("No property item for type %s found\n", prop->getEditorName());
qWarning("No property item for type %s found\n", editor.c_str());
continue;
}
else {
if(boost::ends_with(info.name,"*"))
item->setLinked(true);
PropertyItem* child = (PropertyItem*)item;
child->setParent(rootItem);
rootItem->appendChild(child);
child->setPropertyName(QString::fromLatin1(prop->getName()));
child->setPropertyData(*it);
setPropertyItemName(child,prop->getName(),groupName);
child->setPropertyData(info.props);
}
}
}
@@ -279,11 +305,13 @@ void PropertyModel::updateProperty(const App::Property& prop)
void PropertyModel::appendProperty(const App::Property& prop)
{
QString editor = QString::fromLatin1(prop.getEditorName());
if (!editor.isEmpty()) {
PropertyItem* item = PropertyItemFactory::instance().createPropertyItem(prop.getEditorName());
std::string editor(prop.getEditorName());
if(editor.empty() && PropertyView::showAll())
editor = "Gui::PropertyEditor::PropertyItem";
if (!editor.empty()) {
PropertyItem* item = PropertyItemFactory::instance().createPropertyItem(editor.c_str());
if (!item) {
qWarning("No property item for type %s found\n", prop.getEditorName());
qWarning("No property item for type %s found\n", editor.c_str());
return;
}
@@ -354,7 +382,8 @@ void PropertyModel::appendProperty(const App::Property& prop)
std::vector<App::Property*> data;
data.push_back(const_cast<App::Property*>(&prop));
item->setPropertyName(QString::fromLatin1(prop.getName()));
setPropertyItemName(item,prop.getName(),groupName);
item->setPropertyData(data);
endInsertRows();