From 97c92ae753517fbc44790abaf7a5dac0f62dc76c Mon Sep 17 00:00:00 2001 From: Don Gagne Date: Sat, 10 Oct 2015 13:32:46 -0700 Subject: [PATCH] Add dirty bit support --- resources/Sync.png | Bin 3561 -> 4035 bytes src/MissionEditor/MissionEditor.cc | 5 ++++ src/MissionEditor/MissionEditor.qml | 30 ++++++++++++++++---- src/MissionItem.cc | 24 ++++++++++++++++ src/MissionItem.h | 12 ++++++++ src/QmlControls/QmlObjectListModel.cc | 39 +++++++++++++++++++++++++- src/QmlControls/QmlObjectListModel.h | 16 +++++++++++ 7 files changed, 120 insertions(+), 6 deletions(-) diff --git a/resources/Sync.png b/resources/Sync.png index 646999afcfcb6077ac2b0a6a253dd2fb55b7018a..ea9bcccf7df54515ad6c399c2a26fb5ac7f1ac06 100644 GIT binary patch literal 4035 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 zv}ILScanEdit(); _missionItems = missionManager->copyMissionItems(); _reSequence(); + _missionItems->setDirty(false); emit missionItemsChanged(); emit canEditChanged(_canEdit); @@ -95,6 +96,7 @@ void MissionEditor::setMissionItems(void) if (activeVehicle) { activeVehicle->missionManager()->writeMissionItems(*_missionItems); + _missionItems->setDirty(false); } } @@ -225,6 +227,7 @@ void MissionEditor::loadMissionFromFile(void) _missionItems->clear(); } + _missionItems->setDirty(false); emit canEditChanged(_canEdit); } @@ -250,4 +253,6 @@ void MissionEditor::saveMissionToFile(void) qobject_cast(_missionItems->get(i))->save(out); } } + + _missionItems->setDirty(false); } diff --git a/src/MissionEditor/MissionEditor.qml b/src/MissionEditor/MissionEditor.qml index f648e9a25..1751e19dd 100644 --- a/src/MissionEditor/MissionEditor.qml +++ b/src/MissionEditor/MissionEditor.qml @@ -88,11 +88,6 @@ QGCView { longitude = _homePositionCoordinate.longitude } - QGCLabel { - anchors.bottom: parent.bottom - text: "WIP: Danger, do not fly with this!"; font.pixelSize: ScreenTools.largeFontPixelSize } - - MouseArea { anchors.fill: parent @@ -110,6 +105,31 @@ QGCView { } } + Rectangle { + anchors.horizontalCenter: parent.horizontalCenter + anchors.bottom: parent.bottom + width: parent.width / 3 + height: syncNeededText.height + (ScreenTools.defaultFontPixelWidth * 2) + border.width: 1 + border.color: "white" + color: "black" + opacity: 0.75 + visible: controller.missionItems.dirty + + QGCLabel { + id: syncNeededText + anchors.margins: ScreenTools.defaultFontPixelWidth + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + wrapMode: Text.WordWrap + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + font.pixelSize: ScreenTools.mediumFontPixelSize + text: "You have unsaved changes. Be sure to use the Sync tool to save when ready." + } + } + Rectangle { id: addMissionItemsButton anchors.rightMargin: ScreenTools.defaultFontPixelHeight diff --git a/src/MissionItem.cc b/src/MissionItem.cc index eb47d3d8f..9a9a059bf 100644 --- a/src/MissionItem.cc +++ b/src/MissionItem.cc @@ -83,6 +83,7 @@ MissionItem::MissionItem(QObject* parent, , _isCurrentItem(isCurrentItem) , _reachedTime(0) , _yawRadiansFact(NULL) + ,_dirty(false) { _latitudeFact = new Fact(0, "Latitude:", FactMetaData::valueTypeDouble, this); _longitudeFact = new Fact(0, "Longitude:", FactMetaData::valueTypeDouble, this); @@ -147,6 +148,16 @@ MissionItem::MissionItem(QObject* parent, _altitudeFact->setMetaData(altitudeMetaData); _yawRadiansFact->setMetaData(yawMetaData); _loiterOrbitRadiusFact->setMetaData(loiterOrbitRadiusMetaData); + + // Connect to valueChanged to track dirty state + connect(_latitudeFact, &Fact::valueChanged, this, &MissionItem::_factValueChanged); + connect(_longitudeFact, &Fact::valueChanged, this, &MissionItem::_factValueChanged); + connect(_altitudeFact, &Fact::valueChanged, this, &MissionItem::_factValueChanged); + connect(_yawRadiansFact, &Fact::valueChanged, this, &MissionItem::_factValueChanged); + connect(_loiterOrbitRadiusFact, &Fact::valueChanged, this, &MissionItem::_factValueChanged); + connect(_param1Fact, &Fact::valueChanged, this, &MissionItem::_factValueChanged); + connect(_param2Fact, &Fact::valueChanged, this, &MissionItem::_factValueChanged); + connect(_altitudeRelativeToHomeFact, &Fact::valueChanged, this, &MissionItem::_factValueChanged); } MissionItem::MissionItem(const MissionItem& other, QObject* parent) @@ -187,6 +198,7 @@ const MissionItem& MissionItem::operator=(const MissionItem& other) _autocontinue = other._autocontinue; _reachedTime = other._reachedTime; _altitudeRelativeToHomeFact = other._altitudeRelativeToHomeFact; + _dirty = other._dirty; *_latitudeFact = *other._latitudeFact; *_longitudeFact = *other._longitudeFact; @@ -797,3 +809,15 @@ bool MissionItem::canEdit(void) return false; } } + +void MissionItem::setDirty(bool dirty) +{ + _dirty = dirty; + emit dirtyChanged(_dirty); +} + +void MissionItem::_factValueChanged(QVariant value) +{ + Q_UNUSED(value); + setDirty(true); +} diff --git a/src/MissionItem.h b/src/MissionItem.h index 46610a794..5b350318e 100644 --- a/src/MissionItem.h +++ b/src/MissionItem.h @@ -61,6 +61,9 @@ public: const MissionItem& operator=(const MissionItem& other); + /// Returns true if the item has been modified since the last time dirty was false + Q_PROPERTY(bool dirty READ dirty WRITE setDirty NOTIFY dirtyChanged) + Q_PROPERTY(int sequenceNumber READ sequenceNumber WRITE setSequenceNumber NOTIFY sequenceNumberChanged) Q_PROPERTY(bool isCurrentItem READ isCurrentItem WRITE setIsCurrentItem NOTIFY isCurrentItemChanged) Q_PROPERTY(bool specifiesCoordinate READ specifiesCoordinate NOTIFY commandChanged) @@ -106,6 +109,9 @@ public: double yawDegrees(void) const; void setYawDegrees(double yaw); + bool dirty(void) { return _dirty; } + void setDirty(bool dirty); + // C++ only methods /// Returns true if this item can be edited in the ui @@ -184,6 +190,7 @@ signals: void isCurrentItemChanged(bool isCurrentItem); void coordinateChanged(const QGeoCoordinate& coordinate); void yawChanged(double yaw); + void dirtyChanged(bool dirty); /** @brief Announces a change to the waypoint data */ void changed(MissionItem* wp); @@ -219,6 +226,9 @@ public: void setChanged() { emit changed(this); } + +private slots: + void _factValueChanged(QVariant value); private: QString _oneDecimalString(double value); @@ -254,6 +264,8 @@ private: FactMetaData* _jumpSequenceMetaData; FactMetaData* _jumpRepeatMetaData; + bool _dirty; + static const int _cMavCmd2Name = 9; static const MavCmd2Name_t _rgMavCmd2Name[_cMavCmd2Name]; }; diff --git a/src/QmlControls/QmlObjectListModel.cc b/src/QmlControls/QmlObjectListModel.cc index 58017c847..b9f1da053 100644 --- a/src/QmlControls/QmlObjectListModel.cc +++ b/src/QmlControls/QmlObjectListModel.cc @@ -34,6 +34,7 @@ const int QmlObjectListModel::TextRole = Qt::UserRole + 1; QmlObjectListModel::QmlObjectListModel(QObject* parent) : QAbstractListModel(parent) + , _dirty(false) { } @@ -142,22 +143,36 @@ const QObject* QmlObjectListModel::operator[](int index) const void QmlObjectListModel::clear(void) { while (rowCount()) { - removeRows(0, 1); + removeAt(0); } } void QmlObjectListModel::removeAt(int i) { + setDirty(true); + + // Look for a dirtyChanged signal on the object + if (_objectList[i]->metaObject()->indexOfSignal(QMetaObject::normalizedSignature("dirtyChanged(bool)")) != -1) { + QObject::disconnect(_objectList[i], SIGNAL(dirtyChanged(bool)), this, SLOT(_childDirtyChanged(bool))); + } + removeRows(i, 1); } void QmlObjectListModel::insert(int i, QObject* object) { + setDirty(true); + if (i < 0 || i > _objectList.count()) { qWarning() << "Invalid index index:count" << i << _objectList.count(); } QQmlEngine::setObjectOwnership(object, QQmlEngine::CppOwnership); + + // Look for a dirtyChanged signal on the object + if (object->metaObject()->indexOfSignal(QMetaObject::normalizedSignature("dirtyChanged(bool)")) != -1) { + QObject::connect(object, SIGNAL(dirtyChanged(bool)), this, SLOT(_childDirtyChanged(bool))); + } _objectList.insert(i, object); insertRows(i, 1); @@ -177,3 +192,25 @@ QObject* QmlObjectListModel::get(int index) { return _objectList[index]; } + +void QmlObjectListModel::setDirty(bool dirty) +{ + _dirty = dirty; + + if (!dirty) { + // Need to clear dirty from all children + foreach(QObject* object, _objectList) { + if (object->property("dirty").isValid()) { + object->setProperty("dirty", false); + } + } + } + + emit dirtyChanged(_dirty); +} + +void QmlObjectListModel::_childDirtyChanged(bool dirty) +{ + _dirty |= dirty; + emit dirtyChanged(_dirty); +} diff --git a/src/QmlControls/QmlObjectListModel.h b/src/QmlControls/QmlObjectListModel.h index 9c9b6c2a8..ea3cdd5c6 100644 --- a/src/QmlControls/QmlObjectListModel.h +++ b/src/QmlControls/QmlObjectListModel.h @@ -37,8 +37,18 @@ public: Q_INVOKABLE QObject* get(int index); Q_PROPERTY(int count READ count NOTIFY countChanged) + + /// Returns true if any of the items in the list are dirty. Requires each object to have + /// a dirty property and dirtyChanged signal. + Q_PROPERTY(bool dirty READ dirty WRITE setDirty NOTIFY dirtyChanged) + // Property accessors + int count(void) const; + + bool dirty(void) { return _dirty; } + void setDirty(bool dirty); + void append(QObject* object); void clear(void); void removeAt(int i); @@ -51,6 +61,10 @@ public: signals: void countChanged(int count); + void dirtyChanged(bool dirtyChanged); + +private slots: + void _childDirtyChanged(bool dirty); private: // Overrides from QAbstractListModel @@ -63,6 +77,8 @@ private: private: QList _objectList; + + bool _dirty; static const int ObjectRole; static const int TextRole; -- 2.22.0