diff --git a/qgroundcontrol.qrc b/qgroundcontrol.qrc index 56201fbca7617943b736f2d0705784bce2cae6df..3ca86dddac11e7c94f626281c8bf1bf95239956e 100644 --- a/qgroundcontrol.qrc +++ b/qgroundcontrol.qrc @@ -106,6 +106,7 @@ src/QmlControls/QGCViewMessage.qml src/QmlControls/ParameterEditor.qml + src/QmlControls/ParameterEditorDialog.qml src/ViewWidgets/ParameterEditorWidget.qml src/ViewWidgets/CustomCommandWidget.qml diff --git a/src/AutoPilotPlugins/PX4/PX4ParameterLoader.cc b/src/AutoPilotPlugins/PX4/PX4ParameterLoader.cc index 36e16035ab65c82ec5a5f6250d152c5039cb8cff..ebab9e95dc5f553b0d9f52081c77e10b506999d8 100644 --- a/src/AutoPilotPlugins/PX4/PX4ParameterLoader.cc +++ b/src/AutoPilotPlugins/PX4/PX4ParameterLoader.cc @@ -116,10 +116,11 @@ void PX4ParameterLoader::loadParameterFactMetaData(void) return; } - QString factGroup; - FactMetaData* metaData = NULL; - int xmlState = XmlStateNone; - bool badMetaData = true; + QString factGroup; + QString errorString; + FactMetaData* metaData = NULL; + int xmlState = XmlStateNone; + bool badMetaData = true; while (!xml.atEnd()) { if (xml.isStartElement()) { @@ -221,22 +222,22 @@ void PX4ParameterLoader::loadParameterFactMetaData(void) Q_CHECK_PTR(metaData); if (_mapParameterName2FactMetaData.contains(name)) { // We can't trust the meta dafa since we have dups - qCDebug(PX4ParameterLoaderLog) << "Duplicate parameter found:" << name; + qCWarning(PX4ParameterLoaderLog) << "Duplicate parameter found:" << name; badMetaData = true; // Reset to default meta data _mapParameterName2FactMetaData[name] = metaData; } else { _mapParameterName2FactMetaData[name] = metaData; + metaData->setName(name); metaData->setGroup(factGroup); - if (xml.attributes().hasAttribute("default")) { - bool convertOk; - QVariant varDefault = _stringToTypedVariant(strDefault, metaData->type(), &convertOk); - if (convertOk) { + if (xml.attributes().hasAttribute("default") && !strDefault.isEmpty()) { + QVariant varDefault; + + if (metaData->convertAndValidate(strDefault, false, varDefault, errorString)) { metaData->setDefaultValue(varDefault); } else { - // Non-fatal - qCDebug(PX4ParameterLoaderLog) << "Parameter meta data with bad default value, name:" << name << " type:" << type << " default:" << strDefault; + qCWarning(PX4ParameterLoaderLog) << "Invalid default value, name:" << name << " type:" << type << " default:" << strDefault << " error:" << errorString; } } } @@ -252,12 +253,14 @@ void PX4ParameterLoader::loadParameterFactMetaData(void) if (elementName == "short_desc") { Q_ASSERT(metaData); QString text = xml.readElementText(); + text = text.replace("\n", " "); qCDebug(PX4ParameterLoaderLog) << "Short description:" << text; metaData->setShortDescription(text); } else if (elementName == "long_desc") { Q_ASSERT(metaData); QString text = xml.readElementText(); + text = text.replace("\n", " "); qCDebug(PX4ParameterLoaderLog) << "Long description:" << text; metaData->setLongDescription(text); @@ -265,26 +268,24 @@ void PX4ParameterLoader::loadParameterFactMetaData(void) Q_ASSERT(metaData); QString text = xml.readElementText(); qCDebug(PX4ParameterLoaderLog) << "Min:" << text; - bool convertOk; - QVariant varMin = _stringToTypedVariant(text, metaData->type(), &convertOk); - if (convertOk) { + + QVariant varMin; + if (metaData->convertAndValidate(text, true /* convertOnly */, varMin, errorString)) { metaData->setMin(varMin); } else { - // Non-fatal - qDebug() << "Parameter meta data with bad min value:" << text; + qCWarning(PX4ParameterLoaderLog) << "Invalid min value, name:" << metaData->name() << " type:" << metaData->type() << " min:" << text << " error:" << errorString; } } else if (elementName == "max") { Q_ASSERT(metaData); QString text = xml.readElementText(); qCDebug(PX4ParameterLoaderLog) << "Max:" << text; - bool convertOk; - QVariant varMax = _stringToTypedVariant(text, metaData->type(), &convertOk); - if (convertOk) { + + QVariant varMax; + if (metaData->convertAndValidate(text, true /* convertOnly */, varMax, errorString)) { metaData->setMax(varMax); } else { - // Non-fatal - qDebug() << "Parameter meta data with bad max value:" << text; + qCWarning(PX4ParameterLoaderLog) << "Invalid max value, name:" << metaData->name() << " type:" << metaData->type() << " max:" << text << " error:" << errorString; } } else if (elementName == "unit") { @@ -302,7 +303,16 @@ void PX4ParameterLoader::loadParameterFactMetaData(void) QString elementName = xml.name().toString(); if (elementName == "parameter") { - // Done loading this one parameter + // Done loading this parameter, validate default value + if (metaData->defaultValueAvailable()) { + QVariant var; + + if (!metaData->convertAndValidate(metaData->defaultValue(), false /* convertOnly */, var, errorString)) { + qCWarning(PX4ParameterLoaderLog) << "Invalid default value, name:" << metaData->name() << " type:" << metaData->type() << " default:" << metaData->defaultValue() << " error:" << errorString; + } + } + + // Reset for next parameter metaData = NULL; badMetaData = false; xmlState = XmlStateFoundGroup; diff --git a/src/AutoPilotPlugins/PX4/SafetyComponent.qml b/src/AutoPilotPlugins/PX4/SafetyComponent.qml index b0d70c797b2d5c4d0d9565518fbdc436a88129a9..89a27a51399115ed2ac146c5290bee2a9f47d429 100644 --- a/src/AutoPilotPlugins/PX4/SafetyComponent.qml +++ b/src/AutoPilotPlugins/PX4/SafetyComponent.qml @@ -31,11 +31,12 @@ import QGroundControl.Palette 1.0 import QGroundControl.Controls 1.0 import QGroundControl.ScreenTools 1.0 -FactPanel { - id: panel +QGCView { + id: rootQGCView + viewPanel: view QGCPalette { id: palette; colorGroupEnabled: enabled } - FactPanelController { id: controller; factPanel: panel } + FactPanelController { id: controller; factPanel: rootQGCView } property int flightLineWidth: 2 // width of lines for flight graphic property int loiterAltitudeColumnWidth: 180 // width of loiter altitude column @@ -47,265 +48,271 @@ FactPanel { property int arrowWidth: 18 // width for arrow graphic property int firstColumnWidth: 220 // Width of first column in return home triggers area - Column { + QGCView { + id: view + anchors.fill: parent - QGCLabel { - text: "SAFETY CONFIG" - font.pixelSize: ScreenTools.largeFontPixelSize - } + Column { + anchors.fill: parent - Item { height: 20; width: 10 } // spacer + QGCLabel { + text: "SAFETY CONFIG" + font.pixelSize: ScreenTools.largeFontPixelSize + } - //----------------------------------------------------------------- - //-- Return Home Triggers + Item { height: 20; width: 10 } // spacer - QGCLabel { text: "Triggers For Return Home"; font.pixelSize: ScreenTools.mediumFontPixelSize; } + //----------------------------------------------------------------- + //-- Return Home Triggers - Item { height: 10; width: 10 } // spacer + QGCLabel { text: "Triggers For Return Home"; font.pixelSize: ScreenTools.mediumFontPixelSize; } - Rectangle { - width: parent.width - height: triggerColumn.height - color: palette.windowShade + Item { height: 10; width: 10 } // spacer - Column { - id: triggerColumn - spacing: controlVerticalSpacing - anchors.margins: shadedMargin - anchors.left: parent.left + Rectangle { + width: parent.width + height: triggerColumn.height + color: palette.windowShade - // Top margin - Item { height: 1; width: 10 } + Column { + id: triggerColumn + spacing: controlVerticalSpacing + anchors.margins: shadedMargin + anchors.left: parent.left - Row { - spacing: 10 - QGCLabel { text: "RC Transmitter Signal Loss"; width: firstColumnWidth; anchors.baseline: rcLossField.baseline } - QGCLabel { text: "Return Home after"; anchors.baseline: rcLossField.baseline } - FactTextField { - id: rcLossField - fact: controller.getParameterFact(-1, "COM_RC_LOSS_T") - showUnits: true - } - } + // Top margin + Item { height: 1; width: 10 } - Row { - spacing: 10 - FactCheckBox { - id: telemetryTimeoutCheckbox - anchors.baseline: telemetryLossField.baseline - width: firstColumnWidth - fact: controller.getParameterFact(-1, "COM_DL_LOSS_EN") - checkedValue: 1 - uncheckedValue: 0 - text: "Telemetry Signal Timeout" + Row { + spacing: 10 + QGCLabel { text: "RC Transmitter Signal Loss"; width: firstColumnWidth; anchors.baseline: rcLossField.baseline } + QGCLabel { text: "Return Home after"; anchors.baseline: rcLossField.baseline } + FactTextField { + id: rcLossField + fact: controller.getParameterFact(-1, "COM_RC_LOSS_T") + showUnits: true + } } - QGCLabel { text: "Return Home after"; anchors.baseline: telemetryLossField.baseline } - FactTextField { - id: telemetryLossField - fact: controller.getParameterFact(-1, "COM_DL_LOSS_T") - showUnits: true - enabled: telemetryTimeoutCheckbox.checked + + Row { + spacing: 10 + FactCheckBox { + id: telemetryTimeoutCheckbox + anchors.baseline: telemetryLossField.baseline + width: firstColumnWidth + fact: controller.getParameterFact(-1, "COM_DL_LOSS_EN") + checkedValue: 1 + uncheckedValue: 0 + text: "Telemetry Signal Timeout" + } + QGCLabel { text: "Return Home after"; anchors.baseline: telemetryLossField.baseline } + FactTextField { + id: telemetryLossField + fact: controller.getParameterFact(-1, "COM_DL_LOSS_T") + showUnits: true + enabled: telemetryTimeoutCheckbox.checked + } } - } - // Bottom margin - Item { height: 1; width: 10 } + // Bottom margin + Item { height: 1; width: 10 } + } } - } - - Item { height: 20; width: 10 } // spacer - //----------------------------------------------------------------- - //-- Return Home Settings + Item { height: 20; width: 10 } // spacer - QGCLabel { text: "Return Home Settings"; font.pixelSize: ScreenTools.mediumFontPixelSize; } + //----------------------------------------------------------------- + //-- Return Home Settings - Item { height: 10; width: 10 } // spacer + QGCLabel { text: "Return Home Settings"; font.pixelSize: ScreenTools.mediumFontPixelSize; } - Rectangle { - width: parent.width - height: settingsColumn.height - color: palette.windowShade + Item { height: 10; width: 10 } // spacer - Column { - id: settingsColumn - width: parent.width - anchors.margins: shadedMargin - anchors.left: parent.left + Rectangle { + width: parent.width + height: settingsColumn.height + color: palette.windowShade - Item { height: shadedMargin; width: 10 } // top margin + Column { + id: settingsColumn + width: parent.width + anchors.margins: shadedMargin + anchors.left: parent.left - // This item is the holder for the climb alt and loiter seconds fields - Item { - width: parent.width - height: climbAltitudeColumn.height + Item { height: shadedMargin; width: 10 } // top margin - Column { - id: climbAltitudeColumn - spacing: controlVerticalSpacing - - QGCLabel { text: "Climb to altitude of" } - FactTextField { - id: climbField - fact: controller.getParameterFact(-1, "RTL_RETURN_ALT") - showUnits: true + // This item is the holder for the climb alt and loiter seconds fields + Item { + width: parent.width + height: climbAltitudeColumn.height + + Column { + id: climbAltitudeColumn + spacing: controlVerticalSpacing + + QGCLabel { text: "Climb to altitude of" } + FactTextField { + id: climbField + fact: controller.getParameterFact(-1, "RTL_RETURN_ALT") + showUnits: true + } } - } - Column { - x: flightGraphic.width - 200 - spacing: controlVerticalSpacing + Column { + x: flightGraphic.width - 200 + spacing: controlVerticalSpacing - QGCCheckBox { - id: homeLoiterCheckbox - checked: fact.value > 0 - text: "Loiter at Home altitude for" + QGCCheckBox { + id: homeLoiterCheckbox + checked: fact.value > 0 + text: "Loiter at Home altitude for" - property Fact fact: controller.getParameterFact(-1, "RTL_LAND_DELAY") + property Fact fact: controller.getParameterFact(-1, "RTL_LAND_DELAY") - onClicked: { - fact.value = checked ? 60 : -1 + onClicked: { + fact.value = checked ? 60 : -1 + } } - } - FactTextField { - fact: controller.getParameterFact(-1, "RTL_LAND_DELAY") - showUnits: true - enabled: homeLoiterCheckbox.checked == true + FactTextField { + fact: controller.getParameterFact(-1, "RTL_LAND_DELAY") + showUnits: true + enabled: homeLoiterCheckbox.checked == true + } } } - } - Item { height: 20; width: 10 } // spacer + Item { height: 20; width: 10 } // spacer - // This row holds the flight graphic and the home loiter alt column - Row { - width: parent.width - spacing: 20 + // This row holds the flight graphic and the home loiter alt column + Row { + width: parent.width + spacing: 20 - // Flight graphic - Item { - id: flightGraphic - width: parent.width - loiterAltitudeColumnWidth - height: 200 // controls the height of the flight graphic - - Rectangle { - x: planeWidth / 2 - height: planeImage.y - 5 - width: flightLineWidth - color: palette.button - } - Rectangle { - x: planeWidth / 2 - height: flightLineWidth - width: parent.width - x - color: palette.button - } - Rectangle { - x: parent.width - flightLineWidth - height: parent.height - homeWidth - arrowToHomeSpacing - width: flightLineWidth - color: palette.button - } + // Flight graphic + Item { + id: flightGraphic + width: parent.width - loiterAltitudeColumnWidth + height: 200 // controls the height of the flight graphic - QGCColoredImage { - id: planeImage - y: parent.height - planeWidth - 40 - source: "/qmlimages/SafetyComponentPlane.png" - fillMode: Image.PreserveAspectFit - width: planeWidth - height: planeWidth - smooth: true - color: palette.button - } + Rectangle { + x: planeWidth / 2 + height: planeImage.y - 5 + width: flightLineWidth + color: palette.button + } + Rectangle { + x: planeWidth / 2 + height: flightLineWidth + width: parent.width - x + color: palette.button + } + Rectangle { + x: parent.width - flightLineWidth + height: parent.height - homeWidth - arrowToHomeSpacing + width: flightLineWidth + color: palette.button + } - QGCColoredImage { - x: planeWidth + 70 - y: parent.height - height - 20 - width: 80 - height: parent.height / 2 - source: "/qmlimages/SafetyComponentTree.svg" - fillMode: Image.Stretch - smooth: true - color: palette.windowShadeDark - } + QGCColoredImage { + id: planeImage + y: parent.height - planeWidth - 40 + source: "/qmlimages/SafetyComponentPlane.png" + fillMode: Image.PreserveAspectFit + width: planeWidth + height: planeWidth + smooth: true + color: palette.button + } - QGCColoredImage { - x: planeWidth + 15 - y: parent.height - height - width: 100 - height: parent.height * .75 - source: "/qmlimages/SafetyComponentTree.svg" - fillMode: Image.PreserveAspectFit - smooth: true - color: palette.button - } + QGCColoredImage { + x: planeWidth + 70 + y: parent.height - height - 20 + width: 80 + height: parent.height / 2 + source: "/qmlimages/SafetyComponentTree.svg" + fillMode: Image.Stretch + smooth: true + color: palette.windowShadeDark + } - QGCColoredImage { - x: parent.width - (arrowWidth/2) - 1 - y: parent.height - homeWidth - arrowToHomeSpacing - 2 - source: "/qmlimages/SafetyComponentArrowDown.png" - fillMode: Image.PreserveAspectFit - width: arrowWidth - height: arrowWidth - smooth: true - color: palette.button - } + QGCColoredImage { + x: planeWidth + 15 + y: parent.height - height + width: 100 + height: parent.height * .75 + source: "/qmlimages/SafetyComponentTree.svg" + fillMode: Image.PreserveAspectFit + smooth: true + color: palette.button + } + + QGCColoredImage { + x: parent.width - (arrowWidth/2) - 1 + y: parent.height - homeWidth - arrowToHomeSpacing - 2 + source: "/qmlimages/SafetyComponentArrowDown.png" + fillMode: Image.PreserveAspectFit + width: arrowWidth + height: arrowWidth + smooth: true + color: palette.button + } - QGCColoredImage { - id: homeImage - x: parent.width - (homeWidth / 2) - y: parent.height - homeWidth - source: "/qmlimages/SafetyComponentHome.png" - fillMode: Image.PreserveAspectFit - width: homeWidth - height: homeWidth - smooth: true - color: palette.button + QGCColoredImage { + id: homeImage + x: parent.width - (homeWidth / 2) + y: parent.height - homeWidth + source: "/qmlimages/SafetyComponentHome.png" + fillMode: Image.PreserveAspectFit + width: homeWidth + height: homeWidth + smooth: true + color: palette.button + } } - } - Column { - spacing: controlVerticalSpacing + Column { + spacing: controlVerticalSpacing - QGCLabel { - text: "Home loiter altitude"; - color: palette.text; - enabled: homeLoiterCheckbox.checked === true - } - FactTextField { - id: descendField; - fact: controller.getParameterFact(-1, "RTL_DESCEND_ALT") - enabled: homeLoiterCheckbox.checked === true - showUnits: true + QGCLabel { + text: "Home loiter altitude"; + color: palette.text; + enabled: homeLoiterCheckbox.checked === true + } + FactTextField { + id: descendField; + fact: controller.getParameterFact(-1, "RTL_DESCEND_ALT") + enabled: homeLoiterCheckbox.checked === true + showUnits: true + } } } - } - Item { height: shadedMargin; width: 10 } // bottom margin + Item { height: shadedMargin; width: 10 } // bottom margin + } } - } - QGCLabel { - width: parent.width - font.pixelSize: ScreenTools.mediumFontPixelSize - text: "Warning: You have an advanced safety configuration set using the NAV_RCL_OBC parameter. The above settings may not apply."; - visible: fact.value !== 0 - wrapMode: Text.Wrap + QGCLabel { + width: parent.width + font.pixelSize: ScreenTools.mediumFontPixelSize + text: "Warning: You have an advanced safety configuration set using the NAV_RCL_OBC parameter. The above settings may not apply."; + visible: fact.value !== 0 + wrapMode: Text.Wrap - property Fact fact: controller.getParameterFact(-1, "NAV_RCL_OBC") - } + property Fact fact: controller.getParameterFact(-1, "NAV_RCL_OBC") + } - QGCLabel { - width: parent.width - font.pixelSize: ScreenTools.mediumFontPixelSize - text: "Warning: You have an advanced safety configuration set using the NAV_DLL_OBC parameter. The above settings may not apply."; - visible: fact.value !== 0 - wrapMode: Text.Wrap + QGCLabel { + width: parent.width + font.pixelSize: ScreenTools.mediumFontPixelSize + text: "Warning: You have an advanced safety configuration set using the NAV_DLL_OBC parameter. The above settings may not apply."; + visible: fact.value !== 0 + wrapMode: Text.Wrap - property Fact fact: controller.getParameterFact(-1, "NAV_DLL_OBC") + property Fact fact: controller.getParameterFact(-1, "NAV_DLL_OBC") + } } - } + } // QGCVIew } diff --git a/src/FactSystem/Fact.cc b/src/FactSystem/Fact.cc index dc70c8313b6d6e81d2468e30bd25bcd740a66711..54e33dc906239547c0a0a3a7d8f69f72fb18ed61 100644 --- a/src/FactSystem/Fact.cc +++ b/src/FactSystem/Fact.cc @@ -51,34 +51,19 @@ Fact::Fact(int componentId, QString name, FactMetaData::ValueType_t type, QObjec void Fact::setValue(const QVariant& value) { - QVariant newValue; - - switch (type()) { - case FactMetaData::valueTypeInt8: - case FactMetaData::valueTypeInt16: - case FactMetaData::valueTypeInt32: - newValue = QVariant(value.toInt()); - break; - - case FactMetaData::valueTypeUint8: - case FactMetaData::valueTypeUint16: - case FactMetaData::valueTypeUint32: - newValue = QVariant(value.toUInt()); - break; - - case FactMetaData::valueTypeFloat: - newValue = QVariant(value.toFloat()); - break; - - case FactMetaData::valueTypeDouble: - newValue = QVariant(value.toDouble()); - break; - } - - if (newValue != _value) { - _value.setValue(newValue); - emit valueChanged(_value); - emit _containerValueChanged(_value); + if (_metaData) { + QVariant typedValue; + QString errorString; + + if (_metaData->convertAndValidate(value, true /* convertOnly */, typedValue, errorString)) { + if (typedValue != _value) { + _value.setValue(typedValue); + emit valueChanged(_value); + emit _containerValueChanged(_value); + } + } + } else { + qWarning() << "Meta data pointer missing"; } } @@ -110,11 +95,15 @@ QString Fact::valueString(void) const QVariant Fact::defaultValue(void) { - Q_ASSERT(_metaData); - if (!_metaData->defaultValueAvailable()) { - qDebug() << "Access to unavailable default value"; + if (_metaData) { + if (!_metaData->defaultValueAvailable()) { + qDebug() << "Access to unavailable default value"; + } + return _metaData->defaultValue(); + } else { + qWarning() << "Meta data pointer missing"; + return QVariant(0); } - return _metaData->defaultValue(); } FactMetaData::ValueType_t Fact::type(void) @@ -124,38 +113,82 @@ FactMetaData::ValueType_t Fact::type(void) QString Fact::shortDescription(void) { - Q_ASSERT(_metaData); - return _metaData->shortDescription(); + if (_metaData) { + return _metaData->shortDescription(); + } else { + qWarning() << "Meta data pointer missing"; + return QString(); + } } QString Fact::longDescription(void) { - Q_ASSERT(_metaData); - return _metaData->longDescription(); + if (_metaData) { + return _metaData->longDescription(); + } else { + qWarning() << "Meta data pointer missing"; + return QString(); + } } QString Fact::units(void) { - Q_ASSERT(_metaData); - return _metaData->units(); + if (_metaData) { + return _metaData->units(); + } else { + qWarning() << "Meta data pointer missing"; + return QString(); + } } QVariant Fact::min(void) { - Q_ASSERT(_metaData); - return _metaData->min(); + if (_metaData) { + return _metaData->min(); + } else { + qWarning() << "Meta data pointer missing"; + return QVariant(0); + } } QVariant Fact::max(void) { - Q_ASSERT(_metaData); - return _metaData->max(); + if (_metaData) { + return _metaData->max(); + } else { + qWarning() << "Meta data pointer missing"; + return QVariant(0); + } +} + +bool Fact::minIsDefaultForType(void) +{ + if (_metaData) { + return _metaData->minIsDefaultForType(); + } else { + qWarning() << "Meta data pointer missing"; + return false; + } +} + +bool Fact::maxIsDefaultForType(void) +{ + if (_metaData) { + return _metaData->maxIsDefaultForType(); + } else { + qWarning() << "Meta data pointer missing"; + return false; + } } QString Fact::group(void) { - Q_ASSERT(_metaData); - return _metaData->group(); + if (_metaData) { + return _metaData->group(); + } else { + qWarning() << "Meta data pointer missing"; + return QString(); + } } void Fact::setMetaData(FactMetaData* metaData) @@ -165,16 +198,40 @@ void Fact::setMetaData(FactMetaData* metaData) bool Fact::valueEqualsDefault(void) { - Q_ASSERT(_metaData); - if (_metaData->defaultValueAvailable()) { - return _metaData->defaultValue() == value(); + if (_metaData) { + if (_metaData->defaultValueAvailable()) { + return _metaData->defaultValue() == value(); + } else { + return false; + } } else { + qWarning() << "Meta data pointer missing"; return false; } } bool Fact::defaultValueAvailable(void) { - Q_ASSERT(_metaData); - return _metaData->defaultValueAvailable(); -} \ No newline at end of file + if (_metaData) { + return _metaData->defaultValueAvailable(); + } else { + qWarning() << "Meta data pointer missing"; + return false; + } +} + +QString Fact::validate(const QString& value, bool convertOnly) +{ + if (_metaData) { + + QVariant typedValue; + QString errorString; + + _metaData->convertAndValidate(value, convertOnly, typedValue, errorString); + + return errorString; + } else { + qWarning() << "Meta data pointer missing"; + return QString("Internal error: Meta data pointer missing"); + } +} diff --git a/src/FactSystem/Fact.h b/src/FactSystem/Fact.h index a8b486f749466907e91228e8bc17f3e605cb73e7..bcb1d298936603627ea06d65aa007dccb1b7362e 100644 --- a/src/FactSystem/Fact.h +++ b/src/FactSystem/Fact.h @@ -55,9 +55,15 @@ public: Q_PROPERTY(QString shortDescription READ shortDescription CONSTANT) Q_PROPERTY(QString longDescription READ longDescription CONSTANT) Q_PROPERTY(QVariant min READ min CONSTANT) + Q_PROPERTY(bool minIsDefaultForType READ minIsDefaultForType CONSTANT) Q_PROPERTY(QVariant max READ max CONSTANT) + Q_PROPERTY(bool maxIsDefaultForType READ maxIsDefaultForType CONSTANT) Q_PROPERTY(QString group READ group CONSTANT) + /// Convert and validate value + /// @param convertOnly true: validate type conversion only, false: validate against meta data as well + Q_INVOKABLE QString validate(const QString& value, bool convertOnly); + // Property system methods QString name(void) const; @@ -73,7 +79,9 @@ public: QString longDescription(void); QString units(void); QVariant min(void); + bool minIsDefaultForType(void); QVariant max(void); + bool maxIsDefaultForType(void); QString group(void); /// Sets the meta data associated with the Fact. diff --git a/src/FactSystem/FactControls/FactTextField.qml b/src/FactSystem/FactControls/FactTextField.qml index 5beff149ab79d989161e6d333d208e67efd6d77e..ac3b601b924d8d18f92a8ad788cff1bae382a61a 100644 --- a/src/FactSystem/FactControls/FactTextField.qml +++ b/src/FactSystem/FactControls/FactTextField.qml @@ -1,14 +1,44 @@ import QtQuick 2.2 import QtQuick.Controls 1.2 import QtQuick.Controls.Styles 1.2 +import QtQuick.Dialogs 1.2 + import QGroundControl.FactSystem 1.0 import QGroundControl.Palette 1.0 import QGroundControl.Controls 1.0 QGCTextField { - property Fact fact: null - text: fact.valueString + id: _textField + + property Fact fact: null + property string _validateString + + text: fact.valueString unitsLabel: fact.units - onEditingFinished: fact.value = text + + onEditingFinished: { + if (qgcView) { + var errorString = fact.validate(text, false /* convertOnly */) + if (errorString == "") { + fact.value = text + } else { + _validateString = text + qgcView.showDialog(editorDialogComponent, "Invalid Parameter Value", 50, StandardButton.Save) + } + } else { + fact.value = text + fact.valueChanged(fact.value) + } + } + + Component { + id: editorDialogComponent + + ParameterEditorDialog { + validate: true + validateValue: _validateString + fact: _textField.fact + } + } } diff --git a/src/FactSystem/FactMetaData.cc b/src/FactSystem/FactMetaData.cc index 308445a527739382a16d54988569e7b2fe2d274b..33377ac0897521c4d900ca4a82149a0016475fed 100644 --- a/src/FactSystem/FactMetaData.cc +++ b/src/FactSystem/FactMetaData.cc @@ -39,7 +39,9 @@ FactMetaData::FactMetaData(ValueType_t type, QObject* parent) : _defaultValue(0), _defaultValueAvailable(false), _min(_minForType()), - _max(_maxForType()) + _max(_maxForType()), + _minIsDefaultForType(true), + _maxIsDefaultForType(true) { } @@ -68,6 +70,7 @@ void FactMetaData::setMin(const QVariant& min) { if (min > _minForType()) { _min = min; + _minIsDefaultForType = false; } else { qWarning() << "Attempt to set min below allowable value"; _min = _minForType(); @@ -81,6 +84,7 @@ void FactMetaData::setMax(const QVariant& max) _max = _maxForType(); } else { _max = max; + _maxIsDefaultForType = false; } } @@ -133,3 +137,58 @@ QVariant FactMetaData::_maxForType(void) // Make windows compiler happy, even switch is full cased return QVariant(); } + +bool FactMetaData::convertAndValidate(const QVariant& value, bool convertOnly, QVariant& typedValue, QString& errorString) +{ + bool convertOk; + + errorString.clear(); + + switch (type()) { + case FactMetaData::valueTypeInt8: + case FactMetaData::valueTypeInt16: + case FactMetaData::valueTypeInt32: + typedValue = QVariant(value.toInt(&convertOk)); + if (!convertOnly && convertOk) { + if (min() > typedValue || typedValue > max()) { + errorString = QString("Value must be within %1 and %2").arg(min().toInt()).arg(max().toInt()); + } + } + break; + + case FactMetaData::valueTypeUint8: + case FactMetaData::valueTypeUint16: + case FactMetaData::valueTypeUint32: + typedValue = QVariant(value.toUInt(&convertOk)); + if (!convertOnly && convertOk) { + if (min() > typedValue || typedValue > max()) { + errorString = QString("Value must be within %1 and %2").arg(min().toUInt()).arg(max().toUInt()); + } + } + break; + + case FactMetaData::valueTypeFloat: + typedValue = QVariant(value.toFloat(&convertOk)); + if (!convertOnly && convertOk) { + if (min() > typedValue || typedValue > max()) { + errorString = QString("Value must be within %1 and %2").arg(min().toFloat()).arg(max().toFloat()); + } + } + break; + + case FactMetaData::valueTypeDouble: + typedValue = QVariant(value.toDouble(&convertOk)); + if (!convertOnly && convertOk) { + if (min() > typedValue || typedValue > max()) { + errorString = QString("Value must be within %1 and %2").arg(min().toDouble()).arg(max().toDouble()); + } + } + break; + } + + if (!convertOk) { + errorString = "Invalid number"; + } + + return convertOk && errorString.isEmpty(); +} diff --git a/src/FactSystem/FactMetaData.h b/src/FactSystem/FactMetaData.h index 2d218b06e77f75f52c562e3f0092d1f58064c444..2afcdf1981545d4d0941f5701d4741a65a5218d2 100644 --- a/src/FactSystem/FactMetaData.h +++ b/src/FactSystem/FactMetaData.h @@ -55,6 +55,7 @@ public: FactMetaData(ValueType_t type, QObject* parent = NULL); // Property accessors + QString name(void) { return _name; } QString group(void) { return _group; } ValueType_t type(void) { return _type; } QVariant defaultValue(void); @@ -64,8 +65,11 @@ public: QString units(void) { return _units; } QVariant min(void) { return _min; } QVariant max(void) { return _max; } + bool minIsDefaultForType(void) { return _minIsDefaultForType; } + bool maxIsDefaultForType(void) { return _maxIsDefaultForType; } // Property setters + void setName(const QString& name) { _name = name; } void setGroup(const QString& group) { _group = group; } void setDefaultValue(const QVariant& defaultValue); void setShortDescription(const QString& shortDescription) { _shortDescription = shortDescription; } @@ -73,11 +77,20 @@ public: void setUnits(const QString& units) { _units = units; } void setMin(const QVariant& max); void setMax(const QVariant& max); + + /// Converts the specified value, validating against meta data + /// @param value Value to convert, can be string + /// @param convertOnly true: convert to correct type only, do not validate against meta data + /// @param typeValue Converted value, correctly typed + /// @param errorString Error string if convert fails + /// @returns false: Convert failed, errorString set + bool convertAndValidate(const QVariant& value, bool convertOnly, QVariant& typedValue, QString& errorString); private: QVariant _minForType(void); QVariant _maxForType(void); + QString _name; QString _group; ValueType_t _type; QVariant _defaultValue; @@ -87,6 +100,8 @@ private: QString _units; QVariant _min; QVariant _max; + bool _minIsDefaultForType; + bool _maxIsDefaultForType; }; #endif \ No newline at end of file diff --git a/src/QGCApplication.cc b/src/QGCApplication.cc index b31af8540463028d490997925cd86e3ea47ac573..180cb971bcf5b9095ff8641ab6597179ee210fea 100644 --- a/src/QGCApplication.cc +++ b/src/QGCApplication.cc @@ -167,6 +167,11 @@ QGCApplication::QGCApplication(int &argc, char* argv[], bool unitTesting) if (fullLogging) { QLoggingCategory::setFilterRules(QStringLiteral("*Log=true")); } else { + if (_runningUnitTests) { + // We need to turn off these warnings until the firmware meta data is cleaned up + QLoggingCategory::setFilterRules(QStringLiteral("PX4ParameterLoaderLog.warning=false")); + } + // First thing we want to do is set up the qtlogging.ini file. If it doesn't already exist we copy // it to the correct location. This way default debug builds will have logging turned off. diff --git a/src/QmlControls/ParameterEditor.qml b/src/QmlControls/ParameterEditor.qml index 8d9db803db4d74cf0d0edc961e967c81f25f11aa..6c9522f387811d183039a089be0dca6f9ca278cb 100644 --- a/src/QmlControls/ParameterEditor.qml +++ b/src/QmlControls/ParameterEditor.qml @@ -51,88 +51,94 @@ QGCView { ParameterEditorController { id: controller; factPanel: panel } - QGCViewPanel { - id: panel - anchors.fill: parent + Component { + id: editorDialogComponent - Component { - id: factRowsComponent + ParameterEditorDialog { fact: __editorDialogFact } + } // Component - Editor Dialog - Column { - id: factColumn - x: __leftMargin + Component { + id: factRowsComponent - QGCLabel { - height: defaultTextHeight + (ScreenTools.defaultFontPizelSize * 0.5) - text: group - verticalAlignment: Text.AlignVCenter - font.pixelSize: ScreenTools.mediumFontPixelSize - } + Column { + id: factColumn + x: __leftMargin - Rectangle { - width: parent.width - height: 1 - color: __qgcPal.text - } + QGCLabel { + text: group + verticalAlignment: Text.AlignVCenter + font.pixelSize: ScreenTools.mediumFontPixelSize + } - Repeater { - model: controller.getFactsForGroup(componentId, group) + Rectangle { + width: parent.width + height: 1 + color: __qgcPal.text + } - Column { - property Fact modelFact: controller.getParameterFact(componentId, modelData) - - Item { - x: __leftMargin - width: parent.width - height: defaultTextHeight + (ScreenTools.defaultFontPixelSize * 0.5) - - QGCLabel { - id: nameLabel - width: defaultTextWidth * (__maxParamChars + 1) - height: parent.height - verticalAlignment: Text.AlignVCenter - text: modelFact.name - } + Repeater { + model: controller.getFactsForGroup(componentId, group) - QGCLabel { - id: valueLabel - width: defaultTextWidth * 20 - height: parent.height - anchors.left: nameLabel.right - verticalAlignment: Text.AlignVCenter - color: modelFact.valueEqualsDefault ? __qgcPal.text : "orange" - text: modelFact.valueString + " " + modelFact.units - } + Column { + property Fact modelFact: controller.getParameterFact(componentId, modelData) - QGCLabel { - height: parent.height - anchors.left: valueLabel.right - verticalAlignment: Text.AlignVCenter - visible: fullMode - text: modelFact.shortDescription - } + Item { + x: __leftMargin + width: parent.width + height: ScreenTools.defaultFontPixelSize * 1.75 - MouseArea { - anchors.fill: parent - acceptedButtons: Qt.LeftButton + QGCLabel { + id: nameLabel + width: defaultTextWidth * (__maxParamChars + 1) + height: parent.height + verticalAlignment: Text.AlignVCenter + text: modelFact.name + } - onClicked: { - __editorDialogFact = modelFact - showDialog(editorDialogComponent, "Parameter Editor", fullMode ? 50 : -1, StandardButton.Cancel | StandardButton.Save) - } - } + QGCLabel { + id: valueLabel + width: defaultTextWidth * 20 + height: parent.height + anchors.left: nameLabel.right + verticalAlignment: Text.AlignVCenter + color: modelFact.valueEqualsDefault ? __qgcPal.text : "orange" + text: modelFact.valueString + " " + modelFact.units + } + + QGCLabel { + height: parent.height + anchors.left: valueLabel.right + verticalAlignment: Text.AlignVCenter + visible: fullMode + text: modelFact.shortDescription } - Rectangle { - x: __leftMargin - width: factColumn.width - __leftMargin - __rightMargin - height: 1 - color: __qgcPal.windowShade + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.LeftButton + + onClicked: { + __editorDialogFact = modelFact + showDialog(editorDialogComponent, "Parameter Editor", fullMode ? 50 : -1, StandardButton.Cancel | StandardButton.Save) + } } - } // Column - Fact - } // Repeater - Facts - } // Column - Facts - } // Component - factRowsComponent + } + + Rectangle { + x: __leftMargin + width: factColumn.width - __leftMargin - __rightMargin + height: 1 + color: __qgcPal.windowShade + } + } // Column - Fact + } // Repeater - Facts + } // Column - Facts + } // Component - factRowsComponent + + QGCViewPanel { + id: panel + anchors.fill: parent + Column { anchors.fill: parent @@ -255,106 +261,5 @@ QGCView { } // ScrollView - Facts } // Item - Group ScrollView + Facts } // Column - Outer - } - - Component { - id: editorDialogComponent - - QGCViewDialog { - id: editorDialog - - ParameterEditorController { id: controller; factPanel: editorDialog } - - property bool fullMode: true - - function accept() { - __editorDialogFact.value = valueField.text - editorDialog.hideDialog() - } - - Column { - spacing: defaultTextHeight - anchors.left: parent.left - anchors.right: parent.right - - QGCLabel { - width: parent.width - wrapMode: Text.WordWrap - text: __editorDialogFact.shortDescription ? __editorDialogFact.shortDescription : "Description missing" - } - - QGCLabel { - width: parent.width - wrapMode: Text.WordWrap - visible: __editorDialogFact.longDescription - text: __editorDialogFact.longDescription - } - - QGCTextField { - id: valueField - text: __editorDialogFact.valueString - } - - QGCLabel { text: __editorDialogFact.name } - - Row { - spacing: defaultTextWidth - - QGCLabel { text: "Units:" } - QGCLabel { text: __editorDialogFact.units ? __editorDialogFact.units : "none" } - } - - Row { - spacing: defaultTextWidth - - QGCLabel { text: "Minimum value:" } - QGCLabel { text: __editorDialogFact.min } - } - - Row { - spacing: defaultTextWidth - - QGCLabel { text: "Maxmimum value:" } - QGCLabel { text: __editorDialogFact.max } - } - - Row { - spacing: defaultTextWidth - - QGCLabel { text: "Default value:" } - QGCLabel { text: __editorDialogFact.defaultValueAvailable ? __editorDialogFact.defaultValue : "none" } - } - - QGCLabel { - width: parent.width - wrapMode: Text.WordWrap - text: "Warning: Modifying parameters while vehicle is in flight can lead to vehicle instability and possible vehicle loss. " + - "Make sure you know what you are doing and double-check your values before Save!" - } - } // Column - Fact information - - - QGCButton { - anchors.rightMargin: defaultTextWidth - anchors.right: rcButton.left - anchors.bottom: parent.bottom - visible: __editorDialogFact.defaultValueAvailable - text: "Reset to default" - - onClicked: { - __editorDialogFact.value = __editorDialogFact.defaultValue - editorDialog.hideDialog() - } - } - - QGCButton { - id: rcButton - anchors.right: parent.right - anchors.bottom: parent.bottom - visible: __editorDialogFact.defaultValueAvailable - text: "Set RC to Param..." - onClicked: controller.setRCToParam(__editorDialogFact.name) - } - } // Rectangle - editorDialog - } // Component - Editor Dialog + } // QGCViewPanel } // QGCView diff --git a/src/QmlControls/ParameterEditorDialog.qml b/src/QmlControls/ParameterEditorDialog.qml new file mode 100644 index 0000000000000000000000000000000000000000..54b1ac87ec1405b6bc52ee8739056a1686390b93 --- /dev/null +++ b/src/QmlControls/ParameterEditorDialog.qml @@ -0,0 +1,164 @@ +/*===================================================================== + + QGroundControl Open Source Ground Control Station + + (c) 2009 - 2015 QGROUNDCONTROL PROJECT + + This file is part of the QGROUNDCONTROL project + + QGROUNDCONTROL 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 3 of the License, or + (at your option) any later version. + + QGROUNDCONTROL 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 QGROUNDCONTROL. If not, see . + + ======================================================================*/ + +/// @file +/// @author Don Gagne + +import QtQuick 2.3 +import QtQuick.Controls 1.3 +import QtQuick.Controls.Styles 1.2 +import QtQuick.Dialogs 1.2 + +import QGroundControl.Controls 1.0 +import QGroundControl.Palette 1.0 +import QGroundControl.Controllers 1.0 +import QGroundControl.FactSystem 1.0 +import QGroundControl.FactControls 1.0 + +QGCViewDialog { + property Fact fact + property bool validate: false + property string validateValue + + ParameterEditorController { id: controller; factPanel: parent } + + function accept() { + var errorString = fact.validate(valueField.text, forceSave.checked) + if (errorString == "") { + fact.value = valueField.text + fact.valueChanged(fact.value) + hideDialog() + } else { + validationError.text = errorString + forceSave.visible = true + } + } + + Component.onCompleted: { + if (validate) { + validationError.text = fact.validate(validateValue, false /* convertOnly */) + forceSave.visible = true + } + } + + Column { + spacing: defaultTextHeight + anchors.left: parent.left + anchors.right: parent.right + + QGCLabel { + width: parent.width + wrapMode: Text.WordWrap + text: fact.shortDescription ? fact.shortDescription : "Description missing" + } + + QGCLabel { + width: parent.width + wrapMode: Text.WordWrap + visible: fact.longDescription + text: fact.longDescription + } + + QGCTextField { + id: valueField + text: validate ? validateValue : fact.valueString + } + + QGCLabel { text: fact.name } + + Row { + spacing: defaultTextWidth + + QGCLabel { text: "Units:" } + QGCLabel { text: fact.units ? fact.units : "none" } + } + + Row { + spacing: defaultTextWidth + visible: !fact.minIsDefaultForType + + QGCLabel { text: "Minimum value:" } + QGCLabel { text: fact.min } + } + + Row { + spacing: defaultTextWidth + visible: !fact.maxIsDefaultForType + + QGCLabel { text: "Maximum value:" } + QGCLabel { text: fact.max } + } + + Row { + spacing: defaultTextWidth + + QGCLabel { text: "Default value:" } + QGCLabel { text: fact.defaultValueAvailable ? fact.defaultValue : "none" } + } + + QGCLabel { + width: parent.width + wrapMode: Text.WordWrap + text: "Warning: Modifying parameters while vehicle is in flight can lead to vehicle instability and possible vehicle loss. " + + "Make sure you know what you are doing and double-check your values before Save!" + } + + QGCLabel { + id: validationError + width: parent.width + wrapMode: Text.WordWrap + color: "yellow" + } + + QGCCheckBox { + id: forceSave + visible: false + text: "Force save (dangerous!)" + } + } // Column - Fact information + + + QGCButton { + id: bottomButton + anchors.rightMargin: defaultTextWidth + anchors.right: rcButton.left + anchors.bottom: parent.bottom + visible: fact.defaultValueAvailable + text: "Reset to default" + + onClicked: { + fact.value = fact.defaultValue + fact.valueChanged(fact.value) + hideDialog() + } + } + + QGCButton { + id: rcButton + anchors.right: parent.right + anchors.bottom: parent.bottom + text: "Set RC to Param..." + visible: !validate + onClicked: controller.setRCToParam(fact.name) + } +} // QGCViewDialog diff --git a/src/QmlControls/QGCView.qml b/src/QmlControls/QGCView.qml index 9360cc7c1de595b0b07c1143773feebac54ac05a..28532ae0bcd41e0fc64156c6bd46f8b53aff9071 100644 --- a/src/QmlControls/QGCView.qml +++ b/src/QmlControls/QGCView.qml @@ -37,7 +37,8 @@ import QGroundControl.FactControls 1.0 FactPanel { id: __rootItem - property bool completedSignalled: false + property var qgcView: __rootItem /// Used by Fact controls for validation dialogs + property bool completedSignalled: false property var viewPanel diff --git a/src/QmlControls/QGroundControl.Controls.qmldir b/src/QmlControls/QGroundControl.Controls.qmldir index c158fd07b1d90f13cbd080addd453c2e207177b4..6f58994b62a2fe7f9b64b46667a0eae73a590ed0 100644 --- a/src/QmlControls/QGroundControl.Controls.qmldir +++ b/src/QmlControls/QGroundControl.Controls.qmldir @@ -14,9 +14,11 @@ SubMenuButton 1.0 SubMenuButton.qml IndicatorButton 1.0 IndicatorButton.qml VehicleRotationCal 1.0 VehicleRotationCal.qml VehicleSummaryRow 1.0 VehicleSummaryRow.qml -ParameterEditor 1.0 ParameterEditor.qml ViewWidget 1.0 ViewWidget.qml +ParameterEditor 1.0 ParameterEditor.qml +ParameterEditorDialog 1.0 ParameterEditorDialog.qml + QGCView 1.0 QGCView.qml QGCViewPanel 1.0 QGCViewPanel.qml QGCViewDialog 1.0 QGCViewDialog.qml