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:
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user