1 /* This file is part of Step.
2    Copyright (C) 2007 Vladimir Kuznetsov <ks.vladimir@gmail.com>
3 
4    Step is free software; you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation; either version 2 of the License, or
7    (at your option) any later version.
8 
9    Step is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13 
14    You should have received a copy of the GNU General Public License
15    along with Step; if not, write to the Free Software
16    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
17 */
18 
19 #include "propertiesbrowser.h"
20 
21 #include "settings.h"
22 
23 #include "worldfactory.h"
24 #include "unitscalc.h"
25 
26 #include "worldmodel.h"
27 #include <stepcore/object.h>
28 #include <stepcore/solver.h>
29 #include <stepcore/types.h>
30 
31 #include <QAbstractItemModel>
32 #include <QApplication>
33 #include <QHBoxLayout>
34 #include <QItemEditorFactory>
35 #include <QMouseEvent>
36 #include <QTreeView>
37 
38 #include <KColorButton>
39 #include <KComboBox>
40 #include <KLineEdit>
41 #include <KLocalizedString>
42 
43 #include "choicesmodel.h"
44 
45 class PropertiesBrowserModel: public QAbstractItemModel
46 {
47 public:
48     PropertiesBrowserModel(WorldModel* worldModel, QObject* parent = 0);
49 
50     QVariant data(const QModelIndex &index, int role) const override;
51     QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;
52     QModelIndex parent(const QModelIndex &index) const override;
53     int rowCount(const QModelIndex &parent = QModelIndex()) const override;
54     int columnCount(const QModelIndex &parent = QModelIndex()) const override;
55     QVariant headerData(int section, Qt::Orientation orientation,
56                          int role = Qt::DisplayRole) const override;
57 
58     Qt::ItemFlags flags(const QModelIndex &index) const override;
59     bool setData(const QModelIndex &index, const QVariant &value, int role) override;
60 
61     void setObject(StepCore::Object* object);
object()62     StepCore::Object* object() { return _object; }
63 
64     void emitDataChanged(bool dynamicOnly);
65 
66 protected:
67     WorldModel* _worldModel;
68     StepCore::Object*       _object;
69     StepCore::Item*         _item;
70     StepCore::ObjectErrors* _objectErrors;
71     ChoicesModel* _solverChoices;
72     QList<int> _subRows;
73 };
74 
PropertiesBrowserModel(WorldModel * worldModel,QObject * parent)75 PropertiesBrowserModel::PropertiesBrowserModel(WorldModel* worldModel, QObject* parent)
76     : QAbstractItemModel(parent), _worldModel(worldModel), _object(NULL)
77 {
78     _solverChoices = new ChoicesModel(this);
79 
80     // Prepare solver list
81     foreach(const QString &name, _worldModel->worldFactory()->orderedMetaObjects()) {
82         const StepCore::MetaObject* metaObject = _worldModel->worldFactory()->metaObject(name);
83         if(metaObject->isAbstract()) continue;
84         if(!metaObject->inherits(StepCore::Solver::staticMetaObject())) continue;
85         QString solverName = QString(metaObject->className()).remove(QStringLiteral("Solver"));
86         QStandardItem* item = new QStandardItem(solverName);
87         item->setToolTip(QString(metaObject->descriptionTr()));
88         _solverChoices->appendRow(item);
89     }
90 }
91 
setObject(StepCore::Object * object)92 void PropertiesBrowserModel::setObject(StepCore::Object* object)
93 {
94     beginResetModel();
95     _object = object;
96 
97     _subRows.clear();
98     if(_object != NULL) {
99         _worldModel->simulationPause();
100 
101         _item = dynamic_cast<StepCore::Item*>(_object);
102         if(_item) {
103             if(_item->world()->errorsCalculation()) _objectErrors = _item->objectErrors();
104             else _objectErrors = _item->tryGetObjectErrors();
105         } else {
106             _objectErrors = NULL;
107         }
108 
109         for(int i=0; i<_object->metaObject()->propertyCount(); ++i) {
110             const StepCore::MetaProperty* p = _object->metaObject()->property(i);
111             if(p->userTypeId() == qMetaTypeId<StepCore::Vector2dList >())
112                 _subRows << p->readVariant(_object).value<StepCore::Vector2dList >().size();
113             else _subRows << 0;
114         }
115     }
116 
117     endResetModel();
118 }
119 
emitDataChanged(bool dynamicOnly)120 void PropertiesBrowserModel::emitDataChanged(bool dynamicOnly)
121 {
122     if(_object == NULL) return;
123 
124     _worldModel->simulationPause();
125 
126     _item = dynamic_cast<StepCore::Item*>(_object);
127     if(_item) {
128         if(_item->world()->errorsCalculation()) _objectErrors = _item->objectErrors();
129         else _objectErrors = _item->tryGetObjectErrors();
130     } else {
131         _objectErrors = NULL;
132     }
133 
134     for(int i=0; i<_object->metaObject()->propertyCount(); i++) {
135         const StepCore::MetaProperty* p = _object->metaObject()->property(i);
136         if(dynamicOnly && !p->isDynamic()) continue;
137         if(p->userTypeId() == qMetaTypeId<StepCore::Vector2dList >()) {
138             int r = p->readVariant(_object).value<StepCore::Vector2dList >().size();
139             if(r > _subRows[i]) {
140                 beginInsertRows(index(i, 0), _subRows[i], r-1);
141                 _subRows[i] = r;
142                 endInsertRows();
143             } else if(r < _subRows[i]) {
144                 beginRemoveRows(index(i, 0), r, _subRows[i]-1);
145                 _subRows[i] = r;
146                 endRemoveRows();
147             }
148             if(r != 0) emit dataChanged(index(0,0,index(i,0)), index(r-1,1,index(i,0))); // XXX?
149         }
150         emit dataChanged(index(i,1), index(i,1));
151     }
152     //emit dataChanged(index(0,1), index(rowCount()-1,1));
153 }
154 
data(const QModelIndex & index,int role) const155 QVariant PropertiesBrowserModel::data(const QModelIndex &index, int role) const
156 {
157     if(_object == NULL) return QVariant();
158 
159     if(!index.isValid()) return QVariant();
160 
161     if(index.internalId() == 0) {
162         const StepCore::MetaProperty* p = _object->metaObject()->property(index.row());
163         if(role == Qt::DisplayRole || role == Qt::EditRole) {
164             if(index.column() == 0) return p->nameTr();
165             else if(index.column() == 1) {
166                 _worldModel->simulationPause();
167 
168                 // Solver type combobox ?
169                 if(index.row() == 1 && dynamic_cast<StepCore::Solver*>(_object)) {
170                     if(role == Qt::DisplayRole) return p->readString(_object).remove(QStringLiteral("Solver"));
171                     else return QVariant::fromValue(_solverChoices);
172                 }
173 
174                 if(p->userTypeId() == QMetaType::Double ||
175                     p->userTypeId() == qMetaTypeId<StepCore::Vector2d>() ||
176                     p->userTypeId() == qMetaTypeId<StepCore::Vector2dList >()) {
177                     return _worldModel->formatProperty(_object, _objectErrors, p,
178                                 role == Qt::EditRole ? WorldModel::FormatEditable : WorldModel::FormatFlags(0));
179                 } else if(p->userTypeId() == qMetaTypeId<StepCore::Object*>()) {
180                     return _worldModel->formatName(p->readVariant(_object).value<StepCore::Object*>());
181                 } else if(p->userTypeId() == qMetaTypeId<StepCore::Color>()) {
182                     Q_ASSERT( !_objectErrors || !_objectErrors->metaObject()->property(p->name() + "Variance") );
183                     Q_ASSERT( p->units().isEmpty() );
184                     if(role == Qt::EditRole)
185                         return QColor::fromRgba(p->readVariant(_object).value<StepCore::Color>());
186                     else
187                         return p->readString(_object);
188                 } else if(p->userTypeId() == QMetaType::Bool) {
189                     Q_ASSERT( !_objectErrors || !_objectErrors->metaObject()->property(p->name() + "Variance") );
190                     Q_ASSERT( p->units().isEmpty() );
191                     return p->readVariant(_object);
192                 } else {
193                     // default type
194                     // XXX: add error information
195                     //if(pe) error = QString::fromUtf8(" ± ").append(pe->readString(_objectErrors)).append(units);
196                     //if(pv) qDebug() << "Unhandled property variance type" << endl;
197                     Q_ASSERT( !_objectErrors || !_objectErrors->metaObject()->property(p->name() + "Variance") );
198                     Q_ASSERT( p->units().isEmpty() );
199                     if(role == Qt::EditRole) return _worldModel->formatProperty(_object, _objectErrors, p,
200                                         WorldModel::FormatHideUnits | WorldModel::FormatEditable);
201                     else return _worldModel->formatProperty(_object, _objectErrors, p, WorldModel::FormatHideUnits);
202                 }
203                 ///*if(p->userTypeId() < (int) QVariant::UserType) return p->readVariant(_object);
204                 //else*/ return p->readString(_object); // XXX: default delegate for double looks ugly!
205             }
206         } else if(index.column() == 1 && role == Qt::ForegroundRole) {
207             if(!p->isWritable()) {
208                 if(index.row() != 1 || !dynamic_cast<StepCore::Solver*>(_object))
209                     return QBrush(Qt::darkGray); // XXX: how to get scheme color ?
210             }
211         } else if(role == Qt::ToolTipRole) {
212             if(index.row() == 1 && index.column() == 1 && dynamic_cast<StepCore::Solver*>(_object)) {
213                 return _object->metaObject()->descriptionTr();
214             }
215             return p->descriptionTr(); // XXX: translation
216         } else if(index.column() == 1 && role == Qt::DecorationRole &&
217                     p->userTypeId() == qMetaTypeId<StepCore::Color>()) {
218             QPixmap pix(8, 8);
219             pix.fill(QColor::fromRgba(p->readVariant(_object).value<StepCore::Color>()));
220             return pix;
221         } else if(index.column() == 0 && role == Qt::DecorationRole &&
222                     p->userTypeId() == qMetaTypeId<StepCore::Vector2dList >() &&
223                     rowCount(index) > 0) {
224             // XXX: A hack to have nested properties shifted
225             static QPixmap empySmallPix;
226             if(empySmallPix.isNull()) {
227                 empySmallPix = QPixmap(8,8); //XXX
228                 empySmallPix.fill(QColor(0,0,0,0));
229             }
230             return empySmallPix;
231         }
232     } else { // index.internalId() != 0
233         const StepCore::MetaProperty* p = _object->metaObject()->property(index.internalId()-1);
234         if(role == Qt::DisplayRole || role == Qt::EditRole) {
235             if(index.column() == 0) return QStringLiteral("%1[%2]").arg(p->nameTr()).arg(index.row());
236             else if(index.column() == 1) {
237 #ifdef __GNUC__
238 #warning XXX: add error information for lists
239 #endif
240                 QString units;
241                 if(role == Qt::DisplayRole && !p->units().isEmpty())
242                     units.append(" [").append(p->units()).append("]");
243 #ifdef STEP_WITH_UNITSCALC
244 //                else if(role == Qt::EditRole && !p->units().isEmpty())
245 //                    units.append(" ").append(p->units());
246 #endif
247                 int pr = Settings::floatDisplayPrecision();
248                 //int pr = role == Qt::DisplayRole ? Settings::floatDisplayPrecision() : 16;
249                 _worldModel->simulationPause();
250                 StepCore::Vector2d v =
251                         p->readVariant(_object).value<StepCore::Vector2dList >()[index.row()];
252                 return QStringLiteral("(%1,%2)%3").arg(v[0], 0, 'g', pr).arg(v[1], 0, 'g', pr).arg(units);
253             }
254         } else if(role == Qt::ForegroundRole && index.column() == 1) {
255             if(!p->isWritable()) {
256                 return QBrush(Qt::darkGray); // XXX: how to get scheme color ?
257             }
258         } else if(role == Qt::ToolTipRole) {
259             return p->descriptionTr(); // XXX: translation
260         }
261     }
262 
263     return QVariant();
264 }
265 
setData(const QModelIndex & index,const QVariant & value,int role)266 bool PropertiesBrowserModel::setData(const QModelIndex &index, const QVariant &value, int role)
267 {
268     if(_object == NULL) return false;
269 
270     if(index.isValid() && index.column() == 1 && role == Qt::EditRole) {
271         _worldModel->simulationPause();
272         if(index.internalId() == 0) {
273             if(index.row() == 0) { // name // XXX: do it more generally
274                 if(!_worldModel->checkUniqueName(value.toString())) return false; // XXX: error message
275             }
276             if(index.row() == 1 && dynamic_cast<StepCore::Solver*>(_object)) {
277                 if(value.toString() != _object->metaObject()->className()) {
278                     beginResetModel();
279                     _worldModel->beginMacro(i18n("Change solver type"));
280                     _object = _worldModel->newSolver(value.toString() + "Solver");
281                     Q_ASSERT(_object != NULL);
282                     _worldModel->endMacro();
283                     endResetModel();
284                 }
285             } else {
286                 const StepCore::MetaProperty* p = _object->metaObject()->property(index.row());
287                 const StepCore::MetaProperty* pv = _objectErrors ?
288                         _objectErrors->metaObject()->property(p->name() + "Variance") : NULL;
289 
290                 if(p->userTypeId() == qMetaTypeId<StepCore::Object*>()) {
291                     Q_ASSERT(!pv);
292                     StepCore::Object* obj = _worldModel->world()->object(value.toString());
293                     if(!obj) return false;
294                     _worldModel->beginMacro(i18n("Change %1.%2", _object->name(), p->nameTr()));
295                     _worldModel->setProperty(_object, p, QVariant::fromValue(obj));
296                     _worldModel->endMacro();
297                     return true;
298                 } else if(p->userTypeId() == qMetaTypeId<StepCore::Color>()) {
299                     Q_ASSERT(!pv);
300                     _worldModel->beginMacro(i18n("Change %1.%2", _object->name(), p->nameTr()));
301                     _worldModel->setProperty(_object, p, value.type() == QVariant::String ? value :
302                                     QVariant::fromValue(StepCore::Color(value.value<QColor>().rgba())));
303                     _worldModel->endMacro();
304                     return true;
305                 } else if(p->userTypeId() == qMetaTypeId<bool>()) {
306                     Q_ASSERT(!pv);
307                     _worldModel->beginMacro(i18n("Change %1.%2", _object->name(), p->nameTr()));
308                     _worldModel->setProperty(_object, p, value);
309                     _worldModel->endMacro();
310                     return true;
311                 } else if(p->userTypeId() == qMetaTypeId<QString>()) {
312                     Q_ASSERT(!pv);
313                     if(index.row() == 0)
314                         _worldModel->beginMacro(i18n("Rename %1 to %2", _object->name(), value.toString()));
315                     else
316                         _worldModel->beginMacro(i18n("Change %1.%2", _object->name(), p->nameTr()));
317                     _worldModel->setProperty(_object, p, value);
318                     _worldModel->endMacro();
319                     return true;
320                 }
321 
322                 QVariant v = value;
323                 QVariant vv;
324 
325                 // Try to find ± sign
326                 if(v.canConvert(QVariant::String)) {
327                     QString str = v.toString();
328                     int idx = str.indexOf(QStringLiteral("±"));
329                     if(idx >= 0) {
330                         v = str.left(idx);
331                         vv = str.mid(idx+1);
332                     }
333                 }
334 
335 #ifdef STEP_WITH_UNITSCALC
336                     // Convert units
337                     if(p->userTypeId() == QMetaType::Double) {
338                         double number = 0;
339                         if(UnitsCalc::self()->parseNumber(v.toString(), p->units(), number)) {
340                             v = number;
341                         } else {
342                             return false;
343                         }
344                         if(vv.isValid()) {
345                             if(UnitsCalc::self()->parseNumber(vv.toString(), p->units(), number)) {
346                                 vv = number;
347                             } else {
348                                 return false;
349                             }
350                         }
351                     }
352 #endif
353 
354                 if(vv.isValid()) { // We have got variance value
355                     if(!pv) {
356                         // check if _objectErrors can be created
357                         // and current property variance could be set
358                         const StepCore::MetaObject* me =
359                             _worldModel->worldFactory()->metaObject(
360                                 _object->metaObject()->className() + "Errors");
361                         if(!_item || !me || !me->property(p->name() + "Variance"))
362                             return false;
363                     }
364 
365                     bool ok = true;
366                     // Calc variance = square(error)
367                     if(p->userTypeId() == QMetaType::Double) {
368                         vv = StepCore::square(vv.toDouble(&ok));
369                     } else if(p->userTypeId() == qMetaTypeId<StepCore::Vector2d>()) {
370                         StepCore::Vector2d svv;
371                         svv = StepCore::stringToType<StepCore::Vector2d>(vv.toString(), &ok);
372                         svv[0] *= svv[0]; svv[1] *= svv[1];
373                         vv = QVariant::fromValue(svv);
374                     /* XXX
375                      * {} else if(p->userTypeId() == qMetaTypeId<StepCore::Vector2dList >())
376                         ve = QVariant::fromValue(StepCore::Vector2dList());*/
377                     } else {
378 //                         qDebug() << "Unhandled property variance type" << endl;
379                         return false;
380                     }
381                     if(!ok) return false;
382 
383                 } else { // vv.isValid()
384                     if(pv) { // We have to zero variance since we got exact value
385                         if(p->userTypeId() == QMetaType::Double) {
386                             vv = 0;
387                         } else if(p->userTypeId() == qMetaTypeId<StepCore::Vector2d>()) {
388                             StepCore::Vector2d svv = StepCore::Vector2d::Zero();
389                             vv = QVariant::fromValue(svv);
390                         /* XXX
391                          * } else if(p->userTypeId() == qMetaTypeId<StepCore::Vector2dList >())
392                             ve = QVariant::fromValue(StepCore::Vector2dList());*/
393                         } else {
394                             qWarning("Unhandled property variance type");
395                             return false;
396                         }
397                     }
398                 }
399 
400                 _worldModel->beginMacro(i18n("Change %1.%2", _object->name(), p->nameTr()));
401                 _worldModel->setProperty(_object, p, v);
402                 if(vv.isValid() && !pv) {
403                     // XXX: Make this undo-able
404                     _objectErrors = _item->objectErrors();
405                     pv = _objectErrors->metaObject()->property(p->name() + "Variance");
406                 }
407                 if(pv) _worldModel->setProperty(_objectErrors, pv, vv);
408                 _worldModel->endMacro();
409             }
410         } else {
411             const StepCore::MetaProperty* p = _object->metaObject()->property(index.internalId()-1);
412             StepCore::Vector2dList v =
413                         p->readVariant(_object).value<StepCore::Vector2dList >();
414             bool ok;
415             v[index.row()] = StepCore::stringToType<StepCore::Vector2d>(value.toString(), &ok);
416             if(!ok) return true; // dataChanged should be emitted anyway
417             _worldModel->beginMacro(i18n("Change %1.%2", _object->name(), p->nameTr()));
418             _worldModel->setProperty(_object, p, QVariant::fromValue(v));
419             _worldModel->endMacro();
420         }
421         return true;
422     }
423     return false;
424 }
425 
index(int row,int column,const QModelIndex & parent) const426 QModelIndex PropertiesBrowserModel::index(int row, int column, const QModelIndex &parent) const
427 {
428     if(_object == NULL) return QModelIndex();
429     if(!parent.isValid()) return createIndex(row, column);
430 
431     if(parent.internalId() == 0 && _subRows[parent.row()] != 0)
432         return createIndex(row, column, parent.row()+1);
433 
434     return QModelIndex();
435 }
436 
parent(const QModelIndex & index) const437 QModelIndex PropertiesBrowserModel::parent(const QModelIndex& index) const
438 {
439     if(index.isValid() && index.internalId() != 0)
440         return createIndex(index.internalId()-1, 0, nullptr);
441     return QModelIndex();
442 }
443 
rowCount(const QModelIndex & parent) const444 int PropertiesBrowserModel::rowCount(const QModelIndex &parent) const
445 {
446     if(_object == NULL) return 0;
447     else if(parent.isValid()) {
448         if(parent.column() == 0 && parent.internalId() == 0) return _subRows[parent.row()];
449         return 0;
450     }
451     else return _object->metaObject()->propertyCount();
452 }
453 
columnCount(const QModelIndex &) const454 int PropertiesBrowserModel::columnCount(const QModelIndex& /*parent*/) const
455 {
456     return 2;
457 }
458 
headerData(int section,Qt::Orientation,int role) const459 QVariant PropertiesBrowserModel::headerData(int section, Qt::Orientation /*orientation*/,
460                                       int role) const
461 {
462     if (role != Qt::DisplayRole) return QVariant();
463     switch(section) {
464         case 0: return i18n("Property");
465         case 1: return i18n("Value");
466         default: return QVariant();
467     }
468 }
469 
flags(const QModelIndex & index) const470 Qt::ItemFlags PropertiesBrowserModel::flags(const QModelIndex &index) const
471 {
472     Qt::ItemFlags flags = QAbstractItemModel::flags(index);
473 
474     if(_object && index.isValid() && index.column() == 1) {
475         if(index.internalId() == 0) {
476             if(_object->metaObject()->property(index.row())->isWritable() ||
477                 (index.row()==1 && dynamic_cast<StepCore::Solver*>(_object))) flags |= Qt::ItemIsEditable;
478         } else {
479             if(_object->metaObject()->property(index.internalId()-1)->isWritable()) flags |= Qt::ItemIsEditable;
480         }
481     }
482 
483     return flags;
484 }
485 
createEditor(QWidget * parent,const QStyleOptionViewItem &,const QModelIndex & index) const486 QWidget* PropertiesBrowserDelegate::createEditor(QWidget* parent,
487                 const QStyleOptionViewItem& /*option*/, const QModelIndex& index) const
488 {
489     QVariant data = index.data(Qt::EditRole);
490     int userType = data.userType();
491     if(userType == qMetaTypeId<ChoicesModel*>()) {
492         KComboBox* editor = new KComboBox(parent);
493         editor->setModel(data.value<ChoicesModel*>());
494         connect(editor, SIGNAL(activated(int)), this, SLOT(editorActivated()));
495         editor->installEventFilter(const_cast<PropertiesBrowserDelegate*>(this));
496         const_cast<PropertiesBrowserDelegate*>(this)->_editor = editor;
497         const_cast<PropertiesBrowserDelegate*>(this)->_comboBox = editor;
498         const_cast<PropertiesBrowserDelegate*>(this)->_editorType = SolverChoiser;
499         return editor;
500 
501     } else if(userType == QMetaType::QColor) {
502         QWidget* editor = new QWidget(parent);
503 
504         KLineEdit* lineEdit = new KLineEdit(editor);
505         lineEdit->setFrame(false);
506 
507         KColorButton* colorButton = new KColorButton(editor);
508         // XXX: do not use hard-coded pixel sizes
509         colorButton->setMinimumWidth(15);
510         colorButton->setMaximumWidth(15);
511         connect(colorButton, &KColorButton::changed, this, &PropertiesBrowserDelegate::editorActivated);
512 
513         QHBoxLayout* layout = new QHBoxLayout(editor);
514         layout->setContentsMargins(0,0,0,0);
515         layout->setSpacing(0);
516         layout->addWidget(lineEdit);
517         layout->addWidget(colorButton);
518 
519         editor->setFocusProxy(lineEdit);
520         editor->installEventFilter(const_cast<PropertiesBrowserDelegate*>(this));
521 
522         const_cast<PropertiesBrowserDelegate*>(this)->_editor = editor;
523         const_cast<PropertiesBrowserDelegate*>(this)->_colorButton = colorButton;
524         const_cast<PropertiesBrowserDelegate*>(this)->_lineEdit = lineEdit;
525         const_cast<PropertiesBrowserDelegate*>(this)->_editorType = ColorChoiser;
526         return editor;
527 
528     } else if(userType == QMetaType::Bool) {
529         KComboBox* editor = new KComboBox(parent);
530         editor->addItem(i18n("false"));
531         editor->addItem(i18n("true"));
532         connect(editor, SIGNAL(activated(int)), this, SLOT(editorActivated()));
533         editor->installEventFilter(const_cast<PropertiesBrowserDelegate*>(this));
534         const_cast<PropertiesBrowserDelegate*>(this)->_editor = editor;
535         const_cast<PropertiesBrowserDelegate*>(this)->_comboBox = editor;
536         const_cast<PropertiesBrowserDelegate*>(this)->_editorType = BoolChoiser;
537         return editor;
538 
539     } else {
540         const_cast<PropertiesBrowserDelegate*>(this)->_editorType = Standard;
541         const QItemEditorFactory *factory = itemEditorFactory();
542         if(!factory) factory = QItemEditorFactory::defaultFactory();
543         return factory->createEditor(static_cast<QVariant::Type>(userType), parent);
544     }
545 }
546 
setEditorData(QWidget * editor,const QModelIndex & index) const547 void PropertiesBrowserDelegate::setEditorData(QWidget* editor, const QModelIndex& index) const
548 {
549     if(_editorType == SolverChoiser) {
550         QVariant data = index.data(Qt::DisplayRole);
551         ChoicesModel* cm = static_cast<ChoicesModel*>(_comboBox->model());
552         QList<QStandardItem*> items = cm->findItems(data.toString());
553         Q_ASSERT(items.count() == 1);
554         _comboBox->setCurrentIndex( cm->indexFromItem(items[0]).row() );
555     } else if(_editorType == ColorChoiser) {
556         QVariant data = index.data(Qt::EditRole);
557         QVariant data1 = index.data(Qt::DisplayRole);
558         _updating = true;
559         _colorButton->setColor(data.value<QColor>());
560         _lineEdit->setText(data1.toString());
561         _updating = false;
562     } else if(_editorType == BoolChoiser) {
563         bool value = index.data(Qt::EditRole).toBool();
564         _comboBox->setCurrentIndex(value ? 1 : 0);
565     } else QItemDelegate::setEditorData(editor, index);
566 }
567 
setModelData(QWidget * editor,QAbstractItemModel * model,const QModelIndex & index) const568 void PropertiesBrowserDelegate::setModelData(QWidget* editor, QAbstractItemModel* model,
569                    const QModelIndex& index) const
570 {
571     if(_editorType == SolverChoiser) {
572         model->setData(index, _comboBox->currentText());
573     } else if(_editorType == ColorChoiser) {
574         model->setData(index, _lineEdit->text());
575     } else if(_editorType == BoolChoiser) {
576         model->setData(index, _comboBox->currentIndex());
577     } else QItemDelegate::setModelData(editor, model, index);
578 }
579 
editorActivated()580 void PropertiesBrowserDelegate::editorActivated()
581 {
582     if(!_updating) {
583         if(_editorType == ColorChoiser) {
584             QRgb v = _colorButton->color().rgba();
585             _lineEdit->setText(StepCore::typeToString<StepCore::Color>(v));
586         }
587         emit commitData(_editor);
588         emit closeEditor(_editor);
589     }
590 }
591 
592 class PropertiesBrowserView: public QTreeView
593 {
594 public:
595     PropertiesBrowserView(QWidget* parent = 0);
596 protected:
597     void changeEvent(QEvent* event) override;
598     void mousePressEvent(QMouseEvent* event) override;
599     void drawBranches(QPainter *painter, const QRect &rect, const QModelIndex &index) const override;
600     QStyleOptionViewItem viewOptions() const override;
601     const int _windowsDecoSize;
602     bool _macStyle;
603 };
604 
PropertiesBrowserView(QWidget * parent)605 PropertiesBrowserView::PropertiesBrowserView(QWidget* parent)
606         : QTreeView(parent), _windowsDecoSize(9)
607 {
608     _macStyle = QApplication::style()->inherits("QMacStyle");
609 }
610 
changeEvent(QEvent * event)611 void PropertiesBrowserView::changeEvent(QEvent* event)
612 {
613     if(event->type() == QEvent::StyleChange)
614         _macStyle = QApplication::style()->inherits("QMacStyle");
615 }
616 
mousePressEvent(QMouseEvent * event)617 void PropertiesBrowserView::mousePressEvent(QMouseEvent* event)
618 {
619     if(columnAt(event->x()) == 0) {
620         QModelIndex idx = indexAt(event->pos());
621         if(idx.isValid() && !idx.parent().isValid() && idx.model()->rowCount(idx) > 0) {
622             QRect primitive = visualRect(idx); primitive.setWidth(indentation());
623             if (!_macStyle) {
624                 primitive.moveLeft(primitive.left() + (primitive.width() - _windowsDecoSize)/2);
625                 primitive.moveTop(primitive.top() + (primitive.height() - _windowsDecoSize)/2);
626                 primitive.setWidth(_windowsDecoSize);
627                 primitive.setHeight(_windowsDecoSize);
628             }
629             if(primitive.contains(event->pos())) {
630                 setExpanded(idx, !isExpanded(idx));
631 
632                 return;
633             }
634         }
635     }
636     QTreeView::mousePressEvent(event);
637 }
638 
drawBranches(QPainter * painter,const QRect & rect,const QModelIndex & index) const639 void PropertiesBrowserView::drawBranches(QPainter *painter, const QRect &rect, const QModelIndex &index) const
640 {
641     // Inspired by qt-designer code in src/components/propertyeditor/qpropertyeditor.cpp
642     QStyleOptionViewItem opt = viewOptions();
643 
644     if(model()->hasChildren(index)) {
645         opt.state |= QStyle::State_Children;
646 
647         QRect primitive(rect.left() + rect.width() - indentation(), rect.top(),
648                                                     indentation(), rect.height());
649         if(!index.parent().isValid()) {
650             primitive.moveLeft(0);
651         }
652 
653         if (!_macStyle) {
654             primitive.moveLeft(primitive.left() + (primitive.width() - _windowsDecoSize)/2);
655             primitive.moveTop(primitive.top() + (primitive.height() - _windowsDecoSize)/2);
656             primitive.setWidth(_windowsDecoSize);
657             primitive.setHeight(_windowsDecoSize);
658         }
659 
660         opt.rect = primitive;
661 
662         if(isExpanded(index)) opt.state |= QStyle::State_Open;
663         style()->drawPrimitive(QStyle::PE_IndicatorBranch, &opt, painter, this);
664     }
665 }
666 
viewOptions() const667 QStyleOptionViewItem PropertiesBrowserView::viewOptions() const
668 {
669     QStyleOptionViewItem option = QTreeView::viewOptions();
670     option.showDecorationSelected = true;
671     return option;
672 }
673 
PropertiesBrowser(WorldModel * worldModel,QWidget * parent)674 PropertiesBrowser::PropertiesBrowser(WorldModel* worldModel, QWidget* parent)
675     : QDockWidget(i18n("Properties"), parent)
676 {
677     _worldModel = worldModel;
678     _propertiesBrowserModel = new PropertiesBrowserModel(worldModel, this);
679     _treeView = new PropertiesBrowserView(this);
680 
681     _treeView->setAllColumnsShowFocus(true);
682     _treeView->setRootIsDecorated(false);
683     //_treeView->setAlternatingRowColors(true);
684     _treeView->setSelectionMode(QAbstractItemView::NoSelection);
685     _treeView->setSelectionBehavior(QTreeView::SelectRows);
686     _treeView->setEditTriggers(QAbstractItemView::AllEditTriggers);
687     //_treeView->setEditTriggers(/*QAbstractItemView::CurrentChanged | */QAbstractItemView::SelectedClicked |
688     //                           QAbstractItemView::EditKeyPressed | QAbstractItemView::AnyKeyPressed);
689     _treeView->setItemDelegate(new PropertiesBrowserDelegate(_treeView));
690 
691     _treeView->setModel(_propertiesBrowserModel);
692     worldCurrentChanged(_worldModel->worldIndex(), QModelIndex());
693 
694     connect(_worldModel, &QAbstractItemModel::modelReset, this, &PropertiesBrowser::worldModelReset);
695     connect(_worldModel, &WorldModel::worldDataChanged, this, &PropertiesBrowser::worldDataChanged);
696     connect(_worldModel, &QAbstractItemModel::rowsRemoved,
697                                 this, &PropertiesBrowser::worldRowsRemoved);
698 
699     connect(_worldModel->selectionModel(), &QItemSelectionModel::currentChanged,
700                                            this, &PropertiesBrowser::worldCurrentChanged);
701 
702     connect(_treeView->selectionModel(), &QItemSelectionModel::currentChanged,
703                                            this, &PropertiesBrowser::currentChanged);
704 
705     //connect(_treeView, SIGNAL(doubleClicked(QModelIndex)),
706     //                                       this, SLOT(doubleClicked(QModelIndex)));
707 
708     connect(_propertiesBrowserModel, &QAbstractItemModel::rowsInserted,
709                                            this, &PropertiesBrowser::rowsInserted);
710     connect(_propertiesBrowserModel, &QAbstractItemModel::rowsRemoved,
711                                            this, &PropertiesBrowser::rowsRemoved);
712 
713     _treeView->viewport()->installEventFilter(this);
714     //_treeView->setMouseTracking(true);
715 
716     setWidget(_treeView);
717 }
718 
worldModelReset()719 void PropertiesBrowser::worldModelReset()
720 {
721     _propertiesBrowserModel->setObject(NULL);
722 }
723 
worldCurrentChanged(const QModelIndex & current,const QModelIndex &)724 void PropertiesBrowser::worldCurrentChanged(const QModelIndex& current, const QModelIndex& /*previous*/)
725 {
726     _propertiesBrowserModel->setObject(_worldModel->object(current));
727     //_treeView->expandAll();
728     for(int i=0; i<_propertiesBrowserModel->rowCount(); ++i) {
729         QModelIndex index = _propertiesBrowserModel->index(i, 0);
730         if(_propertiesBrowserModel->rowCount(index) <= 10) // XXX: make it configurable
731             _treeView->setExpanded(index, true);
732     }
733 }
734 
worldDataChanged(bool dynamicOnly)735 void PropertiesBrowser::worldDataChanged(bool dynamicOnly)
736 {
737     _propertiesBrowserModel->emitDataChanged(dynamicOnly);
738 }
739 
worldRowsRemoved(const QModelIndex & parent,int start,int end)740 void PropertiesBrowser::worldRowsRemoved(const QModelIndex& parent, int start, int end)
741 {
742     Q_UNUSED(parent)
743     Q_UNUSED(start)
744     Q_UNUSED(end)
745     if(!_worldModel->objectIndex(_propertiesBrowserModel->object()).isValid())
746         _propertiesBrowserModel->setObject(NULL);
747 }
748 
currentChanged(const QModelIndex & current,const QModelIndex &)749 void PropertiesBrowser::currentChanged(const QModelIndex& current, const QModelIndex& /*previous*/)
750 {
751     if(current.isValid() && current.column() == 0)
752         _treeView->selectionModel()->setCurrentIndex(current.sibling(current.row(), 1), QItemSelectionModel::Current);
753 }
754 
rowsInserted(const QModelIndex & parent,int start,int end)755 void PropertiesBrowser::rowsInserted(const QModelIndex& parent, int start, int end)
756 {
757     int rowCount = _propertiesBrowserModel->rowCount(parent);
758     if(rowCount > 10 && (rowCount - (start-end+1)) <= 10) {
759         _treeView->setExpanded(parent, false);
760     }
761 }
762 
rowsRemoved(const QModelIndex & parent,int start,int end)763 void PropertiesBrowser::rowsRemoved(const QModelIndex& parent, int start, int end)
764 {
765     int rowCount = _propertiesBrowserModel->rowCount(parent);
766     if(rowCount <= 10 && rowCount + (start-end+1) > 10) {
767         _treeView->setExpanded(parent, true);
768     }
769 }
770 
771 /*
772 void PropertiesBrowser::doubleClicked(const QModelIndex& index)
773 {
774     qDebug() << "doubleClicked" << endl;
775     if(_propertiesBrowserModel->rowCount(index) > 0) {
776         qDebug() << "   doubleClicked!!!" << endl;
777         _treeView->setExpanded(index, !_treeView->isExpanded(index));
778     }
779 }
780 */
781 
eventFilter(QObject * object,QEvent * event)782 bool PropertiesBrowser::eventFilter(QObject* object, QEvent* event)
783 {
784     if(object == _treeView->viewport() && event->type() == QEvent::MouseButtonDblClick) {
785         QMouseEvent* mouseEvent = static_cast<QMouseEvent*>(event);
786         QModelIndex index = _treeView->indexAt(mouseEvent->pos());
787         if(_propertiesBrowserModel->rowCount(index) > 0)
788             _treeView->setExpanded(index, !_treeView->isExpanded(index));
789     }
790     return false;
791 }
792 
settingsChanged()793 void PropertiesBrowser::settingsChanged()
794 {
795     _propertiesBrowserModel->emitDataChanged(false);
796 }
797 
798 
799