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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 Wbf57S6@lvKAik%opUXO@geCyaT1b!p literal 3561 zcmeAS@N?(olHy`uVBq!ia0y~yU}6Aa4mJh`hA$OYelajGuqAoByD)&kPv_nB3=9mM z1s;*b3=G`DAk4@xYmNj10|R@Br>`sfeJ)NhS%&|IAFO9!;4SlXaSW-r_4aOHp0u@q z%SB1Y-}gT~QFqw)hGn9g+V6Rro7iXG4P7F$|1sEf@`;B1*0n4f+F$Qlr#tOb9C=m;#sim_9EIj8t2!_q*u>N*G*=zOTEuivXs#xR)x`8rXpSyO zT@jO`@LYY>2mQrq7`8<)c!!KE*v`yhTEL+2!;xdAwW`7+4t&@;9C2(PH3YXp(1{Vj03D!oXtBp}Bblg9Zaf9Y_1r z$`FQqp9@$7{u$)&JEI)OAn-?E_s&_K`d}SNjA?qJU_&-47|#Zo`++@xVc*kw_MQjq z9G})R2>fG+V{rJuT*2t@gZTl-wgz#A7JCMeISlB6TnvZg8~i_sHauWIz|C+-o`IF& zA^!o7eM|y>AZiydDEwgz02!*mz){a&_H;S}E7+`mAlKG2h=7a+nF7(N_eY!IAV0)H zxM%}Y!v}T;kaOUst$A*9{_=8$i$A{$a@4bIH}0_vV%T7@amJj&Gc%bQj&`sW$>t{H_ zh6o<`KBVMtiWQGZ6Of^kEDz@HhRAD9vrD*Q2Ay?~+NgZq!edNr&GD>>@UXjU*D2on4^ zBRGITN3_NMnTXpbQHCb@hx~C28zNfl(?moVx>+CgOR};uwAde#7hyQf`mp~e3+sU* z#~;adiQ@lYs&yn`Ns%K3k^&U+7$m7&Te2zILOez)NmW5_V)e{$7`74K&iq< zpCM2DkUWDvD0M6oXV_l<5ET6k2~~^*zsrBKD}*t$?Ek>7@C%d%6hav8{A&dzABKjP ztOwrSe~YY<;RD$C%iImO_kVySN3bgSDM1C__Aoryzzj}465$Ls|C^h*&dm{LkSSq6 z*y#|-nw|@`_5|YxCI*eQ4|;s{8S?(~S)S8-z{}~c7h&LG zyrEnsoB!pDAOjOi)wDm>3{ln$EZhuvCJY)33Z{2IJx*z-gL*325us!k_=dN$2 z4bSTt_AoFoaDmc_3s*w`3j-(?7y>jI4hb^s0eR42DFcTm!w-jh>wd|by?xK5VaA}r zuu(|i4`W2{eMSZbeTF#3JO3C17^W~hG#D5- z{wD|qFfa)GIjFRNfkELHW5z{&uMrvtw${)ICzG#s5{$HZ{wpS{Sg?wbGaSs54>iGb1(!voWK z|3w)X9&Dew?|(Q0L&5LwObiSmKhhf*cKp3ByRF;ye>EdRg9a-D!*-C#70X5E|L=cn zF|W=<%J4U@-i=ZX2DV22zcy1D6RxlxcUSoF(TJfeK0myIvEWDL!{_Tb7~&XM?4|5( zH0NxV*U@X2b7gpQ=z-sc4Kig@7&h=X|2}Y^vEW9bU&Dh`2gVIo3gZq)@&_@dY+8TUC4#F>1LV~{DDqs+j{FomID+TDMm4DB-MTn>yIRxrMC6JglkaPVgA z?Y#Y8Rx?;Uu}B6*L_pN?`w(Tkb3*kQ7#tpcThEZO6O#XnSh>LdVZF-Dz;G!3hc*MB zwMF%T>1F@-*H$qyIB0$aC71MHZ{rym9DYG0z*D+9wre~=>>IO_K>Fyx7I)N6fS$-ux--}E$w zk)c6>mEn#-!Ebeo*<1__AC7Y~JZNkDU_Kj^Qsr403JyE`nC;8TupvR$v=|Z&HhgdgNohalVu&zM_;dEz+Ie0SW3=L14 zKA6wvVo2Dj@F$Rg;bH#=^Tk{Y5jqNg&c0Clqs_1(0Hks~NDTI@OMj8`)>xH2`}KcmHvFj3)$;bty|2o;XH zGi#U_#5kMe!O~3++DpGLXf*%e#qh1cHX%-wp}GHQ)6e5IS`1H$!!@eind#_(`|V|zaX!^8Cq3=JQ`85kTuv9It)lz~Cu9~Y<`^8K-&kxAjlUhMLR zG`vNu`8U2i#D>e7x<^az~xYJ z@`sG$hs6vU3=dr&e`Yz@&$NKSWqR*<5f*!{1}1?x3%h8j!iPV4zzP{3E`40g23FW2 z-hW;is?g4^T;LDbM8=5_6wrI9x|AbfnFFJM&PUg!JPFer7zH#wwl3vK=yPBc zQ2DrYDNjP31LKLd_NPmE67n1vJ6hXRm+>UTfmp4n%Xkvp92hxTTb3>pNU#HmH9uX( zlb{A-HMuSqNZ@l|X sGnYfk#^@<;LWJgolivvf>uj*BW0bTu-qLn$?hBBBr>mdKI;Vst0Pi)&dH?_b diff --git a/src/MissionEditor/MissionEditor.cc b/src/MissionEditor/MissionEditor.cc index 565f0a7cea..3aa1f1a8e6 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 f648e9a250..1751e19dd7 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 eb47d3d8f6..9a9a059bfa 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 46610a7942..5b350318ec 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 58017c8478..b9f1da0530 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 9c9b6c2a84..ea3cdd5c60 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; -- GitLab