Skip to content

Commit

Permalink
feat(departure): Combine pending prenotes and releases into one list (#…
Browse files Browse the repository at this point in the history
…386)

* Create new list from releases list

* Add type column

* Add toggle

* Bootstrap new toggles

* Start prenotes mixin

* Display Prenotes Properly

* Fix bugs

* Test updates

* Docs

* Remove old code

* Style

* Fix test

* Fix another test

* One last test fix

* Fix created at time on release
  • Loading branch information
AndyTWF committed Jan 5, 2022
1 parent 6591503 commit 531b687
Show file tree
Hide file tree
Showing 38 changed files with 1,304 additions and 1,865 deletions.
4 changes: 2 additions & 2 deletions docs/UserGuide/Features/DepartureReleases.md
Expand Up @@ -53,8 +53,8 @@ In addition, there are two audible cues for when a release is accepted or reject

## How Do I Respond To A Release Request?

If you are the target of a release request, you can use the `Departure Release Decision List` to make decisions on individual
releases. This may be toggled on a per-ASR basis using the `OP` menu.
If you are the target of a release request, you can use the `Departure Coordination List` to make decisions on individual
releases. This may be toggled on a per-ASR basis using the `OP` menu. A release request will have `Rls` in the "Type" column.

If there is a pending release request for you, the header bar of the list will flash *_Red_*. You will also receive
an audible ping to let you know that something has come in.
Expand Down
4 changes: 2 additions & 2 deletions docs/UserGuide/Features/PrenoteMessages.md
Expand Up @@ -44,8 +44,8 @@ The controller callsign indicates the status of the prenote:

There are two ways to respond to a prenote, if you are the target controller.

Firstly, you can use the `Pending Prenote List` to acknowledge the message.
This may be toggled on a per-ASR basis using the `OP` menu.
Firstly, you can use the `Departure Coordination List` to acknowledge the message.
This may be toggled on a per-ASR basis using the `OP` menu. A prenote will have `Pre` in the "Type" column.

If there is a pending prenote for you, the header bar of the list will flash *_Red_*. You will also receive
an audible ping to let you know that something has come in.
Expand Down
16 changes: 9 additions & 7 deletions src/plugin/CMakeLists.txt
Expand Up @@ -126,6 +126,10 @@ set(src__datablock
)
source_group("src\\datablock" FILES ${src__datablock})

set(src__departure
departure/DepartureCoordinationList.cpp departure/DepartureCoordinationList.h departure/ToggleDepartureCoordinationList.cpp departure/ToggleDepartureCoordinationList.h departure/DepartureModule.cpp departure/DepartureModule.h)
source_group("src\\departure" FILES ${src__departure})

