File indexing completed on 2024-04-28 07:39:42
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 "worldmodel.h" 0008 0009 #include "clipboard.h" 0010 #include "simulationthread.h" 0011 #include "worldgraphics.h" 0012 0013 #include "settings.h" 0014 0015 #include "worldfactory.h" 0016 #include <stepcore/world.h> 0017 #include <stepcore/xmlfile.h> 0018 #include <stepcore/eulersolver.h> 0019 #include <stepcore/collisionsolver.h> 0020 #include <stepcore/constraintsolver.h> 0021 #include <stepcore/types.h> 0022 0023 #include <QApplication> 0024 #include <QItemSelectionModel> 0025 #include <QMenu> 0026 #include <QTimer> 0027 #include <QUndoStack> 0028 0029 #include <KLocalizedString> 0030 0031 class CommandEditProperty: public QUndoCommand 0032 { 0033 public: 0034 CommandEditProperty(WorldModel* worldModel, StepCore::Object* object, 0035 const StepCore::MetaProperty* property, const QVariant& newValue, bool merge); 0036 0037 int id() const override { return _merge ? 1 : -1; } 0038 bool mergeWith(const QUndoCommand* command) override; 0039 0040 void redo() override; 0041 void undo() override; 0042 0043 protected: 0044 /* It's important to properly compress commands 0045 * or the stack becomes too big and slow */ 0046 struct EditProperty { 0047 StepCore::Object* object; 0048 const StepCore::MetaProperty* property; 0049 QVariant oldValue; 0050 QVariant newValue; 0051 }; 0052 0053 bool _merge; 0054 WorldModel* _worldModel; 0055 QList<EditProperty> _commands; 0056 //QList<StepCore::Object*> _objects; 0057 }; 0058 0059 CommandEditProperty::CommandEditProperty(WorldModel* worldModel, StepCore::Object* object, 0060 const StepCore::MetaProperty* property, const QVariant& newValue, bool merge) 0061 : _merge(merge), _worldModel(worldModel) 0062 { 0063 //qDebug() << "CommandEditProperty: " << object->name() << " " << property->name() << " " << newValue.toString() << endl; 0064 EditProperty p = { object, property, property->readVariant(object), newValue }; 0065 _commands << p;// _objects << object; 0066 } 0067 0068 void CommandEditProperty::redo() 0069 { 0070 bool fullUpdate = false; 0071 foreach(const EditProperty& p, _commands) { 0072 if(p.newValue.type() != QVariant::String) p.property->writeVariant(p.object, p.newValue); 0073 else p.property->writeString(p.object, p.newValue.value<QString>()); 0074 if(!p.property->isDynamic() || p.property->hasSideEffects()) fullUpdate = true; 0075 } 0076 //_worldModel->objectChanged(NULL); 0077 _worldModel->emitChanged(fullUpdate, true); 0078 //foreach(StepCore::Object* object, _objects) _worldModel->objectChanged(object); 0079 } 0080 0081 void CommandEditProperty::undo() 0082 { 0083 bool fullUpdate = false; 0084 foreach(const EditProperty& p, _commands) { 0085 p.property->writeVariant(p.object, p.oldValue); 0086 if(!p.property->isDynamic() || p.property->hasSideEffects()) fullUpdate = true; 0087 } 0088 //_worldModel->objectChanged(NULL); 0089 _worldModel->emitChanged(fullUpdate, true); 0090 //foreach(StepCore::Object* object, _objects) _worldModel->objectChanged(object); 0091 } 0092 0093 bool CommandEditProperty::mergeWith(const QUndoCommand* command) 0094 { 0095 const CommandEditProperty* cmd = dynamic_cast<const CommandEditProperty*>(command); 0096 Q_ASSERT(cmd != nullptr); 0097 if(cmd->_commands.count() != 1) return false; 0098 0099 const EditProperty& p1 = cmd->_commands[0]; 0100 for(int i=0; i < _commands.count(); ++i) { 0101 if(_commands[i].object == p1.object && _commands[i].property == p1.property) { 0102 _commands[i].newValue = p1.newValue; 0103 //if(!_objects.contains(p1.object)) _objects << p1.object; 0104 return true; 0105 } 0106 } 0107 _commands << p1; 0108 //if(!_objects.contains(p1.object)) _objects << p1.object; 0109 return true; 0110 } 0111 0112 class CommandNewItem: public QUndoCommand 0113 { 0114 public: 0115 CommandNewItem(WorldModel* worldModel, StepCore::Item* item, StepCore::ItemGroup* parent, bool create) 0116 : _worldModel(worldModel), _item(item), _parent(parent), _create(create), _shouldDelete(create) { 0117 if(!create) findLinks(item, static_cast<StepCore::ItemGroup*>(_worldModel->world())); 0118 } 0119 ~CommandNewItem() { if(_shouldDelete) delete _item; } 0120 0121 void redo() override; 0122 void undo() override; 0123 0124 protected: 0125 void findLinks(StepCore::Item* itemToMatch, StepCore::ItemGroup* groupToSearch); 0126 void removeItem(); 0127 void readdItem(); 0128 0129 WorldModel* _worldModel; 0130 StepCore::Item* _item; 0131 StepCore::ItemGroup* _parent; 0132 bool _create; 0133 bool _shouldDelete; 0134 0135 typedef QPair<StepCore::Object*, const StepCore::MetaProperty*> Link; 0136 QList<Link> _links; 0137 }; 0138 0139 void CommandNewItem::findLinks(StepCore::Item* itemToMatch, StepCore::ItemGroup* groupToSearch) 0140 { 0141 StepCore::ItemGroup* itemToDeleteGroup = dynamic_cast<StepCore::ItemGroup*>(_item); 0142 0143 for (StepCore::Item *item : groupToSearch->items()) { 0144 if (item == itemToMatch) // no need to find links in itself 0145 continue; 0146 if (itemToDeleteGroup && itemToDeleteGroup->contains(item)) // no need to find links if the item is a child of the item that is being deleted 0147 continue; 0148 const StepCore::MetaObject* mobj = item->metaObject(); 0149 for(int i=0; i<mobj->propertyCount(); ++i) { 0150 if(mobj->property(i)->userTypeId() != qMetaTypeId<StepCore::Object*>()) continue; 0151 if(itemToMatch == mobj->property(i)->readVariant(item).value<StepCore::Object*>()) 0152 _links << qMakePair(static_cast<StepCore::Object*>(item), mobj->property(i)); 0153 } 0154 if(mobj->inherits<StepCore::ItemGroup>()) 0155 findLinks(itemToMatch, static_cast<StepCore::ItemGroup*>(item)); 0156 } 0157 0158 // If the item being deleted is a group, be sure we also find links for all its children 0159 StepCore::ItemGroup* itemToMatchGroup = dynamic_cast<StepCore::ItemGroup*>(itemToMatch); 0160 if (itemToMatchGroup) { 0161 for (StepCore::Item *child : itemToMatchGroup->items()) { 0162 findLinks(child, groupToSearch); 0163 } 0164 } 0165 } 0166 0167 void CommandNewItem::removeItem() 0168 { 0169 foreach(const Link& link, _links) 0170 link.second->writeVariant(link.first, QVariant::fromValue<StepCore::Object*>(NULL)); 0171 //qDebug("%d links removed", _links.count()); 0172 _worldModel->removeCreatedItem(_item); 0173 } 0174 0175 void CommandNewItem::readdItem() 0176 { 0177 _worldModel->addCreatedItem(_item, _parent); 0178 foreach(const Link& link, _links) 0179 link.second->writeVariant(link.first, QVariant::fromValue<StepCore::Object*>(_item)); 0180 //qDebug("%d links restored", _links.count()); 0181 } 0182 0183 void CommandNewItem::redo() 0184 { 0185 if(_create) readdItem(); 0186 else removeItem(); 0187 _shouldDelete = !_create; 0188 } 0189 0190 void CommandNewItem::undo() 0191 { 0192 if(_create) removeItem(); 0193 else readdItem(); 0194 _shouldDelete = _create; 0195 } 0196 0197 class CommandSetSolver: public QUndoCommand 0198 { 0199 public: 0200 CommandSetSolver(WorldModel* worldModel, StepCore::Solver* solver) 0201 : _worldModel(worldModel), _solver(solver) {} 0202 ~CommandSetSolver() { delete _solver; } 0203 void redo() override { _solver = _worldModel->swapSolver(_solver); } 0204 void undo() override { _solver = _worldModel->swapSolver(_solver); } 0205 0206 protected: 0207 WorldModel* _worldModel; 0208 StepCore::Solver* _solver; 0209 }; 0210 0211 class CommandSimulate: public QUndoCommand 0212 { 0213 public: 0214 CommandSimulate(WorldModel* worldModel); 0215 ~CommandSimulate() { delete _worldCopy; } 0216 0217 void done(); 0218 0219 void redo() override; 0220 void undo() override; 0221 0222 const QString& startTime() const { return _startTime; } 0223 const QString& endTime() const { return _endTime; } 0224 0225 protected: 0226 typedef QPair<int, int> PairInt; 0227 PairInt indexToPair(const QModelIndex &index); 0228 QModelIndex pairToIndex(PairInt pair); 0229 0230 WorldModel* _worldModel; 0231 StepCore::World* _worldCopy; 0232 StepCore::World* _oldWorld; 0233 StepCore::World* _newWorld; 0234 0235 QString _startTime; 0236 QString _endTime; 0237 }; 0238 0239 CommandSimulate::PairInt CommandSimulate::indexToPair(const QModelIndex &index) 0240 { 0241 if(index.parent().isValid()) return PairInt(1, index.row()); 0242 else return PairInt(0, index.row()); 0243 } 0244 0245 QModelIndex CommandSimulate::pairToIndex(CommandSimulate::PairInt pair) 0246 { 0247 if(pair.first == 0) { 0248 if(pair.second == 0) return _worldModel->worldIndex(); 0249 else if(pair.second == 1) return _worldModel->solverIndex(); 0250 else if(pair.second == 2) return _worldModel->collisionSolverIndex(); 0251 else if(pair.second == 3) return _worldModel->constraintSolverIndex(); 0252 else return QModelIndex(); 0253 } else return _worldModel->childItemIndex(pair.second); 0254 } 0255 0256 CommandSimulate::CommandSimulate(WorldModel* worldModel) 0257 { 0258 _worldModel = worldModel; 0259 _oldWorld = _worldCopy = _worldModel->_world; 0260 0261 /* 0262 QList<PairInt> selection; 0263 foreach(QModelIndex index, _worldModel->_selectionModel->selection().indexes()) 0264 selection << indexToPair(index); 0265 PairInt current = indexToPair(_worldModel->_selectionModel->currentIndex()); 0266 */ 0267 0268 _worldModel->beginResetModel(); 0269 _newWorld = _worldModel->_world = new StepCore::World(*_oldWorld); 0270 _worldModel->endResetModel(); 0271 0272 _startTime = _worldModel->formatProperty(_worldModel->_world, nullptr, 0273 _worldModel->_world->metaObject()->property(QStringLiteral("time")), 0274 WorldModel::FormatEditable); 0275 0276 /* 0277 foreach(PairInt pair, selection) 0278 _worldModel->_selectionModel->select(pairToIndex(pair), QItemSelectionModel::Select); 0279 _worldModel->_selectionModel->setCurrentIndex(pairToIndex(current), QItemSelectionModel::Current); 0280 */ 0281 } 0282 0283 void CommandSimulate::done() 0284 { 0285 _endTime = _worldModel->formatProperty(_worldModel->_world, nullptr, 0286 _worldModel->_world->metaObject()->property(QStringLiteral("time")), 0287 WorldModel::FormatEditable); 0288 } 0289 0290 void CommandSimulate::redo() 0291 { 0292 if(_newWorld != _worldModel->_world) { 0293 _worldModel->beginResetModel(); 0294 _worldModel->_world = _newWorld; 0295 _worldCopy = _oldWorld; 0296 0297 _worldModel->endResetModel(); 0298 _worldModel->_selectionModel->setCurrentIndex( 0299 _worldModel->worldIndex(), QItemSelectionModel::SelectCurrent); 0300 } 0301 } 0302 0303 void CommandSimulate::undo() 0304 { 0305 _worldModel->beginResetModel(); 0306 _worldModel->_world = _oldWorld; 0307 _worldCopy = _newWorld; 0308 0309 _worldModel->endResetModel(); 0310 _worldModel->_selectionModel->setCurrentIndex( 0311 _worldModel->worldIndex(), QItemSelectionModel::SelectCurrent); 0312 } 0313 0314 WorldModel::WorldModel(QObject* parent) 0315 : QAbstractItemModel(parent), _actions(nullptr) 0316 { 0317 _selectionModel = new QItemSelectionModel(this, this); 0318 _undoStack = new QUndoStack(this); 0319 _clipboard = new Clipboard(this); 0320 _worldFactory = new WorldFactory(); 0321 _world = new StepCore::World(); 0322 0323 _simulationCommand = nullptr; 0324 _simulationTimer = new QTimer(this); 0325 _simulationTimer0 = new QTimer(this); 0326 _simulationTimer0->setSingleShot(true); 0327 _simulationTimer0->setInterval(0); 0328 setSimulationFps(25); // XXX KConfig ? 0329 0330 _simulationThread = new SimulationThread(&_world); 0331 _simulationThread->start(); 0332 0333 connect(_simulationTimer, &QTimer::timeout, 0334 this, &WorldModel::simulationFrameBegin); 0335 connect(_simulationTimer0, &QTimer::timeout, 0336 this, &WorldModel::simulationFrameBegin); 0337 connect(_simulationThread, &SimulationThread::worldEvolveDone, 0338 this, &WorldModel::simulationFrameEnd, Qt::QueuedConnection); 0339 0340 _updatingTimer = new QTimer(this); 0341 _updatingTimer->setSingleShot(true); 0342 _updatingTimer->setInterval(0); 0343 _updatingFullUpdate = false; 0344 _updatingRecalcFn = false; 0345 0346 connect(_updatingTimer, &QTimer::timeout, 0347 this, &WorldModel::doEmitChanged); 0348 0349 resetWorld(); 0350 0351 _simulationFrameWaiting = false; 0352 _simulationFrameSkipped = false; 0353 _simulationStopping = false; 0354 _simulationPaused = false; 0355 0356 } 0357 0358 WorldModel::~WorldModel() 0359 { 0360 delete _simulationThread; 0361 delete _worldFactory; 0362 delete _world; 0363 } 0364 0365 void WorldModel::resetWorld() 0366 { 0367 Q_ASSERT(!isSimulationActive()); 0368 beginResetModel(); 0369 if(_world->name().isEmpty()) { 0370 // XXX: check that loaded items has unique names ! 0371 _world->setName(getUniqueName(QStringLiteral("world"))); 0372 } 0373 if(nullptr == _world->solver()) { 0374 _world->setSolver(new StepCore::AdaptiveEulerSolver()); 0375 _world->solver()->setName(getUniqueName(QStringLiteral("solver"))); 0376 } 0377 if(nullptr == _world->collisionSolver()) { 0378 _world->setCollisionSolver(new StepCore::GJKCollisionSolver()); 0379 _world->collisionSolver()->setName(getUniqueName(QStringLiteral("collisionSolver"))); 0380 } 0381 if(nullptr == _world->constraintSolver()) { 0382 _world->setConstraintSolver(new StepCore::CGConstraintSolver()); 0383 _world->constraintSolver()->setName(getUniqueName(QStringLiteral("constraintSolver"))); 0384 } 0385 _undoStack->clear(); 0386 0387 _world->doCalcFn(); 0388 endResetModel(); 0389 0390 _selectionModel->setCurrentIndex(worldIndex(), QItemSelectionModel::SelectCurrent); 0391 0392 emitChanged(true, false); 0393 } 0394 0395 void WorldModel::emitChanged(bool fullUpdate, bool recalcFn) 0396 { 0397 //qDebug() << "emitChanged(): " << world()->time() << endl; 0398 if(fullUpdate) _updatingFullUpdate = true; 0399 if(recalcFn) _updatingRecalcFn = true; 0400 if(!_updatingTimer->isActive()) _updatingTimer->start(0); 0401 } 0402 0403 void WorldModel::doEmitChanged() 0404 { 0405 if(_updatingRecalcFn) _world->doCalcFn(); 0406 emit worldDataChanged(!_updatingFullUpdate); 0407 if(_updatingFullUpdate) { 0408 emit dataChanged(worldIndex(), constraintSolverIndex()); 0409 } 0410 } 0411 0412 QModelIndex WorldModel::worldIndex() const 0413 { 0414 return createIndex(0, 0, _world); 0415 } 0416 0417 QModelIndex WorldModel::solverIndex() const 0418 { 0419 return createIndex(1, 0, _world->solver()); 0420 } 0421 0422 QModelIndex WorldModel::collisionSolverIndex() const 0423 { 0424 return createIndex(2, 0, _world->collisionSolver()); 0425 } 0426 0427 QModelIndex WorldModel::constraintSolverIndex() const 0428 { 0429 return createIndex(3, 0, _world->constraintSolver()); 0430 } 0431 0432 QModelIndex WorldModel::childItemIndex(int n, StepCore::ItemGroup* group) const 0433 { 0434 if(!group) group = _world; 0435 return createIndex(n, 0, group->childItem(n)); 0436 } 0437 0438 QModelIndex WorldModel::objectIndex(StepCore::Object* obj) const 0439 { 0440 if(obj == _world) return worldIndex(); 0441 else if(obj == _world->solver()) return solverIndex(); 0442 else if(obj == _world->collisionSolver()) return collisionSolverIndex(); 0443 else if(obj == _world->constraintSolver()) return constraintSolverIndex(); 0444 else { 0445 StepCore::Item* item = dynamic_cast<StepCore::Item*>(obj); 0446 for(StepCore::Item* it = item; it != _world; it = it->group()) { 0447 if(it == nullptr) return QModelIndex(); 0448 } 0449 STEPCORE_ASSERT_NOABORT(item && item->group()); 0450 return createIndex(item->group()->childItemIndex(item), 0, item); 0451 //return itemIndex(_world->childItemIndex(dynamic_cast<const StepCore::Item*>(obj))); 0452 } 0453 } 0454 0455 StepCore::Object* WorldModel::object(const QModelIndex& index) const 0456 { 0457 if(index.isValid()) return static_cast<StepCore::Object*>(index.internalPointer()); 0458 else return nullptr; 0459 } 0460 0461 #if 0 0462 void WorldModel::objectChanged(const StepCore::Object* /*object*/) 0463 { 0464 if(!_updating) { 0465 _world->doCalcFn(); 0466 emitChanged(false); 0467 } 0468 } 0469 #endif 0470 0471 StepCore::Item* WorldModel::item(const QModelIndex& index) const 0472 { 0473 return dynamic_cast<StepCore::Item*>(object(index)); 0474 } 0475 0476 QVariant WorldModel::data(const QModelIndex &index, int role) const 0477 { 0478 if(!index.isValid()) return QVariant(); 0479 StepCore::Object* obj = static_cast<StepCore::Object*>(index.internalPointer()); 0480 0481 if(role == Qt::DisplayRole) { 0482 return formatNameFull(obj); 0483 } else if(role == Qt::ToolTipRole) { 0484 const_cast<WorldModel*>(this)->simulationPause(); 0485 return createToolTip(index); // XXX 0486 } else if(role == Qt::DecorationRole) { 0487 if(_worldFactory->hasObjectIcon(obj->metaObject())) 0488 return _worldFactory->objectIcon(obj->metaObject()); 0489 return QVariant(); 0490 } else if(role == FormattedNameRole) { 0491 return formatName(obj); 0492 } else if(role == ClassNameRole) { 0493 return obj->metaObject()->className(); 0494 } 0495 0496 return QVariant(); 0497 } 0498 0499 QModelIndex WorldModel::index(int row, int /*column*/, const QModelIndex &parent) const 0500 { 0501 if(!parent.isValid()) { 0502 if(row == 0) return worldIndex(); 0503 else if(row == 1) return solverIndex(); 0504 else if(row == 2) return collisionSolverIndex(); 0505 else if(row == 3) return constraintSolverIndex(); 0506 } else { 0507 StepCore::ItemGroup* group = dynamic_cast<StepCore::ItemGroup*>(object(parent)); 0508 if(group) return createIndex(row, 0, group->childItem(row)); 0509 //if(parent.internalPointer() == _world) return itemIndex(row); 0510 } 0511 return QModelIndex(); 0512 } 0513 0514 QModelIndex WorldModel::parent(const QModelIndex &index) const 0515 { 0516 if(!index.isValid()) return QModelIndex(); 0517 else if(index.internalPointer() == _world) return QModelIndex(); 0518 else if(index.internalPointer() == _world->solver()) return QModelIndex(); 0519 else if(index.internalPointer() == _world->collisionSolver()) return QModelIndex(); 0520 else if(index.internalPointer() == _world->constraintSolver()) return QModelIndex(); 0521 else { 0522 StepCore::Item* item = dynamic_cast<StepCore::Item*>(object(index)); 0523 if(item && item->group()) return objectIndex(item->group()); 0524 else return QModelIndex(); 0525 } 0526 } 0527 0528 int WorldModel::rowCount(const QModelIndex &parent) const 0529 { 0530 if(!parent.isValid()) { 0531 Q_ASSERT(_world->solver()); 0532 Q_ASSERT(_world->collisionSolver()); 0533 Q_ASSERT(_world->constraintSolver()); 0534 return 4; 0535 /* 0536 int count = 1; 0537 if(_world->solver()) ++count; 0538 if(_world->collisionSolver()) ++count; 0539 return count; 0540 */ 0541 } 0542 else if(dynamic_cast<StepCore::ItemGroup*>(object(parent))) 0543 return dynamic_cast<StepCore::ItemGroup*>(object(parent))->childItemCount(); 0544 else return 0; 0545 } 0546 0547 int WorldModel::columnCount(const QModelIndex& /*parent*/) const 0548 { 0549 return 1; 0550 } 0551 0552 QString WorldModel::newItemName(const QString& className) 0553 { 0554 return getUniqueName(className); 0555 } 0556 0557 StepCore::Item* WorldModel::createItem(const QString& className, StepCore::ItemGroup* parent) 0558 { 0559 Q_UNUSED(parent) 0560 StepCore::Item* item = _worldFactory->newItem(className); 0561 if(item == nullptr) return nullptr; 0562 item->setName(getUniqueName(className)); 0563 return item; 0564 } 0565 0566 StepCore::Item* WorldModel::newItem(const QString& className, StepCore::ItemGroup* parent) 0567 { 0568 StepCore::Item* item = _worldFactory->newItem(className); 0569 if(item == nullptr) return nullptr; 0570 item->setName(getUniqueName(className)); 0571 pushCommand(new CommandNewItem(this, item, parent, true)); 0572 return item; 0573 } 0574 0575 void WorldModel::addItem(StepCore::Item* item, StepCore::ItemGroup* parent) 0576 { 0577 //if(item->name().isEmpty()) item->setName(getUniqueName(item->metaObject()->className())); 0578 pushCommand(new CommandNewItem(this, item, parent, true)); 0579 } 0580 0581 StepCore::Solver* WorldModel::newSolver(const QString& className) 0582 { 0583 StepCore::Solver* solver = _worldFactory->newSolver(className); 0584 if(solver == nullptr) return nullptr; 0585 0586 // Copy similarly named properties 0587 // XXX: is it right ? 0588 StepCore::Solver* oldSolver = _world->solver(); 0589 for(int i=0; i<oldSolver->metaObject()->propertyCount(); ++i) { 0590 const StepCore::MetaProperty* oldProperty = oldSolver->metaObject()->property(i); 0591 const StepCore::MetaProperty* newProperty = solver->metaObject()->property(oldProperty->name()); 0592 if(newProperty && newProperty->isWritable()) { 0593 newProperty->writeVariant(solver, oldProperty->readVariant(oldSolver)); 0594 } 0595 } 0596 0597 //solver->setName(_world->solver()->name()); 0598 //solver->setName(getUniqueName(name)); // XXX: is it better ? 0599 0600 pushCommand(new CommandSetSolver(this, solver)); 0601 return solver; 0602 } 0603 0604 void WorldModel::deleteItem(StepCore::Item* item) 0605 { 0606 pushCommand(new CommandNewItem(this, item, item->group(), false)); 0607 } 0608 0609 void WorldModel::deleteSelectedItems() 0610 { 0611 simulationPause(); 0612 0613 QList<StepCore::Item*> items; 0614 foreach(QModelIndex index, selectionModel()->selectedIndexes()) { 0615 // Do not delete world item 0616 if (index == worldIndex()) continue; 0617 0618 StepCore::Item* it = item(index); 0619 if (it) items << it; 0620 } 0621 0622 foreach(StepCore::Item* it, items) { 0623 for(StepCore::Item* it1 = it->group(); it1 != nullptr && it1 != _world; it1 = it1->group()) { 0624 if(items.contains(it1)) { items.removeOne(it); break; } 0625 } 0626 } 0627 0628 if(!items.isEmpty()) { 0629 beginMacro(items.count()==1 ? i18n("Delete %1", items[0]->name()) 0630 : i18n("Delete several items")); 0631 foreach(StepCore::Item* it, items) deleteItem(it); 0632 endMacro(); 0633 } 0634 } 0635 0636 void WorldModel::addCreatedItem(StepCore::Item* item, StepCore::ItemGroup* parent) 0637 { 0638 if(!parent) parent = _world; 0639 beginInsertRows(objectIndex(parent), parent->childItemCount(), parent->childItemCount()); 0640 parent->addItem(item); 0641 endInsertRows(); 0642 emitChanged(true, true); 0643 } 0644 0645 void WorldModel::removeCreatedItem(StepCore::Item* item) 0646 { 0647 STEPCORE_ASSERT_NOABORT(item->group()); 0648 QModelIndex index = objectIndex(item); 0649 beginRemoveRows(index.parent(), index.row(), index.row()); 0650 item->group()->removeItem(item); 0651 endRemoveRows(); 0652 emitChanged(true, true); 0653 } 0654 0655 StepCore::Solver* WorldModel::swapSolver(StepCore::Solver* solver) 0656 { 0657 bool selected = selectionModel()->isSelected(solverIndex()); 0658 bool current = selectionModel()->currentIndex() == solverIndex(); 0659 StepCore::Solver* oldSolver = _world->removeSolver(); 0660 _world->setSolver(solver); 0661 if(selected) selectionModel()->select(solverIndex(), QItemSelectionModel::Select); 0662 if(current) selectionModel()->setCurrentIndex(solverIndex(), QItemSelectionModel::Current); 0663 emitChanged(true, true); 0664 return oldSolver; 0665 } 0666 0667 void WorldModel::pushCommand(QUndoCommand* command) 0668 { 0669 Q_ASSERT(!_simulationFrameWaiting || _simulationPaused); 0670 if(!isSimulationActive()) { 0671 _undoStack->push(command); 0672 } else { 0673 command->redo(); 0674 delete command; 0675 } 0676 } 0677 0678 void WorldModel::beginMacro(const QString& text) 0679 { 0680 // XXX: simulation could be started (by hotkey) during macro creation 0681 if(!isSimulationActive()) 0682 _undoStack->beginMacro(text); 0683 } 0684 0685 void WorldModel::endMacro() 0686 { 0687 // XXX: simulation could be stopped during macro creation 0688 if(!isSimulationActive()) 0689 _undoStack->endMacro(); 0690 } 0691 0692 void WorldModel::setProperty(StepCore::Object* object, 0693 const StepCore::MetaProperty* property, const QVariant& value, UndoFlags flags) 0694 { 0695 Q_ASSERT(object != nullptr); Q_ASSERT(property != nullptr); 0696 pushCommand(new CommandEditProperty(this, object, property, value, !flags.testFlag(UndoNoMerge))); 0697 } 0698 0699 QString WorldModel::formatName(const StepCore::Object* object) const 0700 { 0701 if(!object) return i18n("<no object>"); 0702 else if(!object->name().isEmpty()) return object->name(); 0703 return i18n("<unnamed>"); 0704 } 0705 0706 QString WorldModel::formatNameFull(const StepCore::Object* object) const 0707 { 0708 if(!object) return i18n("<no object>"); 0709 return i18n("%1: %2", formatName(object), object->metaObject()->classNameTr()); 0710 } 0711 0712 QString WorldModel::formatProperty(const StepCore::Object* object, 0713 const StepCore::Object* objectErrors, 0714 const StepCore::MetaProperty* property, 0715 FormatFlags flags) const 0716 { 0717 int pr = Settings::floatDisplayPrecision(); 0718 0719 QString units; 0720 if(!flags.testFlag(FormatHideUnits)) { 0721 if(!flags.testFlag(FormatEditable) && !property->units().isEmpty()) 0722 units.append(" [").append(property->units()).append("]"); 0723 #ifdef STEP_WITH_UNITSCALC 0724 else if(flags.testFlag(FormatEditable) && !property->units().isEmpty() 0725 && property->userTypeId() == QMetaType::Double) 0726 units.append(" ").append(property->units()); 0727 #endif 0728 } 0729 0730 const StepCore::MetaProperty* pv = objectErrors ? 0731 objectErrors->metaObject()->property(property->name() + "Variance") : nullptr; 0732 0733 // Common property types 0734 if(property->userTypeId() == QMetaType::Double) { 0735 QString error; 0736 if(pv) error = QStringLiteral(" ± %1") 0737 .arg(sqrt(pv->readVariant(objectErrors).toDouble()), 0, 'g', pr) 0738 .append(units); 0739 return QString::number(property->readVariant(object).toDouble(), 'g', pr).append(units).append(error); 0740 } else if(property->userTypeId() == qMetaTypeId<StepCore::Vector2d>()) { 0741 QString error; 0742 if(pv) { 0743 StepCore::Vector2d vv = pv->readVariant(objectErrors).value<StepCore::Vector2d>(); 0744 error = QStringLiteral(" ± (%1,%2)") 0745 .arg(sqrt(vv[0]), 0, 'g', pr).arg(sqrt(vv[1]), 0, 'g', pr).append(units); 0746 } 0747 StepCore::Vector2d v = property->readVariant(object).value<StepCore::Vector2d>(); 0748 return QStringLiteral("(%1,%2)").arg(v[0], 0, 'g', pr).arg(v[1], 0, 'g', pr).append(units).append(error); 0749 } else if(property->userTypeId() == qMetaTypeId<StepCore::Vector2dList >() ) { 0750 // XXX: add error information 0751 // if(pv) qDebug() << "Unhandled property variance type" << endl; 0752 StepCore::Vector2dList list = 0753 property->readVariant(object).value<StepCore::Vector2dList >(); 0754 QString string; 0755 unsigned int end = flags.testFlag(FormatEditable) ? list.size() : qMin<unsigned int>(10, list.size()); 0756 for(unsigned int i=0; i<end; ++i) { 0757 if(!string.isEmpty()) string += ','; 0758 string += QStringLiteral("(%1,%2)").arg(list[i][0], 0, 'g', pr) 0759 .arg(list[i][1], 0, 'g', pr); 0760 } 0761 if(!flags.testFlag(FormatEditable) && end != 0 && end < list.size()) string += QLatin1String(",..."); 0762 string.append(units); 0763 return string; 0764 } else { 0765 // default type 0766 // XXX: add error information 0767 //if(pe) error = QString::fromUtf8(" ± ").append(pe->readString(_objectErrors)).append(units); 0768 //if(pv) qDebug() << "Unhandled property variance type" << endl; 0769 Q_ASSERT(!pv); 0770 QString str = property->readString(object); 0771 if(!flags.testFlag(FormatEditable) && str.length() > 50) 0772 str = str.left(50).append("..."); 0773 return str.append(units); 0774 } 0775 } 0776 0777 QString WorldModel::createToolTip(const QModelIndex& index) const 0778 { 0779 //Q_ASSERT(object != NULL); 0780 Q_ASSERT(index.isValid()); 0781 QString toolTip = i18n("<nobr><h4><u>%1</u></h4></nobr>", index.data(Qt::DisplayRole).toString()); 0782 toolTip += QLatin1String("<table>"); 0783 0784 StepCore::Object* object = this->object(index); 0785 StepCore::Item* item = dynamic_cast<StepCore::Item*>(object); 0786 const StepCore::Object* objectErrors = nullptr; 0787 if(item) { 0788 if(world()->errorsCalculation()) objectErrors = item->objectErrors(); 0789 else objectErrors = item->tryGetObjectErrors(); 0790 } 0791 0792 for(int i=0; i<object->metaObject()->propertyCount(); ++i) { 0793 const StepCore::MetaProperty* p = object->metaObject()->property(i); 0794 /*QString units; 0795 if(!p->units().isEmpty()) 0796 units.append(" [").append(p->units()).append("]"); 0797 QString value = p->readString(object); 0798 if(p->userTypeId() == qMetaTypeId<StepCore::Vector2dList >()) { 0799 // XXX: don't use readString in this case ! 0800 value.replace("),(", QString(")%1<br />(").arg(units)); 0801 value.append(units); 0802 if(value.count("<br />") > 10) { 0803 value = value.section("<br />", 0, 9); 0804 value.append("<br />..."); 0805 } 0806 } else { 0807 value.append(units); 0808 }*/ 0809 toolTip += i18n("<tr><td>%1 </td><td>%2</td></tr>", p->nameTr(), 0810 formatProperty(object, objectErrors, p)); 0811 } 0812 toolTip += QLatin1String("</table>"); 0813 //qDebug("%s", toolTip.toAscii().constData()); 0814 return toolTip; 0815 } 0816 0817 QMenu* WorldModel::createContextMenu(const QModelIndex& index) 0818 { 0819 //Q_ASSERT(object != NULL); 0820 Q_ASSERT(index.isValid()); 0821 QMenu* menu = new QMenu(); 0822 menu->addSection(index.data(Qt::DisplayRole).toString()); 0823 ItemMenuHandler* handler = _worldFactory->newItemMenuHandler(object(index), this, menu); 0824 handler->populateMenu(menu, _actions); 0825 return menu; 0826 } 0827 0828 void WorldModel::clearWorld() 0829 { 0830 _world->clear(); 0831 resetWorld(); 0832 } 0833 0834 bool WorldModel::saveXml(QIODevice* device) 0835 { 0836 StepCore::XmlFile file(device); 0837 0838 if(file.save(_world)) { 0839 return true; 0840 } else { 0841 _errorString = file.errorString(); 0842 return false; 0843 } 0844 } 0845 0846 bool WorldModel::loadXml(QIODevice* device) 0847 { 0848 _world->clear(); 0849 StepCore::XmlFile file(device); 0850 bool ret = file.load(_world, _worldFactory); 0851 if(!ret) { _world->clear(); _errorString = file.errorString(); } 0852 resetWorld(); 0853 return ret; 0854 } 0855 0856 bool WorldModel::checkUniqueName(const QString& name) const 0857 { 0858 if(name.isEmpty()) return false; 0859 if(_world->object(name) != nullptr) return false; 0860 return true; 0861 } 0862 0863 QString WorldModel::getUniqueName(const QString& className) const 0864 { 0865 QString classNameCopy = className; 0866 classNameCopy[0] = classNameCopy[0].toLower(); 0867 for(int n=1; ; ++n) { 0868 QString name = classNameCopy + QString::number(n); 0869 if(checkUniqueName(name)) return name; 0870 } 0871 return QString(); 0872 } 0873 0874 void WorldModel::simulationPause() 0875 { 0876 if(!_simulationFrameWaiting || _simulationPaused) return; 0877 0878 // Try to lock but do not wait mode than one frame 0879 if(_simulationThread->mutex()->tryLock(1000/_simulationFps)) { 0880 // We still need to setAbortEvolve(true) since 0881 // _simulationThread->doWorldEvolve() could be called before 0882 _world->setEvolveAbort(true); 0883 } else { 0884 //qDebug() << "simulationPause: simulation aborted" << endl; 0885 Q_ASSERT(isSimulationActive()); 0886 Q_ASSERT(_simulationCommand); 0887 Q_ASSERT(_simulationFrameWaiting); 0888 //Q_ASSERT(!_simulationStopping); 0889 0890 _world->setEvolveAbort(true); 0891 0892 // Mutex will be locked as soon as current frame is aborted 0893 _simulationThread->mutex()->lock(); 0894 } 0895 // We can release mutex just now since new simulation frame can 0896 // only be started from this thread when control returns to event loop 0897 _simulationThread->mutex()->unlock(); 0898 _simulationPaused = true; 0899 0900 //qDebug() << "simulationPause!" << endl; 0901 // XXX: do we need to reset evolveAbort here (and add threadAbort var) ? 0902 // XXX: do we need to call emitChanged here ? 0903 } 0904 0905 void WorldModel::setSimulationFps(int simulationFps) 0906 { 0907 _simulationFps = simulationFps; 0908 _simulationTimer->setInterval(1000/simulationFps); 0909 } 0910 0911 bool WorldModel::isSimulationActive() 0912 { 0913 return _simulationTimer->isActive(); 0914 } 0915 0916 void WorldModel::simulationStart() 0917 { 0918 Q_ASSERT(!isSimulationActive()); 0919 Q_ASSERT(!_simulationCommand); 0920 0921 //_undoStack->beginMacro(i18n("Simulate")); 0922 _simulationCommand = new CommandSimulate(this); 0923 _world->setEvolveAbort(false); 0924 _simulationFrameWaiting = false; 0925 _simulationFrameSkipped = false; 0926 _simulationStopping = false; 0927 _simulationPaused = false; 0928 0929 _simulationFrames = 0; 0930 _simulationStartTime = QTime::currentTime(); 0931 _simulationTimer->start(); 0932 0933 //emitChanged(); 0934 } 0935 0936 void WorldModel::simulationStop() 0937 { 0938 Q_ASSERT(isSimulationActive()); 0939 Q_ASSERT(_simulationCommand); 0940 _simulationStopping = true; 0941 if(_simulationFrameWaiting) { 0942 simulationPause(); 0943 } else { 0944 simulationFrameEnd(0); 0945 } 0946 } 0947 0948 void WorldModel::simulationFrameBegin() 0949 { 0950 Q_ASSERT(isSimulationActive()); 0951 Q_ASSERT(_simulationCommand); 0952 Q_ASSERT(!_simulationStopping); 0953 0954 //qDebug() << "simulationFrameBegin(): " << world()->time() << endl; 0955 0956 if(_simulationFrameWaiting) { // TODO: warn user 0957 _simulationFrameSkipped = true; 0958 return; 0959 } 0960 0961 if(_updatingTimer->isActive()) { 0962 // Wait for updating 0963 _simulationTimer0->start(); 0964 return; 0965 } 0966 0967 //qDebug("emit simulationDoFrame() t=%#x", int(QThread::currentThread())); 0968 //qDebug() << "simulationFrameBegin" << endl; 0969 _simulationFrameWaiting = true; 0970 _simulationPaused = false; 0971 _simulationThread->doWorldEvolve(1.0/_simulationFps); 0972 //qDebug("emitted simulationDoFrame()"); 0973 } 0974 0975 void WorldModel::simulationFrameEnd(int result) 0976 { 0977 Q_ASSERT(isSimulationActive()); 0978 Q_ASSERT(_simulationCommand); 0979 Q_ASSERT(_simulationFrameWaiting || _simulationStopping); 0980 0981 ++_simulationFrames; 0982 if(_simulationFrames == 50) { 0983 qDebug("FPS: %f", double(_simulationFrames) / 0984 _simulationStartTime.msecsTo(QTime::currentTime()) * 1000); 0985 _simulationStartTime = QTime::currentTime(); 0986 _simulationFrames = 0; 0987 } 0988 0989 //qDebug() << "simulationFrameEnd" << endl; 0990 0991 // It's OK to be aborted 0992 if(result == StepCore::Solver::Aborted) { 0993 //qDebug("simulation frame aborted!"); 0994 result = StepCore::Solver::OK; 0995 } 0996 0997 // If current frame was aborted we can resume it now 0998 _world->setEvolveAbort(false); 0999 1000 // Update GUI 1001 _simulationFrameWaiting = false; 1002 emitChanged(false, false); 1003 1004 // Stop if requested or simulation error occurred 1005 if(_simulationStopping || result != StepCore::Solver::OK) { 1006 _simulationCommand->done(); 1007 _undoStack->beginMacro(i18n("Simulate %1 → %2", 1008 _simulationCommand->startTime(), _simulationCommand->endTime())); 1009 _undoStack->push(_simulationCommand); 1010 _undoStack->endMacro(); 1011 _simulationCommand = nullptr; 1012 1013 _simulationTimer->stop(); 1014 _simulationTimer0->stop(); 1015 emit simulationStopped(result); 1016 return; 1017 } 1018 1019 if(_simulationFrameSkipped) { 1020 _simulationFrameSkipped = false; 1021 _simulationTimer0->start(); 1022 //QApplication::processEvents(); // XXX 1023 //if(isSimulationActive() && !_simulationFrameWaiting) 1024 // simulationFrameBegin(); 1025 } 1026 } 1027 1028 QList<StepCore::Item*> WorldModel::selectedItems() 1029 { 1030 QList<StepCore::Item*> items; 1031 foreach (QModelIndex index, selectionModel()->selectedIndexes()) { 1032 // Do not delete world item 1033 if (index == worldIndex()) continue; 1034 1035 StepCore::Item* it = item(index); 1036 if (it) items << it; 1037 } 1038 1039 foreach (StepCore::Item* it, items) { 1040 for (StepCore::Item* it1 = it->group(); it1 != nullptr && it1 != _world; it1 = it1->group()) { 1041 if (items.contains(it1)) { 1042 items.removeOne(it); 1043 break; 1044 } 1045 } 1046 } 1047 1048 return items; 1049 } 1050 1051 void WorldModel::cutSelectedItems() 1052 { 1053 simulationPause(); 1054 1055 QList<StepCore::Item*> items = selectedItems(); 1056 1057 _clipboard->copy(items); 1058 1059 if (!items.isEmpty()) { 1060 beginMacro(items.count() == 1 ? i18n("Cut %1", items[0]->name()) : 1061 i18n("Cut several items")); 1062 foreach (StepCore::Item* it, items) deleteItem(it); 1063 endMacro(); 1064 } 1065 } 1066 1067 void WorldModel::copySelectedItems() 1068 { 1069 simulationPause(); 1070 1071 QList<StepCore::Item*> items = selectedItems(); 1072 1073 _clipboard->copy(items); 1074 } 1075 1076 void WorldModel::pasteItems() 1077 { 1078 simulationPause(); 1079 1080 QList<StepCore::Item*> items = _clipboard->paste(_worldFactory); 1081 if (items.isEmpty()) return; 1082 1083 beginMacro(items.count() == 1 ? i18n("Pasted %1", items[0]->name()) : 1084 i18n("Pasted several items")); 1085 QItemSelection selection; 1086 foreach (StepCore::Item* item, items) { 1087 QString className = item->metaObject()->className(); 1088 item->setName(getUniqueName(className)); 1089 addItem(item, _world); 1090 QModelIndex index = objectIndex(item); 1091 selection.select(index, index); 1092 } 1093 if (!selection.isEmpty()) { 1094 selectionModel()->select(selection, QItemSelectionModel::ClearAndSelect); 1095 } 1096 endMacro(); 1097 } 1098 1099 #include "moc_worldmodel.cpp"