diff --git a/qgroundcontrol.pro b/qgroundcontrol.pro index 7e3837254fb5f5e65f0a1c48bb922295dc1c0548..bd4371c13794aedfa1a5c387ab19925166d85fe3 100644 --- a/qgroundcontrol.pro +++ b/qgroundcontrol.pro @@ -461,6 +461,7 @@ HEADERS += \ src/QGCMapPalette.h \ src/QGCMobileFileDialogController.h \ src/QGCPalette.h \ + src/QGCQGeoCoordinate.h \ src/QGCQmlWidgetHolder.h \ src/QGCQuickWidget.h \ src/QGCTemporaryFile.h \ @@ -629,6 +630,7 @@ SOURCES += \ src/QGCMapPalette.cc \ src/QGCMobileFileDialogController.cc \ src/QGCPalette.cc \ + src/QGCQGeoCoordinate.cc \ src/QGCQmlWidgetHolder.cpp \ src/QGCQuickWidget.cc \ src/QGCTemporaryFile.cc \ diff --git a/src/FlightMap/MapItems/MissionItemIndicatorDrag.qml b/src/FlightMap/MapItems/MissionItemIndicatorDrag.qml index c41ca75ef954ca6cff9c8884895a1a680ded2542..720ad594764e2d1619765637c08619d7705ef392 100644 --- a/src/FlightMap/MapItems/MissionItemIndicatorDrag.qml +++ b/src/FlightMap/MapItems/MissionItemIndicatorDrag.qml @@ -14,7 +14,7 @@ import QGroundControl 1.0 import QGroundControl.ScreenTools 1.0 import QGroundControl.Controls 1.0 -/// Use the drag a MissionItemIndicator +/// Use to drag a MissionItemIndicator Rectangle { id: itemDragger x: itemIndicator.x - _touchMarginHorizontal @@ -28,9 +28,11 @@ Rectangle { property var itemIndicator ///< The mission item indicator to drag around property var itemCoordinate ///< Coordinate we are updating during drag + signal clicked + property bool _preventCoordinateBindingLoop: false - property bool _mobile: true//ScreenTools.isMobile + property bool _mobile: ScreenTools.isMobile property real _touchWidth: Math.max(itemIndicator.width, ScreenTools.minTouchPixels) property real _touchHeight: Math.max(itemIndicator.height, ScreenTools.minTouchPixels) property real _touchMarginHorizontal: _mobile ? (_touchWidth - itemIndicator.width) / 2 : 0 @@ -40,7 +42,7 @@ Rectangle { onYChanged: liveDrag() function liveDrag() { - if (!itemDragger._preventCoordinateBindingLoop && Drag.active) { + if (!itemDragger._preventCoordinateBindingLoop && itemDrag.drag.active) { var point = Qt.point(itemDragger.x + _touchMarginHorizontal + itemIndicator.anchorPoint.x, itemDragger.y + _touchMarginVertical + itemIndicator.anchorPoint.y) var coordinate = map.toCoordinate(point) itemDragger._preventCoordinateBindingLoop = true @@ -61,5 +63,15 @@ Rectangle { drag.maximumX: itemDragger.parent.width - parent.width drag.maximumY: itemDragger.parent.height - parent.height preventStealing: true + + onClicked: itemDragger.clicked() + } + + Item { + id: fakeItemIndicator + + Item { + id: anchorPoint + } } } diff --git a/src/MissionEditor/MissionEditor.qml b/src/MissionEditor/MissionEditor.qml index 52fee69cbd3e1f24de157a30d2bfe968822d08e5..a0a0facf8c60dc1b54de78ce31025baeeb640239 100644 --- a/src/MissionEditor/MissionEditor.qml +++ b/src/MissionEditor/MissionEditor.qml @@ -92,15 +92,11 @@ QGCView { MapFitFunctions { id: mapFitFunctions map: editorMap - mapFitViewport: Qt.rect(leftToolWidth, toolbarHeight, editorMap.width - leftToolWidth - rightPanelWidth, editorMap.height - toolbarHeight) + mapFitViewport: editorMap.centerViewport usePlannedHomePosition: true mapGeoFenceController: geoFenceController mapMissionController: missionController mapRallyPointController: rallyPointController - - property real toolbarHeight: qgcView.height - ScreenTools.availableHeight - property real rightPanelWidth: _rightPanelWidth - property real leftToolWidth: toolStrip.x + toolStrip.width } MissionController { @@ -343,6 +339,13 @@ QGCView { anchors.right: parent.right mapName: "MissionEditor" + // This is the center rectangle of the map which is not obscured by tools + property rect centerViewport: Qt.rect(_leftToolWidth, _toolbarHeight, editorMap.width - _leftToolWidth - _rightPanelWidth, editorMap.height - _statusHeight - _toolbarHeight) + + property real _toolbarHeight: qgcView.height - ScreenTools.availableHeight + property real _leftToolWidth: toolStrip.x + toolStrip.width + property real _statusHeight: waypointValuesDisplay.visible ? editorMap.height - waypointValuesDisplay.y : 0 + readonly property real animationDuration: 500 // Initial map position duplicates Fly view position diff --git a/src/MissionEditor/SurveyItemEditor.qml b/src/MissionEditor/SurveyItemEditor.qml index 76d14c62bc563734c2e3fa02dc8bab6ea008d806..a7654cd86f623426c95ec9ee84b124c43f546af5 100644 --- a/src/MissionEditor/SurveyItemEditor.qml +++ b/src/MissionEditor/SurveyItemEditor.qml @@ -124,16 +124,6 @@ Rectangle { property bool _noCameraValueRecalc: false ///< Prevents uneeded recalcs - Connections { - target: missionItem - - onIsCurrentItemChanged: { - if (!missionItem.isCurrentItem) { - polygonEditor.cancelPolygonEdit() - } - } - } - Connections { target: missionItem.camera @@ -459,44 +449,6 @@ Rectangle { Item { height: _sectionSpacer; width: 1; visible: !ScreenTools.isTinyScreen } - SectionHeader { text: qsTr("Polygon") } - - Row { - spacing: ScreenTools.defaultFontPixelWidth - anchors.horizontalCenter: parent.horizontalCenter - - QGCButton { - width: _root.width * 0.45 - text: polygonEditor.drawingPolygon ? qsTr("Finish Draw") : qsTr("Draw") - visible: !polygonEditor .adjustingPolygon - enabled: ((polygonEditor.drawingPolygon && polygonEditor.polygonReady) || !polygonEditor.drawingPolygon) - - onClicked: { - if (polygonEditor.drawingPolygon) { - polygonEditor.finishCapturePolygon() - } else { - polygonEditor.startCapturePolygon() - } - } - } - - QGCButton { - width: _root.width * 0.4 - text: polygonEditor.adjustingPolygon ? qsTr("Finish Adjust") : qsTr("Adjust") - visible: missionItem.polygonPath.length > 0 && !polygonEditor.drawingPolygon - - onClicked: { - if (polygonEditor.adjustingPolygon) { - polygonEditor.finishAdjustPolygon() - } else { - polygonEditor.startAdjustPolygon(missionItem.polygonPath) - } - } - } - } - - Item { height: _sectionSpacer; width: 1; visible: !ScreenTools.isTinyScreen } - SectionHeader { text: qsTr("Statistics") } Grid { @@ -521,10 +473,4 @@ Rectangle { } } } - - PolygonEditor { - id: polygonEditor - map: editorMap - callbackObject: parent - } } diff --git a/src/MissionEditor/SurveyMapVisual.qml b/src/MissionEditor/SurveyMapVisual.qml index eb0fc049c06eb6b6845c398763fbe3f4ebe4edee..c49802799da3e6aafc267715f9a0bd55fb27098e 100644 --- a/src/MissionEditor/SurveyMapVisual.qml +++ b/src/MissionEditor/SurveyMapVisual.qml @@ -16,18 +16,21 @@ import QGroundControl 1.0 import QGroundControl.ScreenTools 1.0 import QGroundControl.Palette 1.0 import QGroundControl.Controls 1.0 +import QGroundControl.FlightMap 1.0 /// Survey Complex Mission Item visuals Item { property var map ///< Map control to place item in - property var _missionItem: object + property var _missionItem: object property var _polygon property var _grid property var _entryCoordinate property var _exitCoordinate + property var _dragHandles + property var _splitHandles - Component.onCompleted: { + function _addVisualElements() { _polygon = polygonComponent.createObject(map) _grid = gridComponent.createObject(map) _entryCoordinate = entryPointComponent.createObject(map) @@ -38,13 +41,78 @@ Item { map.addMapItem(_exitCoordinate) } - Component.onDestruction: { + function _destroyVisualElements() { _polygon.destroy() _grid.destroy() _entryCoordinate.destroy() _exitCoordinate.destroy() } + function _addDragHandles() { + if (!_dragHandles) { + _dragHandles = dragHandlesComponent.createObject(map) + } + if (!_splitHandles) { + _splitHandles = splitHandlesComponent.createObject(map) + } + } + + function _destroyDragHandles() { + if (_dragHandles) { + _dragHandles.destroy() + _dragHandles = undefined + } + if (_splitHandles) { + _splitHandles.destroy() + _splitHandles = undefined + } + } + + /// Add an initial 4 sided polygon if there is none + function _addInitialPolygon() { + if (_missionItem.polygonPath.length < 3) { + // Initial polygon is inset to take 2/3rds space + var rect = map.centerViewport + rect.x += (rect.width * 0.25) / 2 + rect.y += (rect.height * 0.25) / 2 + rect.width *= 0.75 + rect.height *= 0.75 + var topLeft = Qt.point(rect.x, rect.y) + var topRight = Qt.point(rect.x + rect.width, rect.y) + var bottomLeft = Qt.point(rect.x, rect.y + rect.height) + var bottomRight = Qt.point(rect.x + rect.width, rect.y + rect.height) + _missionItem.addPolygonCoordinate(map.toCoordinate(topLeft)) + _missionItem.addPolygonCoordinate(map.toCoordinate(topRight)) + _missionItem.addPolygonCoordinate(map.toCoordinate(bottomRight)) + _missionItem.addPolygonCoordinate(map.toCoordinate(bottomLeft)) + } + } + + Component.onCompleted: { + _addInitialPolygon() + _addVisualElements() + if (_missionItem.isCurrentItem) { + _addDragHandles() + } + } + + Component.onDestruction: { + _destroyVisualElements() + _destroyDragHandles() + } + + Connections { + target: _missionItem + + onIsCurrentItemChanged: { + if (_missionItem.isCurrentItem) { + _addDragHandles() + } else { + _destroyDragHandles() + } + } + } + // Survey area polygon Component { id: polygonComponent @@ -108,4 +176,147 @@ Item { } } } + + Component { + id: splitHandleComponent + + MapQuickItem { + id: mapQuickItem + anchorPoint.x: dragHandle.width / 2 + anchorPoint.y: dragHandle.height / 2 + z: QGroundControl.zOrderMapItems + 1 + + property int vertexIndex + + sourceItem: Rectangle { + id: dragHandle + width: ScreenTools.defaultFontPixelHeight * 1.5 + height: width + radius: width / 2 + color: "white" + opacity: .80 + + QGCLabel { + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + text: "+" + } + + QGCMouseArea { + fillItem: parent + onClicked: _missionItem.splitPolygonSegment(mapQuickItem.vertexIndex) + } + } + } + } + + Component { + id: splitHandlesComponent + + Repeater { + model: _missionItem.polygonPath + + delegate: Item { + property var _splitHandle + property var _vertices: _missionItem.polygonPath + + function _setHandlePosition() { + var nextIndex = index + 1 + if (nextIndex > _vertices.length - 1) { + nextIndex = 0 + } + var distance = _vertices[index].distanceTo(_vertices[nextIndex]) + var azimuth = _vertices[index].azimuthTo(_vertices[nextIndex]) + _splitHandle.coordinate = _vertices[index].atDistanceAndAzimuth(distance / 2, azimuth) + } + + Component.onCompleted: { + _splitHandle = splitHandleComponent.createObject(map) + _splitHandle.vertexIndex = index + _setHandlePosition() + map.addMapItem(_splitHandle) + } + + Component.onDestruction: { + if (_splitHandle) { + _splitHandle.destroy() + } + } + } + } + } + + // Control which is used to drag polygon vertices + Component { + id: dragAreaComponent + + MissionItemIndicatorDrag { + id: dragArea + + property int polygonVertex + + property bool _creationComplete: false + + Component.onCompleted: _creationComplete = true + + onItemCoordinateChanged: { + if (_creationComplete) { + // During component creation some bad coordinate values got through which screws up polygon draw + _missionItem.adjustPolygonCoordinate(polygonVertex, itemCoordinate) + } + } + + onClicked: _missionItem.removePolygonVertex(polygonVertex) + } + } + + Component { + id: dragHandleComponent + + MapQuickItem { + id: mapQuickItem + anchorPoint.x: dragHandle.width / 2 + anchorPoint.y: dragHandle.height / 2 + z: QGroundControl.zOrderMapItems + 2 + + sourceItem: Rectangle { + id: dragHandle + width: ScreenTools.defaultFontPixelHeight * 1.5 + height: width + radius: width / 2 + color: "white" + opacity: .80 + } + } + } + + // Add all polygon vertex drag handles to the map + Component { + id: dragHandlesComponent + + Repeater { + model: _missionItem.polygonModel + + delegate: Item { + property var _visuals: [ ] + + Component.onCompleted: { + var dragHandle = dragHandleComponent.createObject(map) + dragHandle.coordinate = Qt.binding(function() { return object.coordinate }) + map.addMapItem(dragHandle) + var dragArea = dragAreaComponent.createObject(map, { "itemIndicator": dragHandle, "itemCoordinate": object.coordinate, "polygonVertex": index }) + _visuals.push(dragHandle) + _visuals.push(dragArea) + } + + Component.onDestruction: { + for (var i=0; i<_visuals.length; i++) { + _visuals[i].destroy() + } + _visuals = [ ] + } + } + } + } } + diff --git a/src/MissionManager/SurveyMissionItem.cc b/src/MissionManager/SurveyMissionItem.cc index 328621a7f6ec06877d377e2422934534842c5533..d8ac44ae66e77f7bbbc923df99b10e1af398e8df 100644 --- a/src/MissionManager/SurveyMissionItem.cc +++ b/src/MissionManager/SurveyMissionItem.cc @@ -13,6 +13,7 @@ #include "MissionController.h" #include "QGCGeo.h" #include "QGroundControlQmlGlobal.h" +#include "QGCQGeoCoordinate.h" #include @@ -167,6 +168,8 @@ void SurveyMissionItem::clearPolygon(void) // will cause the polygon to go away. _polygonPath.clear(); + _polygonModel.clearAndDeleteContents(); + _clearGrid(); setDirty(true); @@ -176,6 +179,8 @@ void SurveyMissionItem::clearPolygon(void) void SurveyMissionItem::addPolygonCoordinate(const QGeoCoordinate coordinate) { + _polygonModel.append(new QGCQGeoCoordinate(coordinate, this)); + _polygonPath << QVariant::fromValue(coordinate); emit polygonPathChanged(); @@ -191,12 +196,72 @@ void SurveyMissionItem::addPolygonCoordinate(const QGeoCoordinate coordinate) void SurveyMissionItem::adjustPolygonCoordinate(int vertexIndex, const QGeoCoordinate coordinate) { + if (vertexIndex < 0 && vertexIndex > _polygonPath.length() - 1) { + qWarning() << "Call to adjustPolygonCoordinate with bad vertexIndex:count" << vertexIndex << _polygonPath.length(); + return; + } + + _polygonModel.value(vertexIndex)->setCoordinate(coordinate); _polygonPath[vertexIndex] = QVariant::fromValue(coordinate); emit polygonPathChanged(); _generateGrid(); setDirty(true); } +void SurveyMissionItem::splitPolygonSegment(int vertexIndex) +{ + int nextIndex = vertexIndex + 1; + if (nextIndex > _polygonPath.length() - 1) { + nextIndex = 0; + } + + QGeoCoordinate firstVertex = _polygonPath[vertexIndex].value(); + QGeoCoordinate nextVertex = _polygonPath[nextIndex].value(); + + double distance = firstVertex.distanceTo(nextVertex); + double azimuth = firstVertex.azimuthTo(nextVertex); + QGeoCoordinate newVertex = firstVertex.atDistanceAndAzimuth(distance / 2, azimuth); + + if (nextIndex == 0) { + addPolygonCoordinate(newVertex); + } else { + _polygonModel.insert(nextIndex, new QGCQGeoCoordinate(newVertex, this)); + _polygonPath.insert(nextIndex, QVariant::fromValue(newVertex)); + emit polygonPathChanged(); + + int pointCount = _polygonPath.count(); + if (pointCount >= 3) { + if (pointCount == 3) { + emit specifiesCoordinateChanged(); + } + _generateGrid(); + } + setDirty(true); + } +} + +void SurveyMissionItem::removePolygonVertex(int vertexIndex) +{ + if (vertexIndex < 0 && vertexIndex > _polygonPath.length() - 1) { + qWarning() << "Call to removePolygonCoordinate with bad vertexIndex:count" << vertexIndex << _polygonPath.length(); + return; + } + + if (_polygonPath.length() <= 3) { + // Don't allow the user to trash the polygon + return; + } + + QObject* coordObj = _polygonModel.removeAt(vertexIndex); + coordObj->deleteLater(); + + _polygonPath.removeAt(vertexIndex); + emit polygonPathChanged(); + + _generateGrid(); + setDirty(true); +} + int SurveyMissionItem::lastSequenceNumber(void) const { int lastSeq = _sequenceNumber; @@ -433,6 +498,7 @@ bool SurveyMissionItem::load(const QJsonObject& complexObject, int sequenceNumbe return false; } _polygonPath << QVariant::fromValue(pointCoord); + _polygonModel.append(new QGCQGeoCoordinate(pointCoord, this)); } _generateGrid(); diff --git a/src/MissionManager/SurveyMissionItem.h b/src/MissionManager/SurveyMissionItem.h index 713b02e4c381330ce947427b9a5071e52bfbc696..37b0e8d46619e03c1efc77ac7a98d72695df4532 100644 --- a/src/MissionManager/SurveyMissionItem.h +++ b/src/MissionManager/SurveyMissionItem.h @@ -53,11 +53,21 @@ public: Q_PROPERTY(int cameraShots READ cameraShots NOTIFY cameraShotsChanged) Q_PROPERTY(double coveredArea READ coveredArea NOTIFY coveredAreaChanged) + // The polygon vertices are also exposed as a list mode since MapItemView will only work with a QAbstractItemModel as + // opposed to polygonPath which is a QVariantList. + Q_PROPERTY(QmlObjectListModel* polygonModel READ polygonModel CONSTANT) + Q_INVOKABLE void clearPolygon(void); Q_INVOKABLE void addPolygonCoordinate(const QGeoCoordinate coordinate); Q_INVOKABLE void adjustPolygonCoordinate(int vertexIndex, const QGeoCoordinate coordinate); + Q_INVOKABLE void removePolygonVertex(int vertexIndex); + + // Splits the segment between vertextIndex and the next vertex in half + Q_INVOKABLE void splitPolygonSegment(int vertexIndex); + + QVariantList polygonPath (void) { return _polygonPath; } + QmlObjectListModel* polygonModel(void) { return &_polygonModel; } - QVariantList polygonPath(void) { return _polygonPath; } QVariantList gridPoints (void) { return _gridPoints; } Fact* manualGrid (void) { return &_manualGridFact; } @@ -171,13 +181,14 @@ private: void _setCoveredArea(double coveredArea); void _cameraValueChanged(void); - int _sequenceNumber; - bool _dirty; - QVariantList _polygonPath; - QVariantList _gridPoints; - QGeoCoordinate _coordinate; - QGeoCoordinate _exitCoordinate; - bool _cameraOrientationFixed; + int _sequenceNumber; + bool _dirty; + QVariantList _polygonPath; + QmlObjectListModel _polygonModel; + QVariantList _gridPoints; + QGeoCoordinate _coordinate; + QGeoCoordinate _exitCoordinate; + bool _cameraOrientationFixed; double _surveyDistance; int _cameraShots; diff --git a/src/QGCQGeoCoordinate.cc b/src/QGCQGeoCoordinate.cc new file mode 100644 index 0000000000000000000000000000000000000000..c19e2af10f08303594c64d2f17f8e838e83dacb0 --- /dev/null +++ b/src/QGCQGeoCoordinate.cc @@ -0,0 +1,25 @@ +/**************************************************************************** + * + * (c) 2009-2016 QGROUNDCONTROL PROJECT + * + * QGroundControl is licensed according to the terms in the file + * COPYING.md in the root of the source code directory. + * + ****************************************************************************/ + +#include "QGCQGeoCoordinate.h" + +QGCQGeoCoordinate::QGCQGeoCoordinate(const QGeoCoordinate& coord, QObject* parent) + : QObject(parent) + , _coordinate(coord) +{ + +} + +void QGCQGeoCoordinate::setCoordinate(const QGeoCoordinate& coordinate) +{ + if (_coordinate != coordinate) { + _coordinate = coordinate; + emit coordinateChanged(coordinate); + } +} diff --git a/src/QGCQGeoCoordinate.h b/src/QGCQGeoCoordinate.h new file mode 100644 index 0000000000000000000000000000000000000000..b3c4a13bfc7d94ff966cf608781b2836c714a5f0 --- /dev/null +++ b/src/QGCQGeoCoordinate.h @@ -0,0 +1,33 @@ +/**************************************************************************** + * + * (c) 2009-2016 QGROUNDCONTROL PROJECT + * + * QGroundControl is licensed according to the terms in the file + * COPYING.md in the root of the source code directory. + * + ****************************************************************************/ + +#pragma once + +#include +#include + +/// This is a QGeoCoordinate within a QObject such that it can be used on a QmlObjectListModel +class QGCQGeoCoordinate : public QObject +{ + Q_OBJECT + +public: + QGCQGeoCoordinate(const QGeoCoordinate& coord, QObject* parent = NULL); + + Q_PROPERTY(QGeoCoordinate coordinate READ coordinate WRITE setCoordinate NOTIFY coordinateChanged) + + QGeoCoordinate coordinate(void) const { return _coordinate; } + void setCoordinate(const QGeoCoordinate& coordinate); + +signals: + void coordinateChanged(QGeoCoordinate coordinate); + +private: + QGeoCoordinate _coordinate; +}; diff --git a/src/QmlControls/QmlObjectListModel.h b/src/QmlControls/QmlObjectListModel.h index aefcac74ceb4a5073c5ff45739208a31651b3dbb..2515d8de0de4c02ac56728a510ff58f03d2b2f8b 100644 --- a/src/QmlControls/QmlObjectListModel.h +++ b/src/QmlControls/QmlObjectListModel.h @@ -51,7 +51,7 @@ public: /// Calls deleteLater on all items and this itself. void deleteListAndContents(void); - /// Clears the list and calls delete on each entry + /// Clears the list and calls deleteLater on each entry void clearAndDeleteContents(void); signals: