Files
create/src/Gui/propertyeditor/PropertyModel.cpp
berniev 75acacd1b7 Gui: Use auto and range-based for (#7481)
* On lines where the variable type is obvious from inspection, avoid repeating the type using auto. 
* When possible use a ranged for loop instead of begin() and end() iterators
2022-09-14 13:25:13 -05:00

516 lines
18 KiB
C++

/***************************************************************************
* Copyright (c) 2004 Werner Mayer <wmayer[at]users.sourceforge.net> *
* *
* This file is part of the FreeCAD CAx development system. *
* *
* This library is free software; you can redistribute it and/or *
* modify it under the terms of the GNU Library General Public *
* License as published by the Free Software Foundation; either *
* version 2 of the License, or (at your option) any later version. *
* *
* This library is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU Library General Public License for more details. *
* *
* You should have received a copy of the GNU Library General Public *
* License along with this library; see the file COPYING.LIB. If not, *
* write to the Free Software Foundation, Inc., 59 Temple Place, *
* Suite 330, Boston, MA 02111-1307, USA *
* *
***************************************************************************/
#include "PreCompiled.h"
#ifndef _PreComp_
# include <boost/algorithm/string/predicate.hpp>
#endif
#include "PropertyItem.h"
#include "PropertyModel.h"
#include "PropertyView.h"
using namespace Gui;
using namespace Gui::PropertyEditor;
/* TRANSLATOR Gui::PropertyEditor::PropertyModel */
PropertyModel::PropertyModel(QObject* parent)
: QAbstractItemModel(parent)
{
rootItem = static_cast<PropertyItem*>(PropertyItem::create());
}
PropertyModel::~PropertyModel()
{
delete rootItem;
}
QModelIndex PropertyModel::buddy ( const QModelIndex & index ) const
{
if (index.column() == 1)
return index;
return index.sibling(index.row(), 1);
}
int PropertyModel::columnCount ( const QModelIndex & parent ) const
{
// <property, value>, hence always 2
if (parent.isValid())
return static_cast<PropertyItem*>(parent.internalPointer())->columnCount();
else
return rootItem->columnCount();
}
QVariant PropertyModel::data ( const QModelIndex & index, int role ) const
{
if (!index.isValid())
return QVariant();
auto item = static_cast<PropertyItem*>(index.internalPointer());
return item->data(index.column(), role);
}
bool PropertyModel::setData(const QModelIndex& index, const QVariant & value, int role)
{
if (!index.isValid())
return false;
// we check whether the data has really changed, otherwise we ignore it
if (role == Qt::EditRole) {
auto item = static_cast<PropertyItem*>(index.internalPointer());
QVariant data = item->data(index.column(), role);
if (data.type() == QVariant::Double && value.type() == QVariant::Double) {
// since we store some properties as floats we get some round-off
// errors here. Thus, we use an epsilon here.
// NOTE: Since 0.14 PropertyFloat uses double precision, so this is maybe unnecessary now?
double d = data.toDouble();
double v = value.toDouble();
if (fabs(d-v) > DBL_EPSILON)
return item->setData(value);
}
// Special case handling for quantities
else if (data.canConvert<Base::Quantity>() && value.canConvert<Base::Quantity>()) {
const Base::Quantity& val1 = data.value<Base::Quantity>();
const Base::Quantity& val2 = value.value<Base::Quantity>();
if (!(val1 == val2))
return item->setData(value);
}
else if (data != value)
return item->setData(value);
}
return true;
}
Qt::ItemFlags PropertyModel::flags(const QModelIndex &index) const
{
auto item = static_cast<PropertyItem*>(index.internalPointer());
return item->flags(index.column());
}
QModelIndex PropertyModel::index ( int row, int column, const QModelIndex & parent ) const
{
PropertyItem *parentItem;
if (!parent.isValid())
parentItem = rootItem;
else
parentItem = static_cast<PropertyItem*>(parent.internalPointer());
PropertyItem *childItem = parentItem->child(row);
if (childItem)
return createIndex(row, column, childItem);
else
return QModelIndex();
}
QModelIndex PropertyModel::parent ( const QModelIndex & index ) const
{
if (!index.isValid())
return QModelIndex();
auto childItem = static_cast<PropertyItem*>(index.internalPointer());
PropertyItem *parentItem = childItem->parent();
if (parentItem == rootItem)
return QModelIndex();
return createIndex(parentItem->row(), 0, parentItem);
}
int PropertyModel::rowCount ( const QModelIndex & parent ) const
{
PropertyItem *parentItem;
if (!parent.isValid())
parentItem = rootItem;
else
parentItem = static_cast<PropertyItem*>(parent.internalPointer());
return parentItem->childCount();
}
QVariant PropertyModel::headerData (int section, Qt::Orientation orientation, int role) const
{
if (orientation == Qt::Horizontal) {
if (role != Qt::DisplayRole)
return QVariant();
if (section == 0)
return tr("Property");
if (section == 1)
return tr("Value");
}
return QVariant();
}
bool PropertyModel::setHeaderData (int, Qt::Orientation, const QVariant &, int)
{
return false;
}
QStringList PropertyModel::propertyPathFromIndex(const QModelIndex& index) const
{
QStringList path;
if (index.isValid()) {
auto item = static_cast<PropertyItem*>(index.internalPointer());
while (item && item != this->rootItem) {
path.push_front(item->propertyName());
item = item->parent();
}
}
return path;
}
QModelIndex PropertyModel::propertyIndexFromPath(const QStringList& path) const
{
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 index;
}
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, 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 == nullptr || 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)
{
// 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;
}
// First step, init group info
for (auto &v : groupItems) {
auto &groupInfo = v.second;
groupInfo.children.clear();
}
// 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 (const auto & jt : props) {
App::Property* prop = jt.second.front();
PropertyItem *item = nullptr;
for (auto prop : jt.second) {
auto it = itemMap.find(prop);
if (it == itemMap.end() || !it->second)
continue;
item = it->second;
break;
}
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);
}
else
item->updateData();
}
// 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;
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);
Q_EMIT dataChanged(data, data);
updateChildren(item, column, data);
}
void PropertyModel::appendProperty(const App::Property& _prop)
{
auto prop = const_cast<App::Property*>(&_prop);
if (!prop->getName())
return;
auto it = itemMap.find(prop);
if (it == itemMap.end() || !it->second)
return;
PropertyItem *item = createPropertyItem(prop);
GroupInfo &groupInfo = getGroupInfo(prop);
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)
{
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();
}
}
void PropertyModel::updateChildren(PropertyItem* item, int column, const QModelIndex& parent)
{
int numChild = item->childCount();
if (numChild > 0) {
QModelIndex topLeft = this->index(0, column, parent);
QModelIndex bottomRight = this->index(numChild, column, parent);
Q_EMIT dataChanged(topLeft, bottomRight);
}
}
bool PropertyModel::removeRows(int row, int count, const QModelIndex& parent)
{
PropertyItem* item;
if (!parent.isValid())
item = rootItem;
else
item = static_cast<PropertyItem*>(parent.internalPointer());
int start = row;
int end = row+count-1;
beginRemoveRows(parent, start, end);
item->removeChildren(start, end);
endRemoveRows();
return true;
}
#include "moc_PropertyModel.cpp"