diff --git a/resources/Sync.png b/resources/Sync.png index 646999afcfcb6077ac2b0a6a253dd2fb55b7018a..ea9bcccf7df54515ad6c399c2a26fb5ac7f1ac06 100644 Binary files a/resources/Sync.png and b/resources/Sync.png differ diff --git a/src/MissionEditor/MissionEditor.cc b/src/MissionEditor/MissionEditor.cc index 565f0a7cea8d5b5e3ddf62fa50e0ef95765b5b88..3aa1f1a8e624c8492404478806c8ab0b148daef9 100644 --- a/src/MissionEditor/MissionEditor.cc +++ b/src/MissionEditor/MissionEditor.cc @@ -73,6 +73,7 @@ void MissionEditor::_newMissionItemsAvailable(void) _canEdit = missionManager->canEdit(); _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 f648e9a25067451b4340f7b991b69a8f0e8f24f9..1751e19dd71cef96816d62d781577c4ed3d9eb50 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 eb47d3d8f67e9306889ac233bb36515a22269816..9a9a059bfa1b4bd505ba82b34d75364832ea8f87 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 46610a79421cf6f1bcc7276a05eb0a4372e797b7..5b350318ec07d2f378479218465a442ecfdee274 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 58017c8478ae75f86a55ddbf35026a7da960370e..b9f1da05303a1aa8cbcaa3af6e3a83065850af1e 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 9c9b6c2a84ab039bc5b6cb0788db35e799c7eb14..ea3cdd5c6086b81337ce0c7be545812ef2bda587 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;