Commit a575fc04 authored by Don Gagne's avatar Don Gagne

Merge pull request #2052 from DonLakeFlyer/ItemIndicators

Share item indicator display code between Plan/Fly views
parents 53c6a9f1 42af3324
......@@ -228,6 +228,7 @@ HEADERS += \
src/MG.h \
src/MissionEditor/MissionEditorController.h \
src/MissionManager/MissionManager.h \
src/MissionManager/MissionController.h \
src/QGC.h \
src/QGCApplication.h \
src/QGCComboBox.h \
......@@ -346,6 +347,7 @@ SOURCES += \
src/main.cc \
src/MissionEditor/MissionEditorController.cc \
src/MissionManager/MissionManager.cc \
src/MissionManager/MissionController.cc \
src/QGC.cc \
src/QGCApplication.cc \
src/QGCComboBox.cc \
......
......@@ -163,8 +163,6 @@
<file alias="QGroundControl/FlightMap/qmldir">src/FlightMap/qmldir</file>
<file alias="QGroundControl/FlightMap/FlightMap.qml">src/FlightMap/FlightMap.qml</file>
<file alias="QGroundControl/FlightMap/QGCVideoBackground.qml">src/FlightMap/QGCVideoBackground.qml</file>
<!-- FlightMap Widgets -->
<file alias="QGroundControl/FlightMap/QGCAltitudeWidget.qml">src/FlightMap/Widgets/QGCAltitudeWidget.qml</file>
<file alias="QGroundControl/FlightMap/QGCArtificialHorizon.qml">src/FlightMap/Widgets/QGCArtificialHorizon.qml</file>
<file alias="QGroundControl/FlightMap/QGCAttitudeWidget.qml">src/FlightMap/Widgets/QGCAttitudeWidget.qml</file>
......@@ -176,10 +174,9 @@
<file alias="QGroundControl/FlightMap/QGCPitchIndicator.qml">src/FlightMap/Widgets/QGCPitchIndicator.qml</file>
<file alias="QGroundControl/FlightMap/QGCSlider.qml">src/FlightMap/Widgets/QGCSlider.qml</file>
<file alias="QGroundControl/FlightMap/QGCSpeedWidget.qml">src/FlightMap/Widgets/QGCSpeedWidget.qml</file>
<!-- FlightMap MapQuickItems -->
<file alias="QGroundControl/FlightMap/MissionItemIndicator.qml">src/FlightMap/MapItems/MissionItemIndicator.qml</file>
<file alias="QGroundControl/FlightMap/VehicleMapItem.qml">src/FlightMap/MapItems/VehicleMapItem.qml</file>
<file alias="QGroundControl/FlightMap/MissionItemView.qml">src/FlightMap/MapItems/MissionItemView.qml</file>
</qresource>
<qresource prefix="/res">
......
......@@ -84,6 +84,7 @@ Item {
property bool _showMap: getBool(QGroundControl.flightMapSettings.loadMapSetting(flightMap.mapName, _showMapBackgroundKey, "1"))
FlightDisplayViewController { id: _controller }
MissionController { id: _missionController }
ExclusiveGroup {
id: _dropButtonsExclusiveGroup
......@@ -178,16 +179,9 @@ Item {
}
// Add the mission items to the map
MapItemView {
model: multiVehicleManager.activeVehicle ? multiVehicleManager.activeVehicle.missionItems : 0
delegate:
MissionItemIndicator {
label: object.sequenceNumber
isCurrentItem: object.isCurrentItem
coordinate: object.coordinate
z: flightMap.zOrderMapItems
}
MissionItemView {
model: _missionController.missionItems
zOrderMapItems: flightMap.zOrderMapItems
}
Loader {
......
/*=====================================================================
QGroundControl Open Source Ground Control Station
(c) 2009, 2015 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
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 <http://www.gnu.org/licenses/>.
======================================================================*/
import QtQuick 2.4
import QtQuick.Controls 1.3
import QtQuick.Dialogs 1.2
import QtLocation 5.3
import QtPositioning 5.3
import QGroundControl 1.0
import QGroundControl.FlightMap 1.0
import QGroundControl.ScreenTools 1.0
import QGroundControl.Controls 1.0
import QGroundControl.Palette 1.0
/// The MissionItemView control is used to add Mission Item Indicators to a FlightMap.
MapItemView {
id: _root
property real zOrderMapItems ///< Z order for indicator
property var itemDragger ///< Set to item drag control if you want to support drag
delegate: MissionItemIndicator {
id: itemIndicator
label: object.sequenceNumber == 0 ? "H" : object.sequenceNumber
isCurrentItem: object.isCurrentItem
coordinate: object.coordinate
z: zOrderMapItems
visible: object.specifiesCoordinate
onClicked: setCurrentItem(object.sequenceNumber)
Connections {
target: object
onIsCurrentItemChanged: {
if (isCurrentItem) {
if (_root.itemDragger) {
// Setup our drag item
if (object.sequenceNumber != 0) {
_root.itemDragger.visible = true
_root.itemDragger.missionItem = Qt.binding(function() { return object })
_root.itemDragger.missionItemIndicator = Qt.binding(function() { return itemIndicator })
} else {
_root.itemDragger.clearItem()
}
}
// Zoom the map and move to the new position
_root.parent.zoomLevel = _root.parent.maxZoomLevel
_root.parent.latitude = object.coordinate.latitude
_root.parent.longitude = object.coordinate.longitude
}
}
}
// These are the non-coordinate child mission items attached to this item
Row {
anchors.top: parent.top
anchors.left: parent.right
Repeater {
model: object.childItems
delegate: MissionItemIndexLabel {
label: object.sequenceNumber
isCurrentItem: object.isCurrentItem
z: 2
onClicked: setCurrentItem(object.sequenceNumber)
}
}
}
}
}
......@@ -17,6 +17,7 @@ QGCPitchIndicator 1.0 QGCPitchIndicator.qml
QGCSlider 1.0 QGCSlider.qml
QGCSpeedWidget 1.0 QGCSpeedWidget.qml
# MapQuickItems
# Map items
VehicleMapItem 1.0 VehicleMapItem.qml
MissionItemIndicator 1.0 MissionItemIndicator.qml
MissionItemView 1.0 MissionItemView.qml
......@@ -215,65 +215,10 @@ QGCView {
}
// Add the mission items to the map
MapItemView {
model: controller.missionItems
delegate:
MissionItemIndicator {
id: itemIndicator
label: object.sequenceNumber == 0 ? (liveHomePositionAvailable ? "H" : "F") : object.sequenceNumber
isCurrentItem: !homePositionManagerButton.checked && object.isCurrentItem
coordinate: object.coordinate
z: editorMap.zOrderMapItems
visible: object.specifiesCoordinate
onClicked: setCurrentItem(object.sequenceNumber)
Connections {
target: object
onIsCurrentItemChanged: {
if (isCurrentItem) {
// Setup our drag item
if (object.sequenceNumber != 0) {
itemEditor.visible = true
itemEditor.missionItem = Qt.binding(function() { return object })
itemEditor.missionItemIndicator = Qt.binding(function() { return itemIndicator })
} else {
itemEditor.clearItem()
}
// Zoom the map and move to the new position
editorMap.zoomLevel = editorMap.maxZoomLevel
editorMap.latitude = object.coordinate.latitude
editorMap.longitude = object.coordinate.longitude
}
}
}
// These are the non-coordinate child mission items attached to this item
Row {
anchors.top: parent.top
anchors.left: parent.right
Repeater {
model: object.childItems
delegate:
MissionItemIndexLabel {
label: object.sequenceNumber
isCurrentItem: !homePositionManagerButton.checked && object.isCurrentItem
z: 2
onClicked: {
setCurrentItem(object.sequenceNumber)
missionItemEditorButton.checked
}
}
}
}
}
MissionItemView {
model: controller.missionItems
zOrderMapItems: editorMap.zOrderMapItems
itemDragger: itemEditor
}
// Add lines between waypoints
......
......@@ -35,7 +35,7 @@ This file is part of the QGROUNDCONTROL project
const char* MissionEditorController::_settingsGroup = "MissionEditorController";
MissionEditorController::MissionEditorController(QWidget *parent)
MissionEditorController::MissionEditorController(QObject *parent)
: QObject(parent)
, _missionItems(NULL)
, _canEdit(true)
......@@ -43,7 +43,7 @@ MissionEditorController::MissionEditorController(QWidget *parent)
, _liveHomePositionAvailable(false)
, _autoSync(false)
, _firstMissionItemSync(false)
, _expectingNewMissionItems(false)
, _missionItemsRequested(false)
{
MultiVehicleManager* multiVehicleMgr = MultiVehicleManager::instance();
......@@ -53,8 +53,8 @@ MissionEditorController::MissionEditorController(QWidget *parent)
if (activeVehicle) {
_activeVehicleChanged(activeVehicle);
} else {
_missionItems = new QmlObjectListModel(this);
_initAllMissionItems();
_missionItemsRequested = true;
_newMissionItemsAvailable();
}
}
......@@ -64,9 +64,9 @@ MissionEditorController::~MissionEditorController()
void MissionEditorController::_newMissionItemsAvailable(void)
{
if (_firstMissionItemSync || !_expectingNewMissionItems) {
if (_firstMissionItemSync) {
// This is the first time the vehicle is seeing items. We have to be careful of transitioning from offline
// to online. Other case is an unexpected set of new items from the vehicle.
// to online.
_firstMissionItemSync = false;
if (_missionItems && _missionItems->count() > 1) {
......@@ -79,24 +79,32 @@ void MissionEditorController::_newMissionItemsAvailable(void)
return;
}
}
} else if (_autoSync) {
// When we are running autoSync we assume the MissionManager is notifying us about the
// items we just sent to it. We keep our own edit list, instead of resetting.
} else if (!_missionItemsRequested) {
// We didn't specifically ask for new mission items. Disregard the new set since it is
// the most likely the set we just sent to the vehicle.
return;
}
_expectingNewMissionItems = false;
_missionItemsRequested = false;
if (_missionItems) {
_deinitAllMissionItems();
_missionItems->deleteLater();
}
MissionManager* missionManager = MultiVehicleManager::instance()->activeVehicle()->missionManager();
_canEdit = missionManager->canEdit();
_missionItems = missionManager->copyMissionItems();
MissionManager* missionManager = NULL;
if (_activeVehicle) {
missionManager = _activeVehicle->missionManager();
}
if (!missionManager || missionManager->inProgress()) {
_canEdit = true;
_missionItems = new QmlObjectListModel(this);
} else {
_canEdit = missionManager->canEdit();
_missionItems = missionManager->copyMissionItems();
}
_initAllMissionItems();
}
......@@ -105,7 +113,7 @@ void MissionEditorController::getMissionItems(void)
Vehicle* activeVehicle = MultiVehicleManager::instance()->activeVehicle();
if (activeVehicle) {
_expectingNewMissionItems = true;
_missionItemsRequested = true;
MissionManager* missionManager = activeVehicle->missionManager();
connect(missionManager, &MissionManager::newMissionItemsAvailable, this, &MissionEditorController::_newMissionItemsAvailable);
activeVehicle->missionManager()->requestMissionItems();
......@@ -390,6 +398,7 @@ void MissionEditorController::_activeVehicleChanged(Vehicle* activeVehicle)
disconnect(_activeVehicle, &Vehicle::homePositionAvailableChanged, this, &MissionEditorController::_activeVehicleHomePositionAvailableChanged);
disconnect(_activeVehicle, &Vehicle::homePositionChanged, this, &MissionEditorController::_activeVehicleHomePositionChanged);
_activeVehicle = NULL;
_newMissionItemsAvailable();
_activeVehicleHomePositionAvailableChanged(false);
}
......@@ -402,8 +411,6 @@ void MissionEditorController::_activeVehicleChanged(Vehicle* activeVehicle)
connect(missionManager, &MissionManager::inProgressChanged, this, &MissionEditorController::_inProgressChanged);
connect(_activeVehicle, &Vehicle::homePositionAvailableChanged, this, &MissionEditorController::_activeVehicleHomePositionAvailableChanged);
connect(_activeVehicle, &Vehicle::homePositionChanged, this, &MissionEditorController::_activeVehicleHomePositionChanged);
_activeVehicleHomePositionChanged(_activeVehicle->homePosition());
_activeVehicleHomePositionAvailableChanged(_activeVehicle->homePositionAvailable());
if (missionManager->inProgress()) {
// Vehicle is still in process of requesting mission items
......@@ -411,8 +418,13 @@ void MissionEditorController::_activeVehicleChanged(Vehicle* activeVehicle)
} else {
// Vehicle already has mission items
_firstMissionItemSync = false;
_newMissionItemsAvailable();
}
_missionItemsRequested = true;
_newMissionItemsAvailable();
_activeVehicleHomePositionChanged(_activeVehicle->homePosition());
_activeVehicleHomePositionAvailableChanged(_activeVehicle->homePositionAvailable());
}
}
......@@ -468,8 +480,10 @@ void MissionEditorController::_autoSyncSend(void)
{
qDebug() << "Auto-syncing with vehicle";
_queuedSend = false;
sendMissionItems();
_missionItems->setDirty(false);
if (_missionItems) {
sendMissionItems();
_missionItems->setDirty(false);
}
}
void MissionEditorController::_inProgressChanged(bool inProgress)
......@@ -478,3 +492,8 @@ void MissionEditorController::_inProgressChanged(bool inProgress)
_autoSyncSend();
}
}
QmlObjectListModel* MissionEditorController::missionItems(void)
{
return _missionItems;
}
......@@ -34,7 +34,7 @@ class MissionEditorController : public QObject
Q_OBJECT
public:
MissionEditorController(QWidget* parent = NULL);
MissionEditorController(QObject* parent = NULL);
~MissionEditorController();
Q_PROPERTY(QmlObjectListModel* missionItems READ missionItems NOTIFY missionItemsChanged)
......@@ -54,7 +54,7 @@ public:
// Property accessors
QmlObjectListModel* missionItems(void) { return _missionItems; }
QmlObjectListModel* missionItems(void);
QmlObjectListModel* waypointLines(void) { return &_waypointLines; }
bool canEdit(void) { return _canEdit; }
bool liveHomePositionAvailable(void) { return _liveHomePositionAvailable; }
......@@ -100,7 +100,7 @@ private:
QGeoCoordinate _liveHomePosition;
bool _autoSync;
bool _firstMissionItemSync;
bool _expectingNewMissionItems;
bool _missionItemsRequested;
bool _queuedSend;
static const char* _settingsGroup;
......
/*=====================================================================
QGroundControl Open Source Ground Control Station
(c) 2009, 2015 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
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 <http://www.gnu.org/licenses/>.
======================================================================*/
#include "MissionController.h"
#include "ScreenToolsController.h"
#include "MultiVehicleManager.h"
#include "MissionManager.h"
#include "CoordinateVector.h"
MissionController::MissionController(QObject *parent)
: QObject(parent)
, _missionItems(NULL)
, _activeVehicle(NULL)
{
MultiVehicleManager* multiVehicleMgr = MultiVehicleManager::instance();
connect(multiVehicleMgr, &MultiVehicleManager::activeVehicleChanged, this, &MissionController::_activeVehicleChanged);
Vehicle* activeVehicle = multiVehicleMgr->activeVehicle();
if (activeVehicle) {
_activeVehicleChanged(activeVehicle);
} else {
_newMissionItemsAvailable();
}
}
MissionController::~MissionController()
{
}
void MissionController::_newMissionItemsAvailable(void)
{
if (_missionItems) {
_missionItems->deleteLater();
}
MissionManager* missionManager = NULL;
Vehicle* activeVehicle = MultiVehicleManager::instance()->activeVehicle();
if (activeVehicle) {
missionManager = activeVehicle->missionManager();
}
if (!missionManager || missionManager->inProgress()) {
_missionItems = new QmlObjectListModel(this);
} else {
_missionItems = missionManager->copyMissionItems();
}
_initAllMissionItems();
}
void MissionController::_recalcWaypointLines(void)
{
bool firstCoordinateItem = true;
MissionItem* lastCoordinateItem = qobject_cast<MissionItem*>(_missionItems->get(0));
_waypointLines.clear();
for (int i=1; i<_missionItems->count(); i++) {
MissionItem* item = qobject_cast<MissionItem*>(_missionItems->get(i));
if (item->specifiesCoordinate()) {
if (firstCoordinateItem) {
if (item->command() == MavlinkQmlSingleton::MAV_CMD_NAV_TAKEOFF) {
// The first coordinate we hit is a takeoff command so link back to home position
_waypointLines.append(new CoordinateVector(qobject_cast<MissionItem*>(_missionItems->get(0))->coordinate(), item->coordinate()));
} else {
// First coordiante is not a takeoff command, it does not link backwards to anything
}
firstCoordinateItem = false;
} else {
// Subsequent coordinate items link to last coordinate item
_waypointLines.append(new CoordinateVector(lastCoordinateItem->coordinate(), item->coordinate()));
}
lastCoordinateItem = item;
}
}
emit waypointLinesChanged();
}
// This will update the child item hierarchy
void MissionController::_recalcChildItems(void)
{
MissionItem* currentParentItem = qobject_cast<MissionItem*>(_missionItems->get(0));
currentParentItem->childItems()->clear();
for (int i=1; i<_missionItems->count(); i++) {
MissionItem* item = qobject_cast<MissionItem*>(_missionItems->get(i));
// Set up non-coordinate item child hierarchy
if (item->specifiesCoordinate()) {
item->childItems()->clear();
currentParentItem = item;
} else {
currentParentItem->childItems()->append(item);
}
}
}
void MissionController::_recalcAll(void)
{
_recalcChildItems();
_recalcWaypointLines();
}
/// Initializes a new set of mission items which may have come from the vehicle or have been loaded from a file
void MissionController::_initAllMissionItems(void)
{
// Add the home position item to the front
MissionItem* homeItem = new MissionItem(this);
homeItem->setHomePositionSpecialCase(true);
homeItem->setCommand(MavlinkQmlSingleton::MAV_CMD_NAV_WAYPOINT);
_missionItems->insert(0, homeItem);
_recalcChildItems();
_recalcWaypointLines();
emit missionItemsChanged();
}
void MissionController::_activeVehicleChanged(Vehicle* activeVehicle)
{
if (_activeVehicle) {
MissionManager* missionManager = _activeVehicle->missionManager();
disconnect(missionManager, &MissionManager::newMissionItemsAvailable, this, &MissionController::_newMissionItemsAvailable);
_activeVehicle = NULL;
}
_activeVehicle = activeVehicle;
if (_activeVehicle) {
MissionManager* missionManager = activeVehicle->missionManager();
connect(missionManager, &MissionManager::newMissionItemsAvailable, this, &MissionController::_newMissionItemsAvailable);
_newMissionItemsAvailable();
}
}
/*=====================================================================
QGroundControl Open Source Ground Control Station
(c) 2009, 2015 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
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 <http://www.gnu.org/licenses/>.
======================================================================*/
#ifndef MissionController_H
#define MissionController_H
#include <QObject>
#include "QmlObjectListModel.h"
#include "Vehicle.h"
/// MissionController is a read only controller for Mission Items
class MissionController : public QObject
{
Q_OBJECT
public:
MissionController(QObject* parent = NULL);
~MissionController();
Q_PROPERTY(QmlObjectListModel* missionItems READ missionItems NOTIFY missionItemsChanged)
Q_PROPERTY(QmlObjectListModel* waypointLines READ waypointLines NOTIFY waypointLinesChanged)
// Property accessors
QmlObjectListModel* missionItems(void) { return _missionItems; }
QmlObjectListModel* waypointLines(void) { return &_waypointLines; }
signals:
void missionItemsChanged(void);
void waypointLinesChanged(void);
private slots:
void _newMissionItemsAvailable();
void _activeVehicleChanged(Vehicle* activeVehicle);
private:
void _recalcWaypointLines(void);
void _recalcChildItems(void);
void _recalcAll(void);
void _initAllMissionItems(void);
private:
QmlObjectListModel* _missionItems;
QmlObjectListModel _waypointLines;
Vehicle* _activeVehicle;
};
#endif
......@@ -61,6 +61,7 @@ void MissionManager::writeMissionItems(const QmlObjectListModel& missionItems, b
for (int i=skipFirstItem ? 1: 0; i<missionItems.count(); i++) {
_missionItems.append(new MissionItem(*qobject_cast<const MissionItem*>(missionItems[i])));
}
emit newMissionItemsAvailable();
if (skipFirstItem) {
for (int i=0; i<_missionItems.count(); i++) {
......@@ -576,4 +577,4 @@ QString MissionManager::_missionResultToString(MAV_MISSION_RESULT result)
qWarning(MissionManagerLog) << "Fell off end of switch statement";
return QString("QGC Internal Error");
}
}
\ No newline at end of file
}
......@@ -160,9 +160,11 @@ void MissionManagerTest::_writeItems(MockLinkMissionItemHandler::FailureMode_t f
// Send the items to the vehicle
_missionManager->writeMissionItems(*list, false /* skipFirstItem */);
// writeMissionItems should emit inProgressChanged signal before returning so no need to wait for it
// writeMissionItems should emit these signals before returning:
// inProgressChanged
// newMissionItemsAvailable
QVERIFY(_missionManager->inProgress());
QCOMPARE(_multiSpy->checkOnlySignalByMask(inProgressChangedSignalMask), true);
QCOMPARE(_multiSpy->checkSignalByMask(inProgressChangedSignalMask | newMissionItemsAvailableSignalMask), true);
_checkInProgressValues(true);
_multiSpy->clearAllSignals();
......
......@@ -88,6 +88,7 @@
#include "QGCQGeoCoordinate.h"
#include "CoordinateVector.h"
#include "MainToolBarController.h"
#include "MissionController.h"
#include "MissionEditorController.h"
#include "FlightDisplayViewController.h"
#include "VideoSurface.h"
......@@ -353,6 +354,7 @@ void QGCApplication::_initCommon(void)
qmlRegisterType<RadioComponentController> ("QGroundControl.Controllers", 1, 0, "RadioComponentController");
qmlRegisterType<ScreenToolsController> ("QGroundControl.Controllers", 1, 0, "ScreenToolsController");
qmlRegisterType<MainToolBarController> ("QGroundControl.Controllers", 1, 0, "MainToolBarController");
qmlRegisterType<MissionController> ("QGroundControl.Controllers", 1, 0, "MissionController");
qmlRegisterType<MissionEditorController> ("QGroundControl.Controllers", 1, 0, "MissionEditorController");
qmlRegisterType<FlightDisplayViewController> ("QGroundControl.Controllers", 1, 0, "FlightDisplayViewController");
......
......@@ -224,7 +224,7 @@ Item {
//-- Indicators
Row {
id: row12
x: desktopToolsLoader.item.width + horizontalMargins
x: horizontalMargins + (ScreenTools.isMobile ? 0: desktopToolsLoader.item.width)
height: cellHeight
spacing: cellSpacerSize
anchors.top: desktopToolsLoader.top
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment