diff --git a/src/plugin/CMakeLists.txt b/src/plugin/CMakeLists.txt index c67fcfb0c..3563826e7 100644 --- a/src/plugin/CMakeLists.txt +++ b/src/plugin/CMakeLists.txt @@ -10,7 +10,6 @@ set(resource source_group("resource" FILES ${resource}) set(src__aircraft - aircraft/CallsignSelectionListInterface.h aircraft/CallsignSelectionProviderInterface.h aircraft/CallsignSelectionList.cpp aircraft/CallsignSelectionList.h aircraft/CallsignSelectionListFactory.cpp aircraft/CallsignSelectionListFactory.h @@ -244,7 +243,7 @@ set(src__graphics "graphics/GdiGraphicsWrapper.cpp" "graphics/GdiGraphicsWrapper.h" "graphics/GdiplusBrushes.h" -) + graphics/FontManager.cpp graphics/FontManager.h graphics/StringFormatManager.cpp graphics/StringFormatManager.h) source_group("src\\graphics" FILES ${src__graphics}) set(src__handoff @@ -449,6 +448,16 @@ set(src__intention ) source_group("src\\intention" FILES ${src__intention}) +set(src__list + list/ListItemProviderInterface.h + list/PopupListInterface.h + list/PopupList.cpp list/PopupList.h + list/PopupListFactory.cpp list/PopupListFactory.h + list/ListItem.h + list/ListItemCheckedStatus.h + list/PopupListFactoryBootstrap.cpp list/PopupListFactoryBootstrap.h) +source_group("src\\list" FILES ${src__list}) + set(src__login "login/Login.cpp" "login/Login.h" @@ -706,7 +715,7 @@ set(src__radarscreen "radarscreen/ScreenControlsBootstrap.h" "radarscreen/UKRadarScreen.cpp" "radarscreen/UKRadarScreen.h" - radarscreen/RadarRenderableInterface.cpp radarscreen/ConfigurableDisplayInterface.cpp) + radarscreen/RadarRenderableInterface.cpp radarscreen/ConfigurableDisplayInterface.cpp radarscreen/MenuToggleableDisplayInterface.h radarscreen/ToggleDisplayFromMenu.cpp radarscreen/ToggleDisplayFromMenu.h radarscreen/MenuToggleableDisplayFactory.cpp radarscreen/MenuToggleableDisplayFactory.h) source_group("src\\radarscreen" FILES ${src__radarscreen}) set(src__regional @@ -889,7 +898,9 @@ set(src__wake wake/WakeSchemeCollectionFactory.cpp wake/WakeSchemeCollectionFactory.h wake/WakeCategoryMapperInterface.h - wake/FlightplanWakeCategoryMapper.cpp wake/FlightplanWakeCategoryMapper.h) + wake/FlightplanWakeCategoryMapper.cpp wake/FlightplanWakeCategoryMapper.h + wake/WakeCalculatorDisplay.cpp wake/WakeCalculatorDisplay.h + wake/WakeCalculatorOptions.cpp wake/WakeCalculatorOptions.h wake/LeadWakeCallsignProvider.cpp wake/LeadWakeCallsignProvider.h wake/FollowingWakeCallsignProvider.cpp wake/FollowingWakeCallsignProvider.h wake/WakeSchemeProvider.cpp wake/WakeSchemeProvider.h wake/WakeIntervalFormatter.cpp wake/WakeIntervalFormatter.h) source_group("src\\wake" FILES ${src__wake}) set(ALL_FILES @@ -918,6 +929,7 @@ set(ALL_FILES ${src__initialheading} ${src__integration} ${src__intention} + ${src__list} ${src__login} ${src__message} ${src__metar} diff --git a/src/plugin/aircraft/CallsignSelectionList.cpp b/src/plugin/aircraft/CallsignSelectionList.cpp index e8f220960..cc2bd4138 100644 --- a/src/plugin/aircraft/CallsignSelectionList.cpp +++ b/src/plugin/aircraft/CallsignSelectionList.cpp @@ -1,42 +1,36 @@ #include "CallsignSelectionList.h" #include "CallsignSelectionProviderInterface.h" -#include "euroscope/EuroscopePluginLoopbackInterface.h" -#include "plugin/PopupMenuItem.h" +#include "list/ListItem.h" namespace UKControllerPlugin::Aircraft { - CallsignSelectionList::CallsignSelectionList( - std::shared_ptr callsignProvider, - Euroscope::EuroscopePluginLoopbackInterface& plugin, - int callbackFunctionId) - : callsignProvider(std::move(callsignProvider)), plugin(plugin), callbackFunctionId(callbackFunctionId) + CallsignSelectionList::CallsignSelectionList(std::shared_ptr callsignProvider) + : callsignProvider(std::move(callsignProvider)) { } - void CallsignSelectionList::TriggerList(const POINT& location) + int CallsignSelectionList::ListColumns() { - const auto callsigns = this->callsignProvider->GetCallsigns(); - if (callsigns.empty()) { - return; - } - - plugin.TriggerPopupList(RECT{location.x, location.y, location.x + 50, location.y + 100}, "Select Aircraft", 1); + return 1; + } - Plugin::PopupMenuItem menuItem; - menuItem.secondValue = ""; - menuItem.callbackFunctionId = callbackFunctionId; - menuItem.fixedPosition = false; - menuItem.disabled = false; - menuItem.checked = EuroScopePlugIn::POPUP_ELEMENT_NO_CHECKBOX; + std::string CallsignSelectionList::ListName() + { + return "Select Aircraft"; + } - for (const auto& callsign : callsigns) { - menuItem.firstValue = callsign; - plugin.AddItemToPopupList(menuItem); + std::list> CallsignSelectionList::ListItems() + { + std::list> items; + for (const auto& callsign : this->callsignProvider->GetCallsigns()) { + items.push_back( + std::make_shared(callsign, "", false, false, List::ListItemCheckedStatus::NoCheckbox)); } + return items; } - void CallsignSelectionList::CallsignSelected(const std::string& callsign) + void CallsignSelectionList::ItemSelected(const std::string& item) { - callsignProvider->CallsignSelected(callsign); + this->callsignProvider->CallsignSelected(item); } } // namespace UKControllerPlugin::Aircraft diff --git a/src/plugin/aircraft/CallsignSelectionList.h b/src/plugin/aircraft/CallsignSelectionList.h index 16d93b805..6b4c99600 100644 --- a/src/plugin/aircraft/CallsignSelectionList.h +++ b/src/plugin/aircraft/CallsignSelectionList.h @@ -1,5 +1,5 @@ #pragma once -#include "CallsignSelectionListInterface.h" +#include "list/ListItemProviderInterface.h" namespace UKControllerPlugin::Euroscope { class EuroscopePluginLoopbackInterface; @@ -11,25 +11,18 @@ namespace UKControllerPlugin::Aircraft { /** * Implements a callsign selection list. */ - class CallsignSelectionList : public CallsignSelectionListInterface + class CallsignSelectionList : public List::ListItemProviderInterface { public: - CallsignSelectionList( - std::shared_ptr callsignProvider, - Euroscope::EuroscopePluginLoopbackInterface& plugin, - int callbackFunctionId); + CallsignSelectionList(std::shared_ptr callsignProvider); ~CallsignSelectionList() = default; - void TriggerList(const POINT& location) override; - void CallsignSelected(const std::string& callsign); + auto ListColumns() -> int override; + auto ListName() -> std::string override; + auto ListItems() -> std::list> override; + void ItemSelected(const std::string& item) override; private: // Provides the callsigns std::shared_ptr callsignProvider; - - // The plugin for triggering lists - Euroscope::EuroscopePluginLoopbackInterface& plugin; - - // The callback function - int callbackFunctionId; }; } // namespace UKControllerPlugin::Aircraft diff --git a/src/plugin/aircraft/CallsignSelectionListFactory.cpp b/src/plugin/aircraft/CallsignSelectionListFactory.cpp index 905b1f47f..651d4d634 100644 --- a/src/plugin/aircraft/CallsignSelectionListFactory.cpp +++ b/src/plugin/aircraft/CallsignSelectionListFactory.cpp @@ -1,33 +1,19 @@ #include "CallsignSelectionList.h" #include "CallsignSelectionListFactory.h" #include "CallsignSelectionProviderInterface.h" -#include "euroscope/CallbackFunction.h" -#include "euroscope/EuroscopePluginLoopbackInterface.h" -#include "plugin/FunctionCallEventHandler.h" +#include "list/PopupListFactory.h" namespace UKControllerPlugin::Aircraft { - CallsignSelectionListFactory::CallsignSelectionListFactory( - Plugin::FunctionCallEventHandler& functionHandler, Euroscope::EuroscopePluginLoopbackInterface& plugin) - : functionHandler(functionHandler), plugin(plugin) + CallsignSelectionListFactory::CallsignSelectionListFactory(List::PopupListFactory& listFactory) + : listFactory(listFactory) { } auto CallsignSelectionListFactory::Create( std::shared_ptr provider, const std::string& description) const - -> std::shared_ptr + -> std::shared_ptr { - int callbackId = functionHandler.ReserveNextDynamicFunctionId(); - auto selectionList = std::make_shared(std::move(provider), plugin, callbackId); - - Euroscope::CallbackFunction callback( - callbackId, - description, - [selectionList](int functionId, const std::string& subject, RECT screenObjectArea) { - selectionList->CallsignSelected(subject); - }); - functionHandler.RegisterFunctionCall(callback); - - return selectionList; + return listFactory.Create(std::make_shared(provider), description); } } // namespace UKControllerPlugin::Aircraft diff --git a/src/plugin/aircraft/CallsignSelectionListFactory.h b/src/plugin/aircraft/CallsignSelectionListFactory.h index b4277f371..e5dbb840c 100644 --- a/src/plugin/aircraft/CallsignSelectionListFactory.h +++ b/src/plugin/aircraft/CallsignSelectionListFactory.h @@ -1,13 +1,9 @@ #pragma once -namespace UKControllerPlugin { - namespace Euroscope { - class EuroscopePluginLoopbackInterface; - } // namespace Euroscope - namespace Plugin { - class FunctionCallEventHandler; - } // namespace Plugin -} // namespace UKControllerPlugin +namespace UKControllerPlugin::List { + class PopupListInterface; + class PopupListFactory; +} // namespace UKControllerPlugin::List namespace UKControllerPlugin::Aircraft { class CallsignSelectionProviderInterface; @@ -16,17 +12,13 @@ namespace UKControllerPlugin::Aircraft { class CallsignSelectionListFactory { public: - CallsignSelectionListFactory( - Plugin::FunctionCallEventHandler& functionHandler, Euroscope::EuroscopePluginLoopbackInterface& plugin); + CallsignSelectionListFactory(List::PopupListFactory& listFactory); [[nodiscard]] auto Create(std::shared_ptr provider, const std::string& description) const - -> std::shared_ptr; + -> std::shared_ptr; private: - // For registering the callback function - Plugin::FunctionCallEventHandler& functionHandler; - - // To pass to the list - Euroscope::EuroscopePluginLoopbackInterface& plugin; + // For creating lists + List::PopupListFactory& listFactory; }; } // namespace UKControllerPlugin::Aircraft diff --git a/src/plugin/aircraft/CallsignSelectionListFactoryBootstrap.cpp b/src/plugin/aircraft/CallsignSelectionListFactoryBootstrap.cpp index b5b0315e7..a4e9d8528 100644 --- a/src/plugin/aircraft/CallsignSelectionListFactoryBootstrap.cpp +++ b/src/plugin/aircraft/CallsignSelectionListFactoryBootstrap.cpp @@ -7,6 +7,6 @@ namespace UKControllerPlugin::Aircraft { void BootstrapPlugin(Bootstrap::PersistenceContainer& container) { container.callsignSelectionListFactory = - std::make_unique(*container.pluginFunctionHandlers, *container.plugin); + std::make_unique(*container.popupListFactory); } } // namespace UKControllerPlugin::Aircraft diff --git a/src/plugin/aircraft/CallsignSelectionListInterface.h b/src/plugin/aircraft/CallsignSelectionListInterface.h deleted file mode 100644 index bdc3e6549..000000000 --- a/src/plugin/aircraft/CallsignSelectionListInterface.h +++ /dev/null @@ -1,14 +0,0 @@ -#pragma once - -namespace UKControllerPlugin::Aircraft { - /** - * An interface for classes that trigger a callsign - * selection list. - */ - class CallsignSelectionListInterface - { - public: - virtual ~CallsignSelectionListInterface() = default; - virtual void TriggerList(const POINT& location) = 0; - }; -} // namespace UKControllerPlugin::Aircraft diff --git a/src/plugin/bootstrap/InitialisePlugin.cpp b/src/plugin/bootstrap/InitialisePlugin.cpp index 7e1a5f463..30a1ce474 100644 --- a/src/plugin/bootstrap/InitialisePlugin.cpp +++ b/src/plugin/bootstrap/InitialisePlugin.cpp @@ -25,6 +25,7 @@ #include "initialheading/InitialHeadingModule.h" #include "integration/IntegrationModule.h" #include "intention/IntentionCodeModule.h" +#include "list/PopupListFactoryBootstrap.h" #include "log/LoggerBootstrap.h" #include "login/LoginModule.h" #include "message/UserMessagerBootstrap.h" @@ -186,6 +187,7 @@ namespace UKControllerPlugin { } Integration::BootstrapPlugin(*this->container, duplicatePlugin->Duplicate(), winsockInitialised); + List::BootstrapPlugin(*this->container); Aircraft::BootstrapPlugin(*this->container); // Boostrap all the modules at a plugin level diff --git a/src/plugin/bootstrap/PersistenceContainer.cpp b/src/plugin/bootstrap/PersistenceContainer.cpp index cbc6fc3fa..7570f72e8 100644 --- a/src/plugin/bootstrap/PersistenceContainer.cpp +++ b/src/plugin/bootstrap/PersistenceContainer.cpp @@ -36,6 +36,7 @@ #include "intention/IntentionCodeCache.h" #include "intention/IntentionCodeGenerator.h" #include "intention/SectorExitRepository.h" +#include "list/PopupListFactory.h" #include "login/Login.h" #include "message/UserMessager.h" #include "metar/MetarEventHandlerCollection.h" diff --git a/src/plugin/bootstrap/PersistenceContainer.h b/src/plugin/bootstrap/PersistenceContainer.h index bb607a620..e58bbb190 100644 --- a/src/plugin/bootstrap/PersistenceContainer.h +++ b/src/plugin/bootstrap/PersistenceContainer.h @@ -72,6 +72,9 @@ namespace UKControllerPlugin { namespace InitialAltitude { class InitialAltitudeEventHandler; } // namespace InitialAltitude + namespace List { + class PopupListFactory; + } // namespace List namespace Metar { class MetarEventHandlerCollection; } // namespace Metar @@ -244,5 +247,6 @@ namespace UKControllerPlugin::Bootstrap { // Some factories std::unique_ptr callsignSelectionListFactory; + std::unique_ptr popupListFactory; }; } // namespace UKControllerPlugin::Bootstrap diff --git a/src/plugin/components/TitleBar.cpp b/src/plugin/components/TitleBar.cpp index cfba563c8..b1ceccfa9 100644 --- a/src/plugin/components/TitleBar.cpp +++ b/src/plugin/components/TitleBar.cpp @@ -1,7 +1,7 @@ -#include "components/TitleBar.h" +#include "ClickableArea.h" +#include "TitleBar.h" #include "euroscope/EuroscopeRadarLoopbackInterface.h" #include "graphics/GdiGraphicsInterface.h" -#include "components/ClickableArea.h" namespace UKControllerPlugin::Components { std::shared_ptr TitleBar::Create(std::wstring title, Gdiplus::Rect area) @@ -71,4 +71,20 @@ namespace UKControllerPlugin::Components { TitleBar::TitleBar(std::wstring title, Gdiplus::Rect area) : title(std::move(title)), area(area) { } + + std::shared_ptr TitleBar::WithDefaultBackgroundBrush() + { + return this->WithBackgroundBrush(std::make_shared(Gdiplus::Color(130, 50, 154))); + } + + std::shared_ptr TitleBar::WithDefaultTextBrush() + { + return this->WithTextBrush(std::make_shared(Gdiplus::Color(227, 227, 227))); + } + + std::shared_ptr TitleBar::WithDefaultBorder() + { + return this->WithBorder(std::make_shared(Gdiplus::Color(255, 255, 255))); + } + } // namespace UKControllerPlugin::Components diff --git a/src/plugin/components/TitleBar.h b/src/plugin/components/TitleBar.h index 107c2abcc..3c6464980 100644 --- a/src/plugin/components/TitleBar.h +++ b/src/plugin/components/TitleBar.h @@ -18,9 +18,12 @@ namespace UKControllerPlugin::Components { { public: static std::shared_ptr Create(std::wstring title, Gdiplus::Rect area); + std::shared_ptr WithDefaultBackgroundBrush(); std::shared_ptr WithBackgroundBrush(std::shared_ptr brush); std::shared_ptr WithTextBrush(std::shared_ptr brush); + std::shared_ptr WithDefaultTextBrush(); std::shared_ptr WithBorder(std::shared_ptr pen); + std::shared_ptr WithDefaultBorder(); std::shared_ptr WithDrag(int screenObjectId); std::shared_ptr WithPosition(Gdiplus::Rect area); std::shared_ptr WithTitle(std::wstring title); diff --git a/src/plugin/graphics/FontManager.cpp b/src/plugin/graphics/FontManager.cpp new file mode 100644 index 000000000..1c2d0ccdb --- /dev/null +++ b/src/plugin/graphics/FontManager.cpp @@ -0,0 +1,33 @@ +#include "FontManager.h" + +namespace UKControllerPlugin::Graphics { + + auto FontManager::Instance() -> FontManager& + { + static std::unique_ptr instance; + if (instance == nullptr) { + instance = + std::unique_ptr(new FontManager(std::make_unique(L"EuroScope"))); + } + + return *instance; + } + + auto FontManager::GetDefault() -> const Gdiplus::Font& + { + return this->Get(9); + } + + auto FontManager::Get(int size) -> const Gdiplus::Font& + { + if (this->fonts.count(size) == 0) { + this->fonts[size] = std::make_shared(family.get(), size); + } + + return *this->fonts.at(size); + } + + FontManager::FontManager(std::unique_ptr family) : family(std::move(family)) + { + } +} // namespace UKControllerPlugin::Graphics diff --git a/src/plugin/graphics/FontManager.h b/src/plugin/graphics/FontManager.h new file mode 100644 index 000000000..88585292f --- /dev/null +++ b/src/plugin/graphics/FontManager.h @@ -0,0 +1,26 @@ +#pragma once + +namespace UKControllerPlugin::Graphics { + /** + * Manages font objects for the entire plugin so we dont + * have many duplicates + */ + class FontManager + { + public: + [[nodiscard]] static auto Instance() -> FontManager&; + [[nodiscard]] auto Get(int size) -> const Gdiplus::Font&; + [[nodiscard]] auto GetDefault() -> const Gdiplus::Font&; + + protected: + FontManager(std::unique_ptr family); + static std::unique_ptr instance; + + private: + // The font family + std::unique_ptr family; + + // The fonts + std::map> fonts; + }; +} // namespace UKControllerPlugin::Graphics diff --git a/src/plugin/graphics/GdiGraphicsInterface.h b/src/plugin/graphics/GdiGraphicsInterface.h index e4152ac93..2abd7fc32 100644 --- a/src/plugin/graphics/GdiGraphicsInterface.h +++ b/src/plugin/graphics/GdiGraphicsInterface.h @@ -2,7 +2,7 @@ namespace Gdiplus { class RectF; -} // namespace Gdiplus +} // namespace Gdiplus namespace UKControllerPlugin { namespace Windows { @@ -14,50 +14,40 @@ namespace UKControllerPlugin { class GdiGraphicsInterface { public: - virtual ~GdiGraphicsInterface() = default; - virtual void DrawRect(const Gdiplus::RectF & area, const Gdiplus::Pen & pen) = 0; - virtual void DrawRect(const Gdiplus::Rect & area, const Gdiplus::Pen & pen) = 0; - virtual void DrawRect(const RECT & area, const Gdiplus::Pen & pen) = 0; - virtual void DrawCircle(const Gdiplus::RectF & area, const Gdiplus::Pen & pen) = 0; - virtual void FillCircle(const Gdiplus::RectF& area, const Gdiplus::Brush& brush) = 0; - virtual void DrawCircle(const Gdiplus::Rect & area, const Gdiplus::Pen & pen) = 0; - virtual void DrawDiamond(const Gdiplus::RectF & area, const Gdiplus::Pen & pen) = 0; - virtual void FillDiamond(const Gdiplus::RectF& area, const Gdiplus::Brush& brush) = 0; - virtual void DrawLine( - const Gdiplus::Pen & pen, - const Gdiplus::Point & start, - const Gdiplus::Point & end - ) = 0; - virtual void DrawLine( - const Gdiplus::Pen& pen, - const Gdiplus::PointF& start, - const Gdiplus::PointF& end - ) = 0; - virtual void DrawPath(const Gdiplus::GraphicsPath & path, const Gdiplus::Pen & pen) = 0; - virtual void DrawString( - std::wstring text, - const Gdiplus::RectF & area, - const Gdiplus::Brush & brush - ) = 0; - virtual void DrawString( - std::wstring text, - const Gdiplus::Rect & area, - const Gdiplus::Brush & brush - ) = 0; - virtual void DrawString(std::wstring text, const RECT & area, const Gdiplus::Brush & brush) = 0; - virtual void FillRect(const Gdiplus::RectF & area, const Gdiplus::Brush & brush) = 0; - virtual void FillRect(const Gdiplus::Rect & area, const Gdiplus::Brush & brush) = 0; - virtual void FillRect(const RECT & area, const Gdiplus::Brush & brush) = 0; - virtual void SetAntialias(bool setting) = 0; - virtual void SetDeviceHandle(HDC & handle) = 0; - virtual void Clipped(Gdiplus::Region& clipRegion, std::function drawFunction) = 0; - virtual void Translated(Gdiplus::REAL x, Gdiplus::REAL y, std::function drawFunction) = 0; - virtual std::shared_ptr GetTransform() = 0; - virtual Gdiplus::RectF GetClipBounds() = 0; - virtual void Scaled(Gdiplus::REAL x, Gdiplus::REAL y, std::function drawFunction) = 0; - virtual void Rotated(Gdiplus::REAL angle, std::function drawFunction) = 0; - virtual void FillPolygon(Gdiplus::Point* points, const Gdiplus::Brush& brush, int numPoints) = 0; - + virtual ~GdiGraphicsInterface() = default; + virtual void DrawRect(const Gdiplus::RectF& area, const Gdiplus::Pen& pen) = 0; + virtual void DrawRect(const Gdiplus::Rect& area, const Gdiplus::Pen& pen) = 0; + virtual void DrawRect(const RECT& area, const Gdiplus::Pen& pen) = 0; + virtual void DrawCircle(const Gdiplus::RectF& area, const Gdiplus::Pen& pen) = 0; + virtual void FillCircle(const Gdiplus::RectF& area, const Gdiplus::Brush& brush) = 0; + virtual void DrawCircle(const Gdiplus::Rect& area, const Gdiplus::Pen& pen) = 0; + virtual void DrawDiamond(const Gdiplus::RectF& area, const Gdiplus::Pen& pen) = 0; + virtual void FillDiamond(const Gdiplus::RectF& area, const Gdiplus::Brush& brush) = 0; + virtual void DrawLine(const Gdiplus::Pen& pen, const Gdiplus::Point& start, const Gdiplus::Point& end) = 0; + virtual void + DrawLine(const Gdiplus::Pen& pen, const Gdiplus::PointF& start, const Gdiplus::PointF& end) = 0; + virtual void DrawPath(const Gdiplus::GraphicsPath& path, const Gdiplus::Pen& pen) = 0; + virtual void DrawString(std::wstring text, const Gdiplus::RectF& area, const Gdiplus::Brush& brush) = 0; + virtual void DrawString(std::wstring text, const Gdiplus::Rect& area, const Gdiplus::Brush& brush) = 0; + virtual void DrawString( + const std::wstring& text, + const Gdiplus::Rect& area, + const Gdiplus::Brush& brush, + const Gdiplus::StringFormat& format, + const Gdiplus::Font& font) = 0; + virtual void DrawString(std::wstring text, const RECT& area, const Gdiplus::Brush& brush) = 0; + virtual void FillRect(const Gdiplus::RectF& area, const Gdiplus::Brush& brush) = 0; + virtual void FillRect(const Gdiplus::Rect& area, const Gdiplus::Brush& brush) = 0; + virtual void FillRect(const RECT& area, const Gdiplus::Brush& brush) = 0; + virtual void SetAntialias(bool setting) = 0; + virtual void SetDeviceHandle(HDC& handle) = 0; + virtual void Clipped(Gdiplus::Region& clipRegion, std::function drawFunction) = 0; + virtual void Translated(Gdiplus::REAL x, Gdiplus::REAL y, std::function drawFunction) = 0; + virtual std::shared_ptr GetTransform() = 0; + virtual Gdiplus::RectF GetClipBounds() = 0; + virtual void Scaled(Gdiplus::REAL x, Gdiplus::REAL y, std::function drawFunction) = 0; + virtual void Rotated(Gdiplus::REAL angle, std::function drawFunction) = 0; + virtual void FillPolygon(Gdiplus::Point* points, const Gdiplus::Brush& brush, int numPoints) = 0; }; - } // namespace Windows -} // namespace UKControllerPlugin + } // namespace Windows +} // namespace UKControllerPlugin diff --git a/src/plugin/graphics/GdiGraphicsWrapper.cpp b/src/plugin/graphics/GdiGraphicsWrapper.cpp index 0d89241ef..ea28836d9 100644 --- a/src/plugin/graphics/GdiGraphicsWrapper.cpp +++ b/src/plugin/graphics/GdiGraphicsWrapper.cpp @@ -1,28 +1,13 @@ +#include "FontManager.h" #include "GdiGraphicsWrapper.h" +#include "StringFormatManager.h" + +using UKControllerPlugin::Graphics::FontManager; +using UKControllerPlugin::Graphics::StringFormatManager; namespace UKControllerPlugin { namespace Windows { - GdiGraphicsWrapper::GdiGraphicsWrapper(void) - { - this->stringFormat = - std::make_unique(Gdiplus::StringFormatFlags::StringFormatFlagsNoClip); - this->stringFormat->SetAlignment(Gdiplus::StringAlignment::StringAlignmentCenter); - this->stringFormat->SetLineAlignment(Gdiplus::StringAlignment::StringAlignmentCenter); - this->font = - CreateFont(13, '\0', '\0', '\0', 400, '\0', '\0', '\0', '\x1', '\0', '\0', '\0', '\0', L"EuroScope"); - } - - GdiGraphicsWrapper::GdiGraphicsWrapper(std::unique_ptr graphics) - : graphics(std::move(graphics)) - { - } - - GdiGraphicsWrapper::~GdiGraphicsWrapper(void) - { - DeleteObject(this->font); - } - /* Draws the outline of a rectangle to the screen. */ @@ -101,7 +86,12 @@ namespace UKControllerPlugin { void GdiGraphicsWrapper::DrawString(std::wstring text, const Gdiplus::RectF& area, const Gdiplus::Brush& brush) { api->DrawString( - text.c_str(), text.size(), this->euroscopeFont.get(), area, this->stringFormat.get(), &brush); + text.c_str(), + text.size(), + &FontManager::Instance().GetDefault(), + area, + &StringFormatManager::Instance().GetCentreAlign(), + &brush); } /* @@ -109,14 +99,12 @@ namespace UKControllerPlugin { */ void GdiGraphicsWrapper::DrawString(std::wstring text, const Gdiplus::Rect& area, const Gdiplus::Brush& brush) { - Gdiplus::RectF areaFloat = { - static_cast(area.X), - static_cast(area.Y), - static_cast(area.Width), - static_cast(area.Height)}; - - api->DrawString( - text.c_str(), text.size(), this->euroscopeFont.get(), areaFloat, this->stringFormat.get(), &brush); + this->DrawString( + text, + area, + brush, + StringFormatManager::Instance().GetCentreAlign(), + FontManager::Instance().GetDefault()); } /* @@ -131,7 +119,12 @@ namespace UKControllerPlugin { static_cast(area.bottom - area.top)}; api->DrawString( - text.c_str(), text.size(), this->euroscopeFont.get(), areaFloat, this->stringFormat.get(), &brush); + text.c_str(), + text.size(), + &FontManager::Instance().GetDefault(), + areaFloat, + &StringFormatManager::Instance().GetCentreAlign(), + &brush); } /* @@ -172,8 +165,7 @@ namespace UKControllerPlugin { */ void GdiGraphicsWrapper::SetDeviceHandle(HDC& handle) { - this->api.reset(new Gdiplus::Graphics(handle)); - this->euroscopeFont.reset(new Gdiplus::Font(handle, font)); + this->api.reset(Gdiplus::Graphics::FromHDC(handle)); } void GdiGraphicsWrapper::Clipped(Gdiplus::Region& clipRegion, std::function drawFunction) @@ -241,5 +233,21 @@ namespace UKControllerPlugin { Gdiplus::PointF(area.X + (area.Width / 2), area.Y)}; api->FillPolygon(&brush, points, 4); } + + void GdiGraphicsWrapper::DrawString( + const std::wstring& text, + const Gdiplus::Rect& area, + const Gdiplus::Brush& brush, + const Gdiplus::StringFormat& format, + const Gdiplus::Font& font) + { + Gdiplus::RectF areaFloat = { + static_cast(area.X), + static_cast(area.Y), + static_cast(area.Width), + static_cast(area.Height)}; + + api->DrawString(text.c_str(), text.size(), &font, areaFloat, &format, &brush); + } } // namespace Windows } // namespace UKControllerPlugin diff --git a/src/plugin/graphics/GdiGraphicsWrapper.h b/src/plugin/graphics/GdiGraphicsWrapper.h index ba70c65b5..8e19d95b5 100644 --- a/src/plugin/graphics/GdiGraphicsWrapper.h +++ b/src/plugin/graphics/GdiGraphicsWrapper.h @@ -4,7 +4,7 @@ // Forward declare namespace Gdiplus { class Graphics; -} // namespace Gdiplus +} // namespace Gdiplus // END namespace UKControllerPlugin { @@ -16,56 +16,41 @@ namespace UKControllerPlugin { class GdiGraphicsWrapper : public GdiGraphicsInterface { public: - GdiGraphicsWrapper(void); - explicit GdiGraphicsWrapper(std::unique_ptr graphics); - ~GdiGraphicsWrapper(void); - void DrawRect(const Gdiplus::RectF & area, const Gdiplus::Pen & pen) override; - void DrawRect(const Gdiplus::Rect & area, const Gdiplus::Pen & pen) override; - void DrawRect(const RECT & area, const Gdiplus::Pen & pen) override; - void DrawCircle(const Gdiplus::RectF & area, const Gdiplus::Pen & pen) override; - void DrawCircle(const Gdiplus::Rect & area, const Gdiplus::Pen & pen) override; - void DrawDiamond(const Gdiplus::RectF & area, const Gdiplus::Pen & pen) override; - void DrawLine( - const Gdiplus::Pen & pen, - const Gdiplus::Point & start, - const Gdiplus::Point & end - ) override; - void DrawLine( - const Gdiplus::Pen& pen, - const Gdiplus::PointF& start, - const Gdiplus::PointF& end - ) override; - void DrawPath(const Gdiplus::GraphicsPath & path, const Gdiplus::Pen & pen) override; - void DrawString(std::wstring text, const Gdiplus::RectF & area, const Gdiplus::Brush & brush) override; - void DrawString(std::wstring text, const Gdiplus::Rect & area, const Gdiplus::Brush & brush) override; - void DrawString(std::wstring text, const RECT & area, const Gdiplus::Brush & brush) override; - void FillRect(const Gdiplus::RectF & area, const Gdiplus::Brush & brush) override; - void FillRect(const Gdiplus::Rect & area, const Gdiplus::Brush & brush) override; - void FillRect(const RECT & area, const Gdiplus::Brush & brush) override; - void SetAntialias(bool setting) override; - void SetDeviceHandle(HDC & handle) override; - void Clipped(Gdiplus::Region& clipRegion, std::function drawFunction) override; - void Translated(Gdiplus::REAL x, Gdiplus::REAL y, std::function drawFunction) override; - void Scaled(Gdiplus::REAL x, Gdiplus::REAL y, std::function drawFunction) override; - std::shared_ptr GetTransform() override; - Gdiplus::RectF GetClipBounds() override; - void Rotated(Gdiplus::REAL angle, std::function drawFunction) override; - void FillPolygon(Gdiplus::Point* points, const Gdiplus::Brush& brush, int numPoints) override; - void FillCircle(const Gdiplus::RectF& area, const Gdiplus::Brush& brush) override; - void FillDiamond(const Gdiplus::RectF& area, const Gdiplus::Brush& brush) override; + void DrawRect(const Gdiplus::RectF& area, const Gdiplus::Pen& pen) override; + void DrawRect(const Gdiplus::Rect& area, const Gdiplus::Pen& pen) override; + void DrawRect(const RECT& area, const Gdiplus::Pen& pen) override; + void DrawCircle(const Gdiplus::RectF& area, const Gdiplus::Pen& pen) override; + void DrawCircle(const Gdiplus::Rect& area, const Gdiplus::Pen& pen) override; + void DrawDiamond(const Gdiplus::RectF& area, const Gdiplus::Pen& pen) override; + void DrawLine(const Gdiplus::Pen& pen, const Gdiplus::Point& start, const Gdiplus::Point& end) override; + void DrawLine(const Gdiplus::Pen& pen, const Gdiplus::PointF& start, const Gdiplus::PointF& end) override; + void DrawPath(const Gdiplus::GraphicsPath& path, const Gdiplus::Pen& pen) override; + void DrawString(std::wstring text, const Gdiplus::RectF& area, const Gdiplus::Brush& brush) override; + void DrawString(std::wstring text, const Gdiplus::Rect& area, const Gdiplus::Brush& brush) override; + void DrawString( + const std::wstring& text, + const Gdiplus::Rect& area, + const Gdiplus::Brush& brush, + const Gdiplus::StringFormat& format, + const Gdiplus::Font& font) override; + void DrawString(std::wstring text, const RECT& area, const Gdiplus::Brush& brush) override; + void FillRect(const Gdiplus::RectF& area, const Gdiplus::Brush& brush) override; + void FillRect(const Gdiplus::Rect& area, const Gdiplus::Brush& brush) override; + void FillRect(const RECT& area, const Gdiplus::Brush& brush) override; + void SetAntialias(bool setting) override; + void SetDeviceHandle(HDC& handle) override; + void Clipped(Gdiplus::Region& clipRegion, std::function drawFunction) override; + void Translated(Gdiplus::REAL x, Gdiplus::REAL y, std::function drawFunction) override; + void Scaled(Gdiplus::REAL x, Gdiplus::REAL y, std::function drawFunction) override; + std::shared_ptr GetTransform() override; + Gdiplus::RectF GetClipBounds() override; + void Rotated(Gdiplus::REAL angle, std::function drawFunction) override; + void FillPolygon(Gdiplus::Point* points, const Gdiplus::Brush& brush, int numPoints) override; + void FillCircle(const Gdiplus::RectF& area, const Gdiplus::Brush& brush) override; + void FillDiamond(const Gdiplus::RectF& area, const Gdiplus::Brush& brush) override; private: - std::unique_ptr api; - - // The Gdiplus font - std::unique_ptr euroscopeFont; - - // String format - std::unique_ptr stringFormat; - - // The font to use - raw Windows - HFONT font; - std::unique_ptr graphics; + std::unique_ptr api; }; - } // namespace Windows -} // namespace UKControllerPlugin + } // namespace Windows +} // namespace UKControllerPlugin diff --git a/src/plugin/graphics/StringFormatManager.cpp b/src/plugin/graphics/StringFormatManager.cpp new file mode 100644 index 000000000..3e8ce5c79 --- /dev/null +++ b/src/plugin/graphics/StringFormatManager.cpp @@ -0,0 +1,40 @@ +#include "StringFormatManager.h" + +namespace UKControllerPlugin::Graphics { + + StringFormatManager::StringFormatManager() + { + } + + auto StringFormatManager::Instance() -> StringFormatManager& + { + static std::unique_ptr instance; + if (instance == nullptr) { + instance = std::unique_ptr(new StringFormatManager); + } + + return *instance; + } + + auto StringFormatManager::GetAligned(int id, Gdiplus::StringAlignment textAlign) -> const Gdiplus::StringFormat& + { + if (this->formats.count(id) == 0) { + this->formats[id] = + std::make_shared(Gdiplus::StringFormatFlags::StringFormatFlagsNoClip); + this->formats.at(id)->SetAlignment(textAlign); + this->formats.at(id)->SetLineAlignment(Gdiplus::StringAlignment::StringAlignmentCenter); + } + + return *this->formats.at(id); + } + + auto StringFormatManager::GetCentreAlign() -> const Gdiplus::StringFormat& + { + return this->GetAligned(1, Gdiplus::StringAlignment::StringAlignmentCenter); + } + + auto StringFormatManager::GetLeftAlign() -> const Gdiplus::StringFormat& + { + return this->GetAligned(2, Gdiplus::StringAlignment::StringAlignmentNear); + } +} // namespace UKControllerPlugin::Graphics diff --git a/src/plugin/graphics/StringFormatManager.h b/src/plugin/graphics/StringFormatManager.h new file mode 100644 index 000000000..4c08c1c85 --- /dev/null +++ b/src/plugin/graphics/StringFormatManager.h @@ -0,0 +1,23 @@ +#pragma once + +namespace UKControllerPlugin::Graphics { + /** + * Stores string formats + */ + class StringFormatManager + { + public: + [[nodiscard]] static auto Instance() -> StringFormatManager&; + [[nodiscard]] auto GetCentreAlign() -> const Gdiplus::StringFormat&; + [[nodiscard]] auto GetLeftAlign() -> const Gdiplus::StringFormat&; + + protected: + StringFormatManager(); + auto GetAligned(int id, Gdiplus::StringAlignment alignment) -> const Gdiplus::StringFormat&; + static std::unique_ptr instance; + + private: + // The fonts + std::map> formats; + }; +} // namespace UKControllerPlugin::Graphics diff --git a/src/plugin/hold/HoldDisplay.cpp b/src/plugin/hold/HoldDisplay.cpp index d961b77cf..1654d079f 100644 --- a/src/plugin/hold/HoldDisplay.cpp +++ b/src/plugin/hold/HoldDisplay.cpp @@ -7,7 +7,6 @@ #include "HoldingAircraft.h" #include "HoldingData.h" #include "PublishedHoldCollection.h" -#include "aircraft/CallsignSelectionListInterface.h" #include "dialog/DialogManager.h" #include "euroscope/EuroScopeCFlightPlanInterface.h" #include "euroscope/EuroScopeCRadarTargetInterface.h" @@ -15,6 +14,7 @@ #include "euroscope/EuroscopeRadarLoopbackInterface.h" #include "euroscope/UserSetting.h" #include "graphics/GdiGraphicsInterface.h" +#include "list/PopupListInterface.h" #include "navaids/Navaid.h" using UKControllerPlugin::Dialog::DialogManager; @@ -34,7 +34,7 @@ namespace UKControllerPlugin { const Navaids::Navaid& navaid, const PublishedHoldCollection& publishedHoldCollection, const DialogManager& dialogManager, - std::shared_ptr addAircraftSelector) + std::shared_ptr addAircraftSelector) : navaid(navaid), publishedHolds(publishedHoldCollection.GetForFix(navaid.identifier)), holdManager(holdManager), plugin(plugin), dialogManager(dialogManager), publishedHoldCollection(publishedHoldCollection), addAircraftSelector(addAircraftSelector), @@ -113,7 +113,7 @@ namespace UKControllerPlugin { void HoldDisplay::ButtonRightClicked(const std::string& button) { if (button == "add") { - addAircraftSelector->TriggerList({this->addButtonClickRect.left, this->addButtonClickRect.top}); + addAircraftSelector->Trigger({this->addButtonClickRect.left, this->addButtonClickRect.top}); } } diff --git a/src/plugin/hold/HoldDisplay.h b/src/plugin/hold/HoldDisplay.h index 545b27677..fe8a01441 100644 --- a/src/plugin/hold/HoldDisplay.h +++ b/src/plugin/hold/HoldDisplay.h @@ -13,6 +13,9 @@ namespace UKControllerPlugin { class EuroscopeRadarLoopbackInterface; class UserSetting; } // namespace Euroscope + namespace List { + class PopupListInterface; + } // namespace List namespace Navaids { struct Navaid; } // namespace Navaids @@ -39,7 +42,7 @@ namespace UKControllerPlugin { const Navaids::Navaid& navaid, const PublishedHoldCollection& publishedHoldCollection, const Dialog::DialogManager& dialogManager, - std::shared_ptr addAircraftSelector); + std::shared_ptr addAircraftSelector); void ButtonClicked(std::string button); void ButtonRightClicked(const std::string& button); void CallsignClicked( @@ -164,7 +167,7 @@ namespace UKControllerPlugin { const PublishedHoldCollection& publishedHoldCollection; // For selecting aircraft to add to the hold - std::shared_ptr addAircraftSelector; + std::shared_ptr addAircraftSelector; // Brushes const Gdiplus::SolidBrush titleBarTextBrush; diff --git a/src/plugin/list/ListItem.h b/src/plugin/list/ListItem.h new file mode 100644 index 000000000..e7d0bd989 --- /dev/null +++ b/src/plugin/list/ListItem.h @@ -0,0 +1,33 @@ +#pragma once +#include "ListItemCheckedStatus.h" + +namespace UKControllerPlugin::List { + using ListItem = struct ListItem + { + ListItem( + std::string firstColumn, + std::string secondColumn, + bool disabled, + bool fixedPosition, + ListItemCheckedStatus checked) + : firstColumn(std::move(firstColumn)), secondColumn(std::move(secondColumn)), disabled(disabled), + fixedPosition(fixedPosition), checked(checked) + { + } + + // First column value, used in the callback if selected + std::string firstColumn; + + // Seconc column value, not required + std::string secondColumn; + + // Is the item greyed out + bool disabled; + + // Does the item have a fixed position + bool fixedPosition; + + // Checkbox status + ListItemCheckedStatus checked; + }; +} // namespace UKControllerPlugin::List diff --git a/src/plugin/list/ListItemCheckedStatus.h b/src/plugin/list/ListItemCheckedStatus.h new file mode 100644 index 000000000..6e25d88c6 --- /dev/null +++ b/src/plugin/list/ListItemCheckedStatus.h @@ -0,0 +1,13 @@ +#pragma once + +namespace UKControllerPlugin::List { + /** + * Helpful enum class around the ES list statuses. + */ + enum class ListItemCheckedStatus : int + { + NoCheckbox = EuroScopePlugIn::POPUP_ELEMENT_NO_CHECKBOX, + NotChecked = EuroScopePlugIn::POPUP_ELEMENT_UNCHECKED, + Checked = EuroScopePlugIn::POPUP_ELEMENT_CHECKED, + }; +} // namespace UKControllerPlugin::List diff --git a/src/plugin/list/ListItemProviderInterface.h b/src/plugin/list/ListItemProviderInterface.h new file mode 100644 index 000000000..aebb26fa9 --- /dev/null +++ b/src/plugin/list/ListItemProviderInterface.h @@ -0,0 +1,18 @@ +#pragma once + +namespace UKControllerPlugin::List { + struct ListItem; + + /** + * An interface for a class that provides items for a popup list. + */ + class ListItemProviderInterface + { + public: + virtual ~ListItemProviderInterface() = default; + [[nodiscard]] virtual auto ListColumns() -> int = 0; + [[nodiscard]] virtual auto ListName() -> std::string = 0; + [[nodiscard]] virtual auto ListItems() -> std::list> = 0; + virtual void ItemSelected(const std::string& item) = 0; + }; +} // namespace UKControllerPlugin::List diff --git a/src/plugin/list/PopupList.cpp b/src/plugin/list/PopupList.cpp new file mode 100644 index 000000000..14f307290 --- /dev/null +++ b/src/plugin/list/PopupList.cpp @@ -0,0 +1,43 @@ +#include "ListItem.h" +#include "ListItemProviderInterface.h" +#include "PopupList.h" +#include "euroscope/EuroscopePluginLoopbackInterface.h" +#include "plugin/PopupMenuItem.h" + +using UKControllerPlugin::Plugin::PopupMenuItem; + +namespace UKControllerPlugin::List { + + PopupList::PopupList( + std::shared_ptr itemProvider, + Euroscope::EuroscopePluginLoopbackInterface& plugin, + int callbackId) + : itemProvider(std::move(itemProvider)), plugin(plugin), callbackId(callbackId) + { + } + + void PopupList::Trigger(const POINT& location) + { + const auto items = itemProvider->ListItems(); + if (items.empty()) { + return; + } + + this->plugin.TriggerPopupList( + {location.x, location.y, location.x + 200, location.y + 400}, + itemProvider->ListName(), + itemProvider->ListColumns()); + + for (const auto& item : items) { + PopupMenuItem popupItem; + popupItem.firstValue = item->firstColumn; + popupItem.secondValue = item->secondColumn; + popupItem.checked = static_cast(item->checked); + popupItem.disabled = item->disabled; + popupItem.fixedPosition = item->fixedPosition; + popupItem.callbackFunctionId = callbackId; + + this->plugin.AddItemToPopupList(popupItem); + } + } +} // namespace UKControllerPlugin::List diff --git a/src/plugin/list/PopupList.h b/src/plugin/list/PopupList.h new file mode 100644 index 000000000..0061dd53a --- /dev/null +++ b/src/plugin/list/PopupList.h @@ -0,0 +1,33 @@ +#pragma once +#include "PopupListInterface.h" + +namespace UKControllerPlugin::Euroscope { + class EuroscopePluginLoopbackInterface; +} // namespace UKControllerPlugin::Euroscope + +namespace UKControllerPlugin::List { + class ListItemProviderInterface; + + /* + * A class that wraps EuroScopes popup list. + */ + class PopupList : public PopupListInterface + { + public: + PopupList( + std::shared_ptr itemProvider, + Euroscope::EuroscopePluginLoopbackInterface& plugin, + int callbackId); + void Trigger(const POINT& location) override; + + private: + // Provider of list data + std::shared_ptr itemProvider; + + // Plugin for triggering the list + Euroscope::EuroscopePluginLoopbackInterface& plugin; + + // The callback id for this list when an item is selected + int callbackId; + }; +} // namespace UKControllerPlugin::List diff --git a/src/plugin/list/PopupListFactory.cpp b/src/plugin/list/PopupListFactory.cpp new file mode 100644 index 000000000..551f8a7f3 --- /dev/null +++ b/src/plugin/list/PopupListFactory.cpp @@ -0,0 +1,28 @@ +#include "ListItemProviderInterface.h" +#include "PopupList.h" +#include "PopupListFactory.h" +#include "plugin/FunctionCallEventHandler.h" + +namespace UKControllerPlugin::List { + PopupListFactory::PopupListFactory( + Plugin::FunctionCallEventHandler& functionHandler, Euroscope::EuroscopePluginLoopbackInterface& plugin) + : functionHandler(functionHandler), plugin(plugin) + { + } + + auto + PopupListFactory::Create(std::shared_ptr provider, const std::string& description) const + -> std::shared_ptr + { + const auto callbackId = functionHandler.ReserveNextDynamicFunctionId(); + auto selectionList = std::make_shared(provider, plugin, callbackId); + + Euroscope::CallbackFunction callback( + callbackId, description, [provider](int functionId, const std::string& subject, RECT screenObjectArea) { + provider->ItemSelected(subject); + }); + functionHandler.RegisterFunctionCall(callback); + + return selectionList; + } +} // namespace UKControllerPlugin::List diff --git a/src/plugin/list/PopupListFactory.h b/src/plugin/list/PopupListFactory.h new file mode 100644 index 000000000..234da1f78 --- /dev/null +++ b/src/plugin/list/PopupListFactory.h @@ -0,0 +1,35 @@ +#pragma once + +namespace UKControllerPlugin { + namespace Euroscope { + class EuroscopePluginLoopbackInterface; + } // namespace Euroscope + namespace Plugin { + class FunctionCallEventHandler; + } // namespace Plugin +} // namespace UKControllerPlugin + +namespace UKControllerPlugin::List { + class PopupListInterface; + class ListItemProviderInterface; + + /** + * For building popup lists. + */ + class PopupListFactory + { + public: + PopupListFactory( + Plugin::FunctionCallEventHandler& functionHandler, Euroscope::EuroscopePluginLoopbackInterface& plugin); + [[nodiscard]] auto + Create(std::shared_ptr provider, const std::string& description) const + -> std::shared_ptr; + + private: + // For registering the callback function + Plugin::FunctionCallEventHandler& functionHandler; + + // To pass to the list + Euroscope::EuroscopePluginLoopbackInterface& plugin; + }; +} // namespace UKControllerPlugin::List diff --git a/src/plugin/list/PopupListFactoryBootstrap.cpp b/src/plugin/list/PopupListFactoryBootstrap.cpp new file mode 100644 index 000000000..432745ed9 --- /dev/null +++ b/src/plugin/list/PopupListFactoryBootstrap.cpp @@ -0,0 +1,13 @@ +#include "PopupListFactory.h" +#include "PopupListFactoryBootstrap.h" +#include "bootstrap/PersistenceContainer.h" +#include "plugin/UKPlugin.h" + +namespace UKControllerPlugin::List { + + void BootstrapPlugin(Bootstrap::PersistenceContainer& container) + { + container.popupListFactory = + std::make_unique(*container.pluginFunctionHandlers, *container.plugin); + } +} // namespace UKControllerPlugin::List diff --git a/src/plugin/list/PopupListFactoryBootstrap.h b/src/plugin/list/PopupListFactoryBootstrap.h new file mode 100644 index 000000000..a33caba47 --- /dev/null +++ b/src/plugin/list/PopupListFactoryBootstrap.h @@ -0,0 +1,9 @@ +#pragma once + +namespace UKControllerPlugin::Bootstrap { + struct PersistenceContainer; +} // namespace UKControllerPlugin::Bootstrap + +namespace UKControllerPlugin::List { + void BootstrapPlugin(Bootstrap::PersistenceContainer& container); +} // namespace UKControllerPlugin::List diff --git a/src/plugin/list/PopupListInterface.h b/src/plugin/list/PopupListInterface.h new file mode 100644 index 000000000..d85adb3b3 --- /dev/null +++ b/src/plugin/list/PopupListInterface.h @@ -0,0 +1,13 @@ +#pragma once + +namespace UKControllerPlugin::List { + /** + * Interface for a class wrapping EuroScopes popup list. + */ + class PopupListInterface + { + public: + virtual ~PopupListInterface() = default; + virtual void Trigger(const POINT& location) = 0; + }; +} // namespace UKControllerPlugin::List diff --git a/src/plugin/radarscreen/MenuToggleableDisplayFactory.cpp b/src/plugin/radarscreen/MenuToggleableDisplayFactory.cpp new file mode 100644 index 000000000..9b220911a --- /dev/null +++ b/src/plugin/radarscreen/MenuToggleableDisplayFactory.cpp @@ -0,0 +1,26 @@ +#include "radarscreen/ConfigurableDisplayCollection.h" +#include "MenuToggleableDisplayFactory.h" +#include "ToggleDisplayFromMenu.h" +#include "plugin/FunctionCallEventHandler.h" + +namespace UKControllerPlugin::RadarScreen { + + MenuToggleableDisplayFactory::MenuToggleableDisplayFactory( + Plugin::FunctionCallEventHandler& callbacks, ConfigurableDisplayCollection& configurables) + : callbacks(callbacks), configurables(configurables) + { + } + + void MenuToggleableDisplayFactory::RegisterDisplay( + std::shared_ptr display, std::string description) const + { + const int callbackId = callbacks.ReserveNextDynamicFunctionId(); + auto toggle = std::make_shared(display, callbackId); + + callbacks.RegisterFunctionCall(Euroscope::CallbackFunction( + callbackId, description, [toggle](int functionId, const std::string& subject, RECT screenObjectArea) { + toggle->Configure(functionId, subject, screenObjectArea); + })); + configurables.RegisterDisplay(toggle); + } +} // namespace UKControllerPlugin::RadarScreen diff --git a/src/plugin/radarscreen/MenuToggleableDisplayFactory.h b/src/plugin/radarscreen/MenuToggleableDisplayFactory.h new file mode 100644 index 000000000..82e6f463a --- /dev/null +++ b/src/plugin/radarscreen/MenuToggleableDisplayFactory.h @@ -0,0 +1,25 @@ +#pragma once + +namespace UKControllerPlugin::Plugin { + class FunctionCallEventHandler; +} // namespace UKControllerPlugin::Plugin + +namespace UKControllerPlugin::RadarScreen { + class ConfigurableDisplayCollection; + class MenuToggleableDisplayInterface; + + class MenuToggleableDisplayFactory + { + public: + MenuToggleableDisplayFactory( + Plugin::FunctionCallEventHandler& callbacks, ConfigurableDisplayCollection& configurables); + void RegisterDisplay(std::shared_ptr display, std::string description) const; + + private: + // For registering the callback + Plugin::FunctionCallEventHandler& callbacks; + + // For registering the configuration item + ConfigurableDisplayCollection& configurables; + }; +} // namespace UKControllerPlugin::RadarScreen diff --git a/src/plugin/radarscreen/MenuToggleableDisplayInterface.h b/src/plugin/radarscreen/MenuToggleableDisplayInterface.h new file mode 100644 index 000000000..ab70b59de --- /dev/null +++ b/src/plugin/radarscreen/MenuToggleableDisplayInterface.h @@ -0,0 +1,16 @@ +#pragma once + +namespace UKControllerPlugin::RadarScreen { + /** + * An interface for classes that want to have a visibility toggle + * in the configuration menu. + */ + class MenuToggleableDisplayInterface + { + public: + virtual ~MenuToggleableDisplayInterface() = default; + [[nodiscard]] virtual auto MenuItem() const -> std::string = 0; + [[nodiscard]] virtual auto IsVisible() const -> bool = 0; + virtual void Toggle() = 0; + }; +} // namespace UKControllerPlugin::RadarScreen diff --git a/src/plugin/radarscreen/RadarScreenFactory.cpp b/src/plugin/radarscreen/RadarScreenFactory.cpp index aa8d0a190..25ce29efe 100644 --- a/src/plugin/radarscreen/RadarScreenFactory.cpp +++ b/src/plugin/radarscreen/RadarScreenFactory.cpp @@ -1,4 +1,5 @@ #include "ConfigurableDisplayCollection.h" +#include "MenuToggleableDisplayFactory.h" #include "PositionResetCommand.h" #include "RadarScreenFactory.h" #include "ScreenControlsBootstrap.h" @@ -21,6 +22,7 @@ #include "releases/ReleaseModule.h" #include "sectorfile/SectorFileBootstrap.h" #include "srd/SrdModule.h" +#include "wake/WakeModule.h" using UKControllerPlugin::Bootstrap::HelperBootstrap; using UKControllerPlugin::Bootstrap::PersistenceContainer; @@ -52,6 +54,7 @@ namespace UKControllerPlugin::RadarScreen { AsrEventHandlerCollection userSettingHandlers; ConfigurableDisplayCollection configurableDisplays; CommandHandlerCollection commandHandlers; + MenuToggleableDisplayFactory displayFactory(*this->persistence.pluginFunctionHandlers, configurableDisplays); // Run bootstrap HelperBootstrap::BootstrapApiConfigurationItem(persistence, configurableDisplays); @@ -107,6 +110,7 @@ namespace UKControllerPlugin::RadarScreen { PrenoteModule::BootstrapRadarScreen(this->persistence, renderers); Departure::BootstrapRadarScreen(this->persistence, renderers, configurableDisplays, userSettingHandlers); MissedApproach::BootstrapRadarScreen(this->persistence, renderers, configurableDisplays, userSettingHandlers); + Wake::BootstrapRadarScreen(this->persistence, renderers, userSettingHandlers, displayFactory); // Register command for position resets this->persistence.commandHandlers->RegisterHandler(std::make_shared(renderers)); diff --git a/src/plugin/radarscreen/ToggleDisplayFromMenu.cpp b/src/plugin/radarscreen/ToggleDisplayFromMenu.cpp new file mode 100644 index 000000000..4c04bbcb5 --- /dev/null +++ b/src/plugin/radarscreen/ToggleDisplayFromMenu.cpp @@ -0,0 +1,30 @@ +#include "MenuToggleableDisplayInterface.h" +#include "ToggleDisplayFromMenu.h" +#include "list/ListItemCheckedStatus.h" + +namespace UKControllerPlugin::RadarScreen { + + ToggleDisplayFromMenu::ToggleDisplayFromMenu( + std::shared_ptr display, int callbackId) + : display(std::move(display)), callbackId(callbackId) + { + } + + void ToggleDisplayFromMenu::Configure(int functionId, std::string subject, RECT screenObjectArea) + { + display->Toggle(); + } + + auto ToggleDisplayFromMenu::GetConfigurationMenuItem() const -> Plugin::PopupMenuItem + { + Plugin::PopupMenuItem item; + item.firstValue = display->MenuItem(); + item.checked = static_cast( + display->IsVisible() ? List::ListItemCheckedStatus::Checked : List::ListItemCheckedStatus::NotChecked); + item.disabled = false; + item.fixedPosition = false; + item.callbackFunctionId = callbackId; + + return item; + } +} // namespace UKControllerPlugin::RadarScreen diff --git a/src/plugin/radarscreen/ToggleDisplayFromMenu.h b/src/plugin/radarscreen/ToggleDisplayFromMenu.h new file mode 100644 index 000000000..0ca194303 --- /dev/null +++ b/src/plugin/radarscreen/ToggleDisplayFromMenu.h @@ -0,0 +1,21 @@ +#pragma once +#include "radarscreen/ConfigurableDisplayInterface.h" + +namespace UKControllerPlugin::RadarScreen { + class MenuToggleableDisplayInterface; + + class ToggleDisplayFromMenu : public ConfigurableDisplayInterface + { + public: + ToggleDisplayFromMenu(std::shared_ptr display, int callbackId); + void Configure(int functionId, std::string subject, RECT screenObjectArea) override; + auto GetConfigurationMenuItem() const -> UKControllerPlugin::Plugin::PopupMenuItem override; + + private: + // The display that we can toggle + const std::shared_ptr display; + + // The callback id + int callbackId; + }; +} // namespace UKControllerPlugin::RadarScreen diff --git a/src/plugin/wake/FollowingWakeCallsignProvider.cpp b/src/plugin/wake/FollowingWakeCallsignProvider.cpp new file mode 100644 index 000000000..a8fce1b1f --- /dev/null +++ b/src/plugin/wake/FollowingWakeCallsignProvider.cpp @@ -0,0 +1,40 @@ +#include "FollowingWakeCallsignProvider.h" +#include "WakeCalculatorOptions.h" +#include "euroscope/EuroScopeCFlightPlanInterface.h" +#include "euroscope/EuroscopePluginLoopbackInterface.h" + +namespace UKControllerPlugin::Wake { + + FollowingWakeCallsignProvider::FollowingWakeCallsignProvider( + Euroscope::EuroscopePluginLoopbackInterface& plugin, std::shared_ptr options) + : plugin(plugin), options(options) + { + } + + auto FollowingWakeCallsignProvider::GetCallsigns() -> std::set + { + std::set callsigns; + const auto leadFlightplan = plugin.GetFlightplanForCallsign(this->options->LeadAircraft()); + if (!leadFlightplan) { + return callsigns; + } + + plugin.ApplyFunctionToAllFlightplans( + [&callsigns, &leadFlightplan]( + const std::shared_ptr& flightplan, + const std::shared_ptr& radarTarget) { + if (leadFlightplan->GetCallsign() == flightplan->GetCallsign() || + leadFlightplan->GetOrigin() != flightplan->GetOrigin()) { + return; + } + + callsigns.insert(flightplan->GetCallsign()); + }); + return callsigns; + } + + void FollowingWakeCallsignProvider::CallsignSelected(const std::string& callsign) + { + this->options->FollowingAircraft(callsign); + } +} // namespace UKControllerPlugin::Wake diff --git a/src/plugin/wake/FollowingWakeCallsignProvider.h b/src/plugin/wake/FollowingWakeCallsignProvider.h new file mode 100644 index 000000000..0a2fa3143 --- /dev/null +++ b/src/plugin/wake/FollowingWakeCallsignProvider.h @@ -0,0 +1,31 @@ +#pragma once +#include "aircraft/CallsignSelectionProviderInterface.h" + +namespace UKControllerPlugin { + namespace Euroscope { + class EuroscopePluginLoopbackInterface; + } // namespace Euroscope +} // namespace UKControllerPlugin + +namespace UKControllerPlugin::Wake { + class WakeCalculatorOptions; + + /* + * Provides the following callsign list for the wake calculator. + */ + class FollowingWakeCallsignProvider : public Aircraft::CallsignSelectionProviderInterface + { + public: + FollowingWakeCallsignProvider( + Euroscope::EuroscopePluginLoopbackInterface& plugin, std::shared_ptr options); + auto GetCallsigns() -> std::set override; + void CallsignSelected(const std::string& callsign) override; + + private: + // Plugin for looping flightplans + Euroscope::EuroscopePluginLoopbackInterface& plugin; + + // The options for the calculator + std::shared_ptr options; + }; +} // namespace UKControllerPlugin::Wake diff --git a/src/plugin/wake/LeadWakeCallsignProvider.cpp b/src/plugin/wake/LeadWakeCallsignProvider.cpp new file mode 100644 index 000000000..df42fa16c --- /dev/null +++ b/src/plugin/wake/LeadWakeCallsignProvider.cpp @@ -0,0 +1,40 @@ +#include "LeadWakeCallsignProvider.h" +#include "WakeCalculatorOptions.h" +#include "euroscope/EuroScopeCFlightPlanInterface.h" +#include "euroscope/EuroscopePluginLoopbackInterface.h" +#include "ownership/AirfieldServiceProviderCollection.h" + +namespace UKControllerPlugin::Wake { + LeadWakeCallsignProvider::LeadWakeCallsignProvider( + const Ownership::AirfieldServiceProviderCollection& serviceProvisions, + Euroscope::EuroscopePluginLoopbackInterface& plugin, + std::shared_ptr options) + : serviceProvisions(serviceProvisions), plugin(plugin), options(options) + { + } + + auto LeadWakeCallsignProvider::GetCallsigns() -> std::set + { + std::set callsigns; + auto airfields = serviceProvisions.GetAirfieldsWhereUserProvidingServices( + Ownership::ServiceType::Delivery | Ownership::ServiceType::Ground | Ownership::ServiceType::Tower); + + plugin.ApplyFunctionToAllFlightplans( + [&callsigns, &airfields]( + const std::shared_ptr& flightplan, + const std::shared_ptr& radarTarget) { + if (std::find(airfields.begin(), airfields.end(), flightplan->GetOrigin()) == airfields.cend()) { + return; + } + + callsigns.insert(flightplan->GetCallsign()); + }); + return callsigns; + } + + void LeadWakeCallsignProvider::CallsignSelected(const std::string& callsign) + { + options->LeadAircraft(callsign); + options->FollowingAircraft(""); + } +} // namespace UKControllerPlugin::Wake diff --git a/src/plugin/wake/LeadWakeCallsignProvider.h b/src/plugin/wake/LeadWakeCallsignProvider.h new file mode 100644 index 000000000..92c8a946c --- /dev/null +++ b/src/plugin/wake/LeadWakeCallsignProvider.h @@ -0,0 +1,40 @@ +#pragma once +#include "aircraft/CallsignSelectionProviderInterface.h" + +namespace UKControllerPlugin { + namespace Euroscope { + class EuroscopePluginLoopbackInterface; + } // namespace Euroscope + + namespace Ownership { + class AirfieldServiceProviderCollection; + } // namespace Ownership +} // namespace UKControllerPlugin + +namespace UKControllerPlugin::Wake { + class WakeCalculatorOptions; + + /* + * Provides the lead callsign list for the wake calculator. + */ + class LeadWakeCallsignProvider : public Aircraft::CallsignSelectionProviderInterface + { + public: + LeadWakeCallsignProvider( + const Ownership::AirfieldServiceProviderCollection& serviceProvisions, + Euroscope::EuroscopePluginLoopbackInterface& plugin, + std::shared_ptr options); + auto GetCallsigns() -> std::set override; + void CallsignSelected(const std::string& callsign) override; + + private: + // Whos providing what, where + const Ownership::AirfieldServiceProviderCollection& serviceProvisions; + + // Plugin for looping flightplans + Euroscope::EuroscopePluginLoopbackInterface& plugin; + + // The options for the calculator + std::shared_ptr options; + }; +} // namespace UKControllerPlugin::Wake diff --git a/src/plugin/wake/WakeCalculatorDisplay.cpp b/src/plugin/wake/WakeCalculatorDisplay.cpp new file mode 100644 index 000000000..a51286fa3 --- /dev/null +++ b/src/plugin/wake/WakeCalculatorDisplay.cpp @@ -0,0 +1,306 @@ +#include "WakeCategory.h" +#include "WakeCalculatorDisplay.h" +#include "WakeCategoryMapperInterface.h" +#include "WakeCalculatorOptions.h" +#include "WakeIntervalFormatter.h" +#include "components/ClickableArea.h" +#include "components/TitleBar.h" +#include "euroscope/EuroscopePluginLoopbackInterface.h" +#include "euroscope/EuroscopeRadarLoopbackInterface.h" +#include "euroscope/UserSetting.h" +#include "graphics/FontManager.h" +#include "graphics/GdiGraphicsInterface.h" +#include "graphics/StringFormatManager.h" +#include "helper/HelperFunctions.h" +#include "list/PopupListInterface.h" + +namespace UKControllerPlugin::Wake { + + WakeCalculatorDisplay::WakeCalculatorDisplay( + std::shared_ptr options, + std::shared_ptr leadCallsignSelector, + std::shared_ptr followCallsignSelector, + std::shared_ptr wakeSchemeSelector, + Euroscope::EuroscopePluginLoopbackInterface& plugin, + int screenObjectId) + : options(std::move(options)), leadCallsignSelector(std::move(leadCallsignSelector)), + followCallsignSelector(std::move(followCallsignSelector)), wakeSchemeSelector(std::move(wakeSchemeSelector)), + plugin(plugin), screenObjectId(screenObjectId), + titleBar(Components::TitleBar::Create(L"Wake Turbulence Calculator", TitleBarArea()) + ->WithDrag(this->screenObjectId) + ->WithDefaultBackgroundBrush() + ->WithDefaultTextBrush()), + backgroundBrush(std::make_shared(BACKGROUND_COLOUR)), + textBrush(std::make_shared(TEXT_COLOUR)), + resultBrush(std::make_shared(RESULT_COLOUR)), + dividingLinePen(std::make_shared(TEXT_COLOUR)), + leadClickspot(Components::ClickableArea::Create(leadTextArea, screenObjectId, "leadcallsign", false)), + followingClickspot( + Components::ClickableArea::Create(followingTextArea, screenObjectId, "followcallsign", false)), + schemeClickspot(Components::ClickableArea::Create(schemeTextArea, screenObjectId, "scheme", false)), + intermediateClickspot( + Components::ClickableArea::Create(intermediateTextArea, screenObjectId, "intermediate", false)) + { + this->Move({DEFAULT_WINDOW_POSITION.x, DEFAULT_WINDOW_POSITION.y, 0, 0}, ""); + } + + auto WakeCalculatorDisplay::IsVisible() const -> bool + { + return this->visible; + } + + void WakeCalculatorDisplay::LeftClick( + Euroscope::EuroscopeRadarLoopbackInterface& radarScreen, + int objectId, + const std::string& objectDescription, + POINT mousePos, + RECT itemArea) + { + if (objectDescription == "leadcallsign") { + leadCallsignSelector->Trigger(mousePos); + return; + } + + if (objectDescription == "followcallsign") { + followCallsignSelector->Trigger(mousePos); + return; + } + + if (objectDescription == "scheme") { + wakeSchemeSelector->Trigger(mousePos); + return; + } + + if (objectDescription == "intermediate") { + options->Intermediate(!options->Intermediate()); + return; + } + } + + void WakeCalculatorDisplay::Move(RECT position, std::string objectDescription) + { + this->windowPosition = {position.left, position.top}; + this->contentArea = WindowContentArea(); + this->titleBar->WithPosition(TitleBarArea()); + + // Scheme + this->schemeStaticArea = {TEXT_INSET, TitleBarArea().GetBottom() + TEXT_INSET, 60, 20}; + this->schemeTextArea = { + this->schemeStaticArea.GetRight() + TEXT_INSET, TitleBarArea().GetBottom() + TEXT_INSET, 75, 20}; + this->schemeClickspot->WithPosition(this->schemeTextArea); + + // Intermediate + this->intermediateStaticArea = {schemeTextArea.GetRight() + TEXT_INSET, schemeStaticArea.GetTop(), 105, 20}; + this->intermediateTextArea = { + intermediateStaticArea.GetRight() + TEXT_INSET, schemeStaticArea.GetTop(), 30, 20}; + this->intermediateClickspot->WithPosition(this->intermediateTextArea); + + // Lead + this->leadStaticArea = {TEXT_INSET, schemeStaticArea.GetBottom() + TEXT_INSET, 45, 20}; + this->leadTextArea = { + leadStaticArea.GetRight() + TEXT_INSET, schemeStaticArea.GetBottom() + TEXT_INSET, 110, 20}; + this->leadClickspot->WithPosition(this->leadTextArea); + + // Following + this->followingStaticArea = {leadTextArea.GetRight() + TEXT_INSET, leadStaticArea.GetTop(), 60, 20}; + this->followingTextArea = {followingStaticArea.GetRight() + TEXT_INSET, leadStaticArea.GetTop(), 110, 20}; + this->followingClickspot->WithPosition(this->followingTextArea); + + // Dividing line + this->dividingLineStart = Gdiplus::Point(0, followingTextArea.GetBottom() + TEXT_INSET); + this->dividingLineEnd = Gdiplus::Point(WINDOW_WIDTH, followingTextArea.GetBottom() + TEXT_INSET); + + // Category comparison + this->comparisonTextArea = {TEXT_INSET, dividingLineEnd.Y + TEXT_INSET, WINDOW_WIDTH - (2 * TEXT_INSET), 20}; + + // Calculation result + this->calculationResultArea = {TEXT_INSET, comparisonTextArea.GetBottom(), WINDOW_WIDTH - (2 * TEXT_INSET), 60}; + } + + void WakeCalculatorDisplay::Render( + Windows::GdiGraphicsInterface& graphics, Euroscope::EuroscopeRadarLoopbackInterface& radarScreen) + { + graphics.Translated(windowPosition.x, windowPosition.y, [&graphics, &radarScreen, this]() { + graphics.FillRect(this->contentArea, *backgroundBrush); + this->RenderScheme(graphics, radarScreen); + this->RenderIntermediate(graphics, radarScreen); + this->RenderLead(graphics, radarScreen); + this->RenderFollowing(graphics, radarScreen); + this->RenderDividingLine(graphics); + this->RenderSeparationRequirement(graphics); + titleBar->Draw(graphics, radarScreen); + }); + } + + void WakeCalculatorDisplay::ResetPosition() + { + this->Move({DEFAULT_WINDOW_POSITION.x, DEFAULT_WINDOW_POSITION.y, 0, 0}, ""); + } + + void WakeCalculatorDisplay::AsrLoadedEvent(Euroscope::UserSetting& userSetting) + { + this->Move( + {userSetting.GetIntegerEntry(ASR_KEY_X_POS, DEFAULT_WINDOW_POSITION.x), + userSetting.GetIntegerEntry(ASR_KEY_Y_POS, DEFAULT_WINDOW_POSITION.y), + 0, + 0}, + ""); + this->visible = userSetting.GetBooleanEntry(ASR_KEY_VISIBILITY, false); + } + + void WakeCalculatorDisplay::AsrClosingEvent(Euroscope::UserSetting& userSetting) + { + userSetting.Save(ASR_KEY_X_POS, ASR_DESCRIPTION_X_POS, windowPosition.x); + userSetting.Save(ASR_KEY_Y_POS, ASR_DESCRIPTION_Y_POS, windowPosition.y); + userSetting.Save(ASR_KEY_VISIBILITY, ASR_DESCRIPTION_VISIBILITY, visible); + } + + auto WakeCalculatorDisplay::TitleBarArea() -> Gdiplus::Rect + { + return {0, 0, WINDOW_WIDTH, TITLE_BAR_HEIGHT}; + } + + auto WakeCalculatorDisplay::WindowContentArea() -> Gdiplus::Rect + { + return {0, TITLE_BAR_HEIGHT, WINDOW_WIDTH, CONTENT_HEIGHT}; + } + + void WakeCalculatorDisplay::RenderScheme( + Windows::GdiGraphicsInterface& graphics, Euroscope::EuroscopeRadarLoopbackInterface& radarScreen) + { + graphics.DrawString( + L"Scheme:", + schemeStaticArea, + *textBrush, + Graphics::StringFormatManager::Instance().GetLeftAlign(), + Graphics::FontManager::Instance().GetDefault()); + graphics.DrawString( + HelperFunctions::ConvertToWideString(options->Scheme()), + schemeTextArea, + *textBrush, + Graphics::StringFormatManager::Instance().GetLeftAlign(), + Graphics::FontManager::Instance().GetDefault()); + this->schemeClickspot->Apply(graphics, radarScreen); + } + + void WakeCalculatorDisplay::RenderLead( + Windows::GdiGraphicsInterface& graphics, Euroscope::EuroscopeRadarLoopbackInterface& radarScreen) + { + const auto lead = options->LeadAircraft(); + graphics.DrawString( + L"Lead:", + leadStaticArea, + *textBrush, + Graphics::StringFormatManager::Instance().GetLeftAlign(), + Graphics::FontManager::Instance().GetDefault()); + graphics.DrawString( + HelperFunctions::ConvertToWideString(lead.empty() ? "--" : lead), + leadTextArea, + *textBrush, + Graphics::StringFormatManager::Instance().GetLeftAlign(), + Graphics::FontManager::Instance().GetDefault()); + this->leadClickspot->Apply(graphics, radarScreen); + } + + void WakeCalculatorDisplay::RenderFollowing( + Windows::GdiGraphicsInterface& graphics, Euroscope::EuroscopeRadarLoopbackInterface& radarScreen) + { + const auto following = options->FollowingAircraft(); + graphics.DrawString( + L"Follow:", + followingStaticArea, + *textBrush, + Graphics::StringFormatManager::Instance().GetLeftAlign(), + Graphics::FontManager::Instance().GetDefault()); + graphics.DrawString( + HelperFunctions::ConvertToWideString(following.empty() ? "--" : following), + followingTextArea, + *textBrush, + Graphics::StringFormatManager::Instance().GetLeftAlign(), + Graphics::FontManager::Instance().GetDefault()); + this->followingClickspot->Apply(graphics, radarScreen); + } + + void WakeCalculatorDisplay::RenderIntermediate( + Windows::GdiGraphicsInterface& graphics, Euroscope::EuroscopeRadarLoopbackInterface& radarScreen) + { + graphics.DrawString( + L"Intermediate:", + intermediateStaticArea, + *textBrush, + Graphics::StringFormatManager::Instance().GetLeftAlign(), + Graphics::FontManager::Instance().GetDefault()); + graphics.DrawString( + options->Intermediate() ? L"Yes" : L"No", + intermediateTextArea, + *textBrush, + Graphics::StringFormatManager::Instance().GetLeftAlign(), + Graphics::FontManager::Instance().GetDefault()); + this->intermediateClickspot->Apply(graphics, radarScreen); + } + + void WakeCalculatorDisplay::RenderDividingLine(Windows::GdiGraphicsInterface& graphics) + { + graphics.DrawLine(*dividingLinePen, dividingLineStart, dividingLineEnd); + } + + void WakeCalculatorDisplay::RenderSeparationRequirement(Windows::GdiGraphicsInterface& graphics) + { + // Check for the mapper and required flightplans + const auto mapper = options->SchemeMapper(); + const auto leadFlightplan = plugin.GetFlightplanForCallsign(options->LeadAircraft()); + const auto followingFlightplan = plugin.GetFlightplanForCallsign(options->FollowingAircraft()); + if (options->SchemeMapper() == nullptr || leadFlightplan == nullptr || followingFlightplan == nullptr) { + return; + } + + // Check for the categories; + const auto leadCategory = mapper->MapForFlightplan(*leadFlightplan); + const auto followingCategory = mapper->MapForFlightplan(*followingFlightplan); + if (leadCategory == nullptr || followingCategory == nullptr) { + return; + } + + // Summarise the WTC comparisons + const std::wstring categoryComparison = HelperFunctions::ConvertToWideString(leadCategory->Code()) + + L" followed by " + + HelperFunctions::ConvertToWideString(followingCategory->Code()) + + (options->Intermediate() ? L" (intermediate)" : L""); + + graphics.DrawString(categoryComparison, comparisonTextArea, *textBrush); + + // Check for intervals and display if present + const auto departureInterval = leadCategory->DepartureInterval(*followingCategory, options->Intermediate()); + if (departureInterval == nullptr) { + graphics.DrawString( + L"--", + calculationResultArea, + *resultBrush, + Graphics::StringFormatManager::Instance().GetCentreAlign(), + Graphics::FontManager::Instance().Get(16)); + return; + } + + graphics.DrawString( + FormatInterval(*departureInterval), + calculationResultArea, + *resultBrush, + Graphics::StringFormatManager::Instance().GetCentreAlign(), + Graphics::FontManager::Instance().Get(16)); + } + + auto WakeCalculatorDisplay::Position() const -> const POINT& + { + return windowPosition; + } + + auto WakeCalculatorDisplay::MenuItem() const -> std::string + { + return "Display Wake Turbulence Calculator"; + } + + void WakeCalculatorDisplay::Toggle() + { + this->visible = !this->visible; + } +} // namespace UKControllerPlugin::Wake diff --git a/src/plugin/wake/WakeCalculatorDisplay.h b/src/plugin/wake/WakeCalculatorDisplay.h new file mode 100644 index 000000000..95bb9e17f --- /dev/null +++ b/src/plugin/wake/WakeCalculatorDisplay.h @@ -0,0 +1,134 @@ +#pragma once +#include "euroscope/AsrEventHandlerInterface.h" +#include "radarscreen/MenuToggleableDisplayInterface.h" +#include "radarscreen/RadarRenderableInterface.h" + +namespace UKControllerPlugin { + namespace Components { + class ClickableArea; + class TitleBar; + } // namespace Components + namespace List { + class PopupListInterface; + } // namespace List + namespace Euroscope { + class EuroscopePluginLoopbackInterface; + } // namespace Euroscope +} // namespace UKControllerPlugin + +namespace UKControllerPlugin::Wake { + class WakeCalculatorOptions; + + class WakeCalculatorDisplay : public RadarScreen::RadarRenderableInterface, + public RadarScreen::MenuToggleableDisplayInterface, + public Euroscope::AsrEventHandlerInterface + { + public: + WakeCalculatorDisplay( + std::shared_ptr options, + std::shared_ptr leadCallsignSelector, + std::shared_ptr followCallsignSelector, + std::shared_ptr wakeSchemeSelector, + Euroscope::EuroscopePluginLoopbackInterface& plugin, + int screenObjectId); + [[nodiscard]] auto IsVisible() const -> bool override; + void LeftClick( + Euroscope::EuroscopeRadarLoopbackInterface& radarScreen, + int objectId, + const std::string& objectDescription, + POINT mousePos, + RECT itemArea) override; + void Move(RECT position, std::string objectDescription) override; + void Render( + Windows::GdiGraphicsInterface& graphics, Euroscope::EuroscopeRadarLoopbackInterface& radarScreen) override; + void ResetPosition() override; + void AsrLoadedEvent(Euroscope::UserSetting& userSetting) override; + void AsrClosingEvent(Euroscope::UserSetting& userSetting) override; + [[nodiscard]] auto Position() const -> const POINT&; + auto MenuItem() const -> std::string override; + void Toggle() override; + + private: + [[nodiscard]] static auto TitleBarArea() -> Gdiplus::Rect; + [[nodiscard]] static auto WindowContentArea() -> Gdiplus::Rect; + void + RenderScheme(Windows::GdiGraphicsInterface& graphics, Euroscope::EuroscopeRadarLoopbackInterface& radarScreen); + void + RenderLead(Windows::GdiGraphicsInterface& graphics, Euroscope::EuroscopeRadarLoopbackInterface& radarScreen); + void RenderFollowing( + Windows::GdiGraphicsInterface& graphics, Euroscope::EuroscopeRadarLoopbackInterface& radarScreen); + void RenderIntermediate( + Windows::GdiGraphicsInterface& graphics, Euroscope::EuroscopeRadarLoopbackInterface& radarScreen); + void RenderDividingLine(Windows::GdiGraphicsInterface& graphics); + void RenderSeparationRequirement(Windows::GdiGraphicsInterface& graphics); + + // The coordinate of the top left of the window + inline static const POINT DEFAULT_WINDOW_POSITION{200, 200}; + POINT windowPosition = DEFAULT_WINDOW_POSITION; + + inline static const int WINDOW_WIDTH = 350; + inline static const int TITLE_BAR_HEIGHT = 20; + inline static const int CONTENT_HEIGHT = 125; + inline static const int TEXT_INSET = 5; + + // Some options + std::shared_ptr options; + + // For selecting callsigns + const std::shared_ptr leadCallsignSelector; + const std::shared_ptr followCallsignSelector; + + // For selecting wake schemes + const std::shared_ptr wakeSchemeSelector; + + // For getting flightplans + Euroscope::EuroscopePluginLoopbackInterface& plugin; + + // The screen object id for click + const int screenObjectId; + + // The titlebar + std::shared_ptr titleBar; + + // Pens and brushes + const Gdiplus::Color BACKGROUND_COLOUR = Gdiplus::Color(64, 64, 64); + const Gdiplus::Color TEXT_COLOUR = Gdiplus::Color(225, 225, 225); + const Gdiplus::Color RESULT_COLOUR = Gdiplus::Color(55, 249, 1); + std::shared_ptr backgroundBrush; + std::shared_ptr textBrush; + std::shared_ptr resultBrush; + std::shared_ptr dividingLinePen; + + // Drawing rects + Gdiplus::Rect contentArea; + Gdiplus::Rect schemeStaticArea; + Gdiplus::Rect schemeTextArea; + Gdiplus::Rect intermediateStaticArea; + Gdiplus::Rect intermediateTextArea; + Gdiplus::Rect leadStaticArea; + Gdiplus::Rect leadTextArea; + Gdiplus::Rect followingStaticArea; + Gdiplus::Rect followingTextArea; + Gdiplus::Rect comparisonTextArea; + Gdiplus::Point dividingLineStart; + Gdiplus::Point dividingLineEnd; + Gdiplus::Rect calculationResultArea; + + // Clickspot rects + std::shared_ptr leadClickspot; + std::shared_ptr followingClickspot; + std::shared_ptr schemeClickspot; + std::shared_ptr intermediateClickspot; + + // ASR Things + const std::string ASR_KEY_VISIBILITY = "wakeCalculatorVisibility"; + const std::string ASR_DESCRIPTION_VISIBILITY = "Wake Calculator Visibility"; + const std::string ASR_KEY_X_POS = "wakeCalculatorXPosition"; + const std::string ASR_DESCRIPTION_X_POS = "Wake Calculator X Position"; + const std::string ASR_KEY_Y_POS = "wakeCalculatorYPosition"; + const std::string ASR_DESCRIPTION_Y_POS = "Wake Calculator Y Position"; + + // Visibility + bool visible = false; + }; +} // namespace UKControllerPlugin::Wake diff --git a/src/plugin/wake/WakeCalculatorOptions.cpp b/src/plugin/wake/WakeCalculatorOptions.cpp new file mode 100644 index 000000000..89bbe3577 --- /dev/null +++ b/src/plugin/wake/WakeCalculatorOptions.cpp @@ -0,0 +1,53 @@ +#include "WakeCalculatorOptions.h" + +namespace UKControllerPlugin::Wake { + auto WakeCalculatorOptions::LeadAircraft() const -> const std::string& + { + return leadAircraft; + } + + void WakeCalculatorOptions::LeadAircraft(const std::string& leadAircraft) + { + this->leadAircraft = leadAircraft; + } + + auto WakeCalculatorOptions::FollowingAircraft() const -> const std::string& + { + return followingAircraft; + } + + void WakeCalculatorOptions::FollowingAircraft(const std::string& followingAircraft) + { + this->followingAircraft = followingAircraft; + } + + auto WakeCalculatorOptions::Scheme() const -> const std::string& + { + return scheme; + } + + void WakeCalculatorOptions::Scheme(const std::string& scheme) + { + this->scheme = scheme; + } + + auto WakeCalculatorOptions::Intermediate() const -> bool + { + return intermediate; + } + + void WakeCalculatorOptions::Intermediate(bool intermediate) + { + this->intermediate = intermediate; + } + + auto WakeCalculatorOptions::SchemeMapper() const -> std::shared_ptr + { + return schemeMapper; + } + + void WakeCalculatorOptions::SchemeMapper(const std::shared_ptr mapper) + { + schemeMapper = mapper; + } +} // namespace UKControllerPlugin::Wake diff --git a/src/plugin/wake/WakeCalculatorOptions.h b/src/plugin/wake/WakeCalculatorOptions.h new file mode 100644 index 000000000..58972b63b --- /dev/null +++ b/src/plugin/wake/WakeCalculatorOptions.h @@ -0,0 +1,39 @@ +#pragma once + +namespace UKControllerPlugin::Wake { + class WakeCategoryMapperInterface; + + /** + * Selected options in relation to the Wake Calculator Display + */ + class WakeCalculatorOptions + { + public: + [[nodiscard]] auto LeadAircraft() const -> const std::string&; + void LeadAircraft(const std::string& leadAircraft); + [[nodiscard]] auto FollowingAircraft() const -> const std::string&; + void FollowingAircraft(const std::string& followingAircraft); + [[nodiscard]] auto Scheme() const -> const std::string&; + void Scheme(const std::string& scheme); + [[nodiscard]] auto SchemeMapper() const -> std::shared_ptr; + void SchemeMapper(const std::shared_ptr mapper); + [[nodiscard]] auto Intermediate() const -> bool; + void Intermediate(bool intermediate); + + private: + // Who the lead aircraft is + std::string leadAircraft; + + // Who the following aircraft is + std::string followingAircraft; + + // Are we doing intermediate departures + bool intermediate = false; + + // What scheme we're using + std::string scheme; + + // For scheme mapping + std::shared_ptr schemeMapper; + }; +} // namespace UKControllerPlugin::Wake diff --git a/src/plugin/wake/WakeCategory.cpp b/src/plugin/wake/WakeCategory.cpp index d73d2f54a..ce833147b 100644 --- a/src/plugin/wake/WakeCategory.cpp +++ b/src/plugin/wake/WakeCategory.cpp @@ -1,3 +1,4 @@ +#include "DepartureWakeInterval.h" #include "WakeCategory.h" namespace UKControllerPlugin::Wake { @@ -37,4 +38,18 @@ namespace UKControllerPlugin::Wake { { return subsequentDepartureIntervals; } + + auto WakeCategory::DepartureInterval(const WakeCategory& nextAircraftCategory, bool intermediate) const + -> std::shared_ptr + { + auto matchingInterval = std::find_if( + subsequentDepartureIntervals.cbegin(), + subsequentDepartureIntervals.cend(), + [&nextAircraftCategory, &intermediate](const std::shared_ptr& interval) -> bool { + return interval->intervalIsIntermediate == intermediate && + interval->subsequentWakeCategoryId == nextAircraftCategory.Id(); + }); + + return matchingInterval == subsequentDepartureIntervals.cend() ? nullptr : *matchingInterval; + } } // namespace UKControllerPlugin::Wake diff --git a/src/plugin/wake/WakeCategory.h b/src/plugin/wake/WakeCategory.h index 328e452fe..fc47ffbdd 100644 --- a/src/plugin/wake/WakeCategory.h +++ b/src/plugin/wake/WakeCategory.h @@ -1,7 +1,5 @@ #pragma once -#include "WakeCategoryFactory.h" - namespace UKControllerPlugin::Wake { struct DepartureWakeInterval; @@ -24,6 +22,8 @@ namespace UKControllerPlugin::Wake { [[nodiscard]] auto RelativeWeighting() const -> int; [[nodiscard]] auto SubsequentDepartureIntervals() const -> const std::list>&; + [[nodiscard]] auto DepartureInterval(const WakeCategory& nextAircraftCategory, bool intermediate) const + -> std::shared_ptr; private: // The id in the API diff --git a/src/plugin/wake/WakeIntervalFormatter.cpp b/src/plugin/wake/WakeIntervalFormatter.cpp new file mode 100644 index 000000000..c00234be0 --- /dev/null +++ b/src/plugin/wake/WakeIntervalFormatter.cpp @@ -0,0 +1,20 @@ +#include "DepartureWakeInterval.h" +#include "WakeIntervalFormatter.h" + +namespace UKControllerPlugin::Wake { + + auto FormatInterval(const DepartureWakeInterval& interval) -> std::wstring + { + if (interval.intervalUnit == "s") { + if (interval.intervalValue % 60 == 0) { + return std::to_wstring(interval.intervalValue / 60) + L" mins"; + } else { + return std::to_wstring(interval.intervalValue) + L" secs"; + } + } else if (interval.intervalUnit == "nm") { + return std::to_wstring(interval.intervalValue) + L" " + L"nm"; + } + + return L"--"; + } +} // namespace UKControllerPlugin::Wake diff --git a/src/plugin/wake/WakeIntervalFormatter.h b/src/plugin/wake/WakeIntervalFormatter.h new file mode 100644 index 000000000..476ebbf2a --- /dev/null +++ b/src/plugin/wake/WakeIntervalFormatter.h @@ -0,0 +1,7 @@ +#pragma once + +namespace UKControllerPlugin::Wake { + struct DepartureWakeInterval; + + [[nodiscard]] auto FormatInterval(const DepartureWakeInterval& interval) -> std::wstring; +} // namespace UKControllerPlugin::Wake diff --git a/src/plugin/wake/WakeModule.cpp b/src/plugin/wake/WakeModule.cpp index b99c93c89..7e5edff2d 100644 --- a/src/plugin/wake/WakeModule.cpp +++ b/src/plugin/wake/WakeModule.cpp @@ -1,12 +1,24 @@ +#include "aircraft/CallsignSelectionListFactory.h" #include "FlightplanWakeCategoryMapper.h" +#include "FollowingWakeCallsignProvider.h" +#include "LeadWakeCallsignProvider.h" +#include "WakeCalculatorDisplay.h" +#include "WakeCalculatorOptions.h" #include "WakeCategoryEventHandler.h" #include "WakeModule.h" #include "WakeScheme.h" #include "WakeSchemeCollection.h" #include "WakeSchemeCollectionFactory.h" +#include "WakeSchemeProvider.h" #include "bootstrap/PersistenceContainer.h" #include "dependency/DependencyLoaderInterface.h" +#include "euroscope/AsrEventHandlerCollection.h" #include "flightplan/FlightPlanEventHandlerCollection.h" +#include "list/PopupList.h" +#include "list/PopupListFactory.h" +#include "plugin/UKPlugin.h" +#include "radarscreen/MenuToggleableDisplayFactory.h" +#include "radarscreen/RadarRenderableCollection.h" #include "tag/TagItemCollection.h" using UKControllerPlugin::Bootstrap::PersistenceContainer; @@ -16,8 +28,8 @@ using UKControllerPlugin::Wake::WakeCategoryEventHandler; namespace UKControllerPlugin::Wake { std::unique_ptr defaultScheme; // NOLINT - std::unique_ptr ukMapper; - std::unique_ptr recatMapper; + std::shared_ptr ukMapper; + std::shared_ptr recatMapper; /* Bootstrap everything @@ -34,9 +46,9 @@ namespace UKControllerPlugin::Wake { // Create the mappers const auto ukScheme = container.wakeSchemes->GetByKey("UK"); const auto recatScheme = container.wakeSchemes->GetByKey("RECAT_EU"); - ukMapper = std::make_unique( + ukMapper = std::make_shared( ukScheme ? *ukScheme : *defaultScheme, *container.aircraftTypeMapper); - recatMapper = std::make_unique( + recatMapper = std::make_shared( recatScheme ? *recatScheme : *defaultScheme, *container.aircraftTypeMapper); // Create handler and register @@ -49,4 +61,34 @@ namespace UKControllerPlugin::Wake { container.tagHandler->RegisterTagItem(handler->tagItemIdUkRecatCombined, handler); container.tagHandler->RegisterTagItem(handler->tagItemIdAircraftTypeRecat, handler); } + + void BootstrapRadarScreen( + const Bootstrap::PersistenceContainer& container, + RadarScreen::RadarRenderableCollection& renderables, + Euroscope::AsrEventHandlerCollection& asrHandlers, + const RadarScreen::MenuToggleableDisplayFactory& toggleableDisplayFactory) + { + auto options = std::make_shared(); + options->Scheme("UK"); + options->SchemeMapper(ukMapper); + + const auto rendererId = renderables.ReserveRendererIdentifier(); + const auto renderer = std::make_shared( + options, + container.callsignSelectionListFactory->Create( + std::make_shared(*container.airfieldOwnership, *container.plugin, options), + "Wake Calculator Lead Aircraft"), + container.callsignSelectionListFactory->Create( + std::make_shared(*container.plugin, options), + "Wake Calculator Following Aircraft"), + container.popupListFactory->Create( + std::make_shared(options, *container.wakeSchemes, *container.aircraftTypeMapper), + "Wake Calculator Scheme"), + *container.plugin, + renderables.ReserveScreenObjectIdentifier(rendererId)); + + renderables.RegisterRenderer(rendererId, renderer, RadarScreen::RadarRenderableCollection::afterLists); + asrHandlers.RegisterHandler(renderer); + toggleableDisplayFactory.RegisterDisplay(renderer, "Toggle Wake Turbulence Calculator"); + } } // namespace UKControllerPlugin::Wake diff --git a/src/plugin/wake/WakeModule.h b/src/plugin/wake/WakeModule.h index 630428943..c07020672 100644 --- a/src/plugin/wake/WakeModule.h +++ b/src/plugin/wake/WakeModule.h @@ -7,6 +7,13 @@ namespace UKControllerPlugin { namespace Dependency { class DependencyLoaderInterface; } // namespace Dependency + namespace Euroscope { + class AsrEventHandlerCollection; + } // namespace Euroscope + namespace RadarScreen { + class MenuToggleableDisplayFactory; + class RadarRenderableCollection; + } // namespace RadarScreen } // namespace UKControllerPlugin namespace UKControllerPlugin::Wake { @@ -14,4 +21,10 @@ namespace UKControllerPlugin::Wake { void BootstrapPlugin( UKControllerPlugin::Bootstrap::PersistenceContainer& container, UKControllerPlugin::Dependency::DependencyLoaderInterface& dependencies); + + void BootstrapRadarScreen( + const UKControllerPlugin::Bootstrap::PersistenceContainer& container, + RadarScreen::RadarRenderableCollection& renderables, + Euroscope::AsrEventHandlerCollection& asrHandlers, + const RadarScreen::MenuToggleableDisplayFactory& toggleableDisplayFactory); } // namespace UKControllerPlugin::Wake diff --git a/src/plugin/wake/WakeSchemeCollection.cpp b/src/plugin/wake/WakeSchemeCollection.cpp index f54ca902e..e1abe2f66 100644 --- a/src/plugin/wake/WakeSchemeCollection.cpp +++ b/src/plugin/wake/WakeSchemeCollection.cpp @@ -29,4 +29,35 @@ namespace UKControllerPlugin::Wake { return scheme == schemes.cend() ? nullptr : scheme->second; } + + auto WakeSchemeCollection::GetByName(const std::string& name) const -> std::shared_ptr + { + auto scheme = std::find_if( + schemes.cbegin(), + schemes.cend(), + [&name](const std::pair>& schemePair) -> bool { + return schemePair.second->Name() == name; + }); + + return scheme == schemes.cend() ? nullptr : scheme->second; + } + + auto WakeSchemeCollection::FirstWhere(const std::function predicate) const + -> std::shared_ptr + { + for (const auto& scheme : schemes) { + if (predicate(*scheme.second)) { + return scheme.second; + } + } + + return nullptr; + } + + void WakeSchemeCollection::ForEach(const std::function callback) const + { + for (const auto& scheme : schemes) { + callback(*scheme.second); + } + } } // namespace UKControllerPlugin::Wake diff --git a/src/plugin/wake/WakeSchemeCollection.h b/src/plugin/wake/WakeSchemeCollection.h index 8d217787b..1b97aeb34 100644 --- a/src/plugin/wake/WakeSchemeCollection.h +++ b/src/plugin/wake/WakeSchemeCollection.h @@ -11,6 +11,10 @@ namespace UKControllerPlugin::Wake { void Add(std::shared_ptr scheme); [[nodiscard]] auto Count() const -> size_t; [[nodiscard]] auto GetByKey(const std::string& key) const -> std::shared_ptr; + [[nodiscard]] auto GetByName(const std::string& name) const -> std::shared_ptr; + [[nodiscard]] auto FirstWhere(const std::function predicate) const + -> std::shared_ptr; + void ForEach(const std::function callback) const; private: std::map> schemes; diff --git a/src/plugin/wake/WakeSchemeProvider.cpp b/src/plugin/wake/WakeSchemeProvider.cpp new file mode 100644 index 000000000..4f5cdd387 --- /dev/null +++ b/src/plugin/wake/WakeSchemeProvider.cpp @@ -0,0 +1,50 @@ +#include "FlightplanWakeCategoryMapper.h" +#include "WakeCalculatorOptions.h" +#include "WakeScheme.h" +#include "WakeSchemeCollection.h" +#include "WakeSchemeProvider.h" +#include "list/ListItem.h" +#include "list/ListItemCheckedStatus.h" + +namespace UKControllerPlugin::Wake { + + WakeSchemeProvider::WakeSchemeProvider( + std::shared_ptr options, + const WakeSchemeCollection& schemes, + const Aircraft::AircraftTypeMapperInterface& aircraftTypeMapper) + : options(options), schemes(schemes), aircraftTypeMapper(aircraftTypeMapper) + { + } + + auto WakeSchemeProvider::ListColumns() -> int + { + return 1; + } + + auto WakeSchemeProvider::ListName() -> std::string + { + return "Scheme"; + } + + auto WakeSchemeProvider::ListItems() -> std::list> + { + std::list> items; + schemes.ForEach([&items](const WakeScheme& scheme) { + items.push_back(std::make_shared( + scheme.Name(), "", false, false, List::ListItemCheckedStatus::NoCheckbox)); + }); + return items; + } + + void WakeSchemeProvider::ItemSelected(const std::string& item) + { + const auto scheme = schemes.GetByName(item); + if (scheme == nullptr) { + LogError("Selected invalid wake scheme"); + return; + } + + options->Scheme(scheme->Name()); + options->SchemeMapper(std::make_shared(*scheme, aircraftTypeMapper)); + } +} // namespace UKControllerPlugin::Wake diff --git a/src/plugin/wake/WakeSchemeProvider.h b/src/plugin/wake/WakeSchemeProvider.h new file mode 100644 index 000000000..a99bda081 --- /dev/null +++ b/src/plugin/wake/WakeSchemeProvider.h @@ -0,0 +1,38 @@ +#pragma once +#include "list/ListItemProviderInterface.h" + +namespace UKControllerPlugin::Aircraft { + class AircraftTypeMapperInterface; +} // namespace UKControllerPlugin::Aircraft + +namespace UKControllerPlugin::Wake { + class WakeSchemeCollection; + class WakeCalculatorOptions; + + /** + * Provides the wake schemes for the selection on the + * calculator display. + */ + class WakeSchemeProvider : public List::ListItemProviderInterface + { + public: + WakeSchemeProvider( + std::shared_ptr options, + const WakeSchemeCollection& schemes, + const Aircraft::AircraftTypeMapperInterface& aircraftTypeMapper); + auto ListColumns() -> int override; + auto ListName() -> std::string override; + auto ListItems() -> std::list> override; + void ItemSelected(const std::string& item) override; + + private: + // Options + std::shared_ptr options; + + // For getting the schemes + const WakeSchemeCollection& schemes; + + // Maps aircraft types for wake mapping + const Aircraft::AircraftTypeMapperInterface& aircraftTypeMapper; + }; +} // namespace UKControllerPlugin::Wake diff --git a/test/plugin/CMakeLists.txt b/test/plugin/CMakeLists.txt index af7998a67..5236c0f33 100644 --- a/test/plugin/CMakeLists.txt +++ b/test/plugin/CMakeLists.txt @@ -231,6 +231,10 @@ set(test__intention ) source_group("test\\intention" FILES ${test__intention}) +set(test__list + list/PopupListTest.cpp list/PopupListFactoryTest.cpp list/PopupListFactoryBootstrapTest.cpp) +source_group("test\\list" FILES ${test__list}) + set(test__login "login/LoginModuleTest.cpp" "login/LoginTest.cpp" @@ -331,9 +335,9 @@ set(test__mock "mock/MockUserSettingAwareInterface.h" "mock/MockUserSettingProviderInterface.h" mock/MockCallsignSelectionProvider.h - mock/MockCallsignSelectionList.h mock/MockAircraftTypeMapper.h - mock/MockWakeCategoryMapper.h) + mock/MockWakeCategoryMapper.h + mock/MockListItemProvider.h mock/MockPopupList.h) source_group("test\\mock" FILES ${test__mock}) set(test__navaids @@ -431,7 +435,7 @@ set(test__radarscreen "radarscreen/RadarRenderableCollectionTest.cpp" "radarscreen/ScreenControlsBootstrapTest.cpp" "radarscreen/ScreenControlsTest.cpp" -) + radarscreen/ToggleDisplayFromMenuTest.cpp mock/MockMenuToggleableDisplay.h radarscreen/MenuToggleableDisplayFactoryTest.cpp) source_group("test\\radarscreen" FILES ${test__radarscreen}) set(test__regional @@ -545,7 +549,7 @@ set(test__wake wake/WakeSchemeFactoryTest.cpp wake/WakeSchemeCollectionTest.cpp wake/WakeSchemeCollectionFactoryTest.cpp - wake/FlightplanWakeCategoryMapperTest.cpp) + wake/FlightplanWakeCategoryMapperTest.cpp wake/FollowingWakeCallsignProviderTest.cpp wake/LeadWakeCallsignProviderTest.cpp wake/WakeCalculatorOptionsTest.cpp wake/WakeSchemeProviderTest.cpp wake/WakeCalculatorDisplayTest.cpp wake/WakeIntervalFormatterTest.cpp) source_group("test\\wake" FILES ${test__wake}) set(ALL_FILES @@ -572,6 +576,7 @@ set(ALL_FILES ${test__initialheading} ${test__integration} ${test__intention} + ${test__list} ${test__login} ${test__message} ${test__metar} diff --git a/test/plugin/aircraft/CallsignSelectionListFactoryTest.cpp b/test/plugin/aircraft/CallsignSelectionListFactoryTest.cpp index 660c5372e..f833372ce 100644 --- a/test/plugin/aircraft/CallsignSelectionListFactoryTest.cpp +++ b/test/plugin/aircraft/CallsignSelectionListFactoryTest.cpp @@ -1,10 +1,13 @@ #include "aircraft/CallsignSelectionList.h" #include "aircraft/CallsignSelectionListFactory.h" +#include "list/PopupList.h" +#include "list/PopupListFactory.h" #include "plugin/FunctionCallEventHandler.h" #include "plugin/PopupMenuItem.h" using UKControllerPlugin::Aircraft::CallsignSelectionList; using UKControllerPlugin::Aircraft::CallsignSelectionListFactory; +using UKControllerPlugin::List::PopupListFactory; using UKControllerPlugin::Plugin::FunctionCallEventHandler; using UKControllerPlugin::Plugin::PopupMenuItem; @@ -14,13 +17,16 @@ namespace UKControllerPluginTest::Aircraft { public: CallsignSelectionListFactoryTest() : callsignProvider(std::make_shared>()), - factory(functionHandler, plugin) + listFactory(functionHandler, plugin), factory(listFactory) { } FunctionCallEventHandler functionHandler; std::shared_ptr> callsignProvider; + testing::NiceMock radarTarget; + testing::NiceMock flightplan; testing::NiceMock plugin; + PopupListFactory listFactory; CallsignSelectionListFactory factory; }; @@ -32,11 +38,9 @@ namespace UKControllerPluginTest::Aircraft { TEST_F(CallsignSelectionListFactoryTest, CallbackFunctionTriggersCallsignSelected) { - auto list = factory.Create(callsignProvider, "Some Function"); - - EXPECT_CALL(*callsignProvider, CallsignSelected("BAW123")).Times(1); - - list->CallsignSelected("BAW123"); + static_cast(factory.Create(callsignProvider, "Some Function")); + functionHandler.CallFunction( + functionHandler.ReserveNextDynamicFunctionId() - 1, "foo", flightplan, radarTarget, {}, RECT{}); } TEST_F(CallsignSelectionListFactoryTest, ListIsTriggeredWithCorrectId) @@ -45,7 +49,7 @@ namespace UKControllerPluginTest::Aircraft { EXPECT_CALL(*callsignProvider, GetCallsigns).Times(1).WillOnce(testing::Return(std::set{"ABCD"})); - EXPECT_CALL(plugin, TriggerPopupList(RectEq(RECT{1, 2, 51, 102}), "Select Aircraft", 1)).Times(1); + EXPECT_CALL(plugin, TriggerPopupList(RectEq(RECT{1, 2, 201, 402}), "Select Aircraft", 1)).Times(1); PopupMenuItem expectedItem; expectedItem.firstValue = "ABCD"; @@ -57,6 +61,6 @@ namespace UKControllerPluginTest::Aircraft { EXPECT_CALL(plugin, AddItemToPopupList(expectedItem)).Times(1); - list->TriggerList({1, 2}); + list->Trigger({1, 2}); } } // namespace UKControllerPluginTest::Aircraft diff --git a/test/plugin/aircraft/CallsignSelectionListTest.cpp b/test/plugin/aircraft/CallsignSelectionListTest.cpp index b09ec39a1..8c7720153 100644 --- a/test/plugin/aircraft/CallsignSelectionListTest.cpp +++ b/test/plugin/aircraft/CallsignSelectionListTest.cpp @@ -1,7 +1,10 @@ #include "aircraft/CallsignSelectionList.h" +#include "list/ListItem.h" #include "plugin/PopupMenuItem.h" using UKControllerPlugin::Aircraft::CallsignSelectionList; +using UKControllerPlugin::List::ListItem; +using UKControllerPlugin::List::ListItemCheckedStatus; using UKControllerPlugin::Plugin::PopupMenuItem; namespace UKControllerPluginTest::Aircraft { @@ -10,56 +13,59 @@ namespace UKControllerPluginTest::Aircraft { public: CallsignSelectionListTest() : callsignProvider(std::make_shared>()), - list(callsignProvider, plugin, 123) + list(callsignProvider) { } std::shared_ptr> callsignProvider; - testing::NiceMock plugin; CallsignSelectionList list; }; - TEST_F(CallsignSelectionListTest, ItDoesntTriggerListIfNoCallsigns) + TEST_F(CallsignSelectionListTest, ItHasASingleColumn) { - EXPECT_CALL(*callsignProvider, GetCallsigns).Times(1).WillOnce(testing::Return(std::set())); - - EXPECT_CALL(plugin, TriggerPopupList).Times(0); + EXPECT_EQ(1, list.ListColumns()); + } - list.TriggerList({1, 2}); + TEST_F(CallsignSelectionListTest, ItHasAName) + { + EXPECT_EQ("Select Aircraft", list.ListName()); } - TEST_F(CallsignSelectionListTest, ItDisplaysCallsignList) + TEST_F(CallsignSelectionListTest, ItReturnsListItems) { EXPECT_CALL(*callsignProvider, GetCallsigns) .Times(1) .WillOnce(testing::Return(std::set{"ABCD", "EFGH", "IJKL"})); - EXPECT_CALL(plugin, TriggerPopupList(RectEq(RECT{1, 2, 51, 102}), "Select Aircraft", 1)).Times(1); - - PopupMenuItem expectedItem; - expectedItem.firstValue = "ABCD"; - expectedItem.secondValue = ""; - expectedItem.disabled = false; - expectedItem.fixedPosition = false; - expectedItem.checked = EuroScopePlugIn::POPUP_ELEMENT_NO_CHECKBOX; - expectedItem.callbackFunctionId = 123; - - EXPECT_CALL(plugin, AddItemToPopupList(expectedItem)).Times(1); - - expectedItem.firstValue = "EFGH"; - - EXPECT_CALL(plugin, AddItemToPopupList(expectedItem)).Times(1); - - expectedItem.firstValue = "IJKL"; - - EXPECT_CALL(plugin, AddItemToPopupList(expectedItem)).Times(1); - - list.TriggerList({1, 2}); + const auto items = list.ListItems(); + EXPECT_EQ(3, items.size()); + + auto itemIterator = items.cbegin(); + const auto item1 = *itemIterator++; + EXPECT_EQ("ABCD", item1->firstColumn); + EXPECT_EQ("", item1->secondColumn); + EXPECT_FALSE(item1->fixedPosition); + EXPECT_FALSE(item1->disabled); + EXPECT_EQ(ListItemCheckedStatus::NoCheckbox, item1->checked); + + const auto item2 = *itemIterator++; + EXPECT_EQ("EFGH", item2->firstColumn); + EXPECT_EQ("", item2->secondColumn); + EXPECT_FALSE(item2->fixedPosition); + EXPECT_FALSE(item2->disabled); + EXPECT_EQ(ListItemCheckedStatus::NoCheckbox, item2->checked); + + const auto item3 = *itemIterator++; + EXPECT_EQ("IJKL", item3->firstColumn); + EXPECT_EQ("", item3->secondColumn); + EXPECT_FALSE(item3->fixedPosition); + EXPECT_FALSE(item3->disabled); + EXPECT_EQ(ListItemCheckedStatus::NoCheckbox, item3->checked); } TEST_F(CallsignSelectionListTest, ItTriggersCallsignSelected) { EXPECT_CALL(*callsignProvider, CallsignSelected("BAW123")).Times(1); - list.CallsignSelected("BAW123"); + list.ItemSelected("BAW123"); } } // namespace UKControllerPluginTest::Aircraft diff --git a/test/plugin/hold/HoldConfigurationMenuItemTest.cpp b/test/plugin/hold/HoldConfigurationMenuItemTest.cpp index 860367c46..5de01bc48 100644 --- a/test/plugin/hold/HoldConfigurationMenuItemTest.cpp +++ b/test/plugin/hold/HoldConfigurationMenuItemTest.cpp @@ -8,6 +8,7 @@ #include "hold/HoldManager.h" #include "hold/HoldDisplayFactory.h" #include "hold/PublishedHoldCollection.h" +#include "list/PopupListFactory.h" #include "navaids/NavaidCollection.h" #include "plugin/FunctionCallEventHandler.h" @@ -22,6 +23,7 @@ using UKControllerPlugin::Hold::HoldDisplayFactory; using UKControllerPlugin::Hold::HoldDisplayManager; using UKControllerPlugin::Hold::HoldManager; using UKControllerPlugin::Hold::PublishedHoldCollection; +using UKControllerPlugin::List::PopupListFactory; using UKControllerPlugin::Navaids::NavaidCollection; using UKControllerPlugin::Plugin::FunctionCallEventHandler; using UKControllerPlugin::Plugin::PopupMenuItem; @@ -37,7 +39,7 @@ namespace UKControllerPluginTest { { public: HoldConfigurationMenuItemTest() - : listFactory(functionHandlers, mockPlugin), dialogManager(mockProvider), + : popupFactory(functionHandlers, mockPlugin), listFactory(popupFactory), dialogManager(mockProvider), holdManager(mockApi, mockTaskRunner), displayFactory(mockPlugin, holdManager, navaids, holds, dialogManager, listFactory), displayManager(new HoldDisplayManager(displayFactory)), menuItem(dialogManager, displayManager, 1) @@ -52,6 +54,7 @@ namespace UKControllerPluginTest { NiceMock mockApi; NiceMock mockPlugin; FunctionCallEventHandler functionHandlers; + PopupListFactory popupFactory; CallsignSelectionListFactory listFactory; NiceMock mockProvider; UKControllerPlugin::Dialog::DialogManager dialogManager; diff --git a/test/plugin/hold/HoldDisplayFactoryTest.cpp b/test/plugin/hold/HoldDisplayFactoryTest.cpp index 003d63c42..4dab3dc7b 100644 --- a/test/plugin/hold/HoldDisplayFactoryTest.cpp +++ b/test/plugin/hold/HoldDisplayFactoryTest.cpp @@ -6,8 +6,9 @@ #include "hold/HoldDisplayFactory.h" #include "hold/HoldManager.h" #include "hold/HoldingData.h" -#include "navaids/NavaidCollection.h" #include "hold/PublishedHoldCollection.h" +#include "list/PopupListFactory.h" +#include "navaids/NavaidCollection.h" #include "navaids/Navaid.h" #include "plugin/FunctionCallEventHandler.h" @@ -20,6 +21,7 @@ using UKControllerPlugin::Hold::HoldDisplayFactory; using UKControllerPlugin::Hold::HoldingData; using UKControllerPlugin::Hold::HoldManager; using UKControllerPlugin::Hold::PublishedHoldCollection; +using UKControllerPlugin::List::PopupListFactory; using UKControllerPlugin::Navaids::Navaid; using UKControllerPlugin::Navaids::NavaidCollection; using UKControllerPlugin::Plugin::FunctionCallEventHandler; @@ -34,8 +36,9 @@ namespace UKControllerPluginTest::Hold { { public: HoldDisplayFactoryTest() - : dialogManager(dialogProvider), callsignSelectionFactory(functionHandlers, mockPlugin), - navaid({1, "TIMBA", EuroScopePlugIn::CPosition()}), holdManager(mockApi, taskRunner), + : dialogManager(dialogProvider), popupFactory(functionHandlers, mockPlugin), + callsignSelectionFactory(popupFactory), navaid({1, "TIMBA", EuroScopePlugIn::CPosition()}), + holdManager(mockApi, taskRunner), factory(mockPlugin, holdManager, navaids, holds, dialogManager, callsignSelectionFactory) { @@ -49,6 +52,7 @@ namespace UKControllerPluginTest::Hold { DialogManager dialogManager; NiceMock mockPlugin; FunctionCallEventHandler functionHandlers; + PopupListFactory popupFactory; CallsignSelectionListFactory callsignSelectionFactory; HoldingData holdData = {1, "TIMBA", "TIMBA TEST", 7000, 15000, 360, "left", {}}; PublishedHoldCollection holds; diff --git a/test/plugin/hold/HoldDisplayManagerTest.cpp b/test/plugin/hold/HoldDisplayManagerTest.cpp index eb899ee88..31a6f0169 100644 --- a/test/plugin/hold/HoldDisplayManagerTest.cpp +++ b/test/plugin/hold/HoldDisplayManagerTest.cpp @@ -8,6 +8,7 @@ #include "hold/HoldDisplayManager.h" #include "hold/HoldManager.h" #include "hold/PublishedHoldCollection.h" +#include "list/PopupListFactory.h" #include "navaids/NavaidCollection.h" #include "plugin/FunctionCallEventHandler.h" @@ -23,6 +24,7 @@ using UKControllerPlugin::Hold::HoldDisplayManager; using UKControllerPlugin::Hold::HoldingData; using UKControllerPlugin::Hold::HoldManager; using UKControllerPlugin::Hold::PublishedHoldCollection; +using UKControllerPlugin::List::PopupListFactory; using UKControllerPlugin::Navaids::Navaid; using UKControllerPlugin::Navaids::NavaidCollection; using UKControllerPlugin::Plugin::FunctionCallEventHandler; @@ -38,7 +40,7 @@ namespace UKControllerPluginTest::Hold { { public: HoldDisplayManagerTest() - : listFactory(functionHandlers, mockPlugin), dialogManager(dialogProvider), + : popupFactory(functionHandlers, mockPlugin), listFactory(popupFactory), dialogManager(dialogProvider), userSetting(mockUserSettingProvider), holdManager(mockApi, taskRunner), displayFactory(mockPlugin, holdManager, navaids, holds, dialogManager, listFactory), displayManager(displayFactory) @@ -55,6 +57,7 @@ namespace UKControllerPluginTest::Hold { NiceMock mockUserSettingProvider; NiceMock mockPlugin; FunctionCallEventHandler functionHandlers; + PopupListFactory popupFactory; CallsignSelectionListFactory listFactory; DialogManager dialogManager; UserSetting userSetting; diff --git a/test/plugin/hold/HoldDisplayTest.cpp b/test/plugin/hold/HoldDisplayTest.cpp index 71ad7ac23..0463e25f4 100644 --- a/test/plugin/hold/HoldDisplayTest.cpp +++ b/test/plugin/hold/HoldDisplayTest.cpp @@ -43,9 +43,9 @@ namespace UKControllerPluginTest { { public: HoldDisplayTest() - : addAircraftList(std::make_shared>()), - dialogManager(mockDialogProvider), userSetting(mockUserSettingProvider), - navaid({2, "TIMBA", EuroScopePlugIn::CPosition()}), holdManager(mockApi, mockTaskRunner), + : addAircraftList(std::make_shared>()), dialogManager(mockDialogProvider), + userSetting(mockUserSettingProvider), navaid({2, "TIMBA", EuroScopePlugIn::CPosition()}), + holdManager(mockApi, mockTaskRunner), display(mockPlugin, holdManager, navaid, publishedHolds, dialogManager, addAircraftList) { this->dialogManager.AddDialog(this->holdDialogData); @@ -64,7 +64,7 @@ namespace UKControllerPluginTest { EuroScopePlugIn::CPosition mayPosition; PublishedHoldCollection publishedHolds; DialogData holdDialogData = {IDD_HOLD_PARAMS, "Test"}; - std::shared_ptr> addAircraftList; + std::shared_ptr> addAircraftList; NiceMock mockTaskRunner; NiceMock mockApi; NiceMock mockDialogProvider; @@ -1011,14 +1011,14 @@ namespace UKControllerPluginTest { TEST_F(HoldDisplayTest, ButtonRightClickedDoesNothingIfNotAddButton) { - EXPECT_CALL(*addAircraftList, TriggerList).Times(0); + EXPECT_CALL(*addAircraftList, Trigger).Times(0); display.ButtonRightClicked("notadd"); } TEST_F(HoldDisplayTest, AddButtonRightClickTriggersTheList) { - EXPECT_CALL(*addAircraftList, TriggerList(PointEq(POINT{265, 118}))).Times(1); + EXPECT_CALL(*addAircraftList, Trigger(PointEq(POINT{265, 118}))).Times(1); display.ButtonRightClicked("add"); } diff --git a/test/plugin/hold/HoldRendererTest.cpp b/test/plugin/hold/HoldRendererTest.cpp index a2f5240f7..9139765cd 100644 --- a/test/plugin/hold/HoldRendererTest.cpp +++ b/test/plugin/hold/HoldRendererTest.cpp @@ -6,6 +6,7 @@ #include "hold/HoldDisplayManager.h" #include "hold/HoldManager.h" #include "hold/HoldDisplayFactory.h" +#include "list/PopupListFactory.h" #include "plugin/FunctionCallEventHandler.h" #include "plugin/PopupMenuItem.h" #include "navaids/NavaidCollection.h" @@ -23,6 +24,7 @@ using UKControllerPlugin::Hold::HoldDisplayManager; using UKControllerPlugin::Hold::HoldManager; using UKControllerPlugin::Hold::HoldRenderer; using UKControllerPlugin::Hold::PublishedHoldCollection; +using UKControllerPlugin::List::PopupListFactory; using UKControllerPlugin::Navaids::NavaidCollection; using UKControllerPlugin::Plugin::FunctionCallEventHandler; using UKControllerPlugin::Plugin::PopupMenuItem; @@ -40,8 +42,8 @@ namespace UKControllerPluginTest { { public: HoldRendererTest() - : callsignSelectionFactory(functionHandlers, mockPlugin), dialogManager(mockDialog), - userSetting(mockUserSettingProvider), holdManager(mockApi, mockTaskRunner), + : popupFactory(functionHandlers, mockPlugin), callsignSelectionFactory(popupFactory), + dialogManager(mockDialog), userSetting(mockUserSettingProvider), holdManager(mockApi, mockTaskRunner), displayFactory(mockPlugin, holdManager, navaids, holds, dialogManager, callsignSelectionFactory), displayManager(new HoldDisplayManager(displayFactory)), renderer(displayManager, 1, 2) { @@ -64,6 +66,7 @@ namespace UKControllerPluginTest { NiceMock mockUserSettingProvider; NiceMock mockPlugin; FunctionCallEventHandler functionHandlers; + PopupListFactory popupFactory; CallsignSelectionListFactory callsignSelectionFactory; NiceMock mockRadarScreen; NiceMock mockApi; diff --git a/test/plugin/hold/HoldSelectionMenuTest.cpp b/test/plugin/hold/HoldSelectionMenuTest.cpp index d36d4ae0f..7967d9c8a 100644 --- a/test/plugin/hold/HoldSelectionMenuTest.cpp +++ b/test/plugin/hold/HoldSelectionMenuTest.cpp @@ -10,6 +10,7 @@ #include "hold/HoldDisplayFactory.h" #include "hold/HoldSelectionMenu.h" #include "hold/PublishedHoldCollection.h" +#include "list/PopupListFactory.h" #include "navaids/NavaidCollection.h" #include "plugin/FunctionCallEventHandler.h" @@ -27,6 +28,7 @@ using UKControllerPlugin::Hold::HoldingData; using UKControllerPlugin::Hold::HoldManager; using UKControllerPlugin::Hold::HoldSelectionMenu; using UKControllerPlugin::Hold::PublishedHoldCollection; +using UKControllerPlugin::List::PopupListFactory; using UKControllerPlugin::Navaids::NavaidCollection; using UKControllerPlugin::Plugin::FunctionCallEventHandler; using UKControllerPlugin::Plugin::PopupMenuItem; @@ -45,8 +47,8 @@ namespace UKControllerPluginTest { { public: HoldSelectionMenuTest() - : listFactory(functionHandlers, mockPlugin), dialogManager(mockDialogProvider), - userSetting(mockUserSettingProvider), + : popupFactory(functionHandlers, mockPlugin), listFactory(popupFactory), + dialogManager(mockDialogProvider), userSetting(mockUserSettingProvider), displayFactory(mockPlugin, holdManager, navaids, holds, dialogManager, listFactory), holdManager(mockApi, mockTaskRunner), holdSelectionMenu(holdManager, mockPlugin, 1) { @@ -72,6 +74,7 @@ namespace UKControllerPluginTest { NiceMock mockTaskRunner; NiceMock mockDialogProvider; FunctionCallEventHandler functionHandlers; + PopupListFactory popupFactory; CallsignSelectionListFactory listFactory; DialogManager dialogManager; NavaidCollection navaids; diff --git a/test/plugin/list/PopupListFactoryBootstrapTest.cpp b/test/plugin/list/PopupListFactoryBootstrapTest.cpp new file mode 100644 index 000000000..ca908fde9 --- /dev/null +++ b/test/plugin/list/PopupListFactoryBootstrapTest.cpp @@ -0,0 +1,19 @@ +#include "bootstrap/PersistenceContainer.h" +#include "list/PopupListFactoryBootstrap.h" + +using UKControllerPlugin::Bootstrap::PersistenceContainer; +using UKControllerPlugin::List::BootstrapPlugin; + +namespace UKControllerPluginTest::List { + class PopupListFactoryBootstrapTest : public testing::Test + { + public: + PersistenceContainer container; + }; + + TEST_F(PopupListFactoryBootstrapTest, ItSetsFactoryOnContainer) + { + BootstrapPlugin(container); + EXPECT_NE(nullptr, container.popupListFactory); + } +} // namespace UKControllerPluginTest::List diff --git a/test/plugin/list/PopupListFactoryTest.cpp b/test/plugin/list/PopupListFactoryTest.cpp new file mode 100644 index 000000000..ae6fef3cb --- /dev/null +++ b/test/plugin/list/PopupListFactoryTest.cpp @@ -0,0 +1,75 @@ +#include "list/ListItem.h" +#include "list/PopupList.h" +#include "list/PopupListFactory.h" +#include "plugin/FunctionCallEventHandler.h" +#include "plugin/PopupMenuItem.h" + +using UKControllerPlugin::List::ListItem; +using UKControllerPlugin::List::ListItemCheckedStatus; +using UKControllerPlugin::List::PopupList; +using UKControllerPlugin::List::PopupListFactory; +using UKControllerPlugin::Plugin::FunctionCallEventHandler; +using UKControllerPlugin::Plugin::PopupMenuItem; + +namespace UKControllerPluginTest::List { + class PopupListFactoryTest : public testing::Test + { + public: + PopupListFactoryTest() + : itemProvider(std::make_shared>()), + factory(functionHandler, plugin) + { + } + + FunctionCallEventHandler functionHandler; + std::shared_ptr> itemProvider; + testing::NiceMock radarTarget; + testing::NiceMock flightplan; + testing::NiceMock plugin; + PopupListFactory factory; + }; + + TEST_F(PopupListFactoryTest, ItRegistersWithFunctionHandler) + { + static_cast(factory.Create(itemProvider, "Some Function")); + EXPECT_TRUE(functionHandler.HasCallbackByDescription("Some Function")); + } + + TEST_F(PopupListFactoryTest, CallbackFunctionTriggersItemSelected) + { + static_cast(factory.Create(itemProvider, "Some Function")); + EXPECT_CALL(*itemProvider, ItemSelected("foo")).Times(1); + + functionHandler.CallFunction( + functionHandler.ReserveNextDynamicFunctionId() - 1, "foo", flightplan, radarTarget, {}, RECT{}); + } + + TEST_F(PopupListFactoryTest, ListIsTriggeredWithCorrectId) + { + auto list = factory.Create(itemProvider, "Some Function"); + + std::list> items; + items.push_back( + std::make_shared("1first", "1second", true, false, ListItemCheckedStatus::NoCheckbox)); + + EXPECT_CALL(*itemProvider, ListColumns).Times(1).WillOnce(testing::Return(2)); + + EXPECT_CALL(*itemProvider, ListName).Times(1).WillOnce(testing::Return("Test list")); + + EXPECT_CALL(*itemProvider, ListItems).Times(1).WillOnce(testing::Return(items)); + + EXPECT_CALL(plugin, TriggerPopupList(RectEq(RECT{1, 2, 201, 402}), "Test list", 2)).Times(1); + + PopupMenuItem expectedItem1; + expectedItem1.firstValue = "1first"; + expectedItem1.secondValue = "1second"; + expectedItem1.disabled = true; + expectedItem1.fixedPosition = false; + expectedItem1.callbackFunctionId = functionHandler.ReserveNextDynamicFunctionId() - 1; + expectedItem1.checked = EuroScopePlugIn::POPUP_ELEMENT_NO_CHECKBOX; + + EXPECT_CALL(plugin, AddItemToPopupList(expectedItem1)).Times(1); + + list->Trigger({1, 2}); + } +} // namespace UKControllerPluginTest::List diff --git a/test/plugin/list/PopupListTest.cpp b/test/plugin/list/PopupListTest.cpp new file mode 100644 index 000000000..b61051ddf --- /dev/null +++ b/test/plugin/list/PopupListTest.cpp @@ -0,0 +1,82 @@ +#include "list/ListItem.h" +#include "list/PopupList.h" +#include "plugin/PopupMenuItem.h" + +using UKControllerPlugin::List::ListItem; +using UKControllerPlugin::List::ListItemCheckedStatus; +using UKControllerPlugin::List::PopupList; +using UKControllerPlugin::Plugin::PopupMenuItem; + +namespace UKControllerPluginTest::List { + class PopupListTest : public testing::Test + { + public: + PopupListTest() + : mockProvider(std::make_shared>()), list(mockProvider, plugin, 55) + { + } + std::shared_ptr> mockProvider; + testing::NiceMock plugin; + PopupList list; + }; + + TEST_F(PopupListTest, ItDoesntTriggerNoItems) + { + EXPECT_CALL(*mockProvider, ListItems) + .Times(1) + .WillOnce(testing::Return(std::list>())); + EXPECT_CALL(plugin, TriggerPopupList(testing::_, testing::_, testing::_)).Times(0); + + list.Trigger({1, 2}); + } + + TEST_F(PopupListTest, ItTriggersList) + { + std::list> items; + items.push_back( + std::make_shared("1first", "1second", true, false, ListItemCheckedStatus::NoCheckbox)); + items.push_back( + std::make_shared("2first", "2second", false, true, ListItemCheckedStatus::NotChecked)); + items.push_back(std::make_shared("3first", "3second", false, false, ListItemCheckedStatus::Checked)); + + EXPECT_CALL(*mockProvider, ListColumns).Times(1).WillOnce(testing::Return(2)); + + EXPECT_CALL(*mockProvider, ListName).Times(1).WillOnce(testing::Return("Test list")); + + EXPECT_CALL(*mockProvider, ListItems).Times(1).WillOnce(testing::Return(items)); + + EXPECT_CALL(plugin, TriggerPopupList(RectEq(RECT{1, 2, 201, 402}), "Test list", 2)).Times(1); + + PopupMenuItem expectedItem1; + expectedItem1.firstValue = "1first"; + expectedItem1.secondValue = "1second"; + expectedItem1.disabled = true; + expectedItem1.fixedPosition = false; + expectedItem1.callbackFunctionId = 55; + expectedItem1.checked = EuroScopePlugIn::POPUP_ELEMENT_NO_CHECKBOX; + + PopupMenuItem expectedItem2; + expectedItem2.firstValue = "2first"; + expectedItem2.secondValue = "2second"; + expectedItem2.disabled = false; + expectedItem2.fixedPosition = true; + expectedItem2.callbackFunctionId = 55; + expectedItem2.checked = EuroScopePlugIn::POPUP_ELEMENT_UNCHECKED; + + PopupMenuItem expectedItem3; + expectedItem3.firstValue = "3first"; + expectedItem3.secondValue = "3second"; + expectedItem3.disabled = false; + expectedItem3.fixedPosition = false; + expectedItem3.callbackFunctionId = 55; + expectedItem3.checked = EuroScopePlugIn::POPUP_ELEMENT_CHECKED; + + EXPECT_CALL(plugin, AddItemToPopupList(expectedItem1)).Times(1); + + EXPECT_CALL(plugin, AddItemToPopupList(expectedItem2)).Times(1); + + EXPECT_CALL(plugin, AddItemToPopupList(expectedItem3)).Times(1); + + list.Trigger({1, 2}); + } +} // namespace UKControllerPluginTest::List diff --git a/test/plugin/mock/MockCallsignSelectionList.h b/test/plugin/mock/MockCallsignSelectionList.h deleted file mode 100644 index 1a19bf368..000000000 --- a/test/plugin/mock/MockCallsignSelectionList.h +++ /dev/null @@ -1,11 +0,0 @@ -#include "aircraft/CallsignSelectionListInterface.h" - -using UKControllerPlugin::Aircraft::CallsignSelectionListInterface; - -namespace UKControllerPluginTest::Aircraft { - class MockCallsignSelectionList : public CallsignSelectionListInterface - { - public: - MOCK_METHOD(void, TriggerList, (const POINT&), (override)); - }; -} // namespace UKControllerPluginTest::Aircraft diff --git a/test/plugin/mock/MockGraphicsInterface.h b/test/plugin/mock/MockGraphicsInterface.h index 20ee83195..a0f667df7 100644 --- a/test/plugin/mock/MockGraphicsInterface.h +++ b/test/plugin/mock/MockGraphicsInterface.h @@ -8,101 +8,116 @@ namespace UKControllerPluginTest { class MockGraphicsInterface : public UKControllerPlugin::Windows::GdiGraphicsInterface { public: - ~MockGraphicsInterface() override = default; - MOCK_METHOD3(DrawLine, void(const Gdiplus::Pen &, const Gdiplus::Point &, const Gdiplus::Point &)); - MOCK_METHOD2(DrawPath, void(const Gdiplus::GraphicsPath &, const Gdiplus::Pen &)); - MOCK_METHOD2(DrawRectRectF, void(const Gdiplus::RectF &, const Gdiplus::Pen &)); - MOCK_METHOD2(DrawRectRect, void(const Gdiplus::Rect &, const Gdiplus::Pen &)); - MOCK_METHOD2(DrawRectRegularRect, void(const RECT &, const Gdiplus::Pen &)); - MOCK_METHOD2(DrawCircle, void(const Gdiplus::RectF &, const Gdiplus::Pen &)); - MOCK_METHOD2(DrawCircle, void(const Gdiplus::Rect &, const Gdiplus::Pen &)); - MOCK_METHOD2(DrawDiamond, void(const Gdiplus::RectF &, const Gdiplus::Pen &)); - MOCK_METHOD3(DrawStringRectF, void(std::wstring, const Gdiplus::RectF &, const Gdiplus::Brush &)); - MOCK_METHOD3(DrawStringRect, void(std::wstring, const Gdiplus::Rect &, const Gdiplus::Brush &)); - MOCK_METHOD3(DrawStringRegularRect, void(std::wstring, const RECT &, const Gdiplus::Brush &)); - MOCK_METHOD2(DrawCircle, void(const Gdiplus::RectF &, const Gdiplus::Brush &)); - MOCK_METHOD2(FillRectRect, void(const Gdiplus::Rect &, const Gdiplus::Brush &)); - MOCK_METHOD2(FillRectRectF, void(const Gdiplus::RectF &, const Gdiplus::Brush &)); - MOCK_METHOD2(FillRectRegularRect, void(const RECT &, const Gdiplus::Brush &)); - MOCK_METHOD1(SetAntialias, void(bool)); - MOCK_METHOD1(SetDeviceHandle, void(HDC &)); - - void Clipped(Gdiplus::Region&, std::function func) override - { - func(); - } - - void Translated(Gdiplus::REAL, Gdiplus::REAL, std::function func) override - { - func(); - } - - void Scaled(Gdiplus::REAL, Gdiplus::REAL, std::function func) override - { - func(); - } - - void Rotated(Gdiplus::REAL, std::function func) override - { - func(); - } - - MOCK_METHOD(std::shared_ptr, GetTransform, (), (override)); - MOCK_METHOD(Gdiplus::RectF, GetClipBounds, (), (override)); - MOCK_METHOD(void, FillPolygon, (Gdiplus::Point*, const Gdiplus::Brush&, int), (override)); - - void DrawRect(const Gdiplus::RectF& area, const Gdiplus::Pen& pen) override - { - this->DrawRectRectF(area, pen); - } - - void DrawRect(const Gdiplus::Rect& area, const Gdiplus::Pen& pen) override - { - this->DrawRectRect(area, pen); - } - - void DrawRect(const RECT& area, const Gdiplus::Pen& pen) override - { - this->DrawRectRegularRect(area, pen); - } - - void DrawString(std::wstring text, const Gdiplus::RectF& area, const Gdiplus::Brush& brush) override - { - this->DrawStringRectF(text, area, brush); - } - - void DrawString(std::wstring text, const Gdiplus::Rect& area, const Gdiplus::Brush& brush) override - { - this->DrawStringRect(text, area, brush); - } - - void DrawString(std::wstring text, const RECT& area, const Gdiplus::Brush& brush) override - { - this->DrawStringRegularRect(text, area, brush); - } - - void FillRect(const Gdiplus::RectF& area, const Gdiplus::Brush& brush) override - { - this->FillRectRectF(area, brush); - } - - void FillRect(const Gdiplus::Rect& area, const Gdiplus::Brush& brush) override - { - this->FillRectRect(area, brush); - } - - void FillRect(const RECT& area, const Gdiplus::Brush& brush) override - { - this->FillRectRegularRect(area, brush); - } - - MOCK_METHOD( - void, - DrawLine, (const Gdiplus::Pen&, const Gdiplus::PointF&, const Gdiplus::PointF&), - (override) - ); - MOCK_METHOD(void, FillCircle, (const Gdiplus::RectF&, const Gdiplus::Brush&), (override)); - MOCK_METHOD(void, FillDiamond, (const Gdiplus::RectF&, const Gdiplus::Brush&), (override)); + ~MockGraphicsInterface() override = default; + MOCK_METHOD3(DrawLine, void(const Gdiplus::Pen&, const Gdiplus::Point&, const Gdiplus::Point&)); + MOCK_METHOD2(DrawPath, void(const Gdiplus::GraphicsPath&, const Gdiplus::Pen&)); + MOCK_METHOD2(DrawRectRectF, void(const Gdiplus::RectF&, const Gdiplus::Pen&)); + MOCK_METHOD2(DrawRectRect, void(const Gdiplus::Rect&, const Gdiplus::Pen&)); + MOCK_METHOD2(DrawRectRegularRect, void(const RECT&, const Gdiplus::Pen&)); + MOCK_METHOD2(DrawCircle, void(const Gdiplus::RectF&, const Gdiplus::Pen&)); + MOCK_METHOD2(DrawCircle, void(const Gdiplus::Rect&, const Gdiplus::Pen&)); + MOCK_METHOD2(DrawDiamond, void(const Gdiplus::RectF&, const Gdiplus::Pen&)); + MOCK_METHOD3(DrawStringRectF, void(std::wstring, const Gdiplus::RectF&, const Gdiplus::Brush&)); + MOCK_METHOD3(DrawStringRect, void(std::wstring, const Gdiplus::Rect&, const Gdiplus::Brush&)); + MOCK_METHOD3(DrawStringRegularRect, void(std::wstring, const RECT&, const Gdiplus::Brush&)); + MOCK_METHOD5( + DrawStringRectFiveArgs, + void( + const std::wstring&, + const Gdiplus::Rect&, + const Gdiplus::Brush&, + const Gdiplus::StringFormat&, + const Gdiplus::Font&)); + MOCK_METHOD2(DrawCircle, void(const Gdiplus::RectF&, const Gdiplus::Brush&)); + MOCK_METHOD2(FillRectRect, void(const Gdiplus::Rect&, const Gdiplus::Brush&)); + MOCK_METHOD2(FillRectRectF, void(const Gdiplus::RectF&, const Gdiplus::Brush&)); + MOCK_METHOD2(FillRectRegularRect, void(const RECT&, const Gdiplus::Brush&)); + MOCK_METHOD1(SetAntialias, void(bool)); + MOCK_METHOD1(SetDeviceHandle, void(HDC&)); + + void Clipped(Gdiplus::Region&, std::function func) override + { + func(); + } + + void Translated(Gdiplus::REAL, Gdiplus::REAL, std::function func) override + { + func(); + } + + void Scaled(Gdiplus::REAL, Gdiplus::REAL, std::function func) override + { + func(); + } + + void Rotated(Gdiplus::REAL, std::function func) override + { + func(); + } + + MOCK_METHOD(std::shared_ptr, GetTransform, (), (override)); + MOCK_METHOD(Gdiplus::RectF, GetClipBounds, (), (override)); + MOCK_METHOD(void, FillPolygon, (Gdiplus::Point*, const Gdiplus::Brush&, int), (override)); + + void DrawRect(const Gdiplus::RectF& area, const Gdiplus::Pen& pen) override + { + this->DrawRectRectF(area, pen); + } + + void DrawRect(const Gdiplus::Rect& area, const Gdiplus::Pen& pen) override + { + this->DrawRectRect(area, pen); + } + + void DrawRect(const RECT& area, const Gdiplus::Pen& pen) override + { + this->DrawRectRegularRect(area, pen); + } + + void DrawString(std::wstring text, const Gdiplus::RectF& area, const Gdiplus::Brush& brush) override + { + this->DrawStringRectF(text, area, brush); + } + + void DrawString(std::wstring text, const Gdiplus::Rect& area, const Gdiplus::Brush& brush) override + { + this->DrawStringRect(text, area, brush); + } + + void DrawString(std::wstring text, const RECT& area, const Gdiplus::Brush& brush) override + { + this->DrawStringRegularRect(text, area, brush); + } + + void DrawString( + const std::wstring& text, + const Gdiplus::Rect& area, + const Gdiplus::Brush& brush, + const Gdiplus::StringFormat& format, + const Gdiplus::Font& font) override + { + this->DrawStringRectFiveArgs(text, area, brush, format, font); + } + + void FillRect(const Gdiplus::RectF& area, const Gdiplus::Brush& brush) override + { + this->FillRectRectF(area, brush); + } + + void FillRect(const Gdiplus::Rect& area, const Gdiplus::Brush& brush) override + { + this->FillRectRect(area, brush); + } + + void FillRect(const RECT& area, const Gdiplus::Brush& brush) override + { + this->FillRectRegularRect(area, brush); + } + + MOCK_METHOD( + void, DrawLine, (const Gdiplus::Pen&, const Gdiplus::PointF&, const Gdiplus::PointF&), (override)); + MOCK_METHOD(void, FillCircle, (const Gdiplus::RectF&, const Gdiplus::Brush&), (override)); + MOCK_METHOD(void, FillDiamond, (const Gdiplus::RectF&, const Gdiplus::Brush&), (override)); }; - } // namespace Windows -} // namespace UKControllerPluginTest + } // namespace Windows +} // namespace UKControllerPluginTest diff --git a/test/plugin/mock/MockListItemProvider.h b/test/plugin/mock/MockListItemProvider.h new file mode 100644 index 000000000..621779088 --- /dev/null +++ b/test/plugin/mock/MockListItemProvider.h @@ -0,0 +1,16 @@ +#pragma once +#include "list/ListItemProviderInterface.h" + +using UKControllerPlugin::List::ListItem; +using UKControllerPlugin::List::ListItemProviderInterface; + +namespace UKControllerPluginTest::List { + class MockListItemProvider : public ListItemProviderInterface + { + public: + MOCK_METHOD(int, ListColumns, (), (override)); + MOCK_METHOD(std::string, ListName, (), (override)); + MOCK_METHOD(std::list>, ListItems, (), (override)); + MOCK_METHOD(void, ItemSelected, (const std::string&), (override)); + }; +} // namespace UKControllerPluginTest::List diff --git a/test/plugin/mock/MockMenuToggleableDisplay.h b/test/plugin/mock/MockMenuToggleableDisplay.h new file mode 100644 index 000000000..eed682f27 --- /dev/null +++ b/test/plugin/mock/MockMenuToggleableDisplay.h @@ -0,0 +1,13 @@ +#include "radarscreen/MenuToggleableDisplayInterface.h" + +using UKControllerPlugin::RadarScreen::MenuToggleableDisplayInterface; + +namespace UKControllerPluginTest::RadarScreen { + class MockMenuToggleableDisplay : public MenuToggleableDisplayInterface + { + public: + MOCK_METHOD(std::string, MenuItem, (), (const, override)); + MOCK_METHOD(bool, IsVisible, (), (const, override)); + MOCK_METHOD(void, Toggle, (), (override)); + }; +} // namespace UKControllerPluginTest::RadarScreen diff --git a/test/plugin/mock/MockPopupList.h b/test/plugin/mock/MockPopupList.h new file mode 100644 index 000000000..fec7529a6 --- /dev/null +++ b/test/plugin/mock/MockPopupList.h @@ -0,0 +1,12 @@ +#pragma once +#include "list/PopupListInterface.h" + +using UKControllerPlugin::List::PopupListInterface; + +namespace UKControllerPluginTest::List { + class MockPopupList : public PopupListInterface + { + public: + MOCK_METHOD(void, Trigger, (const POINT&), (override)); + }; +} // namespace UKControllerPluginTest::List diff --git a/test/plugin/pch/pch.h b/test/plugin/pch/pch.h index 188462eca..236d72ca7 100644 --- a/test/plugin/pch/pch.h +++ b/test/plugin/pch/pch.h @@ -22,7 +22,6 @@ #include "../mock/MockAircraftTypeMapper.h" #include "../mock/MockAirfieldGroup.h" #include "../mock/MockAsrEventHandlerInterface.h" -#include "../mock/MockCallsignSelectionList.h" #include "../mock/MockCallsignSelectionProvider.h" #include "../mock/MockConfigurableDisplay.h" #include "../mock/MockConnection.h" @@ -43,8 +42,11 @@ #include "../mock/MockHistoryTrailDialog.h" #include "../mock/MockHistoryTrailRepository.h" #include "../mock/MockIntegrationActionProcessor.h" +#include "../mock/MockListItemProvider.h" +#include "../mock/MockMenuToggleableDisplay.h" #include "../mock/MockMetarEventHandler.h" #include "../mock/MockOutboundIntegrationEventHandler.h" +#include "../mock/MockPopupList.h" #include "../mock/MockPushEventConnection.h" #include "../mock/MockPushEventProcessor.h" #include "../mock/MockRadarRendererableInterface.h" diff --git a/test/plugin/radarscreen/MenuToggleableDisplayFactoryTest.cpp b/test/plugin/radarscreen/MenuToggleableDisplayFactoryTest.cpp new file mode 100644 index 000000000..a0f12f972 --- /dev/null +++ b/test/plugin/radarscreen/MenuToggleableDisplayFactoryTest.cpp @@ -0,0 +1,42 @@ +#include "plugin/FunctionCallEventHandler.h" +#include "radarscreen/ConfigurableDisplayCollection.h" +#include "radarscreen/MenuToggleableDisplayFactory.h" + +using UKControllerPlugin::Plugin::FunctionCallEventHandler; +using UKControllerPlugin::RadarScreen::ConfigurableDisplayCollection; +using UKControllerPlugin::RadarScreen::MenuToggleableDisplayFactory; + +namespace UKControllerPluginTest::RadarScreen { + class MenuToggleableDisplayFactoryTest : public testing::Test + { + public: + MenuToggleableDisplayFactoryTest() + : mockDisplay(std::make_shared>()), + factory(callbacks, displays) + { + } + + testing::NiceMock mockFlightplan; + testing::NiceMock mockRadarTarget; + std::shared_ptr> mockDisplay; + FunctionCallEventHandler callbacks; + ConfigurableDisplayCollection displays; + MenuToggleableDisplayFactory factory; + }; + + TEST_F(MenuToggleableDisplayFactoryTest, ItRegistersCallbackFunction) + { + EXPECT_CALL(*mockDisplay, Toggle).Times(1); + + factory.RegisterDisplay(mockDisplay, "Test"); + EXPECT_TRUE(callbacks.HasCallbackByDescription("Test")); + callbacks.CallFunction( + callbacks.ReserveNextDynamicFunctionId() - 1, "", mockFlightplan, mockRadarTarget, {}, {}); + } + + TEST_F(MenuToggleableDisplayFactoryTest, ItRegistersConfigurableDisplay) + { + factory.RegisterDisplay(mockDisplay, "Test"); + EXPECT_EQ(1, displays.CountDisplays()); + } +} // namespace UKControllerPluginTest::RadarScreen diff --git a/test/plugin/radarscreen/ToggleDisplayFromMenuTest.cpp b/test/plugin/radarscreen/ToggleDisplayFromMenuTest.cpp new file mode 100644 index 000000000..f234e651c --- /dev/null +++ b/test/plugin/radarscreen/ToggleDisplayFromMenuTest.cpp @@ -0,0 +1,57 @@ +#include "plugin/PopupMenuItem.h" +#include "radarscreen/ToggleDisplayFromMenu.h" + +using UKControllerPlugin::Plugin::PopupMenuItem; +using UKControllerPlugin::RadarScreen::ToggleDisplayFromMenu; + +namespace UKControllerPluginTest::RadarScreen { + class ToggleDisplayFromMenuTest : public testing::Test + { + public: + ToggleDisplayFromMenuTest() + : displayMock(std::make_shared>()), menu(displayMock, 55) + { + } + + std::shared_ptr> displayMock; + ToggleDisplayFromMenu menu; + }; + + TEST_F(ToggleDisplayFromMenuTest, ConfigureTogglesMenu) + { + EXPECT_CALL(*displayMock, Toggle).Times(1); + + menu.Configure(1, "", {}); + } + + TEST_F(ToggleDisplayFromMenuTest, ItReturnsMenuItemChecked) + { + EXPECT_CALL(*displayMock, MenuItem).Times(1).WillOnce(testing::Return("Test Item")); + + EXPECT_CALL(*displayMock, IsVisible).Times(1).WillOnce(testing::Return(true)); + + PopupMenuItem expected; + expected.firstValue = "Test Item"; + expected.fixedPosition = false; + expected.disabled = false; + expected.checked = EuroScopePlugIn::POPUP_ELEMENT_CHECKED; + expected.callbackFunctionId = 55; + + EXPECT_EQ(expected, menu.GetConfigurationMenuItem()); + } + + TEST_F(ToggleDisplayFromMenuTest, ItReturnsMenuItemNotChecked) + { + EXPECT_CALL(*displayMock, MenuItem).Times(1).WillOnce(testing::Return("Test Item")); + EXPECT_CALL(*displayMock, IsVisible).Times(1).WillOnce(testing::Return(false)); + + PopupMenuItem expected; + expected.firstValue = "Test Item"; + expected.fixedPosition = false; + expected.disabled = false; + expected.checked = EuroScopePlugIn::POPUP_ELEMENT_UNCHECKED; + expected.callbackFunctionId = 55; + + EXPECT_EQ(expected, menu.GetConfigurationMenuItem()); + } +} // namespace UKControllerPluginTest::RadarScreen diff --git a/test/plugin/wake/FollowingWakeCallsignProviderTest.cpp b/test/plugin/wake/FollowingWakeCallsignProviderTest.cpp new file mode 100644 index 000000000..95df3059e --- /dev/null +++ b/test/plugin/wake/FollowingWakeCallsignProviderTest.cpp @@ -0,0 +1,75 @@ +#include "wake/FollowingWakeCallsignProvider.h" +#include "wake/WakeCalculatorOptions.h" + +using UKControllerPlugin::Wake::FollowingWakeCallsignProvider; +using UKControllerPlugin::Wake::WakeCalculatorOptions; + +namespace UKControllerPluginTest::Wake { + class FollowingWakeCallsignProviderTest : public testing::Test + { + public: + FollowingWakeCallsignProviderTest() + : options(std::make_shared()), callsignProvider(plugin, options) + { + flightplan1 = std::make_shared>(); + flightplan2 = std::make_shared>(); + radarTarget1 = std::make_shared>(); + radarTarget2 = std::make_shared>(); + + ON_CALL(*flightplan1, GetCallsign).WillByDefault(testing::Return("BAW123")); + ON_CALL(*flightplan1, GetOrigin).WillByDefault(testing::Return("EGLL")); + + ON_CALL(*flightplan2, GetCallsign).WillByDefault(testing::Return("BAW456")); + + options->LeadAircraft("BAW123"); + } + + std::shared_ptr> flightplan1; + std::shared_ptr> flightplan2; + std::shared_ptr> radarTarget1; + std::shared_ptr> radarTarget2; + std::shared_ptr options; + testing::NiceMock plugin; + FollowingWakeCallsignProvider callsignProvider; + }; + + TEST_F(FollowingWakeCallsignProviderTest, ItReturnsRelevantCallsigns) + { + ON_CALL(plugin, GetFlightplanForCallsign("BAW123")).WillByDefault(testing::Return(flightplan1)); + + ON_CALL(*flightplan2, GetOrigin).WillByDefault(testing::Return("EGLL")); + plugin.AddAllFlightplansItem({flightplan1, radarTarget1}); + plugin.AddAllFlightplansItem({flightplan2, radarTarget2}); + + EXPECT_EQ(std::set{"BAW456"}, callsignProvider.GetCallsigns()); + } + + TEST_F(FollowingWakeCallsignProviderTest, ItIgnoresCallsignIfOriginDoesntMatchLead) + { + ON_CALL(plugin, GetFlightplanForCallsign("BAW123")).WillByDefault(testing::Return(flightplan1)); + + ON_CALL(*flightplan2, GetOrigin).WillByDefault(testing::Return("EGKK")); + plugin.AddAllFlightplansItem({flightplan1, radarTarget1}); + plugin.AddAllFlightplansItem({flightplan2, radarTarget2}); + + EXPECT_TRUE(callsignProvider.GetCallsigns().empty()); + } + + TEST_F(FollowingWakeCallsignProviderTest, ItReturnsEmptyIfNoFlightplanForLead) + { + ON_CALL(plugin, GetFlightplanForCallsign("BAW123")).WillByDefault(testing::Return(nullptr)); + plugin.AddAllFlightplansItem({flightplan1, radarTarget1}); + plugin.AddAllFlightplansItem({flightplan2, radarTarget2}); + + EXPECT_TRUE(callsignProvider.GetCallsigns().empty()); + } + + TEST_F(FollowingWakeCallsignProviderTest, SelectingACallsignSetsFollowingCallsign) + { + options->LeadAircraft("BAW456"); + options->FollowingAircraft("BAW999"); + callsignProvider.CallsignSelected("BAW123"); + EXPECT_EQ("BAW123", options->FollowingAircraft()); + EXPECT_EQ("BAW456", options->LeadAircraft()); + } +} // namespace UKControllerPluginTest::Wake diff --git a/test/plugin/wake/LeadWakeCallsignProviderTest.cpp b/test/plugin/wake/LeadWakeCallsignProviderTest.cpp new file mode 100644 index 000000000..eb3c8f2d7 --- /dev/null +++ b/test/plugin/wake/LeadWakeCallsignProviderTest.cpp @@ -0,0 +1,142 @@ +#include "controller/ActiveCallsign.h" +#include "controller/ControllerPosition.h" +#include "ownership/AirfieldServiceProviderCollection.h" +#include "ownership/ServiceProvision.h" +#include "wake/LeadWakeCallsignProvider.h" +#include "wake/WakeCalculatorOptions.h" + +using UKControllerPlugin::Controller::ActiveCallsign; +using UKControllerPlugin::Controller::ControllerPosition; +using UKControllerPlugin::Ownership::AirfieldServiceProviderCollection; +using UKControllerPlugin::Ownership::ServiceProvision; +using UKControllerPlugin::Ownership::ServiceType; +using UKControllerPlugin::Wake::LeadWakeCallsignProvider; +using UKControllerPlugin::Wake::WakeCalculatorOptions; + +namespace UKControllerPluginTest::Wake { + class LeadWakeCallsignProviderTest : public testing::Test + { + public: + LeadWakeCallsignProviderTest() + : position(1, "EGLL_TWR", 118.5, {"EGLL"}, true, false), + callsign(std::make_shared("EGKK_TWR", "Test", position, true)), + options(std::make_shared()), callsignProvider(serviceProviders, plugin, options) + { + flightplan1 = std::make_shared>(); + flightplan2 = std::make_shared>(); + radarTarget1 = std::make_shared>(); + radarTarget2 = std::make_shared>(); + + ON_CALL(*flightplan1, GetCallsign).WillByDefault(testing::Return("BAW123")); + ON_CALL(*flightplan1, GetOrigin).WillByDefault(testing::Return("EGLL")); + + ON_CALL(*flightplan2, GetCallsign).WillByDefault(testing::Return("BAW456")); + ON_CALL(*flightplan2, GetOrigin).WillByDefault(testing::Return("EGLL")); + } + + ControllerPosition position; + std::shared_ptr callsign; + std::shared_ptr> flightplan1; + std::shared_ptr> flightplan2; + std::shared_ptr> radarTarget1; + std::shared_ptr> radarTarget2; + std::shared_ptr options; + testing::NiceMock plugin; + AirfieldServiceProviderCollection serviceProviders; + LeadWakeCallsignProvider callsignProvider; + }; + + TEST_F(LeadWakeCallsignProviderTest, ItReturnsRelevantCallsigns) + { + serviceProviders.SetProvidersForAirfield( + "EGLL", + std::vector>{ + std::make_shared(ServiceType::Tower, callsign)}); + + plugin.AddAllFlightplansItem({flightplan1, radarTarget1}); + plugin.AddAllFlightplansItem({flightplan2, radarTarget2}); + + EXPECT_EQ(std::set({"BAW123", "BAW456"}), callsignProvider.GetCallsigns()); + } + + TEST_F(LeadWakeCallsignProviderTest, ItReturnsRelevantCallsignsGroundControl) + { + serviceProviders.SetProvidersForAirfield( + "EGLL", + std::vector>{ + std::make_shared(ServiceType::Ground, callsign)}); + + plugin.AddAllFlightplansItem({flightplan1, radarTarget1}); + plugin.AddAllFlightplansItem({flightplan2, radarTarget2}); + + EXPECT_EQ(std::set({"BAW123", "BAW456"}), callsignProvider.GetCallsigns()); + } + + TEST_F(LeadWakeCallsignProviderTest, ItReturnsRelevantCallsignsDeliveryControl) + { + serviceProviders.SetProvidersForAirfield( + "EGLL", + std::vector>{ + std::make_shared(ServiceType::Delivery, callsign)}); + + plugin.AddAllFlightplansItem({flightplan1, radarTarget1}); + plugin.AddAllFlightplansItem({flightplan2, radarTarget2}); + + EXPECT_EQ(std::set({"BAW123", "BAW456"}), callsignProvider.GetCallsigns()); + } + + TEST_F(LeadWakeCallsignProviderTest, ItSkipsCallsignsIfWrongService) + { + serviceProviders.SetProvidersForAirfield( + "EGLL", + std::vector>{ + std::make_shared(ServiceType::Approach, callsign)}); + + plugin.AddAllFlightplansItem({flightplan1, radarTarget1}); + + EXPECT_TRUE(callsignProvider.GetCallsigns().empty()); + } + + TEST_F(LeadWakeCallsignProviderTest, ItSkipsCallsignsIfAirfieldService) + { + serviceProviders.SetProvidersForAirfield( + "EGKK", + std::vector>{ + std::make_shared(ServiceType::Tower, callsign)}); + + plugin.AddAllFlightplansItem({flightplan1, radarTarget1}); + + EXPECT_TRUE(callsignProvider.GetCallsigns().empty()); + } + + TEST_F(LeadWakeCallsignProviderTest, ItReturnsNoCallsignsIfNoServicesProvided) + { + plugin.AddAllFlightplansItem({flightplan1, radarTarget1}); + EXPECT_TRUE(callsignProvider.GetCallsigns().empty()); + } + + TEST_F(LeadWakeCallsignProviderTest, ItReturnsNoCallsignsIfNoFlightplans) + { + serviceProviders.SetProvidersForAirfield( + "EGLL", + std::vector>{ + std::make_shared(ServiceType::Tower, callsign)}); + + EXPECT_TRUE(callsignProvider.GetCallsigns().empty()); + } + + TEST_F(LeadWakeCallsignProviderTest, SelectingACallsignSetsLeadCallsign) + { + callsignProvider.CallsignSelected("BAW123"); + EXPECT_EQ("BAW123", options->LeadAircraft()); + } + + TEST_F(LeadWakeCallsignProviderTest, SelectingACallsignUnsetsFollowingCallsign) + { + options->LeadAircraft("BAW456"); + options->FollowingAircraft("BAW999"); + callsignProvider.CallsignSelected("BAW123"); + EXPECT_EQ("BAW123", options->LeadAircraft()); + EXPECT_TRUE(options->FollowingAircraft().empty()); + } +} // namespace UKControllerPluginTest::Wake diff --git a/test/plugin/wake/WakeCalculatorDisplayTest.cpp b/test/plugin/wake/WakeCalculatorDisplayTest.cpp new file mode 100644 index 000000000..ac804f44a --- /dev/null +++ b/test/plugin/wake/WakeCalculatorDisplayTest.cpp @@ -0,0 +1,159 @@ +#include "euroscope/UserSetting.h" +#include "wake/WakeCalculatorDisplay.h" +#include "wake/WakeCalculatorOptions.h" + +using UKControllerPlugin::Euroscope::UserSetting; +using UKControllerPlugin::Wake::WakeCalculatorDisplay; +using UKControllerPlugin::Wake::WakeCalculatorOptions; + +namespace UKControllerPluginTest::Wake { + class WakeCalculatorDisplayTest : public testing::Test + { + public: + WakeCalculatorDisplayTest() + : userSettings(mockAsrProvider), + leadCallsignSelector(std::make_shared>()), + followingCallsignSelector(std::make_shared>()), + wakesSchemeSelector(std::make_shared>()), + options(std::make_shared()), + display(options, leadCallsignSelector, followingCallsignSelector, wakesSchemeSelector, plugin, 1) + { + } + + testing::NiceMock mockAsrProvider; + UserSetting userSettings; + testing::NiceMock radarScreen; + testing::NiceMock plugin; + std::shared_ptr> leadCallsignSelector; + std::shared_ptr> followingCallsignSelector; + std::shared_ptr> wakesSchemeSelector; + std::shared_ptr options; + WakeCalculatorDisplay display; + }; + + TEST_F(WakeCalculatorDisplayTest, ItHasAMenuItem) + { + EXPECT_EQ("Display Wake Turbulence Calculator", display.MenuItem()); + } + + TEST_F(WakeCalculatorDisplayTest, ItCanBeViewToggled) + { + EXPECT_FALSE(display.IsVisible()); + display.Toggle(); + EXPECT_TRUE(display.IsVisible()); + display.Toggle(); + EXPECT_FALSE(display.IsVisible()); + } + + TEST_F(WakeCalculatorDisplayTest, ItHasADefaultPosition) + { + const auto position = display.Position(); + EXPECT_EQ(200, position.x); + EXPECT_EQ(200, position.y); + } + + TEST_F(WakeCalculatorDisplayTest, ItCanBeMoved) + { + display.Move({300, 400, 500, 600}, ""); + const auto position = display.Position(); + EXPECT_EQ(300, position.x); + EXPECT_EQ(400, position.y); + } + + TEST_F(WakeCalculatorDisplayTest, ItCanHaveItsPositionResetToDefault) + { + display.Move({300, 400, 500, 600}, ""); + display.ResetPosition(); + const auto position = display.Position(); + EXPECT_EQ(200, position.x); + EXPECT_EQ(200, position.y); + } + + TEST_F(WakeCalculatorDisplayTest, AsrLoadingLoadsPosition) + { + EXPECT_CALL(mockAsrProvider, GetKey(testing::_)).Times(1).WillRepeatedly(testing::Return("")); + + EXPECT_CALL(mockAsrProvider, GetKey("wakeCalculatorXPosition")).Times(1).WillOnce(testing::Return("250")); + + EXPECT_CALL(mockAsrProvider, GetKey("wakeCalculatorYPosition")).Times(1).WillOnce(testing::Return("150")); + + display.AsrLoadedEvent(userSettings); + const auto position = display.Position(); + EXPECT_EQ(250, position.x); + EXPECT_EQ(150, position.y); + } + + TEST_F(WakeCalculatorDisplayTest, AsrLoadingLoadsVisibility) + { + EXPECT_CALL(mockAsrProvider, GetKey(testing::_)).Times(2).WillRepeatedly(testing::Return("")); + + EXPECT_CALL(mockAsrProvider, GetKey("wakeCalculatorVisibility")).Times(1).WillOnce(testing::Return("1")); + + display.AsrLoadedEvent(userSettings); + EXPECT_TRUE(display.IsVisible()); + } + + TEST_F(WakeCalculatorDisplayTest, AsrLoadingLoadsDefaultPosition) + { + display.Move({300, 400, 500, 600}, ""); + EXPECT_CALL(mockAsrProvider, GetKey(testing::_)).Times(1).WillRepeatedly(testing::Return("")); + EXPECT_CALL(mockAsrProvider, GetKey("wakeCalculatorXPosition")).Times(1).WillOnce(testing::Return("")); + EXPECT_CALL(mockAsrProvider, GetKey("wakeCalculatorYPosition")).Times(1).WillOnce(testing::Return("")); + + display.AsrLoadedEvent(userSettings); + const auto position = display.Position(); + EXPECT_EQ(200, position.x); + EXPECT_EQ(200, position.y); + } + + TEST_F(WakeCalculatorDisplayTest, AsrLoadingLoadsDefaultVisibility) + { + display.Toggle(); + EXPECT_CALL(mockAsrProvider, GetKey(testing::_)).Times(2).WillRepeatedly(testing::Return("")); + EXPECT_CALL(mockAsrProvider, GetKey("wakeCalculatorVisibility")).Times(1).WillOnce(testing::Return("")); + + display.AsrLoadedEvent(userSettings); + EXPECT_FALSE(display.IsVisible()); + } + + TEST_F(WakeCalculatorDisplayTest, AsrClosingSavesFields) + { + display.Toggle(); + display.Move({300, 400, 500, 600}, ""); + EXPECT_CALL(mockAsrProvider, SetKey("wakeCalculatorVisibility", "Wake Calculator Visibility", "1")).Times(1); + EXPECT_CALL(mockAsrProvider, SetKey("wakeCalculatorXPosition", "Wake Calculator X Position", "300")).Times(1); + EXPECT_CALL(mockAsrProvider, SetKey("wakeCalculatorYPosition", "Wake Calculator Y Position", "400")).Times(1); + + display.AsrClosingEvent(userSettings); + } + + TEST_F(WakeCalculatorDisplayTest, LeadCallsignClickTriggersList) + { + EXPECT_CALL(*leadCallsignSelector, Trigger(PointEq(POINT({1, 2})))).Times(1); + + display.LeftClick(radarScreen, 1, "leadcallsign", {1, 2}, {}); + } + + TEST_F(WakeCalculatorDisplayTest, FollowingCallsignClickTriggersList) + { + EXPECT_CALL(*followingCallsignSelector, Trigger(PointEq(POINT({1, 2})))).Times(1); + + display.LeftClick(radarScreen, 1, "followcallsign", {1, 2}, {}); + } + + TEST_F(WakeCalculatorDisplayTest, SchemeClickTriggersList) + { + EXPECT_CALL(*wakesSchemeSelector, Trigger(PointEq(POINT({1, 2})))).Times(1); + + display.LeftClick(radarScreen, 1, "scheme", {1, 2}, {}); + } + + TEST_F(WakeCalculatorDisplayTest, IntermediateClickTriggersList) + { + EXPECT_FALSE(options->Intermediate()); + display.LeftClick(radarScreen, 1, "intermediate", {1, 2}, {}); + EXPECT_TRUE(options->Intermediate()); + display.LeftClick(radarScreen, 1, "intermediate", {1, 2}, {}); + EXPECT_FALSE(options->Intermediate()); + } +} // namespace UKControllerPluginTest::Wake diff --git a/test/plugin/wake/WakeCalculatorOptionsTest.cpp b/test/plugin/wake/WakeCalculatorOptionsTest.cpp new file mode 100644 index 000000000..a4f3e6900 --- /dev/null +++ b/test/plugin/wake/WakeCalculatorOptionsTest.cpp @@ -0,0 +1,67 @@ +#include "wake/WakeCalculatorOptions.h" + +using UKControllerPlugin::Wake::WakeCalculatorOptions; + +namespace UKControllerPluginTest::Wake { + class WakeCalculatorOptionsTest : public testing::Test + { + public: + WakeCalculatorOptions options; + }; + + TEST_F(WakeCalculatorOptionsTest, LeadAircraftStartsBlank) + { + EXPECT_TRUE(options.LeadAircraft().empty()); + } + + TEST_F(WakeCalculatorOptionsTest, FollowingAircraftStartsBlank) + { + EXPECT_TRUE(options.FollowingAircraft().empty()); + } + + TEST_F(WakeCalculatorOptionsTest, SchemeStartsEmpty) + { + EXPECT_TRUE(options.Scheme().empty()); + } + + TEST_F(WakeCalculatorOptionsTest, SchemeMapperStartsEmpty) + { + EXPECT_EQ(nullptr, options.SchemeMapper()); + } + + TEST_F(WakeCalculatorOptionsTest, IntemediateStartsFalse) + { + EXPECT_FALSE(options.Intermediate()); + } + + TEST_F(WakeCalculatorOptionsTest, ItSetsLeadAircraft) + { + options.LeadAircraft("BAW123"); + EXPECT_EQ("BAW123", options.LeadAircraft()); + } + + TEST_F(WakeCalculatorOptionsTest, ItSetsFollowingAircraft) + { + options.FollowingAircraft("BAW123"); + EXPECT_EQ("BAW123", options.FollowingAircraft()); + } + + TEST_F(WakeCalculatorOptionsTest, ItSetsScheme) + { + options.Scheme("ABC"); + EXPECT_EQ("ABC", options.Scheme()); + } + + TEST_F(WakeCalculatorOptionsTest, ItSetsIntermediate) + { + options.Intermediate(true); + EXPECT_TRUE(options.Intermediate()); + } + + TEST_F(WakeCalculatorOptionsTest, ItSetsMapper) + { + auto mapper = std::make_shared(); + options.SchemeMapper(mapper); + EXPECT_EQ(mapper, options.SchemeMapper()); + } +} // namespace UKControllerPluginTest::Wake diff --git a/test/plugin/wake/WakeCategoryTest.cpp b/test/plugin/wake/WakeCategoryTest.cpp index 6c152796d..a59d3e690 100644 --- a/test/plugin/wake/WakeCategoryTest.cpp +++ b/test/plugin/wake/WakeCategoryTest.cpp @@ -9,13 +9,18 @@ namespace UKControllerPluginTest::Wake { { public: WakeCategoryTest() - : wakeIntervals({std::make_shared(3, 60, "nm", true)}), - category(123, "LM", "Lower Medium", 20, wakeIntervals) + : wakeIntervals( + {std::make_shared(3, 60, "nm", true), + std::make_shared(3, 30, "nm", false)}), + category(123, "LM", "Lower Medium", 20, wakeIntervals), + subsequentCategory1(1, "LM", "Lower Medium", 20, {}), subsequentCategory2(3, "LM", "Lower Medium", 20, {}) { } std::list> wakeIntervals; WakeCategory category; + WakeCategory subsequentCategory1; + WakeCategory subsequentCategory2; }; TEST_F(WakeCategoryTest, ItHasAnId) @@ -42,4 +47,19 @@ namespace UKControllerPluginTest::Wake { { EXPECT_EQ(wakeIntervals, category.SubsequentDepartureIntervals()); } + + TEST_F(WakeCategoryTest, ItReturnsSubsequentWakeIntervalIntermediate) + { + EXPECT_EQ(wakeIntervals.front(), category.DepartureInterval(subsequentCategory2, true)); + } + + TEST_F(WakeCategoryTest, ItReturnsSubsequentWakeIntervalNotIntermediate) + { + EXPECT_EQ(wakeIntervals.back(), category.DepartureInterval(subsequentCategory2, false)); + } + + TEST_F(WakeCategoryTest, ItReturnsNullptrNoDepartureIntervalFound) + { + EXPECT_EQ(nullptr, category.DepartureInterval(subsequentCategory1, false)); + } } // namespace UKControllerPluginTest::Wake diff --git a/test/plugin/wake/WakeIntervalFormatterTest.cpp b/test/plugin/wake/WakeIntervalFormatterTest.cpp new file mode 100644 index 000000000..2a9227c5e --- /dev/null +++ b/test/plugin/wake/WakeIntervalFormatterTest.cpp @@ -0,0 +1,35 @@ +#include "wake/DepartureWakeInterval.h" +#include "wake/WakeIntervalFormatter.h" + +using UKControllerPlugin::Wake::DepartureWakeInterval; +using UKControllerPlugin::Wake::FormatInterval; + +namespace UKControllerPluginTest::Wake { + class WakeIntervalFormatterTest : public testing::Test + { + }; + + TEST_F(WakeIntervalFormatterTest, ItReturnsDefaultUnknownUnit) + { + DepartureWakeInterval interval{1, 60, "mm", true}; + EXPECT_EQ(L"--", FormatInterval(interval)); + } + + TEST_F(WakeIntervalFormatterTest, ItReturnsUnitNauticalMiles) + { + DepartureWakeInterval interval{1, 60, "nm", true}; + EXPECT_EQ(L"60 nm", FormatInterval(interval)); + } + + TEST_F(WakeIntervalFormatterTest, ItReturnsMinutes) + { + DepartureWakeInterval interval{1, 120, "s", true}; + EXPECT_EQ(L"2 mins", FormatInterval(interval)); + } + + TEST_F(WakeIntervalFormatterTest, ItReturnsSeconds) + { + DepartureWakeInterval interval{1, 150, "s", true}; + EXPECT_EQ(L"150 secs", FormatInterval(interval)); + } +} // namespace UKControllerPluginTest::Wake diff --git a/test/plugin/wake/WakeModuleTest.cpp b/test/plugin/wake/WakeModuleTest.cpp index 5cfc7ae6f..fc0a0e43c 100644 --- a/test/plugin/wake/WakeModuleTest.cpp +++ b/test/plugin/wake/WakeModuleTest.cpp @@ -1,15 +1,30 @@ +#include "aircraft/CallsignSelectionListFactory.h" #include "bootstrap/PersistenceContainer.h" +#include "euroscope/AsrEventHandlerCollection.h" #include "flightplan/FlightPlanEventHandlerCollection.h" +#include "list/PopupListFactory.h" +#include "radarscreen/ConfigurableDisplayCollection.h" +#include "radarscreen/RadarRenderableCollection.h" +#include "radarscreen/MenuToggleableDisplayFactory.h" +#include "plugin/FunctionCallEventHandler.h" #include "tag/TagItemCollection.h" #include "wake/WakeModule.h" #include "wake/WakeSchemeCollection.h" using ::testing::NiceMock; using ::testing::Test; +using UKControllerPlugin::Aircraft::CallsignSelectionListFactory; using UKControllerPlugin::Bootstrap::PersistenceContainer; +using UKControllerPlugin::Euroscope::AsrEventHandlerCollection; using UKControllerPlugin::Flightplan::FlightPlanEventHandlerCollection; +using UKControllerPlugin::List::PopupListFactory; +using UKControllerPlugin::Plugin::FunctionCallEventHandler; +using UKControllerPlugin::RadarScreen::ConfigurableDisplayCollection; +using UKControllerPlugin::RadarScreen::MenuToggleableDisplayFactory; +using UKControllerPlugin::RadarScreen::RadarRenderableCollection; using UKControllerPlugin::Tag::TagItemCollection; using UKControllerPlugin::Wake::BootstrapPlugin; +using UKControllerPlugin::Wake::BootstrapRadarScreen; using UKControllerPluginTest::Dependency::MockDependencyLoader; namespace UKControllerPluginTest::Wake { @@ -17,12 +32,21 @@ namespace UKControllerPluginTest::Wake { class WakeModuleTest : public Test { public: - WakeModuleTest() + WakeModuleTest() : toggleableFactory(functionCalls, configurableDisplays) { container.flightplanHandler = std::make_unique(); container.tagHandler = std::make_unique(); + container.popupListFactory = std::make_unique(functionCalls, mockPlugin); + container.callsignSelectionListFactory = + std::make_unique(*container.popupListFactory); } + ConfigurableDisplayCollection configurableDisplays; + testing::NiceMock mockPlugin; + AsrEventHandlerCollection asrEventHandlers; + RadarRenderableCollection radarRenderables; + FunctionCallEventHandler functionCalls; + MenuToggleableDisplayFactory toggleableFactory; PersistenceContainer container; NiceMock dependencies; }; @@ -79,4 +103,41 @@ namespace UKControllerPluginTest::Wake { BootstrapPlugin(this->container, this->dependencies); EXPECT_TRUE(this->container.tagHandler->HasHandlerForItemId(115)); } + + TEST_F(WakeModuleTest, ItRegistersCalculatorForRenderers) + { + BootstrapRadarScreen(this->container, radarRenderables, asrEventHandlers, toggleableFactory); + EXPECT_EQ(1, radarRenderables.CountRenderers()); + EXPECT_EQ(1, radarRenderables.CountRenderersInPhase(radarRenderables.afterLists)); + } + + TEST_F(WakeModuleTest, ItRegistersCalculatorForAsrEvents) + { + BootstrapRadarScreen(this->container, radarRenderables, asrEventHandlers, toggleableFactory); + EXPECT_EQ(1, asrEventHandlers.CountHandlers()); + } + + TEST_F(WakeModuleTest, ItRegistersLeadAircraftCallback) + { + BootstrapRadarScreen(this->container, radarRenderables, asrEventHandlers, toggleableFactory); + EXPECT_TRUE(functionCalls.HasCallbackByDescription("Wake Calculator Lead Aircraft")); + } + + TEST_F(WakeModuleTest, ItRegistersFollowingAircraftCallback) + { + BootstrapRadarScreen(this->container, radarRenderables, asrEventHandlers, toggleableFactory); + EXPECT_TRUE(functionCalls.HasCallbackByDescription("Wake Calculator Following Aircraft")); + } + + TEST_F(WakeModuleTest, ItRegistersSchemeCallback) + { + BootstrapRadarScreen(this->container, radarRenderables, asrEventHandlers, toggleableFactory); + EXPECT_TRUE(functionCalls.HasCallbackByDescription("Wake Calculator Scheme")); + } + + TEST_F(WakeModuleTest, ItRegistersToggleableDisplay) + { + BootstrapRadarScreen(this->container, radarRenderables, asrEventHandlers, toggleableFactory); + EXPECT_TRUE(functionCalls.HasCallbackByDescription("Toggle Wake Turbulence Calculator")); + } } // namespace UKControllerPluginTest::Wake diff --git a/test/plugin/wake/WakeSchemeCollectionTest.cpp b/test/plugin/wake/WakeSchemeCollectionTest.cpp index db341d8f2..9546a4b94 100644 --- a/test/plugin/wake/WakeSchemeCollectionTest.cpp +++ b/test/plugin/wake/WakeSchemeCollectionTest.cpp @@ -56,4 +56,41 @@ namespace UKControllerPluginTest::Wake { collection.Add(scheme2); EXPECT_EQ(nullptr, collection.GetByKey("somekey")); } + + TEST_F(WakeSchemeCollectionTest, ItGetsSchemesByName) + { + collection.Add(scheme1); + collection.Add(scheme2); + EXPECT_EQ(scheme2, collection.GetByName("RECAT-EU")); + } + + TEST_F(WakeSchemeCollectionTest, ItReturnsNullptrIfSchemeByNameNotFound) + { + collection.Add(scheme1); + collection.Add(scheme2); + EXPECT_EQ(nullptr, collection.GetByName("somekey")); + } + + TEST_F(WakeSchemeCollectionTest, FirstWhereReturnsMatchingItem) + { + collection.Add(scheme1); + collection.Add(scheme2); + EXPECT_EQ(scheme2, collection.FirstWhere([](const WakeScheme& scheme) -> bool { return scheme.Id() == 2; })); + } + + TEST_F(WakeSchemeCollectionTest, FirstWhereReturnsNullptrNoMatchingItem) + { + collection.Add(scheme1); + collection.Add(scheme2); + EXPECT_EQ(nullptr, collection.FirstWhere([](const WakeScheme& scheme) -> bool { return scheme.Id() == 3; })); + } + + TEST_F(WakeSchemeCollectionTest, ItLoopsTheCollection) + { + std::vector values; + collection.Add(scheme1); + collection.Add(scheme2); + collection.ForEach([&values](const WakeScheme& scheme) { values.push_back(scheme.Id()); }); + EXPECT_EQ(std::vector({1, 2}), values); + } } // namespace UKControllerPluginTest::Wake diff --git a/test/plugin/wake/WakeSchemeProviderTest.cpp b/test/plugin/wake/WakeSchemeProviderTest.cpp new file mode 100644 index 000000000..39e45f181 --- /dev/null +++ b/test/plugin/wake/WakeSchemeProviderTest.cpp @@ -0,0 +1,80 @@ +#include "list/ListItem.h" +#include "wake/WakeCalculatorOptions.h" +#include "wake/WakeSchemeCollection.h" +#include "wake/WakeSchemeProvider.h" +#include "wake/WakeScheme.h" + +using UKControllerPlugin::List::ListItemCheckedStatus; +using UKControllerPlugin::Wake::WakeCalculatorOptions; +using UKControllerPlugin::Wake::WakeScheme; +using UKControllerPlugin::Wake::WakeSchemeCollection; +using UKControllerPlugin::Wake::WakeSchemeProvider; + +namespace UKControllerPluginTest::Wake { + class WakeSchemeProviderTest : public testing::Test + { + public: + WakeSchemeProviderTest() + : scheme1(std::make_shared(1, "UK", "UK", std::list>{})), + scheme2(std::make_shared(2, "RECAT", "RECAT-EU", std::list>{})), + options(std::make_shared()), provider(options, schemes, typeMapper) + { + schemes.Add(scheme1); + schemes.Add(scheme2); + } + + testing::NiceMock typeMapper; + std::shared_ptr scheme1; + std::shared_ptr scheme2; + std::shared_ptr options; + WakeSchemeCollection schemes; + WakeSchemeProvider provider; + }; + + TEST_F(WakeSchemeProviderTest, ItHasOneColumn) + { + EXPECT_EQ(1, provider.ListColumns()); + } + + TEST_F(WakeSchemeProviderTest, ItHasAName) + { + EXPECT_EQ("Scheme", provider.ListName()); + } + + TEST_F(WakeSchemeProviderTest, ItHasItems) + { + auto items = provider.ListItems(); + EXPECT_EQ(2, items.size()); + + auto iterator = items.cbegin(); + auto first = *iterator++; + + EXPECT_EQ("UK", first->firstColumn); + EXPECT_EQ("", first->secondColumn); + EXPECT_FALSE(first->fixedPosition); + EXPECT_FALSE(first->disabled); + EXPECT_EQ(ListItemCheckedStatus::NoCheckbox, first->checked); + + auto second = *iterator++; + + EXPECT_EQ("RECAT-EU", second->firstColumn); + EXPECT_EQ("", first->secondColumn); + EXPECT_FALSE(second->fixedPosition); + EXPECT_FALSE(second->disabled); + EXPECT_EQ(ListItemCheckedStatus::NoCheckbox, second->checked); + } + + TEST_F(WakeSchemeProviderTest, ItSelectsAScheme) + { + provider.ItemSelected("UK"); + EXPECT_EQ("UK", options->Scheme()); + EXPECT_NE(nullptr, options->SchemeMapper()); + } + + TEST_F(WakeSchemeProviderTest, ItDoesntSetSchemeIfNotFound) + { + provider.ItemSelected("ABC"); + EXPECT_TRUE(options->Scheme().empty()); + EXPECT_EQ(nullptr, options->SchemeMapper()); + } +} // namespace UKControllerPluginTest::Wake