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:
Zheng Lei
2022-02-21 19:26:21 +08:00
committed by GitHub
parent 83f0f6c3bb
commit f12ae8a13c
12 changed files with 603 additions and 334 deletions

View File

@@ -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();