/** * @RDE_HEADER@ * Dialog for Simulated Movement * $Id$ * $HeadURL$ * $Author$ * Created by Gerlinde Rudolphus, SLSE, 2018-01-02 */ #include "participant_move_dialog.h" #include "ui_participant_move_dialog.h" #include #include #include #include #include #include #include #include "logger.h" #include #include #include #include #include #include namespace CentralEffectSimulation { static constexpr double s_msConversion = 3.6; // = 3.6 Convert from [km/h] to [m/s] static constexpr int s_timeMilliConversion = 1000; // 1000 Convert from [s] to [ms] ParticipantMoveDialog::ParticipantMoveDialog(QWidget *parent) : LtsWidgets::LtsDialog(parent, LtsWidgets::ButtonCombination::OkApplyCancel) , m_ftc(nullptr) , m_sitMapGuiIfc(nullptr) , m_ui(new Ui::WaypointsEdit) , m_id(0) , m_requestHelper(new LtsUtils::RequestHelper(this)) , m_polyline(QPolygonF()) , m_logger(getLogger("ParticipantMoveDialog")) { m_ui->setupUi(this); m_ui->cbJump->hide(); m_ui->label_5->hide(); m_ui->spSpeed->setValue(30); // 30km/h per default m_ui->lePrefix->setText(tr("Waypoint")); m_uiRequirements->addRequirement({m_ui->lblMoveStart, [this]() { return hasValidEffectStart(m_ui->dteStartTime); }, tr("Begin time is smaller than current time")}); connect (this, &ParticipantMoveDialog::signalFocused, this, &ParticipantMoveDialog::onFocus); setOnlineHelpFilename(QStringLiteral("Situation-Move_Participant_0")); } // end of ctor() //----------------------------------------------------------------------------- ParticipantMoveDialog::~ParticipantMoveDialog() { m_sitMapGuiIfc = nullptr; delete m_ui; emit closeDialog(m_participantData.id()); } // end of dtor() //----------------------------------------------------------------------------- void ParticipantMoveDialog::enableUiElements(bool enable) { m_ui->pbPosition->setEnabled(enable); m_ui->pbClear->setEnabled(enable); } // end of enableUiElements() //----------------------------------------------------------------------------- bool ParticipantMoveDialog::init(LtsSession::LtsSessionDataDds *session, const std::shared_ptr &ppmDtc, const std::shared_ptr &cesDtc, const ParticipantManagement::ParticipantData &data) { m_session = session; m_participantData = data; m_cesDtc = cesDtc; //bool editable = LtsSessionData::LtsSessionData::getInstance()->hasOperatorRight(OperatorRights::BorderlinesEdit); //bool readable = LtsSessionData::LtsSessionData::getInstance()->hasOperatorRight(OperatorRights::BorderlinesShow); //if (!readable && !editable) // return false; if (!m_session->getFtc(m_ftc)) return false; m_session->getForeignGuiInterface(m_sitMapGuiIfc); if (m_sitMapGuiIfc) { connect(m_ui->pbPosition, &QPushButton::clicked, this, &ParticipantMoveDialog::onGetPolyline); connect(m_ui->pbClear, &QPushButton::clicked, this, &ParticipantMoveDialog::clearCoordinates); } else { m_ui->pbPosition->setEnabled(false); m_ui->pbClear->setEnabled(false); } m_ui->dteStartTime->setMinimumTimeMSec(m_session->partitionTime()); m_ui->dteStartTime->setRetrieveCurrentTimeCallback([this]() { return m_session->partitionTime(); }); m_ui->lbIdentifier->setText(QString::fromStdString(data.tacticalIdentifier())); QString partyStr = ParticipantManagement::trPartyName(ppmDtc->party(m_participantData.partyId()).name()); m_ui->lbParty->setText(partyStr); QString typeStr = ParticipantManagement::trTypeName(ppmDtc->type(m_participantData.typeId()).name()); m_ui->lbType->setText(typeStr); // enableUiElements(editable && readable); enableUiElements(true); initTableHeader(); connect(cesDtc.get(), &CentralEffectSimulation::Interface::Dtc::simulatedMovementChanged, this, &ParticipantMoveDialog::onSimulatedMovementChanged); // Get data for all SimulatedMovements { auto sm = [this](const CentralEffectSimulation::SimulatedMovement & sme) { onSimulatedMovementChanged(sme, DataModification::eAdded); }; if (!m_cesDtc->simulatedMovements(sm)) return false; } // Save/Load from Library Button m_buttonBox->connectLibraryButton(this, &ParticipantMoveDialog::onImportButtonClicked, &ParticipantMoveDialog::onExportButtonClicked); if (!m_session->getForeignGuiInterface(m_flmGui)) m_buttonBox->setButtonEnabled(LtsWidgets::DialogButton::Library, false); return true; } // end of init() void ParticipantMoveDialog::onSimulatedMovementChanged(const CentralEffectSimulation::SimulatedMovement &data, DataModification modType) { if (modType == DataModification::eRemoved || m_participantData.id() != data.participantId()) return; INFO_LOG(m_logger) << "onSimulatedMovementChanged"; // Clear data clearContent(); // set new Content m_id = data.id(); m_ui->lePrefix->setText(QString::fromStdString(data.name())); m_ui->cbRepeat->setChecked(data.repeat()); m_ui->cbJump->setChecked(data.jumpToStart()); m_ui->dteStartTime->setMsecsSinceEpoch(data.startTime()); m_polyline.clear(); // Insert data to Vector for (const SimulatedMovementSegment &segment : data.segments()) { const Core::GeoPosition2D &pointDds = segment.position(); insertPos(pointDds.lon(), pointDds.lat(), QString::fromStdString(segment.name()), segment.delay(), segment.speed()); m_polyline.push_back(QPointF(pointDds.lon(), pointDds.lat())); } // Insert data to Map (Preview) if (this->amIFocused()) m_sitMapGuiIfc->displayPreviewObject(this, m_polyline, false); } void ParticipantMoveDialog::showPreview(bool show) { if (m_sitMapGuiIfc) { if (show) m_sitMapGuiIfc->displayPreviewObject(this, m_polyline, false); else m_sitMapGuiIfc->removePreviewObject(); } } void ParticipantMoveDialog::onSimulatedMovementRemoveReply(const Core::FunctionReplyObject &replyObject) { m_requestHelper->requestReply(replyObject); if (!replyObject.error()) { m_sitMapGuiIfc->cancelActiveRubberbands(); m_sitMapGuiIfc->removePreviewObject(); } } void ParticipantMoveDialog::onSimulatedMovementModifyReply(const Core::FunctionReplyObject &replyObject) { m_requestHelper->requestReply(replyObject); if (!replyObject.error()) m_sitMapGuiIfc->removePreviewObject(); } void ParticipantMoveDialog::retranslate() { translate(m_ui, QString::fromStdString(m_participantData.tacticalIdentifier()), false, tr("Waypoints")); // TODO: lock topic? DEBUG_LOG(m_logger) << "retranslate"; } void ParticipantMoveDialog::initTableHeader() { QStringList headers; headers << tr("Identifier") << tr("Idle Time [s]") << tr("Speed [km/h]") << tr("Position"); m_ui->tableWidget->setHorizontalHeaderLabels(headers); m_ui->tableWidget->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); } // idleTime in [s] speed in [m/s] void ParticipantMoveDialog::insertPos(const double lon, const double lat, const QString &identifier, const int idleTime, const double speed) { QLineEdit *le = new QLineEdit; QSpinBox *sbDelay = new QSpinBox; QSpinBox *sbSpeed = new QSpinBox; sbDelay->setMinimum(m_ui->spIdleTime->minimum()); sbDelay->setMaximum(m_ui->spIdleTime->maximum()); sbSpeed->setMinimum(m_ui->spSpeed->minimum()); sbSpeed->setMaximum(m_ui->spSpeed->maximum()); int rowCount = m_ui->tableWidget->rowCount(); LtsCoordWidget::LtsCoordWidget *wdg = new LtsCoordWidget::LtsCoordWidget(); //wdg->setCoordinate( 8.794556, 53.080827); wdg->setCoordinate(lon, lat); wdg->setObjectName(QStringLiteral("positionEdit_") + QString::number(rowCount)); m_ui->tableWidget->insertRow(rowCount); le->setText(identifier); m_ui->tableWidget->setCellWidget(rowCount, Name, le); sbDelay->setValue(idleTime / s_timeMilliConversion); m_ui->tableWidget->setCellWidget(rowCount, IdleTime, sbDelay); sbSpeed->setValue(speed * s_msConversion); m_ui->tableWidget->setCellWidget(rowCount, Speed, sbSpeed); m_ui->tableWidget->setCellWidget(rowCount, Position, wdg); wdg->setDisplayType(LtsCoordWidget::LtsCoordWidget::DisplayType::MGRS); } //----------------------------------------------------------------------------- /* void WaypointsEdit::setData(const Borderline &borderline, bool copy) { DEBUG_LOG(m_logger) << LOGFUNCTION << "borderlineId: " << borderline.id() << " operatorId: " << borderline.operatorId(); } // end of setData() */ //----------------------------------------------------------------------------- void ParticipantMoveDialog::clearContent() { m_ui->lePrefix->clear(); m_ui->cbRepeat->setChecked(false); m_ui->cbJump->setChecked(false); clearCoordinates(); } void ParticipantMoveDialog::clearCoordinates() { if (m_ui) { m_ui->tableWidget->clearContents(); m_ui->tableWidget->setRowCount(0); } // remove PreviewObject: if ( m_sitMapGuiIfc && this->amIFocused()) m_sitMapGuiIfc->removePreviewObject(); } void ParticipantMoveDialog::onFocus() { showPreview(true); m_sitMapGuiIfc->displayPreviewObject(this, m_polyline, false); } void ParticipantMoveDialog::onGetPolyline() { DEBUG_LOG(m_logger) << LOGFUNCTION; if (!m_sitMapGuiIfc) return; if (m_sitMapGuiIfc->mapMode() != SituationMap::MapMode::MapMode_2D) { LtsWidgets::LtsMessageDialog::showMessage(this, tr("Only available in 2D mode")); return; } QPolygonF coordinates; int i = 0; while (true) { LtsCoordWidget::LtsCoordWidget *w = findChild(QStringLiteral("positionEdit_%1").arg(i)); if (!w) break; if (!w->coordinate().isNull()) coordinates << w->coordinate(); ++i; } m_sitMapGuiIfc->getPolyline(this, SLOT(onPolylineData(QPolygonF)), nullptr, 2, 11, -1, -1, coordinates); } // end of onGetPolyline() //----------------------------------------------------------------------------- void ParticipantMoveDialog::onPolylineData(const QPolygonF &polyline) { DEBUG_LOG(m_logger) << LOGFUNCTION << " numberOfPoints: " << polyline.size(); if (polyline.size() < 2) return; m_polyline = polyline; clearCoordinates(); for (int i = 0; i < polyline.size(); i++) insertPos(polyline.at(i).x(), polyline.at(i).y(), m_ui->lePrefix->text() + QLatin1Char('_') + QString::number(i + 1), m_ui->spIdleTime->value() * s_timeMilliConversion, m_ui->spSpeed->value() / s_msConversion); // create PreviewObject: m_sitMapGuiIfc->displayPreviewObject(this, polyline, false); } // end of onPolylineData() //----------------------------------------------------------------------------- bool ParticipantMoveDialog::hasValidEffectStart(LtsWidgets::LtsDateTimeEdit *dte) const { // if there is visible a enabled/disable checkbox -> also allow 0 as effect start const bool bEmptyAllowed = dte->isCheckable(); const auto ms = dte->msecsSinceEpoch(); // no effect start -> ok if (bEmptyAllowed && ms == 0) return true; return ms > m_session->partitionTime(); } SimulatedMovement ParticipantMoveDialog::requestContent() const { std::string name = m_ui->lePrefix->text().toStdString(); int32_t repeat = m_ui->cbRepeat->isChecked(); //int32_t jumpToStart = m_ui->cbJump->isChecked(); int32_t jumpToStart = true; rti::core::int64 startTime = m_ui->dteStartTime->msecsSinceEpoch(); int32_t participantId = m_participantData.id(); int32_t nextPoint = 0; int cntSegments = m_ui->tableWidget->rowCount(); std::vector segments; segments.resize(cntSegments); for (int i = 0; i < cntSegments; i++) { QWidget *widget = m_ui->tableWidget->cellWidget(i, Name); const QString nameSeg = static_cast(widget)->text(); segments[i].name(nameSeg.toStdString()); widget = m_ui->tableWidget->cellWidget(i, IdleTime); const int idleTime = static_cast(widget)->value() * s_timeMilliConversion; segments[i].delay(idleTime); widget = m_ui->tableWidget->cellWidget(i, Speed); const double speed = static_cast(widget)->value() / s_msConversion; segments[i].speed(speed); widget = m_ui->tableWidget->cellWidget(i, Position); const QPointF point = static_cast(widget)->coordinate(); const Core::GeoPosition2D pos(Core::GeoPosition2D(point.x(), point.y())); segments[i].position(pos); } return SimulatedMovement(m_id, name, startTime, participantId, segments, repeat, jumpToStart, nextPoint ); } bool ParticipantMoveDialog::apply() { int cntSegments = m_ui->tableWidget->rowCount(); if (cntSegments > 0) { const SimulatedMovement req = requestContent(); INFO_LOG(m_logger) << "--- onApplyMessage() "; m_requestHelper->addRequest([this, req]() { return m_ftc->simulatedMovementModifyRequest(req, this); }); } else { if (m_id > 0) { Core::RemoveElementRequest req(m_id); m_id = 0; // Next time new Route INFO_LOG(m_logger) << "--- onApplyMessage() "; m_requestHelper->addRequest([req, this]() { return m_ftc->simulatedMovementRemoveRequest(req, this); }); } } return m_requestHelper->startRequests(); } //----------------------------------------------------------------------------- // Save / Load Simulation in DB //----------------------------------------------------------------------------- QByteArray ParticipantMoveDialog::simulatedMovementToJson() const { const SimulatedMovement req = requestContent(); QJsonObject obj; obj[QStringLiteral("XmlType")] = FileManagement::toString(FileManagement::XmlType::CESParticipantMove); obj[QStringLiteral("id")] = req.id(); obj[QStringLiteral("jumpToStart")] = req.jumpToStart(); obj[QStringLiteral("name")] = QString::fromStdString(req.name()); obj[QStringLiteral("nextPoint")] = req.nextPoint(); obj[QStringLiteral("participantId")] = req.participantId(); obj[QStringLiteral("repeat")] = req.repeat(); obj[QStringLiteral("startTime")] = req.startTime(); QJsonArray arr; for (auto it = req.segments().begin(); it != req.segments().end(); ++it) { QJsonObject inner; inner[QStringLiteral("delay")] = it->delay(); inner[QStringLiteral("name")] = QString::fromStdString(it->name()); inner[QStringLiteral("speed")] = it->speed(); inner[QStringLiteral("lat")] = it->position().lat(); inner[QStringLiteral("lon")] = it->position().lon(); arr += inner; } obj[QStringLiteral("segments")] = arr; QJsonDocument doc(obj); return doc.toJson(QJsonDocument::Compact); } SimulatedMovement ParticipantMoveDialog::jsonToSimulatedMovement(const QByteArray &content) const { // already checked that content is not empty, parseable and contains proper XmlType (CESParticipantMove) CentralEffectSimulation::SimulatedMovement result; const QJsonDocument doc(QJsonDocument::fromJson(content)); const QJsonObject obj(doc.object()); result.id(obj[QStringLiteral("id")].toInt()); //TODO Library: check id = 0? result.jumpToStart(obj[QStringLiteral("jumpToStart")].toInt()); result.name(obj[QStringLiteral("name")].toString().toStdString()); result.nextPoint(obj[QStringLiteral("nextPoint")].toInt()); result.participantId(m_participantData.id()); result.repeat(obj[QStringLiteral("repeat")].toInt()); result.startTime(obj[QStringLiteral("startTime")].toDouble()); const QJsonArray arr(obj.value(QStringLiteral("segments")).toArray()); auto &segments = result.segments(); segments.resize(arr.size()); for (int i = 0; i < arr.size(); ++i) { CentralEffectSimulation::SimulatedMovementSegment &segment = segments[i]; const QJsonObject objArr(arr.at(i).toObject()); segment.delay(objArr[QStringLiteral("delay")].toDouble()); segment.name(objArr[QStringLiteral("name")].toString().toStdString()); segment.position().lat(objArr[QStringLiteral("lat")].toDouble()); segment.position().lon(objArr[QStringLiteral("lon")].toDouble()); segment.speed(objArr[QStringLiteral("speed")].toDouble()); } return result; } void ParticipantMoveDialog::onExportButtonClicked() { QJsonObject saveObject; saveObject[QStringLiteral("content")] = QString(); const QByteArray byteArray = simulatedMovementToJson(); int fileId = m_flmGui->saveFileContent(this, FileManagement::FileType::Xml, m_session->exerciseId(), byteArray); if (fileId > 0) DEBUG_LOG(m_logger) << "Saved file with id = " << fileId; else ERROR_LOG(m_logger) << "Could not save file in library "; } void ParticipantMoveDialog::onImportButtonClicked() { DEBUG_LOG(m_logger) << " onImportButtonClicked() "; QByteArray content; int fileId = m_flmGui->loadFileContent(this, content, {FileManagement::FileType::Xml}, m_session->exerciseId(), FileManagement::toString(FileManagement::XmlType::CESParticipantMove)); if (fileId > 0) { if (!m_flmGui->isXmlType(content, FileManagement::toString(FileManagement::XmlType::CESParticipantMove))) { QHash texts({{LtsWidgets::Button::Yes, tr("Ok")}}); LtsWidgets::LtsMessageDialog::showMessage(this, tr("Invalid xml file content"), windowTitle(), LtsWidgets::Behaviour::Modal, LtsWidgets::Button::Yes, texts); } else { SimulatedMovement req = jsonToSimulatedMovement(content); INFO_LOG(m_logger) << "--- onImportButtonClicked() "; m_requestHelper->addRequest([this, req]() { return m_ftc->simulatedMovementModifyRequest(req, this); }); m_requestHelper->startRequests(); } } } } // namespace CentralEffectSimulation