File indexing completed on 2024-04-28 07:39:39

0001 /*.
0002     SPDX-FileCopyrightText: 2007 Vladimir Kuznetsov <ks.vladimir@gmail.com>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "propertiesbrowser.h"
0008 
0009 #include "settings.h"
0010 
0011 #include "worldfactory.h"
0012 #include "unitscalc.h"
0013 
0014 #include "worldmodel.h"
0015 #include <stepcore/object.h>
0016 #include <stepcore/solver.h>
0017 #include <stepcore/types.h>
0018 
0019 #include <QAbstractItemModel>
0020 #include <QApplication>
0021 #include <QHBoxLayout>
0022 #include <QItemEditorFactory>
0023 #include <QMouseEvent>
0024 #include <QTreeView>
0025 
0026 #include <KColorButton>
0027 #include <KComboBox>
0028 #include <KLineEdit>
0029 #include <KLocalizedString>
0030 
0031 #include "choicesmodel.h"
0032 
0033 class PropertiesBrowserModel: public QAbstractItemModel
0034 {
0035 public:
0036     PropertiesBrowserModel(WorldModel* worldModel, QObject* parent = nullptr);
0037 
0038     QVariant data(const QModelIndex &index, int role) const override;
0039     QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;
0040     QModelIndex parent(const QModelIndex &index) const override;
0041     int rowCount(const QModelIndex &parent = QModelIndex()) const override;
0042     int columnCount(const QModelIndex &parent = QModelIndex()) const override;
0043     QVariant headerData(int section, Qt::Orientation orientation,
0044                          int role = Qt::DisplayRole) const override;
0045 
0046     Qt::ItemFlags flags(const QModelIndex &index) const override;
0047     bool setData(const QModelIndex &index, const QVariant &value, int role) override;
0048 
0049     void setObject(StepCore::Object* object);
0050     StepCore::Object* object() { return _object; }
0051 
0052     void emitDataChanged(bool dynamicOnly);
0053 
0054 protected:
0055     WorldModel* _worldModel;
0056     StepCore::Object*       _object;
0057     StepCore::Item*         _item;
0058     StepCore::ObjectErrors* _objectErrors;
0059     ChoicesModel* _solverChoices;
0060     QList<int> _subRows;
0061 };
0062 
0063 PropertiesBrowserModel::PropertiesBrowserModel(WorldModel* worldModel, QObject* parent)
0064     : QAbstractItemModel(parent), _worldModel(worldModel), _object(nullptr)
0065 {
0066     _solverChoices = new ChoicesModel(this);
0067 
0068     // Prepare solver list
0069     foreach(const QString &name, _worldModel->worldFactory()->orderedMetaObjects()) {
0070         const StepCore::MetaObject* metaObject = _worldModel->worldFactory()->metaObject(name);
0071         if(metaObject->isAbstract()) continue;
0072         if(!metaObject->inherits(StepCore::Solver::staticMetaObject())) continue;
0073         QString solverName = QString(metaObject->className()).remove(QStringLiteral("Solver"));
0074         QStandardItem* item = new QStandardItem(solverName);
0075         item->setToolTip(QString(metaObject->descriptionTr()));
0076         _solverChoices->appendRow(item);
0077     }
0078 }
0079 
0080 void PropertiesBrowserModel::setObject(StepCore::Object* object)
0081 {
0082     beginResetModel();
0083     _object = object;
0084 
0085     _subRows.clear();
0086     if(_object != nullptr) {
0087         _worldModel->simulationPause();
0088 
0089         _item = dynamic_cast<StepCore::Item*>(_object);
0090         if(_item) {
0091             if(_item->world()->errorsCalculation()) _objectErrors = _item->objectErrors();
0092             else _objectErrors = _item->tryGetObjectErrors();
0093         } else {
0094             _objectErrors = nullptr;
0095         }
0096 
0097         for(int i=0; i<_object->metaObject()->propertyCount(); ++i) {
0098             const StepCore::MetaProperty* p = _object->metaObject()->property(i);
0099             if(p->userTypeId() == qMetaTypeId<StepCore::Vector2dList >())
0100                 _subRows << p->readVariant(_object).value<StepCore::Vector2dList >().size();
0101             else _subRows << 0;
0102         }
0103     }
0104 
0105     endResetModel();
0106 }
0107 
0108 void PropertiesBrowserModel::emitDataChanged(bool dynamicOnly)
0109 {
0110     if(_object == nullptr) return;
0111 
0112     _worldModel->simulationPause();
0113 
0114     _item = dynamic_cast<StepCore::Item*>(_object);
0115     if(_item) {
0116         if(_item->world()->errorsCalculation()) _objectErrors = _item->objectErrors();
0117         else _objectErrors = _item->tryGetObjectErrors();
0118     } else {
0119         _objectErrors = nullptr;
0120     }
0121 
0122     for(int i=0; i<_object->metaObject()->propertyCount(); i++) {
0123         const StepCore::MetaProperty* p = _object->metaObject()->property(i);
0124         if(dynamicOnly && !p->isDynamic()) continue;
0125         if(p->userTypeId() == qMetaTypeId<StepCore::Vector2dList >()) {
0126             int r = p->readVariant(_object).value<StepCore::Vector2dList >().size();
0127             if(r > _subRows[i]) {
0128                 beginInsertRows(index(i, 0), _subRows[i], r-1);
0129                 _subRows[i] = r;
0130                 endInsertRows();
0131             } else if(r < _subRows[i]) {
0132                 beginRemoveRows(index(i, 0), r, _subRows[i]-1);
0133                 _subRows[i] = r;
0134                 endRemoveRows();
0135             }
0136             if(r != 0) emit dataChanged(index(0,0,index(i,0)), index(r-1,1,index(i,0))); // XXX?
0137         }
0138         emit dataChanged(index(i,1), index(i,1));
0139     }
0140     //emit dataChanged(index(0,1), index(rowCount()-1,1));
0141 }
0142 
0143 QVariant PropertiesBrowserModel::data(const QModelIndex &index, int role) const
0144 {
0145     if(_object == nullptr) return QVariant();
0146 
0147     if(!index.isValid()) return QVariant();
0148 
0149     if(index.internalId() == 0) {
0150         const StepCore::MetaProperty* p = _object->metaObject()->property(index.row());
0151         if(role == Qt::DisplayRole || role == Qt::EditRole) {
0152             if(index.column() == 0) return p->nameTr();
0153             else if(index.column() == 1) {
0154                 _worldModel->simulationPause();
0155 
0156                 // Solver type combobox ?
0157                 if(index.row() == 1 && dynamic_cast<StepCore::Solver*>(_object)) {
0158                     if(role == Qt::DisplayRole) return p->readString(_object).remove(QStringLiteral("Solver"));
0159                     else return QVariant::fromValue(_solverChoices);
0160                 }
0161 
0162                 if(p->userTypeId() == QMetaType::Double ||
0163                     p->userTypeId() == qMetaTypeId<StepCore::Vector2d>() ||
0164                     p->userTypeId() == qMetaTypeId<StepCore::Vector2dList >()) {
0165                     return _worldModel->formatProperty(_object, _objectErrors, p,
0166                                 role == Qt::EditRole ? WorldModel::FormatEditable : WorldModel::FormatFlags());
0167                 } else if(p->userTypeId() == qMetaTypeId<StepCore::Object*>()) {
0168                     return _worldModel->formatName(p->readVariant(_object).value<StepCore::Object*>());
0169                 } else if(p->userTypeId() == qMetaTypeId<StepCore::Color>()) {
0170                     Q_ASSERT( !_objectErrors || !_objectErrors->metaObject()->property(p->name() + "Variance") );
0171                     Q_ASSERT( p->units().isEmpty() );
0172                     if(role == Qt::EditRole)
0173                         return QColor::fromRgba(p->readVariant(_object).value<StepCore::Color>());
0174                     else
0175                         return p->readString(_object);
0176                 } else if(p->userTypeId() == QMetaType::Bool) {
0177                     Q_ASSERT( !_objectErrors || !_objectErrors->metaObject()->property(p->name() + "Variance") );
0178                     Q_ASSERT( p->units().isEmpty() );
0179                     return p->readVariant(_object);
0180                 } else {
0181                     // default type
0182                     // XXX: add error information
0183                     //if(pe) error = QString::fromUtf8(" ± ").append(pe->readString(_objectErrors)).append(units);
0184                     //if(pv) qDebug() << "Unhandled property variance type" << endl;
0185                     Q_ASSERT( !_objectErrors || !_objectErrors->metaObject()->property(p->name() + "Variance") );
0186                     Q_ASSERT( p->units().isEmpty() );
0187                     if(role == Qt::EditRole) return _worldModel->formatProperty(_object, _objectErrors, p,
0188                                         WorldModel::FormatHideUnits | WorldModel::FormatEditable);
0189                     else return _worldModel->formatProperty(_object, _objectErrors, p, WorldModel::FormatHideUnits);
0190                 }
0191                 ///*if(p->userTypeId() < (int) QVariant::UserType) return p->readVariant(_object);
0192                 //else*/ return p->readString(_object); // XXX: default delegate for double looks ugly!
0193             }
0194         } else if(index.column() == 1 && role == Qt::ForegroundRole) {
0195             if(!p->isWritable()) {
0196                 if(index.row() != 1 || !dynamic_cast<StepCore::Solver*>(_object))
0197                     return QBrush(Qt::darkGray); // XXX: how to get scheme color ?
0198             }
0199         } else if(role == Qt::ToolTipRole) {
0200             if(index.row() == 1 && index.column() == 1 && dynamic_cast<StepCore::Solver*>(_object)) {
0201                 return _object->metaObject()->descriptionTr();
0202             }
0203             return p->descriptionTr(); // XXX: translation
0204         } else if(index.column() == 1 && role == Qt::DecorationRole &&
0205                     p->userTypeId() == qMetaTypeId<StepCore::Color>()) {
0206             QPixmap pix(8, 8);
0207             pix.fill(QColor::fromRgba(p->readVariant(_object).value<StepCore::Color>()));
0208             return pix;
0209         } else if(index.column() == 0 && role == Qt::DecorationRole &&
0210                     p->userTypeId() == qMetaTypeId<StepCore::Vector2dList >() &&
0211                     rowCount(index) > 0) {
0212             // XXX: A hack to have nested properties shifted
0213             static QPixmap empySmallPix;
0214             if(empySmallPix.isNull()) {
0215                 empySmallPix = QPixmap(8,8); //XXX
0216                 empySmallPix.fill(QColor(0,0,0,0));
0217             }
0218             return empySmallPix;
0219         }
0220     } else { // index.internalId() != 0
0221         const StepCore::MetaProperty* p = _object->metaObject()->property(index.internalId()-1);
0222         if(role == Qt::DisplayRole || role == Qt::EditRole) {
0223             if(index.column() == 0) return QStringLiteral("%1[%2]").arg(p->nameTr()).arg(index.row());
0224             else if(index.column() == 1) {
0225 #ifdef __GNUC__
0226 #warning XXX: add error information for lists
0227 #endif
0228                 QString units;
0229                 if(role == Qt::DisplayRole && !p->units().isEmpty())
0230                     units.append(" [").append(p->units()).append("]");
0231 #ifdef STEP_WITH_UNITSCALC
0232 //                else if(role == Qt::EditRole && !p->units().isEmpty()) 
0233 //                    units.append(" ").append(p->units());
0234 #endif
0235                 int pr = Settings::floatDisplayPrecision();
0236                 //int pr = role == Qt::DisplayRole ? Settings::floatDisplayPrecision() : 16;
0237                 _worldModel->simulationPause();
0238                 StepCore::Vector2d v =
0239                         p->readVariant(_object).value<StepCore::Vector2dList >()[index.row()];
0240                 return QStringLiteral("(%1,%2)%3").arg(v[0], 0, 'g', pr).arg(v[1], 0, 'g', pr).arg(units);
0241             }
0242         } else if(role == Qt::ForegroundRole && index.column() == 1) {
0243             if(!p->isWritable()) {
0244                 return QBrush(Qt::darkGray); // XXX: how to get scheme color ?
0245             }
0246         } else if(role == Qt::ToolTipRole) {
0247             return p->descriptionTr(); // XXX: translation
0248         }
0249     }
0250 
0251     return QVariant();
0252 }
0253 
0254 bool PropertiesBrowserModel::setData(const QModelIndex &index, const QVariant &value, int role)
0255 {
0256     if(_object == nullptr) return false;
0257 
0258     if(index.isValid() && index.column() == 1 && role == Qt::EditRole) {
0259         _worldModel->simulationPause();
0260         if(index.internalId() == 0) {
0261             if(index.row() == 0) { // name // XXX: do it more generally
0262                 if(!_worldModel->checkUniqueName(value.toString())) return false; // XXX: error message
0263             }
0264             if(index.row() == 1 && dynamic_cast<StepCore::Solver*>(_object)) {
0265                 if(value.toString() != _object->metaObject()->className()) {
0266                     beginResetModel();
0267                     _worldModel->beginMacro(i18n("Change solver type"));
0268                     _object = _worldModel->newSolver(value.toString() + "Solver");
0269                     Q_ASSERT(_object != nullptr);
0270                     _worldModel->endMacro();
0271                     endResetModel();
0272                 }
0273             } else {
0274                 const StepCore::MetaProperty* p = _object->metaObject()->property(index.row());
0275                 const StepCore::MetaProperty* pv = _objectErrors ?
0276                         _objectErrors->metaObject()->property(p->name() + "Variance") : nullptr;
0277 
0278                 if(p->userTypeId() == qMetaTypeId<StepCore::Object*>()) {
0279                     Q_ASSERT(!pv);
0280                     StepCore::Object* obj = _worldModel->world()->object(value.toString());
0281                     if(!obj) return false;
0282                     _worldModel->beginMacro(i18n("Change %1.%2", _object->name(), p->nameTr()));
0283                     _worldModel->setProperty(_object, p, QVariant::fromValue(obj));
0284                     _worldModel->endMacro();
0285                     return true;
0286                 } else if(p->userTypeId() == qMetaTypeId<StepCore::Color>()) {
0287                     Q_ASSERT(!pv);
0288                     _worldModel->beginMacro(i18n("Change %1.%2", _object->name(), p->nameTr()));
0289                     _worldModel->setProperty(_object, p, value.type() == QVariant::String ? value :
0290                                     QVariant::fromValue(StepCore::Color(value.value<QColor>().rgba())));
0291                     _worldModel->endMacro();
0292                     return true;
0293                 } else if(p->userTypeId() == qMetaTypeId<bool>()) {
0294                     Q_ASSERT(!pv);
0295                     _worldModel->beginMacro(i18n("Change %1.%2", _object->name(), p->nameTr()));
0296                     _worldModel->setProperty(_object, p, value);
0297                     _worldModel->endMacro();
0298                     return true;
0299                 } else if(p->userTypeId() == qMetaTypeId<QString>()) {
0300                     Q_ASSERT(!pv);
0301                     if(index.row() == 0)
0302                         _worldModel->beginMacro(i18n("Rename %1 to %2", _object->name(), value.toString()));
0303                     else
0304                         _worldModel->beginMacro(i18n("Change %1.%2", _object->name(), p->nameTr()));
0305                     _worldModel->setProperty(_object, p, value);
0306                     _worldModel->endMacro();
0307                     return true;
0308                 }
0309 
0310                 QVariant v = value;
0311                 QVariant vv;
0312 
0313                 // Try to find ± sign
0314                 if(v.canConvert(QVariant::String)) {
0315                     QString str = v.toString();
0316                     int idx = str.indexOf(QStringLiteral("±"));
0317                     if(idx >= 0) {
0318                         v = str.left(idx);
0319                         vv = str.mid(idx+1);
0320                     }
0321                 }
0322 
0323 #ifdef STEP_WITH_UNITSCALC
0324                     // Convert units
0325                     if(p->userTypeId() == QMetaType::Double) {
0326                         double number = 0;
0327                         if(UnitsCalc::self()->parseNumber(v.toString(), p->units(), number)) {
0328                             v = number;
0329                         } else {
0330                             return false;
0331                         }
0332                         if(vv.isValid()) {
0333                             if(UnitsCalc::self()->parseNumber(vv.toString(), p->units(), number)) {
0334                                 vv = number;
0335                             } else {
0336                                 return false;
0337                             }
0338                         }
0339                     }
0340 #endif
0341 
0342                 if(vv.isValid()) { // We have got variance value
0343                     if(!pv) {
0344                         // check if _objectErrors can be created
0345                         // and current property variance could be set
0346                         const StepCore::MetaObject* me =
0347                             _worldModel->worldFactory()->metaObject(
0348                                 _object->metaObject()->className() + "Errors");
0349                         if(!_item || !me || !me->property(p->name() + "Variance"))
0350                             return false;
0351                     }
0352 
0353                     bool ok = true;
0354                     // Calc variance = square(error)
0355                     if(p->userTypeId() == QMetaType::Double) {
0356                         vv = StepCore::square(vv.toDouble(&ok));
0357                     } else if(p->userTypeId() == qMetaTypeId<StepCore::Vector2d>()) {
0358                         StepCore::Vector2d svv;
0359                         svv = StepCore::stringToType<StepCore::Vector2d>(vv.toString(), &ok);
0360                         svv[0] *= svv[0]; svv[1] *= svv[1];
0361                         vv = QVariant::fromValue(svv);
0362                     /* XXX
0363                      * {} else if(p->userTypeId() == qMetaTypeId<StepCore::Vector2dList >())
0364                         ve = QVariant::fromValue(StepCore::Vector2dList());*/
0365                     } else {
0366 //                         qDebug() << "Unhandled property variance type" << endl;
0367                         return false;
0368                     }
0369                     if(!ok) return false;
0370 
0371                 } else { // vv.isValid()
0372                     if(pv) { // We have to zero variance since we got exact value
0373                         if(p->userTypeId() == QMetaType::Double) {
0374                             vv = 0;
0375                         } else if(p->userTypeId() == qMetaTypeId<StepCore::Vector2d>()) {
0376                             StepCore::Vector2d svv = StepCore::Vector2d::Zero();
0377                             vv = QVariant::fromValue(svv);
0378                         /* XXX
0379                          * } else if(p->userTypeId() == qMetaTypeId<StepCore::Vector2dList >())
0380                             ve = QVariant::fromValue(StepCore::Vector2dList());*/
0381                         } else {
0382                             qWarning("Unhandled property variance type");
0383                             return false;
0384                         }
0385                     }
0386                 }
0387 
0388                 _worldModel->beginMacro(i18n("Change %1.%2", _object->name(), p->nameTr()));
0389                 _worldModel->setProperty(_object, p, v);
0390                 if(vv.isValid() && !pv) {
0391                     // XXX: Make this undo-able
0392                     _objectErrors = _item->objectErrors();
0393                     pv = _objectErrors->metaObject()->property(p->name() + "Variance");
0394                 }
0395                 if(pv) _worldModel->setProperty(_objectErrors, pv, vv);
0396                 _worldModel->endMacro();
0397             }
0398         } else {
0399             const StepCore::MetaProperty* p = _object->metaObject()->property(index.internalId()-1);
0400             StepCore::Vector2dList v =
0401                         p->readVariant(_object).value<StepCore::Vector2dList >();
0402             bool ok;
0403             v[index.row()] = StepCore::stringToType<StepCore::Vector2d>(value.toString(), &ok);
0404             if(!ok) return true; // dataChanged should be emitted anyway
0405             _worldModel->beginMacro(i18n("Change %1.%2", _object->name(), p->nameTr()));
0406             _worldModel->setProperty(_object, p, QVariant::fromValue(v));
0407             _worldModel->endMacro();
0408         }
0409         return true;
0410     }
0411     return false;
0412 }
0413 
0414 QModelIndex PropertiesBrowserModel::index(int row, int column, const QModelIndex &parent) const
0415 {
0416     if(_object == nullptr) return QModelIndex();
0417     if(!parent.isValid()) return createIndex(row, column);
0418 
0419     if(parent.internalId() == 0 && _subRows[parent.row()] != 0)
0420         return createIndex(row, column, parent.row()+1);
0421 
0422     return QModelIndex();
0423 }
0424 
0425 QModelIndex PropertiesBrowserModel::parent(const QModelIndex& index) const
0426 {
0427     if(index.isValid() && index.internalId() != 0)
0428         return createIndex(index.internalId()-1, 0, nullptr);
0429     return QModelIndex();
0430 }
0431 
0432 int PropertiesBrowserModel::rowCount(const QModelIndex &parent) const
0433 {
0434     if(_object == nullptr) return 0;
0435     else if(parent.isValid()) {
0436         if(parent.column() == 0 && parent.internalId() == 0) return _subRows[parent.row()];
0437         return 0;
0438     }
0439     else return _object->metaObject()->propertyCount();
0440 }
0441 
0442 int PropertiesBrowserModel::columnCount(const QModelIndex& /*parent*/) const
0443 {
0444     return 2;
0445 }
0446 
0447 QVariant PropertiesBrowserModel::headerData(int section, Qt::Orientation /*orientation*/,
0448                                       int role) const
0449 {
0450     if (role != Qt::DisplayRole) return QVariant();
0451     switch(section) {
0452         case 0: return i18n("Property");
0453         case 1: return i18n("Value");
0454         default: return QVariant();
0455     }
0456 }
0457 
0458 Qt::ItemFlags PropertiesBrowserModel::flags(const QModelIndex &index) const
0459 {
0460     Qt::ItemFlags flags = QAbstractItemModel::flags(index);
0461 
0462     if(_object && index.isValid() && index.column() == 1) {
0463         if(index.internalId() == 0) {
0464             if(_object->metaObject()->property(index.row())->isWritable() ||
0465                 (index.row()==1 && dynamic_cast<StepCore::Solver*>(_object))) flags |= Qt::ItemIsEditable;
0466         } else {
0467             if(_object->metaObject()->property(index.internalId()-1)->isWritable()) flags |= Qt::ItemIsEditable;
0468         }
0469     }
0470 
0471     return flags;
0472 }
0473 
0474 QWidget* PropertiesBrowserDelegate::createEditor(QWidget* parent,
0475                 const QStyleOptionViewItem& /*option*/, const QModelIndex& index) const
0476 {
0477     QVariant data = index.data(Qt::EditRole);
0478     int userType = data.userType();
0479     if(userType == qMetaTypeId<ChoicesModel*>()) {
0480         KComboBox* editor = new KComboBox(parent);
0481         editor->setModel(data.value<ChoicesModel*>());
0482         connect(editor, SIGNAL(activated(int)), this, SLOT(editorActivated()));
0483         editor->installEventFilter(const_cast<PropertiesBrowserDelegate*>(this));
0484         const_cast<PropertiesBrowserDelegate*>(this)->_editor = editor;
0485         const_cast<PropertiesBrowserDelegate*>(this)->_comboBox = editor;
0486         const_cast<PropertiesBrowserDelegate*>(this)->_editorType = SolverChoiser;
0487         return editor;
0488 
0489     } else if(userType == QMetaType::QColor) {
0490         QWidget* editor = new QWidget(parent);
0491 
0492         KLineEdit* lineEdit = new KLineEdit(editor);
0493         lineEdit->setFrame(false);
0494 
0495         KColorButton* colorButton = new KColorButton(editor);
0496         // XXX: do not use hard-coded pixel sizes
0497         colorButton->setMinimumWidth(15);
0498         colorButton->setMaximumWidth(15);
0499         connect(colorButton, &KColorButton::changed, this, &PropertiesBrowserDelegate::editorActivated);
0500 
0501         QHBoxLayout* layout = new QHBoxLayout(editor);
0502         layout->setContentsMargins(0,0,0,0);
0503         layout->setSpacing(0);
0504         layout->addWidget(lineEdit);
0505         layout->addWidget(colorButton);
0506 
0507         editor->setFocusProxy(lineEdit);
0508         editor->installEventFilter(const_cast<PropertiesBrowserDelegate*>(this));
0509 
0510         const_cast<PropertiesBrowserDelegate*>(this)->_editor = editor;
0511         const_cast<PropertiesBrowserDelegate*>(this)->_colorButton = colorButton;
0512         const_cast<PropertiesBrowserDelegate*>(this)->_lineEdit = lineEdit;
0513         const_cast<PropertiesBrowserDelegate*>(this)->_editorType = ColorChoiser;
0514         return editor;
0515 
0516     } else if(userType == QMetaType::Bool) {
0517         KComboBox* editor = new KComboBox(parent);
0518         editor->addItem(i18n("false"));
0519         editor->addItem(i18n("true"));
0520         connect(editor, SIGNAL(activated(int)), this, SLOT(editorActivated()));
0521         editor->installEventFilter(const_cast<PropertiesBrowserDelegate*>(this));
0522         const_cast<PropertiesBrowserDelegate*>(this)->_editor = editor;
0523         const_cast<PropertiesBrowserDelegate*>(this)->_comboBox = editor;
0524         const_cast<PropertiesBrowserDelegate*>(this)->_editorType = BoolChoiser;
0525         return editor;
0526 
0527     } else {
0528         const_cast<PropertiesBrowserDelegate*>(this)->_editorType = Standard;
0529         const QItemEditorFactory *factory = itemEditorFactory();
0530         if(!factory) factory = QItemEditorFactory::defaultFactory();
0531         return factory->createEditor(static_cast<QVariant::Type>(userType), parent);
0532     }
0533 }
0534 
0535 void PropertiesBrowserDelegate::setEditorData(QWidget* editor, const QModelIndex& index) const
0536 {
0537     if(_editorType == SolverChoiser) {
0538         QVariant data = index.data(Qt::DisplayRole);
0539         ChoicesModel* cm = static_cast<ChoicesModel*>(_comboBox->model());
0540         QList<QStandardItem*> items = cm->findItems(data.toString());
0541         Q_ASSERT(items.count() == 1);
0542         _comboBox->setCurrentIndex( cm->indexFromItem(items[0]).row() );
0543     } else if(_editorType == ColorChoiser) {
0544         QVariant data = index.data(Qt::EditRole);
0545         QVariant data1 = index.data(Qt::DisplayRole);
0546         _updating = true;
0547         _colorButton->setColor(data.value<QColor>());
0548         _lineEdit->setText(data1.toString());
0549         _updating = false;
0550     } else if(_editorType == BoolChoiser) {
0551         bool value = index.data(Qt::EditRole).toBool();
0552         _comboBox->setCurrentIndex(value ? 1 : 0);
0553     } else QItemDelegate::setEditorData(editor, index);
0554 }
0555 
0556 void PropertiesBrowserDelegate::setModelData(QWidget* editor, QAbstractItemModel* model,
0557                    const QModelIndex& index) const
0558 {
0559     if(_editorType == SolverChoiser) {
0560         model->setData(index, _comboBox->currentText());
0561     } else if(_editorType == ColorChoiser) {
0562         model->setData(index, _lineEdit->text());
0563     } else if(_editorType == BoolChoiser) {
0564         model->setData(index, _comboBox->currentIndex());
0565     } else QItemDelegate::setModelData(editor, model, index);
0566 }
0567 
0568 void PropertiesBrowserDelegate::editorActivated()
0569 {
0570     if(!_updating) {
0571         if(_editorType == ColorChoiser) {
0572             QRgb v = _colorButton->color().rgba();
0573             _lineEdit->setText(StepCore::typeToString<StepCore::Color>(v));
0574         }
0575         emit commitData(_editor);
0576         emit closeEditor(_editor);
0577     }
0578 }
0579 
0580 class PropertiesBrowserView: public QTreeView
0581 {
0582 public:
0583     PropertiesBrowserView(QWidget* parent = nullptr);
0584 protected:
0585     void changeEvent(QEvent* event) override;
0586     void mousePressEvent(QMouseEvent* event) override;
0587     void drawBranches(QPainter *painter, const QRect &rect, const QModelIndex &index) const override;
0588 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
0589     void initViewItemOption(QStyleOptionViewItem *option) const override;
0590 #else
0591     QStyleOptionViewItem viewOptions() const override;
0592 #endif
0593     const int _windowsDecoSize;
0594     bool _macStyle;
0595 };
0596 
0597 PropertiesBrowserView::PropertiesBrowserView(QWidget* parent)
0598         : QTreeView(parent), _windowsDecoSize(9)
0599 {
0600     _macStyle = QApplication::style()->inherits("QMacStyle");
0601 }
0602 
0603 void PropertiesBrowserView::changeEvent(QEvent* event)
0604 {
0605     if(event->type() == QEvent::StyleChange)
0606         _macStyle = QApplication::style()->inherits("QMacStyle");
0607 }
0608 
0609 void PropertiesBrowserView::mousePressEvent(QMouseEvent* event)
0610 {
0611     if(columnAt(event->x()) == 0) {
0612         QModelIndex idx = indexAt(event->pos());
0613         if(idx.isValid() && !idx.parent().isValid() && idx.model()->rowCount(idx) > 0) {
0614             QRect primitive = visualRect(idx); primitive.setWidth(indentation());
0615             if (!_macStyle) {
0616                 primitive.moveLeft(primitive.left() + (primitive.width() - _windowsDecoSize)/2);
0617                 primitive.moveTop(primitive.top() + (primitive.height() - _windowsDecoSize)/2);
0618                 primitive.setWidth(_windowsDecoSize);
0619                 primitive.setHeight(_windowsDecoSize);
0620             }
0621             if(primitive.contains(event->pos())) {
0622                 setExpanded(idx, !isExpanded(idx));
0623                 
0624                 return;
0625             }
0626         }
0627     }
0628     QTreeView::mousePressEvent(event);
0629 }
0630 
0631 void PropertiesBrowserView::drawBranches(QPainter *painter, const QRect &rect, const QModelIndex &index) const
0632 {
0633     // Inspired by qt-designer code in src/components/propertyeditor/qpropertyeditor.cpp
0634 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
0635     QStyleOptionViewItem opt;
0636     initViewItemOption(&opt);
0637 #else
0638     QStyleOptionViewItem opt = viewOptions();
0639 #endif
0640 
0641     if(model()->hasChildren(index)) {
0642         opt.state |= QStyle::State_Children;
0643 
0644         QRect primitive(rect.left() + rect.width() - indentation(), rect.top(),
0645                                                     indentation(), rect.height());
0646         if(!index.parent().isValid()) {
0647             primitive.moveLeft(0);
0648         }
0649 
0650         if (!_macStyle) {
0651             primitive.moveLeft(primitive.left() + (primitive.width() - _windowsDecoSize)/2);
0652             primitive.moveTop(primitive.top() + (primitive.height() - _windowsDecoSize)/2);
0653             primitive.setWidth(_windowsDecoSize);
0654             primitive.setHeight(_windowsDecoSize);
0655         }
0656 
0657         opt.rect = primitive;
0658 
0659         if(isExpanded(index)) opt.state |= QStyle::State_Open;
0660         style()->drawPrimitive(QStyle::PE_IndicatorBranch, &opt, painter, this);
0661     }
0662 }
0663 
0664 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
0665 void PropertiesBrowserView::initViewItemOption(QStyleOptionViewItem *option) const
0666 {
0667     if (!option) {
0668         return;
0669     }
0670     QTreeView::initViewItemOption(option);
0671     option->showDecorationSelected = true;
0672 }
0673 #else
0674 QStyleOptionViewItem PropertiesBrowserView::viewOptions() const
0675 {
0676     QStyleOptionViewItem option = QTreeView::viewOptions();
0677     option.showDecorationSelected = true;
0678     return option;
0679 }
0680 #endif
0681 
0682 PropertiesBrowser::PropertiesBrowser(WorldModel* worldModel, QWidget* parent)
0683     : QDockWidget(i18n("Properties"), parent)
0684 {
0685     _worldModel = worldModel;
0686     _propertiesBrowserModel = new PropertiesBrowserModel(worldModel, this);
0687     _treeView = new PropertiesBrowserView(this);
0688 
0689     _treeView->setAllColumnsShowFocus(true);
0690     _treeView->setRootIsDecorated(false);
0691     //_treeView->setAlternatingRowColors(true);
0692     _treeView->setSelectionMode(QAbstractItemView::NoSelection);
0693     _treeView->setSelectionBehavior(QTreeView::SelectRows);
0694     _treeView->setEditTriggers(QAbstractItemView::AllEditTriggers);
0695     //_treeView->setEditTriggers(/*QAbstractItemView::CurrentChanged | */QAbstractItemView::SelectedClicked |
0696     //                           QAbstractItemView::EditKeyPressed | QAbstractItemView::AnyKeyPressed);
0697     _treeView->setItemDelegate(new PropertiesBrowserDelegate(_treeView));
0698 
0699     _treeView->setModel(_propertiesBrowserModel);
0700     worldCurrentChanged(_worldModel->worldIndex(), QModelIndex());
0701 
0702     connect(_worldModel, &QAbstractItemModel::modelReset, this, &PropertiesBrowser::worldModelReset);
0703     connect(_worldModel, &WorldModel::worldDataChanged, this, &PropertiesBrowser::worldDataChanged);
0704     connect(_worldModel, &QAbstractItemModel::rowsRemoved,
0705                                 this, &PropertiesBrowser::worldRowsRemoved);
0706 
0707     connect(_worldModel->selectionModel(), &QItemSelectionModel::currentChanged,
0708                                            this, &PropertiesBrowser::worldCurrentChanged);
0709 
0710     connect(_treeView->selectionModel(), &QItemSelectionModel::currentChanged,
0711                                            this, &PropertiesBrowser::currentChanged);
0712 
0713     //connect(_treeView, SIGNAL(doubleClicked(QModelIndex)),
0714     //                                       this, SLOT(doubleClicked(QModelIndex)));
0715 
0716     connect(_propertiesBrowserModel, &QAbstractItemModel::rowsInserted,
0717                                            this, &PropertiesBrowser::rowsInserted);
0718     connect(_propertiesBrowserModel, &QAbstractItemModel::rowsRemoved,
0719                                            this, &PropertiesBrowser::rowsRemoved);
0720 
0721     _treeView->viewport()->installEventFilter(this);
0722     //_treeView->setMouseTracking(true);
0723 
0724     setWidget(_treeView);
0725 }
0726 
0727 void PropertiesBrowser::worldModelReset()
0728 {
0729     _propertiesBrowserModel->setObject(nullptr);
0730 }
0731 
0732 void PropertiesBrowser::worldCurrentChanged(const QModelIndex& current, const QModelIndex& /*previous*/)
0733 {
0734     _propertiesBrowserModel->setObject(_worldModel->object(current));
0735     //_treeView->expandAll();
0736     for(int i=0; i<_propertiesBrowserModel->rowCount(); ++i) {
0737         QModelIndex index = _propertiesBrowserModel->index(i, 0);
0738         if(_propertiesBrowserModel->rowCount(index) <= 10) // XXX: make it configurable
0739             _treeView->setExpanded(index, true);
0740     }
0741 }
0742 
0743 void PropertiesBrowser::worldDataChanged(bool dynamicOnly)
0744 {
0745     _propertiesBrowserModel->emitDataChanged(dynamicOnly);
0746 }
0747 
0748 void PropertiesBrowser::worldRowsRemoved(const QModelIndex& parent, int start, int end)
0749 {
0750     Q_UNUSED(parent)
0751     Q_UNUSED(start)
0752     Q_UNUSED(end)
0753     if(!_worldModel->objectIndex(_propertiesBrowserModel->object()).isValid())
0754         _propertiesBrowserModel->setObject(nullptr);
0755 }
0756 
0757 void PropertiesBrowser::currentChanged(const QModelIndex& current, const QModelIndex& /*previous*/)
0758 {
0759     if(current.isValid() && current.column() == 0)
0760         _treeView->selectionModel()->setCurrentIndex(current.sibling(current.row(), 1), QItemSelectionModel::Current);
0761 }
0762 
0763 void PropertiesBrowser::rowsInserted(const QModelIndex& parent, int start, int end)
0764 {
0765     int rowCount = _propertiesBrowserModel->rowCount(parent);
0766     if(rowCount > 10 && (rowCount - (start-end+1)) <= 10) {
0767         _treeView->setExpanded(parent, false);
0768     }
0769 }
0770 
0771 void PropertiesBrowser::rowsRemoved(const QModelIndex& parent, int start, int end)
0772 {
0773     int rowCount = _propertiesBrowserModel->rowCount(parent);
0774     if(rowCount <= 10 && rowCount + (start-end+1) > 10) {
0775         _treeView->setExpanded(parent, true);
0776     }
0777 }
0778 
0779 /*
0780 void PropertiesBrowser::doubleClicked(const QModelIndex& index)
0781 {
0782     qDebug() << "doubleClicked" << endl;
0783     if(_propertiesBrowserModel->rowCount(index) > 0) {
0784         qDebug() << "   doubleClicked!!!" << endl;
0785         _treeView->setExpanded(index, !_treeView->isExpanded(index));
0786     }
0787 }
0788 */
0789 
0790 bool PropertiesBrowser::eventFilter(QObject* object, QEvent* event)
0791 {
0792     if(object == _treeView->viewport() && event->type() == QEvent::MouseButtonDblClick) {
0793         QMouseEvent* mouseEvent = static_cast<QMouseEvent*>(event);
0794         QModelIndex index = _treeView->indexAt(mouseEvent->pos());
0795         if(_propertiesBrowserModel->rowCount(index) > 0)
0796             _treeView->setExpanded(index, !_treeView->isExpanded(index));
0797     }
0798     return false;
0799 }
0800 
0801 void PropertiesBrowser::settingsChanged()
0802 {
0803     _propertiesBrowserModel->emitDataChanged(false);
0804 }
0805 
0806 #include "moc_propertiesbrowser.cpp"