diff --git a/step/worldmodel.cc b/step/worldmodel.cc index 8130c7f..07766a3 100644 --- a/step/worldmodel.cc +++ b/step/worldmodel.cc @@ -1,1093 +1,1106 @@ /* This file is part of Step. Copyright (C) 2007 Vladimir Kuznetsov Step is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Step is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Step; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include "worldmodel.h" #include "clipboard.h" #include "simulationthread.h" #include "worldgraphics.h" #include "settings.h" #include "worldfactory.h" #include #include #include #include #include #include #include #include #include #include #include #include class CommandEditProperty: public QUndoCommand { public: CommandEditProperty(WorldModel* worldModel, StepCore::Object* object, const StepCore::MetaProperty* property, const QVariant& newValue, bool merge); int id() const Q_DECL_OVERRIDE { return _merge ? 1 : -1; } bool mergeWith(const QUndoCommand* command) Q_DECL_OVERRIDE; void redo() Q_DECL_OVERRIDE; void undo() Q_DECL_OVERRIDE; protected: /* It's important to properly compress commands * or the stack becomes too big and slow */ struct EditProperty { StepCore::Object* object; const StepCore::MetaProperty* property; QVariant oldValue; QVariant newValue; }; bool _merge; WorldModel* _worldModel; QList _commands; //QList _objects; }; CommandEditProperty::CommandEditProperty(WorldModel* worldModel, StepCore::Object* object, const StepCore::MetaProperty* property, const QVariant& newValue, bool merge) : _merge(merge), _worldModel(worldModel) { //qDebug() << "CommandEditProperty: " << object->name() << " " << property->name() << " " << newValue.toString() << endl; EditProperty p = { object, property, property->readVariant(object), newValue }; _commands << p;// _objects << object; } void CommandEditProperty::redo() { bool fullUpdate = false; foreach(const EditProperty& p, _commands) { if(p.newValue.type() != QVariant::String) p.property->writeVariant(p.object, p.newValue); else p.property->writeString(p.object, p.newValue.value()); if(!p.property->isDynamic() || p.property->hasSideEffects()) fullUpdate = true; } //_worldModel->objectChanged(NULL); _worldModel->emitChanged(fullUpdate, true); //foreach(StepCore::Object* object, _objects) _worldModel->objectChanged(object); } void CommandEditProperty::undo() { bool fullUpdate = false; foreach(const EditProperty& p, _commands) { p.property->writeVariant(p.object, p.oldValue); if(!p.property->isDynamic() || p.property->hasSideEffects()) fullUpdate = true; } //_worldModel->objectChanged(NULL); _worldModel->emitChanged(fullUpdate, true); //foreach(StepCore::Object* object, _objects) _worldModel->objectChanged(object); } bool CommandEditProperty::mergeWith(const QUndoCommand* command) { const CommandEditProperty* cmd = dynamic_cast(command); Q_ASSERT(cmd != NULL); if(cmd->_commands.count() != 1) return false; const EditProperty& p1 = cmd->_commands[0]; for(int i=0; i < _commands.count(); ++i) { if(_commands[i].object == p1.object && _commands[i].property == p1.property) { _commands[i].newValue = p1.newValue; //if(!_objects.contains(p1.object)) _objects << p1.object; return true; } } _commands << p1; //if(!_objects.contains(p1.object)) _objects << p1.object; return true; } class CommandNewItem: public QUndoCommand { public: CommandNewItem(WorldModel* worldModel, StepCore::Item* item, StepCore::ItemGroup* parent, bool create) : _worldModel(worldModel), _item(item), _parent(parent), _create(create), _shouldDelete(create) { - if(!create) findLinks(static_cast(_worldModel->world())); + if(!create) findLinks(item, static_cast(_worldModel->world())); } ~CommandNewItem() { if(_shouldDelete) delete _item; } void redo() Q_DECL_OVERRIDE; void undo() Q_DECL_OVERRIDE; protected: - void findLinks(StepCore::ItemGroup* group); + void findLinks(StepCore::Item* itemToMatch, StepCore::ItemGroup* groupToSearch); void removeItem(); void readdItem(); WorldModel* _worldModel; StepCore::Item* _item; StepCore::ItemGroup* _parent; bool _create; bool _shouldDelete; typedef QPair Link; QList _links; }; -void CommandNewItem::findLinks(StepCore::ItemGroup* group) +void CommandNewItem::findLinks(StepCore::Item* itemToMatch, StepCore::ItemGroup* groupToSearch) { - StepCore::ItemList::const_iterator end = group->items().end(); - for(StepCore::ItemList::const_iterator it = group->items().begin(); it != end; ++it) { - const StepCore::MetaObject* mobj = (*it)->metaObject(); + StepCore::ItemGroup* itemToDeleteGroup = dynamic_cast(_item); + + for (StepCore::Item *item : groupToSearch->items()) { + if (item == itemToMatch) // no need to find links in itself + continue; + if (itemToDeleteGroup && itemToDeleteGroup->contains(item)) // no need to find links if the item is a child of the item that is being deleted + continue; + const StepCore::MetaObject* mobj = item->metaObject(); for(int i=0; ipropertyCount(); ++i) { if(mobj->property(i)->userTypeId() != qMetaTypeId()) continue; - if(_item == mobj->property(i)->readVariant(*it).value()) - _links << qMakePair(static_cast(*it), mobj->property(i)); + if(itemToMatch == mobj->property(i)->readVariant(item).value()) + _links << qMakePair(static_cast(item), mobj->property(i)); } if(mobj->inherits()) - findLinks(static_cast(*it)); + findLinks(itemToMatch, static_cast(item)); + } + + // If the item being deleted is a group, be sure we also find links for all its children + StepCore::ItemGroup* itemToMatchGroup = dynamic_cast(itemToMatch); + if (itemToMatchGroup) { + for (StepCore::Item *child : itemToMatchGroup->items()) { + findLinks(child, groupToSearch); + } } } void CommandNewItem::removeItem() { foreach(const Link& link, _links) link.second->writeVariant(link.first, QVariant::fromValue(NULL)); //qDebug("%d links removed", _links.count()); _worldModel->removeCreatedItem(_item); } void CommandNewItem::readdItem() { _worldModel->addCreatedItem(_item, _parent); foreach(const Link& link, _links) link.second->writeVariant(link.first, QVariant::fromValue(_item)); //qDebug("%d links restored", _links.count()); } void CommandNewItem::redo() { if(_create) readdItem(); else removeItem(); _shouldDelete = !_create; } void CommandNewItem::undo() { if(_create) removeItem(); else readdItem(); _shouldDelete = _create; } class CommandSetSolver: public QUndoCommand { public: CommandSetSolver(WorldModel* worldModel, StepCore::Solver* solver) : _worldModel(worldModel), _solver(solver) {} ~CommandSetSolver() { delete _solver; } void redo() Q_DECL_OVERRIDE { _solver = _worldModel->swapSolver(_solver); } void undo() Q_DECL_OVERRIDE { _solver = _worldModel->swapSolver(_solver); } protected: WorldModel* _worldModel; StepCore::Solver* _solver; }; class CommandSimulate: public QUndoCommand { public: CommandSimulate(WorldModel* worldModel); ~CommandSimulate() { delete _worldCopy; } void done(); void redo() Q_DECL_OVERRIDE; void undo() Q_DECL_OVERRIDE; const QString& startTime() const { return _startTime; } const QString& endTime() const { return _endTime; } protected: typedef QPair PairInt; PairInt indexToPair(const QModelIndex &index); QModelIndex pairToIndex(PairInt pair); WorldModel* _worldModel; StepCore::World* _worldCopy; StepCore::World* _oldWorld; StepCore::World* _newWorld; QString _startTime; QString _endTime; }; CommandSimulate::PairInt CommandSimulate::indexToPair(const QModelIndex &index) { if(index.parent().isValid()) return PairInt(1, index.row()); else return PairInt(0, index.row()); } QModelIndex CommandSimulate::pairToIndex(CommandSimulate::PairInt pair) { if(pair.first == 0) { if(pair.second == 0) return _worldModel->worldIndex(); else if(pair.second == 1) return _worldModel->solverIndex(); else if(pair.second == 2) return _worldModel->collisionSolverIndex(); else if(pair.second == 3) return _worldModel->constraintSolverIndex(); else return QModelIndex(); } else return _worldModel->childItemIndex(pair.second); } CommandSimulate::CommandSimulate(WorldModel* worldModel) { _worldModel = worldModel; _oldWorld = _worldCopy = _worldModel->_world; /* QList selection; foreach(QModelIndex index, _worldModel->_selectionModel->selection().indexes()) selection << indexToPair(index); PairInt current = indexToPair(_worldModel->_selectionModel->currentIndex()); */ _worldModel->beginResetModel(); _newWorld = _worldModel->_world = new StepCore::World(*_oldWorld); _worldModel->endResetModel(); _startTime = _worldModel->formatProperty(_worldModel->_world, NULL, _worldModel->_world->metaObject()->property(QStringLiteral("time")), WorldModel::FormatEditable); /* foreach(PairInt pair, selection) _worldModel->_selectionModel->select(pairToIndex(pair), QItemSelectionModel::Select); _worldModel->_selectionModel->setCurrentIndex(pairToIndex(current), QItemSelectionModel::Current); */ } void CommandSimulate::done() { _endTime = _worldModel->formatProperty(_worldModel->_world, NULL, _worldModel->_world->metaObject()->property(QStringLiteral("time")), WorldModel::FormatEditable); } void CommandSimulate::redo() { if(_newWorld != _worldModel->_world) { _worldModel->beginResetModel(); _worldModel->_world = _newWorld; _worldCopy = _oldWorld; _worldModel->endResetModel(); _worldModel->_selectionModel->setCurrentIndex( _worldModel->worldIndex(), QItemSelectionModel::SelectCurrent); } } void CommandSimulate::undo() { _worldModel->beginResetModel(); _worldModel->_world = _oldWorld; _worldCopy = _newWorld; _worldModel->endResetModel(); _worldModel->_selectionModel->setCurrentIndex( _worldModel->worldIndex(), QItemSelectionModel::SelectCurrent); } WorldModel::WorldModel(QObject* parent) : QAbstractItemModel(parent), _actions(0) { _selectionModel = new QItemSelectionModel(this, this); _undoStack = new QUndoStack(this); _clipboard = new Clipboard(this); _worldFactory = new WorldFactory(); _world = new StepCore::World(); _simulationCommand = NULL; _simulationTimer = new QTimer(this); _simulationTimer0 = new QTimer(this); _simulationTimer0->setSingleShot(true); _simulationTimer0->setInterval(0); setSimulationFps(25); // XXX KConfig ? _simulationThread = new SimulationThread(&_world); _simulationThread->start(); connect(_simulationTimer, &QTimer::timeout, this, &WorldModel::simulationFrameBegin); connect(_simulationTimer0, &QTimer::timeout, this, &WorldModel::simulationFrameBegin); connect(_simulationThread, &SimulationThread::worldEvolveDone, this, &WorldModel::simulationFrameEnd, Qt::QueuedConnection); _updatingTimer = new QTimer(this); _updatingTimer->setSingleShot(true); _updatingTimer->setInterval(0); _updatingFullUpdate = false; _updatingRecalcFn = false; connect(_updatingTimer, &QTimer::timeout, this, &WorldModel::doEmitChanged); resetWorld(); _simulationFrameWaiting = false; _simulationFrameSkipped = false; _simulationStopping = false; _simulationPaused = false; } WorldModel::~WorldModel() { delete _simulationThread; delete _worldFactory; delete _world; } void WorldModel::resetWorld() { Q_ASSERT(!isSimulationActive()); beginResetModel(); if(_world->name().isEmpty()) { // XXX: check that loaded items has unique names ! _world->setName(getUniqueName(QStringLiteral("world"))); } if(NULL == _world->solver()) { _world->setSolver(new StepCore::AdaptiveEulerSolver()); _world->solver()->setName(getUniqueName(QStringLiteral("solver"))); } if(NULL == _world->collisionSolver()) { _world->setCollisionSolver(new StepCore::GJKCollisionSolver()); _world->collisionSolver()->setName(getUniqueName(QStringLiteral("collisionSolver"))); } if(NULL == _world->constraintSolver()) { _world->setConstraintSolver(new StepCore::CGConstraintSolver()); _world->constraintSolver()->setName(getUniqueName(QStringLiteral("constraintSolver"))); } _undoStack->clear(); _world->doCalcFn(); endResetModel(); _selectionModel->setCurrentIndex(worldIndex(), QItemSelectionModel::SelectCurrent); emitChanged(true, false); } void WorldModel::emitChanged(bool fullUpdate, bool recalcFn) { //qDebug() << "emitChanged(): " << world()->time() << endl; if(fullUpdate) _updatingFullUpdate = true; if(recalcFn) _updatingRecalcFn = true; if(!_updatingTimer->isActive()) _updatingTimer->start(0); } void WorldModel::doEmitChanged() { if(_updatingRecalcFn) _world->doCalcFn(); emit worldDataChanged(!_updatingFullUpdate); if(_updatingFullUpdate) { emit dataChanged(worldIndex(), constraintSolverIndex()); } } QModelIndex WorldModel::worldIndex() const { return createIndex(0, 0, _world); } QModelIndex WorldModel::solverIndex() const { return createIndex(1, 0, _world->solver()); } QModelIndex WorldModel::collisionSolverIndex() const { return createIndex(2, 0, _world->collisionSolver()); } QModelIndex WorldModel::constraintSolverIndex() const { return createIndex(3, 0, _world->constraintSolver()); } QModelIndex WorldModel::childItemIndex(int n, StepCore::ItemGroup* group) const { if(!group) group = _world; return createIndex(n, 0, group->childItem(n)); } QModelIndex WorldModel::objectIndex(StepCore::Object* obj) const { if(obj == _world) return worldIndex(); else if(obj == _world->solver()) return solverIndex(); else if(obj == _world->collisionSolver()) return collisionSolverIndex(); else if(obj == _world->constraintSolver()) return constraintSolverIndex(); else { StepCore::Item* item = dynamic_cast(obj); for(StepCore::Item* it = item; it != _world; it = it->group()) { if(it == NULL) return QModelIndex(); } STEPCORE_ASSERT_NOABORT(item && item->group()); return createIndex(item->group()->childItemIndex(item), 0, item); //return itemIndex(_world->childItemIndex(dynamic_cast(obj))); } } StepCore::Object* WorldModel::object(const QModelIndex& index) const { if(index.isValid()) return static_cast(index.internalPointer()); else return NULL; } #if 0 void WorldModel::objectChanged(const StepCore::Object* /*object*/) { if(!_updating) { _world->doCalcFn(); emitChanged(false); } } #endif StepCore::Item* WorldModel::item(const QModelIndex& index) const { return dynamic_cast(object(index)); } QVariant WorldModel::data(const QModelIndex &index, int role) const { if(!index.isValid()) return QVariant(); StepCore::Object* obj = static_cast(index.internalPointer()); if(role == Qt::DisplayRole) { return formatNameFull(obj); } else if(role == Qt::ToolTipRole) { const_cast(this)->simulationPause(); return createToolTip(index); // XXX } else if(role == Qt::DecorationRole) { if(_worldFactory->hasObjectIcon(obj->metaObject())) return _worldFactory->objectIcon(obj->metaObject()); return QVariant(); } else if(role == FormattedNameRole) { return formatName(obj); } else if(role == ClassNameRole) { return obj->metaObject()->className(); } return QVariant(); } QModelIndex WorldModel::index(int row, int /*column*/, const QModelIndex &parent) const { if(!parent.isValid()) { if(row == 0) return worldIndex(); else if(row == 1) return solverIndex(); else if(row == 2) return collisionSolverIndex(); else if(row == 3) return constraintSolverIndex(); } else { StepCore::ItemGroup* group = dynamic_cast(object(parent)); if(group) return createIndex(row, 0, group->childItem(row)); //if(parent.internalPointer() == _world) return itemIndex(row); } return QModelIndex(); } QModelIndex WorldModel::parent(const QModelIndex &index) const { if(!index.isValid()) return QModelIndex(); else if(index.internalPointer() == _world) return QModelIndex(); else if(index.internalPointer() == _world->solver()) return QModelIndex(); else if(index.internalPointer() == _world->collisionSolver()) return QModelIndex(); else if(index.internalPointer() == _world->constraintSolver()) return QModelIndex(); else { StepCore::Item* item = dynamic_cast(object(index)); if(item && item->group()) return objectIndex(item->group()); else return QModelIndex(); } } int WorldModel::rowCount(const QModelIndex &parent) const { if(!parent.isValid()) { Q_ASSERT(_world->solver()); Q_ASSERT(_world->collisionSolver()); Q_ASSERT(_world->constraintSolver()); return 4; /* int count = 1; if(_world->solver()) ++count; if(_world->collisionSolver()) ++count; return count; */ } else if(dynamic_cast(object(parent))) return dynamic_cast(object(parent))->childItemCount(); else return 0; } int WorldModel::columnCount(const QModelIndex& /*parent*/) const { return 1; } QString WorldModel::newItemName(const QString& className) { return getUniqueName(className); } StepCore::Item* WorldModel::createItem(const QString& className, StepCore::ItemGroup* parent) { StepCore::Item* item = _worldFactory->newItem(className); if(item == NULL) return NULL; item->setName(getUniqueName(className)); return item; } StepCore::Item* WorldModel::newItem(const QString& className, StepCore::ItemGroup* parent) { StepCore::Item* item = _worldFactory->newItem(className); if(item == NULL) return NULL; item->setName(getUniqueName(className)); pushCommand(new CommandNewItem(this, item, parent, true)); return item; } void WorldModel::addItem(StepCore::Item* item, StepCore::ItemGroup* parent) { //if(item->name().isEmpty()) item->setName(getUniqueName(item->metaObject()->className())); pushCommand(new CommandNewItem(this, item, parent, true)); } StepCore::Solver* WorldModel::newSolver(const QString& className) { StepCore::Solver* solver = _worldFactory->newSolver(className); if(solver == NULL) return NULL; // Copy similary named properties // XXX: is it right ? StepCore::Solver* oldSolver = _world->solver(); for(int i=0; imetaObject()->propertyCount(); ++i) { const StepCore::MetaProperty* oldProperty = oldSolver->metaObject()->property(i); const StepCore::MetaProperty* newProperty = solver->metaObject()->property(oldProperty->name()); if(newProperty && newProperty->isWritable()) { newProperty->writeVariant(solver, oldProperty->readVariant(oldSolver)); } } //solver->setName(_world->solver()->name()); //solver->setName(getUniqueName(name)); // XXX: is it better ? pushCommand(new CommandSetSolver(this, solver)); return solver; } void WorldModel::deleteItem(StepCore::Item* item) { pushCommand(new CommandNewItem(this, item, item->group(), false)); } void WorldModel::deleteSelectedItems() { simulationPause(); QList items; foreach(QModelIndex index, selectionModel()->selectedIndexes()) { // Do not delete world item if (index == worldIndex()) continue; StepCore::Item* it = item(index); if (it) items << it; } foreach(StepCore::Item* it, items) { for(StepCore::Item* it1 = it->group(); it1 != 0 && it1 != _world; it1 = it1->group()) { if(items.contains(it1)) { items.removeOne(it); break; } } } if(!items.isEmpty()) { beginMacro(items.count()==1 ? i18n("Delete %1", items[0]->name()) : i18n("Delete several items")); foreach(StepCore::Item* it, items) deleteItem(it); endMacro(); } } void WorldModel::addCreatedItem(StepCore::Item* item, StepCore::ItemGroup* parent) { if(!parent) parent = _world; beginInsertRows(objectIndex(parent), parent->childItemCount(), parent->childItemCount()); parent->addItem(item); endInsertRows(); emitChanged(true, true); } void WorldModel::removeCreatedItem(StepCore::Item* item) { STEPCORE_ASSERT_NOABORT(item->group()); QModelIndex index = objectIndex(item); beginRemoveRows(index.parent(), index.row(), index.row()); item->group()->removeItem(item); endRemoveRows(); emitChanged(true, true); } StepCore::Solver* WorldModel::swapSolver(StepCore::Solver* solver) { bool selected = selectionModel()->isSelected(solverIndex()); bool current = selectionModel()->currentIndex() == solverIndex(); StepCore::Solver* oldSolver = _world->removeSolver(); _world->setSolver(solver); if(selected) selectionModel()->select(solverIndex(), QItemSelectionModel::Select); if(current) selectionModel()->setCurrentIndex(solverIndex(), QItemSelectionModel::Current); emitChanged(true, true); return oldSolver; } void WorldModel::pushCommand(QUndoCommand* command) { Q_ASSERT(!_simulationFrameWaiting || _simulationPaused); if(!isSimulationActive()) { _undoStack->push(command); } else { command->redo(); delete command; } } void WorldModel::beginMacro(const QString& text) { // XXX: simulation could be started (by hotkey) during macro creation if(!isSimulationActive()) _undoStack->beginMacro(text); } void WorldModel::endMacro() { // XXX: simulation could be stopped during macro creation if(!isSimulationActive()) _undoStack->endMacro(); } void WorldModel::setProperty(StepCore::Object* object, const StepCore::MetaProperty* property, const QVariant& value, UndoFlags flags) { Q_ASSERT(object != NULL); Q_ASSERT(property != NULL); pushCommand(new CommandEditProperty(this, object, property, value, !flags.testFlag(UndoNoMerge))); } QString WorldModel::formatName(const StepCore::Object* object) const { if(!object) return i18n(""); else if(!object->name().isEmpty()) return object->name(); return i18n(""); } QString WorldModel::formatNameFull(const StepCore::Object* object) const { if(!object) return i18n(""); return i18n("%1: %2", formatName(object), object->metaObject()->classNameTr()); } QString WorldModel::formatProperty(const StepCore::Object* object, const StepCore::Object* objectErrors, const StepCore::MetaProperty* property, FormatFlags flags) const { int pr = Settings::floatDisplayPrecision(); QString units; if(!flags.testFlag(FormatHideUnits)) { if(!flags.testFlag(FormatEditable) && !property->units().isEmpty()) units.append(" [").append(property->units()).append("]"); #ifdef STEP_WITH_UNITSCALC else if(flags.testFlag(FormatEditable) && !property->units().isEmpty() && property->userTypeId() == QMetaType::Double) units.append(" ").append(property->units()); #endif } const StepCore::MetaProperty* pv = objectErrors ? objectErrors->metaObject()->property(property->name() + "Variance") : NULL; // Common property types if(property->userTypeId() == QMetaType::Double) { QString error; if(pv) error = QStringLiteral(" ± %1") .arg(sqrt(pv->readVariant(objectErrors).toDouble()), 0, 'g', pr) .append(units); return QString::number(property->readVariant(object).toDouble(), 'g', pr).append(units).append(error); } else if(property->userTypeId() == qMetaTypeId()) { QString error; if(pv) { StepCore::Vector2d vv = pv->readVariant(objectErrors).value(); error = QStringLiteral(" ± (%1,%2)") .arg(sqrt(vv[0]), 0, 'g', pr).arg(sqrt(vv[1]), 0, 'g', pr).append(units); } StepCore::Vector2d v = property->readVariant(object).value(); return QStringLiteral("(%1,%2)").arg(v[0], 0, 'g', pr).arg(v[1], 0, 'g', pr).append(units).append(error); } else if(property->userTypeId() == qMetaTypeId() ) { // XXX: add error information // if(pv) qDebug() << "Unhandled property variance type" << endl; StepCore::Vector2dList list = property->readVariant(object).value(); QString string; unsigned int end = flags.testFlag(FormatEditable) ? list.size() : qMin(10, list.size()); for(unsigned int i=0; ireadString(_objectErrors)).append(units); //if(pv) qDebug() << "Unhandled property variance type" << endl; Q_ASSERT(!pv); QString str = property->readString(object); if(!flags.testFlag(FormatEditable) && str.length() > 50) str = str.left(50).append("..."); return str.append(units); } } QString WorldModel::createToolTip(const QModelIndex& index) const { //Q_ASSERT(object != NULL); Q_ASSERT(index.isValid()); QString toolTip = i18n("