set(src__dependency
"dependency/DependencyLoader.cpp"
"dependency/DependencyLoader.h"
Expand Down Expand Up @@ -636,8 +640,6 @@ set(src__prenote
prenote/PrenoteMessageStatusView.cpp prenote/PrenoteMessageStatusView.h
prenote/TriggerPrenoteMessageStatusView.cpp prenote/TriggerPrenoteMessageStatusView.h
prenote/AcknowledgePrenoteMessage.cpp prenote/AcknowledgePrenoteMessage.h
prenote/PendingPrenoteList.cpp prenote/PendingPrenoteList.h
prenote/TogglePendingPrenoteList.cpp prenote/TogglePendingPrenoteList.h
prenote/PublishedPrenote.h prenote/PublishedPrenote.cpp
prenote/PublishedPrenoteCollection.cpp prenote/PublishedPrenoteCollection.h
prenote/PublishedPrenoteCollectionFactory.cpp prenote/PublishedPrenoteCollectionFactory.h
Expand Down Expand Up @@ -719,8 +721,6 @@ set(src__releases
"releases/DepartureReleaseColours.h"
"releases/DepartureReleaseCountdownColours.cpp"
"releases/DepartureReleaseCountdownColours.h"
"releases/DepartureReleaseDecisionList.cpp"
"releases/DepartureReleaseDecisionList.h"
"releases/DepartureReleaseEventHandler.cpp"
"releases/DepartureReleaseEventHandler.h"
"releases/DepartureReleaseRequest.cpp"
Expand All @@ -737,9 +737,10 @@ set(src__releases
"releases/ReleaseModule.h"
"releases/RequestDepartureReleaseDialog.cpp"
"releases/RequestDepartureReleaseDialog.h"
"releases/ToggleDepartureReleaseDecisionList.cpp"
"releases/ToggleDepartureReleaseDecisionList.h"
releases/EnrouteRelease.cpp releases/EnrouteReleaseType.cpp releases/RejectDepartureReleaseDialog.cpp releases/RejectDepartureReleaseDialog.h releases/ReleaseApprovalRemarksUserMessage.cpp releases/ReleaseApprovalRemarksUserMessage.h releases/ReleaseRejectionRemarksUserMessage.cpp releases/ReleaseRejectionRemarksUserMessage.h)
releases/EnrouteRelease.cpp releases/EnrouteReleaseType.cpp
releases/RejectDepartureReleaseDialog.cpp releases/RejectDepartureReleaseDialog.h
releases/ReleaseApprovalRemarksUserMessage.cpp releases/ReleaseApprovalRemarksUserMessage.h
releases/ReleaseRejectionRemarksUserMessage.cpp releases/ReleaseRejectionRemarksUserMessage.h)
source_group("src\\releases" FILES ${src__releases})

set(src__sectorfile
Expand Down Expand Up @@ -870,6 +871,7 @@ set(ALL_FILES
${src__controller}
${src__countdown}
${src__datablock}
${src__departure}
${src__dependency}
${src__euroscope}
${src__flightinformationservice}
Expand Down
4 changes: 4 additions & 0 deletions src/plugin/bootstrap/PersistenceContainer.h
Expand Up @@ -88,6 +88,9 @@ namespace UKControllerPlugin {
namespace Plugin {
class FunctionCallEventHandler;
} // namespace Plugin
namespace Prenote {
class PrenoteMessageCollection;
} // namespace Prenote
namespace Push {
class PushEventProcessorCollection;
} // namespace Push
Expand Down Expand Up @@ -161,6 +164,7 @@ namespace UKControllerPlugin::Bootstrap {
std::unique_ptr<UKControllerPlugin::Dialog::DialogManager> dialogManager;
std::unique_ptr<UKControllerPlugin::Setting::SettingRepository> settingsRepository;
std::shared_ptr<UKControllerPlugin::Datablock::DisplayTime> timeFormatting;
std::shared_ptr<UKControllerPlugin::Prenote::PrenoteMessageCollection> prenotes;

// Collections of event handlers
std::unique_ptr<UKControllerPlugin::Flightplan::FlightPlanEventHandlerCollection> flightplanHandler;
Expand Down
282 changes: 282 additions & 0 deletions src/plugin/departure/DepartureCoordinationList.cpp
@@ -0,0 +1,282 @@
#include "DepartureCoordinationList.h"
#include "components/BrushSwitcher.h"
#include "components/Button.h"
#include "components/ClickableArea.h"
#include "components/StandardButtons.h"
#include "components/TitleBar.h"
#include "controller/ActiveCallsignCollection.h"
#include "controller/ControllerPosition.h"
#include "controller/ControllerPositionCollection.h"
#include "euroscope/EuroScopeCFlightPlanInterface.h"
#include "euroscope/EuroscopePluginLoopbackInterface.h"
#include "euroscope/EuroscopeRadarLoopbackInterface.h"
#include "euroscope/UserSetting.h"
#include "graphics/GdiGraphicsInterface.h"
#include "helper/HelperFunctions.h"
#include "prenote/PrenoteMessage.h"
#include "prenote/PrenoteMessageCollection.h"
#include "releases/DepartureReleaseEventHandler.h"
#include "releases/DepartureReleaseRequest.h"
#include "tag/TagData.h"

namespace UKControllerPlugin::Departure {

DepartureCoordinationList::DepartureCoordinationList(
Releases::DepartureReleaseEventHandler& handler,
Prenote::PrenoteMessageCollection& prenotes,
Euroscope::EuroscopePluginLoopbackInterface& plugin,
const Controller::ControllerPositionCollection& controllers,
const Controller::ActiveCallsignCollection& activeCallsigns,
const int screenObjectId)
: controllers(controllers), handler(handler), prenotes(prenotes), plugin(plugin),
activeCallsigns(activeCallsigns), textBrush(OFF_WHITE_COLOUR), screenObjectId(screenObjectId), visible(false),
contentCollapsed(false)
{
this->brushSwitcher = Components::BrushSwitcher::Create(
std::make_shared<Gdiplus::SolidBrush>(TITLE_BAR_BASE_COLOUR), std::chrono::seconds(2))
->AdditionalBrush(std::make_shared<Gdiplus::SolidBrush>(TITLE_BAR_FLASH_COLOUR));

this->titleBar = Components::TitleBar::Create(
L"Departure Coordination Requests", {0, 0, this->titleBarWidth, this->titleBarHeight})
->WithDrag(this->screenObjectId)
->WithBorder(std::make_shared<Gdiplus::Pen>(OFF_WHITE_COLOUR))
->WithBackgroundBrush(std::make_shared<Gdiplus::SolidBrush>(TITLE_BAR_BASE_COLOUR))
->WithTextBrush(std::make_shared<Gdiplus::SolidBrush>(OFF_WHITE_COLOUR));

this->closeButton = Components::Button::Create(
closeButtonOffset, this->screenObjectId, "closeButton", Components::CloseButton());

this->collapseButton = Components::Button::Create(
collapseButtonOffset, this->screenObjectId, "collapseButton", Components::CollapseButton([this]() -> bool {
return this->contentCollapsed;
}));
}

void DepartureCoordinationList::LeftClick(
Euroscope::EuroscopeRadarLoopbackInterface& radarScreen,
int objectId,
const std::string& objectDescription,
POINT mousePos,
RECT itemArea)
{
if (objectDescription == "collapseButton") {
this->contentCollapsed = !this->contentCollapsed;
return;
}

if (objectDescription == "closeButton") {
this->visible = false;
return;
}

const std::string callsign = objectDescription.substr(4);
objectDescription.substr(0, 3) == "Rls"
? radarScreen.TogglePluginTagFunction(
callsign, DEPARTURE_RELEASE_DECISION_TAG_FUNCTION_ID, mousePos, itemArea)
: radarScreen.TogglePluginTagFunction(callsign, ACKNOWLEDGE_PRENOTE_TAG_FUNCTION_ID, mousePos, itemArea);
}

auto DepartureCoordinationList::IsVisible() const -> bool
{
return this->visible;
}

void DepartureCoordinationList::Move(RECT position, std::string objectDescription)
{
this->position = {static_cast<Gdiplus::REAL>(position.left), static_cast<Gdiplus::REAL>(position.top)};
}

void DepartureCoordinationList::Render(
Windows::GdiGraphicsInterface& graphics, Euroscope::EuroscopeRadarLoopbackInterface& radarScreen)
{
auto decisions = this->handler.GetReleasesRequiringUsersDecision();
auto userControllerId = this->activeCallsigns.UserHasCallsign()
? this->activeCallsigns.GetUserCallsign().GetNormalisedPosition().GetId()
: -1;

std::vector<std::shared_ptr<Prenote::PrenoteMessage>> prenoteMessages;
if (userControllerId != -1) {
this->prenotes.Iterate(
[&prenoteMessages, &userControllerId](const std::shared_ptr<Prenote::PrenoteMessage>& message) {
if (message->GetTargetControllerId() == userControllerId && !message->IsAcknowledged()) {
prenoteMessages.push_back(message);
}
});
}

if (decisions.empty() && prenoteMessages.empty()) {
this->titleBar->WithBackgroundBrush(this->brushSwitcher->Base());
} else {
this->titleBar->WithBackgroundBrush(this->brushSwitcher->Next());
}

// Translate to content position
graphics.Translated(
this->position.X,
this->position.Y + static_cast<float>(this->titleBarHeight),
[this, &graphics, &radarScreen, &decisions, &prenoteMessages] {
if (this->contentCollapsed) {
return;
}

// Draw column headers
graphics.DrawString(L"Type", this->typeColumnHeader, this->textBrush);
graphics.DrawString(L"Callsign", this->callsignColumnHeader, this->textBrush);
graphics.DrawString(L"Controller", this->controllerColumnHeader, this->textBrush);
graphics.DrawString(L"Dept", this->airportColumnHeader, this->textBrush);
graphics.DrawString(L"SID", this->sidColumnHeader, this->textBrush);
graphics.DrawString(L"Dest", this->destColumnHeader, this->textBrush);

// Draw each aircraft that we care about
Gdiplus::Rect typeColumn = this->typeColumnHeader;
Gdiplus::Rect callsignColumn = this->callsignColumnHeader;
Gdiplus::Rect controllerColumn = this->controllerColumnHeader;
Gdiplus::Rect airportColumn = this->airportColumnHeader;
Gdiplus::Rect sidColumn = this->sidColumnHeader;
Gdiplus::Rect destColumn = this->destColumnHeader;

auto nextRelease = decisions.cbegin();
auto nextPrenote = prenoteMessages.cbegin();
std::variant<
std::shared_ptr<Releases::DepartureReleaseRequest>,
std::shared_ptr<Prenote::PrenoteMessage>>
listItem;

do {
// Out of things to draw
if (nextRelease == decisions.cend() && nextPrenote == prenoteMessages.cend()) {
break;
}

// Chose the next item in the list - this is, broadly, ordered by created at time
if (nextRelease == decisions.cend()) {
listItem = std::variant<
std::shared_ptr<Releases::DepartureReleaseRequest>,
std::shared_ptr<Prenote::PrenoteMessage>>(*nextPrenote++);
} else if (nextPrenote == prenoteMessages.cend()) {
listItem = std::variant<
std::shared_ptr<Releases::DepartureReleaseRequest>,
std::shared_ptr<Prenote::PrenoteMessage>>(*nextRelease++);
} else if ((*nextPrenote)->GetCreatedAt() < (*nextRelease)->CreatedAt()) {
listItem = std::variant<
std::shared_ptr<Releases::DepartureReleaseRequest>,
std::shared_ptr<Prenote::PrenoteMessage>>(*nextPrenote++);
} else {
listItem = std::variant<
std::shared_ptr<Releases::DepartureReleaseRequest>,
std::shared_ptr<Prenote::PrenoteMessage>>(*nextRelease++);
}

// Shift the cols
typeColumn.Y += lineHeight;
callsignColumn.Y += lineHeight;
controllerColumn.Y += lineHeight;
airportColumn.Y += lineHeight;
sidColumn.Y += lineHeight;
destColumn.Y += lineHeight;

// Type column
const std::string itemType = listItem.index() == 0 ? "Rls" : "Pre";
graphics.DrawString(HelperFunctions::ConvertToWideString(itemType), typeColumn, this->textBrush);

// Callsign column
const std::string callsign =
listItem.index() == 0
? std::get<std::shared_ptr<Releases::DepartureReleaseRequest>>(listItem)->Callsign()
: std::get<std::shared_ptr<Prenote::PrenoteMessage>>(listItem)->GetCallsign();
graphics.DrawString(
HelperFunctions::ConvertToWideString(callsign), callsignColumn, this->textBrush);
std::shared_ptr<Components::ClickableArea> callsignClickspot = Components::ClickableArea::Create(
callsignColumn, this->screenObjectId, itemType + "." + callsign, false);
callsignClickspot->Apply(graphics, radarScreen);

// Controller column
const int controllerId =
listItem.index() == 0
? std::get<std::shared_ptr<Releases::DepartureReleaseRequest>>(listItem)
->RequestingController()
: std::get<std::shared_ptr<Prenote::PrenoteMessage>>(listItem)->GetSendingControllerId();
const std::wstring controller = HelperFunctions::ConvertToWideString(
this->controllers.FetchPositionById(controllerId)->GetCallsign());
graphics.DrawString(controller, controllerColumn, this->textBrush);

auto fp = this->plugin.GetFlightplanForCallsign(callsign);
if (!fp) {
continue;
}

// Remaining FP-driven columns
graphics.DrawString(
HelperFunctions::ConvertToWideString(fp->GetOrigin()), airportColumn, this->textBrush);

graphics.DrawString(
HelperFunctions::ConvertToWideString(fp->GetSidName()), sidColumn, this->textBrush);

graphics.DrawString(
HelperFunctions::ConvertToWideString(fp->GetDestination()), destColumn, this->textBrush);
} while (nextRelease != decisions.cend() || nextPrenote != prenoteMessages.cend());
});

// Translate to window position
graphics.Translated(this->position.X, this->position.Y, [this, &graphics, &radarScreen] {
this->titleBar->Draw(graphics, radarScreen);
this->closeButton->Draw(graphics, radarScreen);
this->collapseButton->Draw(graphics, radarScreen);
});
}

void DepartureCoordinationList::ResetPosition()
{
this->Move(defaultRect, "");
}

void DepartureCoordinationList::AsrLoadedEvent(Euroscope::UserSetting& userSetting)
{
this->visible = userSetting.GetBooleanEntry(GetAsrKey("Visible"), false);

this->contentCollapsed = userSetting.GetBooleanEntry(GetAsrKey("ContentCollapsed"), false);

this->Move(
{userSetting.GetIntegerEntry(GetAsrKey("XPosition"), defaultPosition),
userSetting.GetIntegerEntry(GetAsrKey("YPosition"), defaultPosition),
0,
0},
"");
}

void DepartureCoordinationList::AsrClosingEvent(Euroscope::UserSetting& userSetting)
{
userSetting.Save(GetAsrKey("Visible"), GetAsrDescription("Visible"), this->visible);

userSetting.Save(GetAsrKey("ContentCollapsed"), GetAsrDescription("Content Collapsed"), this->contentCollapsed);

userSetting.Save(GetAsrKey("XPosition"), GetAsrDescription("X Position"), static_cast<int>(this->position.X));

userSetting.Save(GetAsrKey("YPosition"), GetAsrDescription("Y Position"), static_cast<int>(this->position.Y));
}

void DepartureCoordinationList::ToggleVisible()
{
this->visible = !this->visible;
}

auto DepartureCoordinationList::ContentCollapsed() const -> bool
{
return this->contentCollapsed;
}

auto DepartureCoordinationList::Position() const -> Gdiplus::PointF
{
return this->position;
}

auto DepartureCoordinationList::GetAsrKey(const std::string& item) -> std::string
{
return "departureCoordinationList" + item;
}

auto DepartureCoordinationList::GetAsrDescription(const std::string& description) -> std::string
{
return "Departure Coordination List " + description;
}
} // namespace UKControllerPlugin::Departure

0 comments on commit 531b687

Please sign in to comment.