Gui: improve PropertyEditor refresh (#3535)
* Gui: fix PropertyView 'Add property' action * Gui: fix property view font color for linked property * Gui: improve PropertyEditor refresh * Gui: show real property name in property view tool tip * Gui: improve property view tool tip * Gui: fix auto recompute in property view * Gui: remove duplicated PropertyModel signal of dataChanged() * Gui: fix property view update on property change Including changes in document properties * Gui: fix transaction closing on property editor change On editing row removal and on model reset. * Gui: fix property view auto expansion of previous selected item * Gui: improve property editor navigation using tab/shift+tab
This commit is contained in:
@@ -107,7 +107,8 @@ void DlgAddProperty::accept()
|
||||
name = group + "_" + name;
|
||||
|
||||
for(auto c : containers) {
|
||||
if(c->getPropertyByName(name.c_str())) {
|
||||
auto prop = c->getPropertyByName(name.c_str());
|
||||
if(prop && prop->getContainer() == c) {
|
||||
QMessageBox::critical(getMainWindow(),
|
||||
QObject::tr("Invalid name"),
|
||||
QObject::tr("The property '%1' already exists in '%2'").arg(
|
||||
@@ -127,7 +128,7 @@ void DlgAddProperty::accept()
|
||||
e.ReportException();
|
||||
for(auto it2=containers.begin();it2!=it;++it2) {
|
||||
try {
|
||||
(*it)->removeDynamicProperty(name.c_str());
|
||||
(*it2)->removeDynamicProperty(name.c_str());
|
||||
} catch(Base::Exception &e) {
|
||||
e.ReportException();
|
||||
}
|
||||
|
||||
@@ -111,7 +111,7 @@ PropertyView::PropertyView(QWidget *parent)
|
||||
|
||||
this->connectPropData =
|
||||
App::GetApplication().signalChangedObject.connect(boost::bind
|
||||
(&PropertyView::slotChangePropertyData, this, bp::_1, bp::_2));
|
||||
(&PropertyView::slotChangePropertyData, this, bp::_2));
|
||||
this->connectPropView =
|
||||
Gui::Application::Instance->signalChangedObject.connect(boost::bind
|
||||
(&PropertyView::slotChangePropertyView, this, bp::_1, bp::_2));
|
||||
@@ -142,6 +142,8 @@ PropertyView::PropertyView(QWidget *parent)
|
||||
this->connectDelObject =
|
||||
App::GetApplication().signalDeletedObject.connect(
|
||||
boost::bind(&PropertyView::slotDeletedObject, this, bp::_1));
|
||||
this->connectChangedDocument = App::GetApplication().signalChangedDocument.connect(
|
||||
boost::bind(&PropertyView::slotChangePropertyData, this, bp::_2));
|
||||
}
|
||||
|
||||
PropertyView::~PropertyView()
|
||||
@@ -157,6 +159,7 @@ PropertyView::~PropertyView()
|
||||
this->connectDelDocument.disconnect();
|
||||
this->connectDelObject.disconnect();
|
||||
this->connectDelViewObject.disconnect();
|
||||
this->connectChangedDocument.disconnect();
|
||||
}
|
||||
|
||||
static bool _ShowAll;
|
||||
@@ -169,8 +172,11 @@ void PropertyView::setShowAll(bool enable) {
|
||||
if(_ShowAll != enable) {
|
||||
_ShowAll = enable;
|
||||
for(auto view : getMainWindow()->findChildren<PropertyView*>()) {
|
||||
if(view->isVisible())
|
||||
if(view->isVisible()) {
|
||||
view->propertyEditorData->buildUp();
|
||||
view->propertyEditorView->buildUp();
|
||||
view->onTimer();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -187,7 +193,7 @@ void PropertyView::hideEvent(QHideEvent *ev) {
|
||||
|
||||
void PropertyView::showEvent(QShowEvent *ev) {
|
||||
this->attachSelection();
|
||||
this->timer->start(100);
|
||||
this->timer->start(ViewParams::instance()->getPropertyViewTimer());
|
||||
QWidget::showEvent(ev);
|
||||
}
|
||||
|
||||
@@ -209,14 +215,20 @@ void PropertyView::slotRollback() {
|
||||
clearPropertyItemSelection();
|
||||
}
|
||||
|
||||
void PropertyView::slotChangePropertyData(const App::DocumentObject&, const App::Property& prop)
|
||||
void PropertyView::slotChangePropertyData(const App::Property& prop)
|
||||
{
|
||||
propertyEditorData->updateProperty(prop);
|
||||
if (propertyEditorData->propOwners.count(prop.getContainer())) {
|
||||
propertyEditorData->updateProperty(prop);
|
||||
timer->start(ViewParams::instance()->getPropertyViewTimer());
|
||||
}
|
||||
}
|
||||
|
||||
void PropertyView::slotChangePropertyView(const Gui::ViewProvider&, const App::Property& prop)
|
||||
{
|
||||
propertyEditorView->updateProperty(prop);
|
||||
if (propertyEditorView->propOwners.count(prop.getContainer())) {
|
||||
propertyEditorView->updateProperty(prop);
|
||||
timer->start(ViewParams::instance()->getPropertyViewTimer());
|
||||
}
|
||||
}
|
||||
|
||||
bool PropertyView::isPropertyHidden(const App::Property *prop) {
|
||||
@@ -229,10 +241,11 @@ void PropertyView::slotAppendDynamicProperty(const App::Property& prop)
|
||||
if (isPropertyHidden(&prop))
|
||||
return;
|
||||
|
||||
if (propertyEditorData->appendProperty(prop)
|
||||
|| propertyEditorView->appendProperty(prop))
|
||||
App::PropertyContainer* parent = prop.getContainer();
|
||||
if (propertyEditorData->propOwners.count(parent)
|
||||
|| propertyEditorView->propOwners.count(parent))
|
||||
{
|
||||
timer->start(100);
|
||||
timer->start(ViewParams::instance()->getPropertyViewTimer());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -243,34 +256,17 @@ void PropertyView::slotRemoveDynamicProperty(const App::Property& prop)
|
||||
propertyEditorData->removeProperty(prop);
|
||||
else if(propertyEditorView->propOwners.count(parent))
|
||||
propertyEditorView->removeProperty(prop);
|
||||
else
|
||||
return;
|
||||
timer->start(ViewParams::instance()->getPropertyViewTimer());
|
||||
}
|
||||
|
||||
void PropertyView::slotChangePropertyEditor(const App::Document &, const App::Property& prop)
|
||||
{
|
||||
App::PropertyContainer* parent = prop.getContainer();
|
||||
Gui::PropertyEditor::PropertyEditor* editor = nullptr;
|
||||
|
||||
if (parent && propertyEditorData->propOwners.count(parent))
|
||||
editor = propertyEditorData;
|
||||
else if (parent && propertyEditorView->propOwners.count(parent))
|
||||
editor = propertyEditorView;
|
||||
else
|
||||
return;
|
||||
|
||||
if(showAll() || isPropertyHidden(&prop)) {
|
||||
editor->updateEditorMode(prop);
|
||||
return;
|
||||
}
|
||||
for(auto &v : editor->propList) {
|
||||
for(auto p : v.second)
|
||||
if(p == &prop) {
|
||||
editor->updateEditorMode(prop);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// The property is not in the list, probably because it is hidden before.
|
||||
// So perform a full update.
|
||||
timer->start(50);
|
||||
if (propertyEditorData->propOwners.count(parent)
|
||||
|| propertyEditorView->propOwners.count(parent))
|
||||
timer->start(ViewParams::instance()->getPropertyViewTimer());
|
||||
}
|
||||
|
||||
void PropertyView::slotDeleteDocument(const Gui::Document &doc) {
|
||||
@@ -278,7 +274,7 @@ void PropertyView::slotDeleteDocument(const Gui::Document &doc) {
|
||||
propertyEditorView->buildUp();
|
||||
propertyEditorData->buildUp();
|
||||
clearPropertyItemSelection();
|
||||
timer->start(50);
|
||||
timer->start(ViewParams::instance()->getPropertyViewTimer());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -287,7 +283,7 @@ void PropertyView::slotDeletedViewObject(const Gui::ViewProvider &vp) {
|
||||
propertyEditorView->buildUp();
|
||||
propertyEditorData->buildUp();
|
||||
clearPropertyItemSelection();
|
||||
timer->start(50);
|
||||
timer->start(ViewParams::instance()->getPropertyViewTimer());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -296,7 +292,7 @@ void PropertyView::slotDeletedObject(const App::DocumentObject &obj) {
|
||||
propertyEditorView->buildUp();
|
||||
propertyEditorData->buildUp();
|
||||
clearPropertyItemSelection();
|
||||
timer->start(50);
|
||||
timer->start(ViewParams::instance()->getPropertyViewTimer());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -341,23 +337,28 @@ void PropertyView::onSelectionChanged(const SelectionChanges& msg)
|
||||
return;
|
||||
|
||||
// clear the properties.
|
||||
timer->start(50);
|
||||
timer->start(ViewParams::instance()->getPropertyViewTimer());
|
||||
}
|
||||
|
||||
void PropertyView::onTimer() {
|
||||
|
||||
propertyEditorData->buildUp();
|
||||
propertyEditorView->buildUp();
|
||||
clearPropertyItemSelection();
|
||||
timer->stop();
|
||||
|
||||
if(!this->isSelectionAttached())
|
||||
if(!this->isSelectionAttached()) {
|
||||
propertyEditorData->buildUp();
|
||||
propertyEditorView->buildUp();
|
||||
clearPropertyItemSelection();
|
||||
return;
|
||||
}
|
||||
|
||||
if(!Gui::Selection().hasSelection()) {
|
||||
auto gdoc = TreeWidget::selectedDocument();
|
||||
if(!gdoc || !gdoc->getDocument())
|
||||
if(!gdoc || !gdoc->getDocument()) {
|
||||
propertyEditorData->buildUp();
|
||||
propertyEditorView->buildUp();
|
||||
clearPropertyItemSelection();
|
||||
return;
|
||||
}
|
||||
|
||||
PropertyModel::PropertyList docProps;
|
||||
|
||||
|
||||
@@ -81,7 +81,7 @@ protected:
|
||||
|
||||
private:
|
||||
void onSelectionChanged(const SelectionChanges& msg) override;
|
||||
void slotChangePropertyData(const App::DocumentObject&, const App::Property&);
|
||||
void slotChangePropertyData(const App::Property&);
|
||||
void slotChangePropertyView(const Gui::ViewProvider&, const App::Property&);
|
||||
void slotAppendDynamicProperty(const App::Property&);
|
||||
void slotRemoveDynamicProperty(const App::Property&);
|
||||
@@ -109,6 +109,7 @@ private:
|
||||
Connection connectDelDocument;
|
||||
Connection connectDelObject;
|
||||
Connection connectDelViewObject;
|
||||
Connection connectChangedDocument;
|
||||
QTabWidget* tabs;
|
||||
QTimer* timer;
|
||||
};
|
||||
|
||||
@@ -61,6 +61,7 @@ public:
|
||||
FC_VIEW_PARAM(CoinCycleCheck,bool,Bool,true) \
|
||||
FC_VIEW_PARAM(EnablePropertyViewForInactiveDocument,bool,Bool,true) \
|
||||
FC_VIEW_PARAM(ShowSelectionBoundingBox,bool,Bool,false) \
|
||||
FC_VIEW_PARAM(PropertyViewTimer, unsigned long, Unsigned, 100) \
|
||||
|
||||
#undef FC_VIEW_PARAM
|
||||
#define FC_VIEW_PARAM(_name,_ctype,_type,_def) \
|
||||
|
||||
@@ -61,6 +61,7 @@ PropertyEditor::PropertyEditor(QWidget *parent)
|
||||
, delaybuild(false)
|
||||
, binding(false)
|
||||
, checkDocument(false)
|
||||
, closingEditor(false)
|
||||
{
|
||||
propertyModel = new PropertyModel(this);
|
||||
setModel(propertyModel);
|
||||
@@ -73,7 +74,8 @@ PropertyEditor::PropertyEditor(QWidget *parent)
|
||||
setItemDelegate(delegate);
|
||||
|
||||
setAlternatingRowColors(true);
|
||||
setRootIsDecorated(true);
|
||||
setRootIsDecorated(false);
|
||||
setExpandsOnDoubleClick(true);
|
||||
|
||||
QStyleOptionViewItem opt = viewOptions();
|
||||
this->background = opt.palette.dark();
|
||||
@@ -83,6 +85,12 @@ PropertyEditor::PropertyEditor(QWidget *parent)
|
||||
|
||||
connect(this, SIGNAL(activated(const QModelIndex &)), this, SLOT(onItemActivated(const QModelIndex &)));
|
||||
connect(this, SIGNAL(clicked(const QModelIndex &)), this, SLOT(onItemActivated(const QModelIndex &)));
|
||||
connect(this, SIGNAL(expanded(const QModelIndex &)), this, SLOT(onItemExpanded(const QModelIndex &)));
|
||||
connect(this, SIGNAL(collapsed(const QModelIndex &)), this, SLOT(onItemCollapsed(const QModelIndex &)));
|
||||
connect(propertyModel, SIGNAL(rowsMoved(const QModelIndex &, int, int, const QModelIndex &, int)),
|
||||
this, SLOT(onRowsMoved(const QModelIndex &, int, int, const QModelIndex &, int)));
|
||||
connect(propertyModel, SIGNAL(rowsRemoved(const QModelIndex &, int, int)),
|
||||
this, SLOT(onRowsRemoved(const QModelIndex &, int, int)));
|
||||
}
|
||||
|
||||
PropertyEditor::~PropertyEditor()
|
||||
@@ -102,6 +110,20 @@ bool PropertyEditor::isAutomaticExpand(bool) const
|
||||
return autoexpand;
|
||||
}
|
||||
|
||||
void PropertyEditor::onItemExpanded(const QModelIndex &index)
|
||||
{
|
||||
PropertyItem* item = static_cast<PropertyItem*>(index.internalPointer());
|
||||
item->setExpanded(true);
|
||||
for(int i=0, c=item->childCount(); i<c; ++i)
|
||||
setExpanded(propertyModel->index(i, 0, index), item->child(i)->isExpanded());
|
||||
}
|
||||
|
||||
void PropertyEditor::onItemCollapsed(const QModelIndex &index)
|
||||
{
|
||||
PropertyItem* item = static_cast<PropertyItem*>(index.internalPointer());
|
||||
item->setExpanded(false);
|
||||
}
|
||||
|
||||
void PropertyEditor::setAutomaticDocumentUpdate(bool v)
|
||||
{
|
||||
autoupdate = v;
|
||||
@@ -202,19 +224,37 @@ 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;
|
||||
void PropertyEditor::closeEditor()
|
||||
{
|
||||
if (editingIndex.isValid()) {
|
||||
Base::StateLocker guard(closingEditor);
|
||||
bool hasFocus = activeEditor && activeEditor->hasFocus();
|
||||
closePersistentEditor(editingIndex);
|
||||
editingIndex = QPersistentModelIndex();
|
||||
activeEditor = nullptr;
|
||||
if(hasFocus)
|
||||
setFocus();
|
||||
}
|
||||
}
|
||||
|
||||
void PropertyEditor::openEditor(const QModelIndex &index)
|
||||
{
|
||||
if(editingIndex == index && activeEditor)
|
||||
return;
|
||||
|
||||
closeEditor();
|
||||
|
||||
openPersistentEditor(model()->buddy(index));
|
||||
|
||||
if(!editingIndex.isValid() || !autoupdate)
|
||||
return;
|
||||
|
||||
auto &app = App::GetApplication();
|
||||
if(app.getActiveTransaction()) {
|
||||
FC_LOG("editor already transacting " << app.getActiveTransaction());
|
||||
return;
|
||||
}
|
||||
PropertyItem* item = static_cast<PropertyItem*>(index.internalPointer());
|
||||
PropertyItem* item = static_cast<PropertyItem*>(editingIndex.internalPointer());
|
||||
auto items = item->getPropertyData();
|
||||
for(auto propItem=item->parent();items.empty() && propItem;propItem=propItem->parent())
|
||||
items = propItem->getPropertyData();
|
||||
@@ -261,14 +301,13 @@ void PropertyEditor::onItemActivated ( const QModelIndex & index )
|
||||
{
|
||||
if(index.column() != 1)
|
||||
return;
|
||||
edit(model()->buddy(index),AllEditTriggers,0);
|
||||
setupTransaction(index);
|
||||
openEditor(index);
|
||||
}
|
||||
|
||||
void PropertyEditor::recomputeDocument(App::Document* doc)
|
||||
{
|
||||
try {
|
||||
if (doc) {
|
||||
if (App::Document* doc = App::GetApplication().getActiveDocument()) {
|
||||
if (!doc->isTransactionEmpty()) {
|
||||
// Between opening and committing a transaction a recompute
|
||||
// could already have been done
|
||||
@@ -283,7 +322,7 @@ void PropertyEditor::recomputeDocument(App::Document* doc)
|
||||
}
|
||||
catch (const std::exception& e) {
|
||||
Base::Console().Error("Unhandled std::exception caught in PropertyEditor::recomputeDocument.\n"
|
||||
"The error message is: %s\n", e.what());
|
||||
"The error message is: %s\n", e.what());
|
||||
}
|
||||
catch (...) {
|
||||
Base::Console().Error("Unhandled unknown exception caught in PropertyEditor::recomputeDocument.\n");
|
||||
@@ -304,15 +343,37 @@ void PropertyEditor::closeTransaction()
|
||||
|
||||
void PropertyEditor::closeEditor (QWidget * editor, QAbstractItemDelegate::EndEditHint hint)
|
||||
{
|
||||
QTreeView::closeEditor(editor, hint);
|
||||
if (closingEditor)
|
||||
return;
|
||||
|
||||
if (removingRows) {
|
||||
// When removing rows, QTreeView will temporary hide the editor which
|
||||
// will trigger Event::FocusOut and subsequently trigger call of
|
||||
// closeEditor() here. Since we are using persistent editor, QTreeView
|
||||
// will not destroy the editor. But we still needs to call
|
||||
// QTreeView::closeEditor() here, in case the editor belongs to the
|
||||
// removed rows.
|
||||
QTreeView::closeEditor(editor, hint);
|
||||
return;
|
||||
}
|
||||
|
||||
closeTransaction();
|
||||
|
||||
QModelIndex indexSaved = currentIndex();
|
||||
FC_LOG("index saved " << indexSaved.row() << ", " << indexSaved.column());
|
||||
// If we are not removing rows, then QTreeView::closeEditor() does nothing
|
||||
// because we are using persistent editor, so we have to call our own
|
||||
// version of closeEditor()
|
||||
this->closeEditor();
|
||||
|
||||
QModelIndex lastIndex;
|
||||
while(this->state()!=EditingState) {
|
||||
QModelIndex indexSaved = currentIndex();
|
||||
|
||||
if (indexSaved.column() == 0) {
|
||||
// Calling setCurrentIndex() to make sure we focus on column 1 instead of 0.
|
||||
setCurrentIndex(propertyModel->buddy(indexSaved));
|
||||
}
|
||||
|
||||
QModelIndex lastIndex = indexSaved;
|
||||
bool wrapped = false;
|
||||
do {
|
||||
QModelIndex index;
|
||||
if (hint == QAbstractItemDelegate::EditNextItem) {
|
||||
index = moveCursor(MoveDown,Qt::NoModifier);
|
||||
@@ -320,31 +381,122 @@ void PropertyEditor::closeEditor (QWidget * editor, QAbstractItemDelegate::EndEd
|
||||
index = moveCursor(MoveUp,Qt::NoModifier);
|
||||
} else
|
||||
break;
|
||||
if(!index.isValid() || index==lastIndex) {
|
||||
setCurrentIndex(indexSaved);
|
||||
break;
|
||||
if (!index.isValid() || index == lastIndex) {
|
||||
if (wrapped) {
|
||||
setCurrentIndex(propertyModel->buddy(indexSaved));
|
||||
break;
|
||||
}
|
||||
wrapped = true;
|
||||
if (hint == QAbstractItemDelegate::EditNextItem)
|
||||
index = moveCursor(MoveHome, Qt::NoModifier);
|
||||
else
|
||||
index = moveCursor(MoveEnd, Qt::NoModifier);
|
||||
if (!index.isValid() || index == indexSaved)
|
||||
break;
|
||||
}
|
||||
lastIndex = index;
|
||||
setCurrentIndex(index);
|
||||
edit(index,AllEditTriggers,0);
|
||||
}
|
||||
setupTransaction(currentIndex());
|
||||
setCurrentIndex(propertyModel->buddy(index));
|
||||
|
||||
PropertyItem *item = static_cast<PropertyItem*>(index.internalPointer());
|
||||
// Skip readonly item, because the editor will be disabled and hence
|
||||
// does not accept focus, and in turn break Tab/Backtab navigation.
|
||||
if (item && item->isReadOnly())
|
||||
continue;
|
||||
|
||||
openEditor(index);
|
||||
|
||||
} while (!editingIndex.isValid());
|
||||
}
|
||||
|
||||
void PropertyEditor::reset()
|
||||
{
|
||||
QTreeView::reset();
|
||||
|
||||
QModelIndex index;
|
||||
int numRows = propertyModel->rowCount(index);
|
||||
if (numRows > 0)
|
||||
setEditorMode(index, 0, numRows-1);
|
||||
closeTransaction();
|
||||
|
||||
QModelIndex parent;
|
||||
int numRows = propertyModel->rowCount(parent);
|
||||
for (int i=0; i<numRows; ++i) {
|
||||
QModelIndex index = propertyModel->index(i, 0, parent);
|
||||
PropertyItem *item = static_cast<PropertyItem*>(index.internalPointer());
|
||||
if (item->childCount() == 0) {
|
||||
if(item->isSeparator())
|
||||
setRowHidden(i, parent, true);
|
||||
} else
|
||||
setEditorMode(index, 0, item->childCount()-1);
|
||||
if(item->isExpanded())
|
||||
setExpanded(index, true);
|
||||
}
|
||||
}
|
||||
|
||||
void PropertyEditor::onRowsMoved(const QModelIndex &parent, int start, int end, const QModelIndex &dst, int)
|
||||
{
|
||||
if(parent != dst) {
|
||||
PropertyItem *item = static_cast<PropertyItem*>(parent.internalPointer());
|
||||
if(item && item->isSeparator() && item->childCount()==0)
|
||||
setRowHidden(parent.row(), propertyModel->parent(parent), true);
|
||||
item = static_cast<PropertyItem*>(dst.internalPointer());
|
||||
if(item && item->isSeparator() && item->childCount()==end-start+1) {
|
||||
setRowHidden(dst.row(), propertyModel->parent(dst), false);
|
||||
setExpanded(dst, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PropertyEditor::rowsInserted (const QModelIndex & parent, int start, int end)
|
||||
{
|
||||
QTreeView::rowsInserted(parent, start, end);
|
||||
setEditorMode(parent, start, end);
|
||||
|
||||
PropertyItem *item = static_cast<PropertyItem*>(parent.internalPointer());
|
||||
if (item && item->isSeparator() && item->childCount() == end-start+1) {
|
||||
setRowHidden(parent.row(), propertyModel->parent(parent), false);
|
||||
if(item->isExpanded())
|
||||
setExpanded(parent, true);
|
||||
}
|
||||
|
||||
for (int i=start; i<end; ++i) {
|
||||
QModelIndex index = propertyModel->index(i, 0, parent);
|
||||
PropertyItem *child = static_cast<PropertyItem*>(index.internalPointer());
|
||||
if(child->isSeparator()) {
|
||||
// Set group header rows to span all columns
|
||||
setFirstColumnSpanned(i, parent, true);
|
||||
}
|
||||
if(child->isExpanded())
|
||||
setExpanded(index, true);
|
||||
}
|
||||
|
||||
if(parent.isValid())
|
||||
setEditorMode(parent, start, end);
|
||||
}
|
||||
|
||||
void PropertyEditor::rowsAboutToBeRemoved (const QModelIndex & parent, int start, int end)
|
||||
{
|
||||
QTreeView::rowsAboutToBeRemoved(parent, start, end);
|
||||
|
||||
PropertyItem *item = static_cast<PropertyItem*>(parent.internalPointer());
|
||||
if (item && item->isSeparator() && item->childCount() == end-start+1)
|
||||
setRowHidden(parent.row(), propertyModel->parent(parent), true);
|
||||
|
||||
if (editingIndex.isValid()) {
|
||||
if (editingIndex.row() >= start && editingIndex.row() <= end)
|
||||
closeTransaction();
|
||||
else {
|
||||
removingRows = 1;
|
||||
for (QWidget *w = qApp->focusWidget(); w; w = w->parentWidget()) {
|
||||
if(w == activeEditor) {
|
||||
removingRows = -1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PropertyEditor::onRowsRemoved(const QModelIndex &, int, int)
|
||||
{
|
||||
if (removingRows < 0 && activeEditor)
|
||||
activeEditor->setFocus();
|
||||
removingRows = 0;
|
||||
}
|
||||
|
||||
void PropertyEditor::drawBranches(QPainter *painter, const QRect &rect, const QModelIndex &index) const
|
||||
@@ -376,7 +528,10 @@ void PropertyEditor::buildUp(PropertyModel::PropertyList &&props, bool _checkDoc
|
||||
return;
|
||||
}
|
||||
|
||||
closeTransaction();
|
||||
// Do not close transaction here, because we are now doing incremental
|
||||
// update in PropertyModel::buildUp()
|
||||
//
|
||||
// closeTransaction();
|
||||
|
||||
QModelIndex index = this->currentIndex();
|
||||
QStringList propertyPath = propertyModel->propertyPathFromIndex(index);
|
||||
@@ -422,60 +577,9 @@ void PropertyEditor::setEditorMode(const QModelIndex & parent, int start, int en
|
||||
if (!PropertyView::showAll() && propItem && propItem->testStatus(App::Property::Hidden)) {
|
||||
setRowHidden (i, parent, true);
|
||||
}
|
||||
if (propItem && propItem->isSeparator()) {
|
||||
// Set group header rows to span all columns
|
||||
setFirstColumnSpanned(i, parent, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PropertyEditor::updateEditorMode(const App::Property& prop)
|
||||
{
|
||||
// check if the parent object is selected
|
||||
std::string editor = prop.getEditorName();
|
||||
if (!PropertyView::showAll() && editor.empty())
|
||||
return;
|
||||
|
||||
bool hidden = PropertyView::isPropertyHidden(&prop);
|
||||
bool readOnly = prop.testStatus(App::Property::ReadOnly);
|
||||
|
||||
int column = 1;
|
||||
int numRows = propertyModel->rowCount();
|
||||
for (int i=0; i<numRows; i++) {
|
||||
QModelIndex item = propertyModel->index(i, column);
|
||||
PropertyItem* propItem = static_cast<PropertyItem*>(item.internalPointer());
|
||||
if (propItem && propItem->hasProperty(&prop)) {
|
||||
setRowHidden (i, QModelIndex(), hidden);
|
||||
|
||||
propItem->updateData();
|
||||
if (item.isValid()) {
|
||||
updateItemEditor(!readOnly, column, item);
|
||||
dataChanged(item, item);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PropertyEditor::updateItemEditor(bool enable, int column, const QModelIndex& parent)
|
||||
{
|
||||
QWidget* editor = indexWidget(parent);
|
||||
if (editor)
|
||||
editor->setEnabled(enable);
|
||||
|
||||
int numRows = propertyModel->rowCount(parent);
|
||||
for (int i=0; i<numRows; i++) {
|
||||
QModelIndex item = propertyModel->index(i, column, parent);
|
||||
if (item.isValid()) {
|
||||
updateItemEditor(enable, column, item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool PropertyEditor::appendProperty(const App::Property& prop) {
|
||||
return !!propOwners.count(prop.getContainer());
|
||||
}
|
||||
|
||||
void PropertyEditor::removeProperty(const App::Property& prop)
|
||||
{
|
||||
for (PropertyModel::PropertyList::iterator it = propList.begin(); it != propList.end(); ++it) {
|
||||
@@ -647,17 +751,20 @@ void PropertyEditor::contextMenuEvent(QContextMenuEvent *) {
|
||||
break;
|
||||
case MA_Expression:
|
||||
if(contextIndex == currentIndex()) {
|
||||
closePersistentEditor(contextIndex);
|
||||
Base::FlagToggler<> flag(binding);
|
||||
edit(contextIndex,AllEditTriggers,0);
|
||||
setupTransaction(contextIndex);
|
||||
openEditor(contextIndex);
|
||||
}
|
||||
break;
|
||||
case MA_AddProp: {
|
||||
App::AutoTransaction committer("Add property");
|
||||
std::unordered_set<App::PropertyContainer*> containers;
|
||||
for(auto prop : props)
|
||||
containers.insert(prop->getContainer());
|
||||
auto sels = Gui::Selection().getSelection("*");
|
||||
if(sels.size() == 1)
|
||||
containers.insert(sels[0].pObject);
|
||||
else {
|
||||
for(auto prop : props)
|
||||
containers.insert(prop->getContainer());
|
||||
}
|
||||
Gui::Dialog::DlgAddProperty dlg(
|
||||
Gui::getMainWindow(),std::move(containers));
|
||||
dlg.exec();
|
||||
|
||||
@@ -75,8 +75,6 @@ public:
|
||||
/** Builds up the list view with the properties. */
|
||||
void buildUp(PropertyModel::PropertyList &&props = PropertyModel::PropertyList(), bool checkDocument=false);
|
||||
void updateProperty(const App::Property&);
|
||||
void updateEditorMode(const App::Property&);
|
||||
bool appendProperty(const App::Property&);
|
||||
void removeProperty(const App::Property&);
|
||||
void setAutomaticExpand(bool);
|
||||
bool isAutomaticExpand(bool) const;
|
||||
@@ -91,9 +89,15 @@ public:
|
||||
void setGroupTextColor(const QColor& c);
|
||||
|
||||
bool isBinding() const { return binding; }
|
||||
void openEditor(const QModelIndex &index);
|
||||
void closeEditor();
|
||||
|
||||
protected Q_SLOTS:
|
||||
void onItemActivated(const QModelIndex &index);
|
||||
void onItemExpanded(const QModelIndex &index);
|
||||
void onItemCollapsed(const QModelIndex &index);
|
||||
void onRowsMoved(const QModelIndex &parent, int start, int end, const QModelIndex &dst, int row);
|
||||
void onRowsRemoved(const QModelIndex &parent, int start, int end);
|
||||
|
||||
protected:
|
||||
virtual void closeEditor (QWidget * editor, QAbstractItemDelegate::EndEditHint hint);
|
||||
@@ -101,6 +105,7 @@ protected:
|
||||
virtual void editorDestroyed (QObject * editor);
|
||||
virtual void currentChanged (const QModelIndex & current, const QModelIndex & previous);
|
||||
virtual void rowsInserted (const QModelIndex & parent, int start, int end);
|
||||
virtual void rowsAboutToBeRemoved (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);
|
||||
@@ -108,8 +113,6 @@ protected:
|
||||
|
||||
private:
|
||||
void setEditorMode(const QModelIndex & parent, int start, int end);
|
||||
void updateItemEditor(bool enable, int column, const QModelIndex& parent);
|
||||
void setupTransaction(const QModelIndex &);
|
||||
void closeTransaction();
|
||||
void recomputeDocument(App::Document*);
|
||||
|
||||
@@ -125,13 +128,19 @@ private:
|
||||
bool delaybuild;
|
||||
bool binding;
|
||||
bool checkDocument;
|
||||
bool closingEditor;
|
||||
|
||||
int transactionID = 0;
|
||||
|
||||
QColor groupColor;
|
||||
QBrush background;
|
||||
|
||||
QPointer<QWidget> activeEditor;
|
||||
QPersistentModelIndex editingIndex;
|
||||
int removingRows = 0;
|
||||
|
||||
friend class Gui::PropertyView;
|
||||
friend class PropertyItemDelegate;
|
||||
};
|
||||
|
||||
} //namespace PropertyEditor
|
||||
|
||||
@@ -95,7 +95,7 @@ Q_DECLARE_METATYPE(Py::Object)
|
||||
|
||||
PROPERTYITEM_SOURCE(Gui::PropertyEditor::PropertyItem)
|
||||
|
||||
PropertyItem::PropertyItem() : parentItem(0), readonly(false), cleared(false), linked(false)
|
||||
PropertyItem::PropertyItem() : parentItem(0), readonly(false), linked(false), expanded(false)
|
||||
{
|
||||
precision = Base::UnitsApi::getDecimals();
|
||||
setAutoApply(true);
|
||||
@@ -255,6 +255,11 @@ void PropertyItem::removeChildren(int from, int to)
|
||||
}
|
||||
}
|
||||
|
||||
void PropertyItem::moveChild(int from, int to)
|
||||
{
|
||||
childItems.move(from, to);
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief PropertyItem::takeChild
|
||||
* Removes the child at index row but doesn't delete it
|
||||
@@ -305,6 +310,16 @@ bool PropertyItem::isLinked() const
|
||||
return linked;
|
||||
}
|
||||
|
||||
void PropertyItem::setExpanded(bool enable)
|
||||
{
|
||||
expanded = enable;
|
||||
}
|
||||
|
||||
bool PropertyItem::isExpanded() const
|
||||
{
|
||||
return expanded;
|
||||
}
|
||||
|
||||
bool PropertyItem::testStatus(App::Property::Status pos) const
|
||||
{
|
||||
std::vector<App::Property*>::const_iterator it;
|
||||
@@ -468,9 +483,15 @@ QString PropertyItem::propertyName() const
|
||||
return propName;
|
||||
}
|
||||
|
||||
void PropertyItem::setPropertyName(const QString& name)
|
||||
void PropertyItem::setPropertyName(QString name, QString realName)
|
||||
{
|
||||
setObjectName(name);
|
||||
if(realName.size())
|
||||
propName = realName;
|
||||
else
|
||||
propName = name;
|
||||
|
||||
setObjectName(propName);
|
||||
|
||||
QString display;
|
||||
bool upper = false;
|
||||
for (int i=0; i<name.length(); i++) {
|
||||
@@ -552,10 +573,10 @@ QVariant PropertyItem::data(int column, int role) const
|
||||
{
|
||||
// property name
|
||||
if (column == 0) {
|
||||
if (role == Qt::ForegroundRole && linked)
|
||||
if (role == Qt::TextColorRole && linked)
|
||||
return QVariant::fromValue(QColor(0x20,0xaa,0x20));
|
||||
|
||||
if (role == Qt::BackgroundRole || role == Qt::ForegroundRole) {
|
||||
if (role == Qt::BackgroundRole || role == Qt::TextColorRole) {
|
||||
if(PropertyView::showAll()
|
||||
&& propertyItems.size() == 1
|
||||
&& propertyItems.front()->testStatus(App::Property::PropDynamic)
|
||||
@@ -575,11 +596,12 @@ QVariant PropertyItem::data(int column, int role) const
|
||||
return QVariant();
|
||||
}
|
||||
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();
|
||||
QString type = QString::fromLatin1("Type: %1\nName: %2").arg(
|
||||
QString::fromLatin1(propertyItems[0]->getTypeId().getName()), objectName());
|
||||
|
||||
QString doc = PropertyItem::toolTip(propertyItems[0]).toString();
|
||||
if(doc.isEmpty())
|
||||
doc = toolTip(propertyItems[0]).toString();
|
||||
if(doc.size())
|
||||
return type + QLatin1String("\n\n") + doc;
|
||||
return type;
|
||||
@@ -637,8 +659,6 @@ QVariant PropertyItem::data(int column, int role) const
|
||||
|
||||
bool PropertyItem::setData (const QVariant& value)
|
||||
{
|
||||
cleared = false;
|
||||
|
||||
// This is the basic mechanism to set the value to
|
||||
// a property and if no property is set for this item
|
||||
// it delegates it to its parent which sets then the
|
||||
@@ -4136,6 +4156,9 @@ LinkLabel::LinkLabel (QWidget * parent, const App::Property *prop)
|
||||
#endif
|
||||
editButton->setToolTip(tr("Change the linked object"));
|
||||
layout->addWidget(editButton);
|
||||
|
||||
this->setFocusPolicy(Qt::StrongFocus);
|
||||
this->setFocusProxy(label);
|
||||
|
||||
// setLayout(layout);
|
||||
|
||||
@@ -4201,7 +4224,7 @@ void LinkLabel::onEditClicked ()
|
||||
if(!dlg) {
|
||||
dlg = new DlgPropertyLink(this);
|
||||
dlg->init(objProp,true);
|
||||
connect(dlg, SIGNAL(accepted()), this, SLOT(onLinkChanged()));
|
||||
connect(dlg, SIGNAL(finished(int)), this, SLOT(onLinkChanged()));
|
||||
} else
|
||||
dlg->init(objProp,false);
|
||||
dlg->show();
|
||||
|
||||
@@ -76,6 +76,7 @@ class DlgPropertyLink;
|
||||
namespace PropertyEditor {
|
||||
|
||||
class PropertyItem;
|
||||
class PropertyModel;
|
||||
|
||||
/**
|
||||
* The PropertyItemFactory provides methods for the dynamic creation of property items.
|
||||
@@ -149,6 +150,7 @@ public:
|
||||
PropertyItem *parent() const;
|
||||
void appendChild(PropertyItem *child);
|
||||
void insertChild(int, PropertyItem *child);
|
||||
void moveChild(int from, int to);
|
||||
void removeChildren(int from, int to);
|
||||
PropertyItem *takeChild(int);
|
||||
|
||||
@@ -161,16 +163,19 @@ public:
|
||||
void setLinked(bool);
|
||||
bool isLinked() const;
|
||||
|
||||
bool isExpanded() const;
|
||||
void setExpanded(bool e);
|
||||
|
||||
PropertyItem *child(int row);
|
||||
int childCount() const;
|
||||
int columnCount() const;
|
||||
QString propertyName() const;
|
||||
void setPropertyName(const QString&);
|
||||
void setPropertyName(QString name, QString realName=QString());
|
||||
void setPropertyValue(const QString&);
|
||||
virtual QVariant data(int column, int role) const;
|
||||
bool setData (const QVariant& value);
|
||||
Qt::ItemFlags flags(int column) const;
|
||||
int row() const;
|
||||
virtual int row() const;
|
||||
void reset();
|
||||
|
||||
bool hasAnyExpression() const;
|
||||
@@ -197,8 +202,8 @@ protected:
|
||||
QList<PropertyItem*> childItems;
|
||||
bool readonly;
|
||||
int precision;
|
||||
bool cleared;
|
||||
bool linked;
|
||||
bool expanded;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -254,6 +259,14 @@ class GuiExport PropertySeparatorItem : public PropertyItem
|
||||
|
||||
bool isSeparator() const { return true; }
|
||||
QWidget* createEditor(QWidget* parent, const QObject* receiver, const char* method) const;
|
||||
|
||||
virtual int row() const {
|
||||
return _row<0?PropertyItem::row():_row;
|
||||
}
|
||||
|
||||
private:
|
||||
friend PropertyModel;
|
||||
int _row = -1;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -76,10 +76,13 @@ void PropertyItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &
|
||||
}
|
||||
option.palette.setColor(QPalette::Text, color);
|
||||
option.font.setBold(true);
|
||||
option.state &= ~QStyle::State_Selected;
|
||||
}
|
||||
|
||||
if (index.column() == 1) {
|
||||
// Since the group item now parents all the property items and can be
|
||||
// collapsed, it makes sense to have some selection visual clue for it.
|
||||
//
|
||||
// option.state &= ~QStyle::State_Selected;
|
||||
}
|
||||
else if (index.column() == 1) {
|
||||
option.state &= ~QStyle::State_Selected;
|
||||
}
|
||||
|
||||
@@ -131,9 +134,15 @@ QWidget * PropertyItemDelegate::createEditor (QWidget * parent, const QStyleOpti
|
||||
if (!childItem)
|
||||
return 0;
|
||||
|
||||
PropertyEditor *parentEditor = qobject_cast<PropertyEditor*>(this->parent());
|
||||
if(parentEditor)
|
||||
parentEditor->closeEditor();
|
||||
|
||||
if (childItem->isSeparator())
|
||||
return 0;
|
||||
|
||||
FC_LOG("create editor " << index.row() << "," << index.column());
|
||||
|
||||
PropertyEditor *parentEditor = qobject_cast<PropertyEditor*>(this->parent());
|
||||
QWidget* editor;
|
||||
expressionEditor = 0;
|
||||
if(parentEditor && parentEditor->isBinding())
|
||||
@@ -153,6 +162,18 @@ QWidget * PropertyItemDelegate::createEditor (QWidget * parent, const QStyleOpti
|
||||
}
|
||||
this->pressed = false;
|
||||
|
||||
if (editor) {
|
||||
for (auto w : editor->findChildren<QWidget*>()) {
|
||||
if (qobject_cast<QAbstractButton*>(w)
|
||||
|| qobject_cast<QLabel*>(w))
|
||||
{
|
||||
w->installEventFilter(const_cast<PropertyItemDelegate*>(this));
|
||||
}
|
||||
}
|
||||
parentEditor->activeEditor = editor;
|
||||
parentEditor->editingIndex = index;
|
||||
}
|
||||
|
||||
return editor;
|
||||
}
|
||||
|
||||
|
||||
@@ -44,7 +44,6 @@ public:
|
||||
virtual QSize sizeHint(const QStyleOptionViewItem & option, const QModelIndex & index) const;
|
||||
virtual bool editorEvent (QEvent *event, QAbstractItemModel *model,
|
||||
const QStyleOptionViewItem& option, const QModelIndex& index);
|
||||
|
||||
public Q_SLOTS:
|
||||
void valueChanged();
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
#include "PropertyItem.h"
|
||||
#include "PropertyView.h"
|
||||
|
||||
using namespace Gui;
|
||||
using namespace Gui::PropertyEditor;
|
||||
|
||||
|
||||
@@ -178,12 +179,9 @@ QStringList PropertyModel::propertyPathFromIndex(const QModelIndex& index) const
|
||||
QStringList path;
|
||||
if (index.isValid()) {
|
||||
PropertyItem* item = static_cast<PropertyItem*>(index.internalPointer());
|
||||
if (!item->isSeparator()) {
|
||||
do {
|
||||
path.push_front(item->propertyName());
|
||||
item = item->parent();
|
||||
}
|
||||
while (item != this->rootItem && item != 0);
|
||||
while (item && item != this->rootItem) {
|
||||
path.push_front(item->propertyName());
|
||||
item = item->parent();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -192,215 +190,299 @@ QStringList PropertyModel::propertyPathFromIndex(const QModelIndex& index) const
|
||||
|
||||
QModelIndex PropertyModel::propertyIndexFromPath(const QStringList& path) const
|
||||
{
|
||||
QModelIndex parent;
|
||||
for (QStringList::const_iterator it = path.begin(); it != path.end(); ++it) {
|
||||
int rows = this->rowCount(parent);
|
||||
for (int i=0; i<rows; i++) {
|
||||
QModelIndex index = this->index(i, 0, parent);
|
||||
if (index.isValid()) {
|
||||
PropertyItem* item = static_cast<PropertyItem*>(index.internalPointer());
|
||||
if (item->propertyName() == *it) {
|
||||
parent = index;
|
||||
break;
|
||||
}
|
||||
if (path.size() < 2)
|
||||
return QModelIndex();
|
||||
|
||||
auto it = groupItems.find(path.front());
|
||||
if (it == groupItems.end())
|
||||
return QModelIndex();
|
||||
|
||||
PropertyItem *item = it->second.groupItem;
|
||||
QModelIndex index = this->index(item->row(), 0, QModelIndex());
|
||||
|
||||
for (int j=1; j<path.size(); ++j) {
|
||||
bool found = false;
|
||||
for (int i=0, c = item->childCount(); i<c; ++i) {
|
||||
auto child = item->child(i);
|
||||
if (child->propertyName() == path[j]) {
|
||||
index = this->index(i, 1, index);
|
||||
item = child;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
return j==1?QModelIndex():index;
|
||||
}
|
||||
return parent;
|
||||
return index;
|
||||
}
|
||||
|
||||
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);
|
||||
QString realName = name;
|
||||
if(name.size()>groupName.size()+1
|
||||
&& name.startsWith(groupName + QLatin1Char('_')))
|
||||
name = name.right(name.size()-groupName.size()-1);
|
||||
|
||||
item->setPropertyName(name);
|
||||
item->setPropertyName(name, realName);
|
||||
}
|
||||
|
||||
static PropertyItem *createPropertyItem(App::Property *prop)
|
||||
{
|
||||
const char *editor = prop->getEditorName();
|
||||
if (!editor || !editor[0]) {
|
||||
if (PropertyView::showAll())
|
||||
editor = "Gui::PropertyEditor::PropertyItem";
|
||||
else
|
||||
return nullptr;
|
||||
}
|
||||
auto item = static_cast<PropertyItem*>(
|
||||
PropertyItemFactory::instance().createPropertyItem(editor));
|
||||
if (!item) {
|
||||
qWarning("No property item for type %s found\n", editor);
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
PropertyModel::GroupInfo &PropertyModel::getGroupInfo(App::Property *prop)
|
||||
{
|
||||
const char* group = prop->getGroup();
|
||||
bool isEmpty = (group == 0 || group[0] == '\0');
|
||||
QString groupName = QString::fromLatin1(
|
||||
isEmpty ? QT_TRANSLATE_NOOP("App::Property", "Base") : group);
|
||||
|
||||
auto res = groupItems.insert(std::make_pair(groupName, GroupInfo()));
|
||||
if (res.second) {
|
||||
auto &groupInfo = res.first->second;
|
||||
groupInfo.groupItem = static_cast<PropertySeparatorItem*>(PropertySeparatorItem::create());
|
||||
groupInfo.groupItem->setReadOnly(true);
|
||||
groupInfo.groupItem->setExpanded(true);
|
||||
groupInfo.groupItem->setParent(rootItem);
|
||||
groupInfo.groupItem->setPropertyName(groupName);
|
||||
|
||||
auto it = res.first;
|
||||
int row = 0;
|
||||
if (it != groupItems.begin()) {
|
||||
--it;
|
||||
row = it->second.groupItem->_row + 1;
|
||||
++it;
|
||||
}
|
||||
groupInfo.groupItem->_row = row;
|
||||
beginInsertRows(QModelIndex(), row, row);
|
||||
rootItem->insertChild(row, groupInfo.groupItem);
|
||||
// update row index for all group items behind
|
||||
for (++it; it!=groupItems.end(); ++it)
|
||||
++it->second.groupItem->_row;
|
||||
endInsertRows();
|
||||
}
|
||||
|
||||
return res.first->second;
|
||||
}
|
||||
|
||||
void PropertyModel::buildUp(const PropertyModel::PropertyList& props)
|
||||
{
|
||||
beginResetModel();
|
||||
|
||||
// fill up the listview with the properties
|
||||
rootItem->reset();
|
||||
|
||||
// sort the properties into their groups
|
||||
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].emplace_back(jt->first,jt->second);
|
||||
// If props empty, then simply reset all property items, but keep the group
|
||||
// items.
|
||||
if (props.empty()) {
|
||||
beginResetModel();
|
||||
for(auto &v : groupItems) {
|
||||
auto &groupInfo = v.second;
|
||||
groupInfo.groupItem->reset();
|
||||
groupInfo.children.clear();
|
||||
}
|
||||
itemMap.clear();
|
||||
endResetModel();
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
QString groupName = QString::fromLatin1(kt->first.c_str());
|
||||
group->setPropertyName(groupName);
|
||||
// First step, init group info
|
||||
for (auto &v : groupItems) {
|
||||
auto &groupInfo = v.second;
|
||||
groupInfo.children.clear();
|
||||
}
|
||||
|
||||
// setup the items for the properties
|
||||
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", editor.c_str());
|
||||
continue;
|
||||
}
|
||||
else {
|
||||
if(boost::ends_with(info.name,"*"))
|
||||
item->setLinked(true);
|
||||
PropertyItem* child = (PropertyItem*)item;
|
||||
child->setParent(rootItem);
|
||||
rootItem->appendChild(child);
|
||||
// Second step, either find existing items or create new items for the given
|
||||
// properties. There is no signaling of model change at this stage. The
|
||||
// change information is kept pending in GroupInfo::children
|
||||
for (auto jt = props.begin(); jt != props.end(); ++jt) {
|
||||
App::Property* prop = jt->second.front();
|
||||
|
||||
setPropertyItemName(child,prop->getName(),groupName);
|
||||
PropertyItem *item = nullptr;
|
||||
for (auto prop : jt->second) {
|
||||
auto it = itemMap.find(prop);
|
||||
if (it == itemMap.end() || !it->second)
|
||||
continue;
|
||||
item = it->second;
|
||||
break;
|
||||
}
|
||||
|
||||
child->setPropertyData(info.props);
|
||||
}
|
||||
}
|
||||
if (!item) {
|
||||
item = createPropertyItem(prop);
|
||||
if (!item)
|
||||
continue;
|
||||
}
|
||||
|
||||
GroupInfo &groupInfo = getGroupInfo(prop);
|
||||
groupInfo.children.push_back(item);
|
||||
|
||||
item->setLinked(boost::ends_with(jt->first,"*"));
|
||||
setPropertyItemName(item, prop->getName(), groupInfo.groupItem->propertyName());
|
||||
|
||||
if (jt->second != item->getPropertyData()) {
|
||||
for (auto prop : item->getPropertyData())
|
||||
itemMap.erase(prop);
|
||||
for (auto prop : jt->second)
|
||||
itemMap[prop] = item;
|
||||
// TODO: is it necessary to make sure the item has no pending commit?
|
||||
item->setPropertyData(jt->second);
|
||||
}
|
||||
}
|
||||
|
||||
endResetModel();
|
||||
// Third step, signal item insertion and movement.
|
||||
for (auto &v : groupItems) {
|
||||
auto &groupInfo = v.second;
|
||||
int beginChange = -1;
|
||||
int endChange = 0;
|
||||
int beginInsert = -1;
|
||||
int endInsert = 0;
|
||||
int row = -1;
|
||||
QModelIndex midx = this->index(groupInfo.groupItem->_row, 0, QModelIndex());
|
||||
|
||||
auto flushInserts = [&]() {
|
||||
if (beginInsert < 0)
|
||||
return;
|
||||
beginInsertRows(midx, beginInsert, endInsert);
|
||||
for (int i=beginInsert; i<=endInsert; ++i)
|
||||
groupInfo.groupItem->insertChild(i, groupInfo.children[i]);
|
||||
endInsertRows();
|
||||
beginInsert = -1;
|
||||
};
|
||||
|
||||
auto flushChanges = [&]() {
|
||||
if (beginChange < 0)
|
||||
return;
|
||||
(void)endChange;
|
||||
// There is no need to signal dataChange(), because PropertyEditor
|
||||
// will call PropertyModel::updateProperty() on any property
|
||||
// changes.
|
||||
//
|
||||
// dataChanged(this->index(beginChange,0,midx), this->index(endChange,1,midx));
|
||||
beginChange = -1;
|
||||
};
|
||||
|
||||
for (auto item : groupInfo.children) {
|
||||
++row;
|
||||
if (!item->parent()) {
|
||||
flushChanges();
|
||||
item->setParent(groupInfo.groupItem);
|
||||
if (beginInsert < 0)
|
||||
beginInsert = row;
|
||||
endInsert = row;
|
||||
} else {
|
||||
flushInserts();
|
||||
int oldRow = item->row();
|
||||
// Dynamic property can rename group, so must check
|
||||
auto groupItem = item->parent();
|
||||
assert(groupItem);
|
||||
if (oldRow == row && groupItem == groupInfo.groupItem) {
|
||||
if (beginChange < 0)
|
||||
beginChange = row;
|
||||
endChange = row;
|
||||
} else {
|
||||
flushChanges();
|
||||
beginMoveRows(createIndex(groupItem->row(),0,groupItem),
|
||||
oldRow, oldRow, midx, row);
|
||||
if (groupItem == groupInfo.groupItem)
|
||||
groupInfo.groupItem->moveChild(oldRow, row);
|
||||
else {
|
||||
groupItem->takeChild(oldRow);
|
||||
item->setParent(groupInfo.groupItem);
|
||||
groupInfo.groupItem->insertChild(row, item);
|
||||
}
|
||||
endMoveRows();
|
||||
}
|
||||
}
|
||||
}
|
||||
flushChanges();
|
||||
flushInserts();
|
||||
}
|
||||
|
||||
// Final step, signal item removal. This is separated from the above because
|
||||
// of the possibility of moving items between groups.
|
||||
for (auto &v : groupItems) {
|
||||
auto &groupInfo = v.second;
|
||||
int last = groupInfo.groupItem->childCount();
|
||||
int first = static_cast<int>(groupInfo.children.size());
|
||||
if (last > first) {
|
||||
QModelIndex midx = this->index(groupInfo.groupItem->_row,0,QModelIndex());
|
||||
beginRemoveRows(midx, first, last-1);
|
||||
groupInfo.groupItem->removeChildren(first, last-1);
|
||||
endRemoveRows();
|
||||
} else {
|
||||
assert(last == first);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PropertyModel::updateProperty(const App::Property& prop)
|
||||
{
|
||||
auto it = itemMap.find(const_cast<App::Property*>(&prop));
|
||||
if (it == itemMap.end() || !it->second || !it->second->parent())
|
||||
return;
|
||||
|
||||
int column = 1;
|
||||
int numChild = rootItem->childCount();
|
||||
for (int row=0; row<numChild; row++) {
|
||||
PropertyItem* child = rootItem->child(row);
|
||||
if (child->hasProperty(&prop)) {
|
||||
child->updateData();
|
||||
QModelIndex data = this->index(row, column, QModelIndex());
|
||||
if (data.isValid()) {
|
||||
child->assignProperty(&prop);
|
||||
dataChanged(data, data);
|
||||
updateChildren(child, column, data);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
PropertyItem *item = it->second;
|
||||
item->updateData();
|
||||
QModelIndex parent = this->index(item->parent()->row(), 0, QModelIndex());
|
||||
item->assignProperty(&prop);
|
||||
QModelIndex data = this->index(item->row(), column, parent);
|
||||
dataChanged(data, data);
|
||||
updateChildren(item, column, data);
|
||||
}
|
||||
|
||||
void PropertyModel::appendProperty(const App::Property& prop)
|
||||
void PropertyModel::appendProperty(const App::Property& _prop)
|
||||
{
|
||||
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", editor.c_str());
|
||||
return;
|
||||
}
|
||||
auto prop = const_cast<App::Property*>(&_prop);
|
||||
if (!prop->getName())
|
||||
return;
|
||||
auto it = itemMap.find(prop);
|
||||
if (it == itemMap.end() || !it->second)
|
||||
return;
|
||||
|
||||
const char* group = prop.getGroup();
|
||||
bool isEmpty = (group == 0 || group[0] == '\0');
|
||||
std::string grp = isEmpty ? QT_TRANSLATE_NOOP("App::Property", "Base") : group;
|
||||
QString groupName = QString::fromStdString(grp);
|
||||
PropertyItem *item = createPropertyItem(prop);
|
||||
GroupInfo &groupInfo = getGroupInfo(prop);
|
||||
|
||||
// go through all group names and check if one matches
|
||||
int index = -1;
|
||||
for (int i=0; i<rootItem->childCount(); i++) {
|
||||
PropertyItem* child = rootItem->child(i);
|
||||
if (child->isSeparator()) {
|
||||
if (groupName == child->propertyName()) {
|
||||
index = i+1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int numChilds = rootItem->childCount();
|
||||
int first = 0;
|
||||
int last = 0;
|
||||
if (index < 0) {
|
||||
// create a new group
|
||||
first = numChilds;
|
||||
last = first + 1;
|
||||
}
|
||||
else {
|
||||
// the group exists, determine the position before the next group
|
||||
// or at the end if there is no further group
|
||||
for (int i=index; i<rootItem->childCount(); i++) {
|
||||
index++;
|
||||
PropertyItem* child = rootItem->child(i);
|
||||
if (child->isSeparator()) {
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
first = index;
|
||||
last = index;
|
||||
}
|
||||
|
||||
// notify system to add new row
|
||||
beginInsertRows(QModelIndex(), first, last);
|
||||
|
||||
// create a new group at the end with the property
|
||||
if (index < 0) {
|
||||
PropertyItem* group = static_cast<PropertyItem*>(PropertySeparatorItem::create());
|
||||
group->setParent(rootItem);
|
||||
rootItem->appendChild(group);
|
||||
group->setPropertyName(groupName);
|
||||
|
||||
item->setParent(rootItem);
|
||||
rootItem->appendChild(item);
|
||||
}
|
||||
// add the property at the end of its group
|
||||
else if (index < numChilds) {
|
||||
item->setParent(rootItem);
|
||||
rootItem->insertChild(index, item);
|
||||
}
|
||||
// add the property at end
|
||||
else {
|
||||
item->setParent(rootItem);
|
||||
rootItem->appendChild(item);
|
||||
}
|
||||
|
||||
std::vector<App::Property*> data;
|
||||
data.push_back(const_cast<App::Property*>(&prop));
|
||||
|
||||
setPropertyItemName(item,prop.getName(),groupName);
|
||||
item->setPropertyData(data);
|
||||
|
||||
endInsertRows();
|
||||
int row = 0;
|
||||
for (int c=groupInfo.groupItem->childCount(); row<c; ++row) {
|
||||
PropertyItem *child = groupInfo.groupItem->child(row);
|
||||
App::Property *firstProp = item->getFirstProperty();
|
||||
if (firstProp && firstProp->testStatus(App::Property::PropDynamic)
|
||||
&& item->propertyName() >= child->propertyName())
|
||||
break;
|
||||
}
|
||||
|
||||
QModelIndex midx = this->index(groupInfo.groupItem->_row,0,QModelIndex());
|
||||
beginInsertRows(midx, row, row);
|
||||
groupInfo.groupItem->insertChild(row, item);
|
||||
setPropertyItemName(item, prop->getName(), groupInfo.groupItem->propertyName());
|
||||
item->setPropertyData({prop});
|
||||
endInsertRows();
|
||||
}
|
||||
|
||||
void PropertyModel::removeProperty(const App::Property& prop)
|
||||
void PropertyModel::removeProperty(const App::Property& _prop)
|
||||
{
|
||||
int numChild = rootItem->childCount();
|
||||
for (int row=0; row<numChild; row++) {
|
||||
PropertyItem* child = rootItem->child(row);
|
||||
if (child->hasProperty(&prop)) {
|
||||
if (child->removeProperty(&prop)) {
|
||||
removeRow(row, QModelIndex());
|
||||
}
|
||||
break;
|
||||
}
|
||||
auto prop = const_cast<App::Property*>(&_prop);
|
||||
auto it = itemMap.find(prop);
|
||||
if (it == itemMap.end() || !it->second)
|
||||
return;
|
||||
|
||||
PropertyItem *item = it->second;
|
||||
if (item->removeProperty(prop)) {
|
||||
PropertyItem *parent = item->parent();
|
||||
int row = item->row();
|
||||
beginRemoveRows(this->index(parent->row(), 0, QModelIndex()), row, row);
|
||||
parent->removeChildren(row,row);
|
||||
endRemoveRows();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,8 @@
|
||||
#include <QStringList>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <unordered_map>
|
||||
#include "PropertyItem.h"
|
||||
|
||||
namespace App {
|
||||
class Property;
|
||||
@@ -35,7 +37,6 @@ class Property;
|
||||
namespace Gui {
|
||||
namespace PropertyEditor {
|
||||
|
||||
class PropertyItem;
|
||||
class PropertyModel : public QAbstractItemModel
|
||||
{
|
||||
Q_OBJECT
|
||||
@@ -70,8 +71,18 @@ public:
|
||||
private:
|
||||
void updateChildren(PropertyItem* item, int column, const QModelIndex& parent);
|
||||
|
||||
struct GroupInfo {
|
||||
PropertySeparatorItem *groupItem = nullptr;
|
||||
std::vector<PropertyItem *> children;
|
||||
};
|
||||
GroupInfo &getGroupInfo(App::Property *);
|
||||
|
||||
private:
|
||||
PropertyItem *rootItem;
|
||||
|
||||
std::unordered_map<App::Property*, QPointer<PropertyItem> > itemMap;
|
||||
|
||||
std::map<QString, GroupInfo> groupItems;
|
||||
};
|
||||
|
||||
} //namespace PropertyEditor
|
||||
|
||||
Reference in New Issue
Block a user