%1

", index.data(Qt::DisplayRole).toString()); toolTip += QLatin1String(""); StepCore::Object* object = this->object(index); StepCore::Item* item = dynamic_cast(object); const StepCore::Object* objectErrors = NULL; if(item) { if(world()->errorsCalculation()) objectErrors = item->objectErrors(); else objectErrors = item->tryGetObjectErrors(); } for(int i=0; imetaObject()->propertyCount(); ++i) { const StepCore::MetaProperty* p = object->metaObject()->property(i); /*QString units; if(!p->units().isEmpty()) units.append(" [").append(p->units()).append("]"); QString value = p->readString(object); if(p->userTypeId() == qMetaTypeId()) { // XXX: don't use readString in this case ! value.replace("),(", QString(")%1
(").arg(units)); value.append(units); if(value.count("
") > 10) { value = value.section("
", 0, 9); value.append("
..."); } } else { value.append(units); }*/ toolTip += i18n("
", p->nameTr(), formatProperty(object, objectErrors, p)); } toolTip += QLatin1String("
%1  %2
"); //qDebug("%s", toolTip.toAscii().constData()); return toolTip; } QMenu* WorldModel::createContextMenu(const QModelIndex& index) { //Q_ASSERT(object != NULL); Q_ASSERT(index.isValid()); QMenu* menu = new QMenu(); menu->addSection(index.data(Qt::DisplayRole).toString()); ItemMenuHandler* handler = _worldFactory->newItemMenuHandler(object(index), this, menu); handler->populateMenu(menu, _actions); return menu; } void WorldModel::clearWorld() { _world->clear(); resetWorld(); } bool WorldModel::saveXml(QIODevice* device) { StepCore::XmlFile file(device); if(file.save(_world)) { return true; } else { _errorString = file.errorString(); return false; } } bool WorldModel::loadXml(QIODevice* device) { _world->clear(); StepCore::XmlFile file(device); bool ret = file.load(_world, _worldFactory); if(!ret) { _world->clear(); _errorString = file.errorString(); } resetWorld(); return ret; } bool WorldModel::checkUniqueName(const QString& name) const { if(name.isEmpty()) return false; if(_world->object(name) != NULL) return false; return true; } QString WorldModel::getUniqueName(const QString& className) const { QString classNameCopy = className; classNameCopy[0] = classNameCopy[0].toLower(); for(int n=1; ; ++n) { QString name = classNameCopy + QString::number(n); if(checkUniqueName(name)) return name; } return QString(); } void WorldModel::simulationPause() { if(!_simulationFrameWaiting || _simulationPaused) return; // Try to lock but do not wait mode than one frame if(_simulationThread->mutex()->tryLock(1000/_simulationFps)) { // We still need to setAbortEvolve(true) since // _simulationThread->doWorldEvolve() could be called before _world->setEvolveAbort(true); } else { //qDebug() << "simulationPause: simulation aborted" << endl; Q_ASSERT(isSimulationActive()); Q_ASSERT(_simulationCommand); Q_ASSERT(_simulationFrameWaiting); //Q_ASSERT(!_simulationStopping); _world->setEvolveAbort(true); // Mutex will be locked as soon as current frame is aborted _simulationThread->mutex()->lock(); } // We can release mutex just now since new simulation frame can // only be started from this thread when control returns to event loop _simulationThread->mutex()->unlock(); _simulationPaused = true; //qDebug() << "simulationPause!" << endl; // XXX: do we need to reset evolveAbort here (and add threadAbort var) ? // XXX: do we need to call emitChanged here ? } void WorldModel::setSimulationFps(int simulationFps) { _simulationFps = simulationFps; _simulationTimer->setInterval(1000/simulationFps); } bool WorldModel::isSimulationActive() { return _simulationTimer->isActive(); } void WorldModel::simulationStart() { Q_ASSERT(!isSimulationActive()); Q_ASSERT(!_simulationCommand); //_undoStack->beginMacro(i18n("Simulate")); _simulationCommand = new CommandSimulate(this); _world->setEvolveAbort(false); _simulationFrameWaiting = false; _simulationFrameSkipped = false; _simulationStopping = false; _simulationPaused = false; _simulationFrames = 0; _simulationStartTime = QTime::currentTime(); _simulationTimer->start(); //emitChanged(); } void WorldModel::simulationStop() { Q_ASSERT(isSimulationActive()); Q_ASSERT(_simulationCommand); _simulationStopping = true; if(_simulationFrameWaiting) { simulationPause(); } else { simulationFrameEnd(0); } } void WorldModel::simulationFrameBegin() { Q_ASSERT(isSimulationActive()); Q_ASSERT(_simulationCommand); Q_ASSERT(!_simulationStopping); //qDebug() << "simulationFrameBegin(): " << world()->time() << endl; if(_simulationFrameWaiting) { // TODO: warn user _simulationFrameSkipped = true; return; } if(_updatingTimer->isActive()) { // Wait for updating _simulationTimer0->start(); return; } //qDebug("emit simulationDoFrame() t=%#x", int(QThread::currentThread())); //qDebug() << "simulationFrameBegin" << endl; _simulationFrameWaiting = true; _simulationPaused = false; _simulationThread->doWorldEvolve(1.0/_simulationFps); //qDebug("emitted simulationDoFrame()"); } void WorldModel::simulationFrameEnd(int result) { Q_ASSERT(isSimulationActive()); Q_ASSERT(_simulationCommand); Q_ASSERT(_simulationFrameWaiting || _simulationStopping); ++_simulationFrames; if(_simulationFrames == 50) { qDebug("FPS: %f", double(_simulationFrames) / _simulationStartTime.msecsTo(QTime::currentTime()) * 1000); _simulationStartTime = QTime::currentTime(); _simulationFrames = 0; } //qDebug() << "simulationFrameEnd" << endl; // It's OK to be aborted if(result == StepCore::Solver::Aborted) { //qDebug("simulation frame aborted!"); result = StepCore::Solver::OK; } // If current frame was aborted we can resume it now _world->setEvolveAbort(false); // Update GUI _simulationFrameWaiting = false; emitChanged(false, false); // Stop if requested or simulation error occurred if(_simulationStopping || result != StepCore::Solver::OK) { _simulationCommand->done(); _undoStack->beginMacro(i18n("Simulate %1 → %2", _simulationCommand->startTime(), _simulationCommand->endTime())); _undoStack->push(_simulationCommand); _undoStack->endMacro(); _simulationCommand = NULL; _simulationTimer->stop(); _simulationTimer0->stop(); emit simulationStopped(result); return; } if(_simulationFrameSkipped) { _simulationFrameSkipped = false; _simulationTimer0->start(); //QApplication::processEvents(); // XXX //if(isSimulationActive() && !_simulationFrameWaiting) // simulationFrameBegin(); } } QList WorldModel::selectedItems() { QList items; foreach (QModelIndex index, selectionModel()->selectedIndexes()) { // Do not delete world item if (index == worldIndex()) continue; StepCore::Item* it = item(index); if (it) items << it; } foreach (StepCore::Item* it, items) { for (StepCore::Item* it1 = it->group(); it1 != 0 && it1 != _world; it1 = it1->group()) { if (items.contains(it1)) { items.removeOne(it); break; } } } return items; } void WorldModel::cutSelectedItems() { simulationPause(); QList items = selectedItems(); _clipboard->copy(items); if (!items.isEmpty()) { beginMacro(items.count() == 1 ? i18n("Cut %1", items[0]->name()) : i18n("Cut several items")); foreach (StepCore::Item* it, items) deleteItem(it); endMacro(); } } void WorldModel::copySelectedItems() { simulationPause(); QList items = selectedItems(); _clipboard->copy(items); } void WorldModel::pasteItems() { simulationPause(); QList items = _clipboard->paste(_worldFactory); if (items.isEmpty()) return; beginMacro(items.count() == 1 ? i18n("Pasted %1", items[0]->name()) : i18n("Pasted several items")); QItemSelection selection; foreach (StepCore::Item* item, items) { QString className = item->metaObject()->className(); item->setName(getUniqueName(className)); addItem(item, _world); QModelIndex index = objectIndex(item); selection.select(index, index); } if (!selection.isEmpty()) { selectionModel()->select(selection, QItemSelectionModel::ClearAndSelect); } endMacro(); } diff --git a/stepcore/itemgroup.h b/stepcore/itemgroup.h index 99122df..ed31c72 100644 --- a/stepcore/itemgroup.h +++ b/stepcore/itemgroup.h @@ -1,104 +1,106 @@ /* This file is part of StepCore library. Copyright (C) 2007 Vladimir Kuznetsov Copyright (C) 2014 Inge Wallin StepCore library is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. StepCore library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with StepCore; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /** \file itemgroup.h * \brief Contains the ItemGroup object. */ #ifndef STEPCORE_ITEMGROUP_H #define STEPCORE_ITEMGROUP_H #include // XXX: Replace if Qt is enabled. #include "types.h" #include "item.h" namespace StepCore { /** \ingroup world * \brief Groups several items together */ class ItemGroup : public Item { STEPCORE_OBJECT(ItemGroup) public: /** Constructs empty group */ explicit ItemGroup(const QString& name = QString()) : Item(name) {} /** Constructs a copy of the group (deep copy) */ ItemGroup(const ItemGroup& group); /** Destroys the group and all its subitems */ ~ItemGroup(); /** Assignment operator (deep copy) * \warning Do not call this on groups already attached to the world */ ItemGroup& operator=(const ItemGroup& group); /** Get list of all direct child items in the ItemGroup */ const ItemList& items() const { return _items; } + bool contains(const Item* item) const { return std::find(_items.begin(), _items.end(), item) != _items.end(); } + /** Get list of all items in the ItemGroup * \note This operation takes long time since it * recursively traverses all child groups */ ItemList allItems() const { ItemList l; allItems(&l); return l; } /** Get list of all items in the ItemGroup * \param items Array to store items * \note This operation takes long time since it * recursively traverses all child groups */ void allItems(ItemList* items) const; /** Add new item to the group */ virtual void addItem(Item* item); /** Remove item from the group (you should delete item yourself) */ virtual void removeItem(Item* item); /** Delete item from the group (it actually deletes item) */ virtual void deleteItem(Item* item) { removeItem(item); delete item; } /** Deletes all items */ void clear(); /** Finds direct child item in items() */ int childItemIndex(const Item* item) const; /** Get direct child count */ int childItemCount() const { return _items.size(); } /** Get direct child item by its index */ Item* childItem(int index) const { return _items[index]; } /** Get direct child item by its name */ Item* childItem(const QString& name) const; /** Get any descendant item by its name */ Item* item(const QString& name) const; /** Recursively call setWorld for all children objects */ void setWorld(World* world) Q_DECL_OVERRIDE; /** Recursively call worldItemRemoved for all children objects */ void worldItemRemoved(Item* item) Q_DECL_OVERRIDE; private: ItemList _items; }; } // namespace StepCore #endif