diff --git a/ILS_Window_Plugin/ILS_Window_Plugin.vcxproj b/ILS_Window_Plugin/ILS_Window_Plugin.vcxproj index 180c96f..41f863e 100644 --- a/ILS_Window_Plugin/ILS_Window_Plugin.vcxproj +++ b/ILS_Window_Plugin/ILS_Window_Plugin.vcxproj @@ -117,6 +117,7 @@ + @@ -129,11 +130,13 @@ + Create Create + @@ -145,6 +148,8 @@ + + @@ -158,12 +163,14 @@ + + diff --git a/ILS_Window_Plugin/ILS_Window_Plugin.vcxproj.filters b/ILS_Window_Plugin/ILS_Window_Plugin.vcxproj.filters index ac9e572..314da7b 100644 --- a/ILS_Window_Plugin/ILS_Window_Plugin.vcxproj.filters +++ b/ILS_Window_Plugin/ILS_Window_Plugin.vcxproj.filters @@ -81,6 +81,9 @@ Source Files\Theme\X11 + + Source Files + @@ -163,6 +166,9 @@ Header Files\Theme\X11 + + Header Files + diff --git a/ILS_Window_Plugin/IWCdeIconifyBtn.cpp b/ILS_Window_Plugin/IWCdeIconifyBtn.cpp index acb91ef..b98c7bd 100644 --- a/ILS_Window_Plugin/IWCdeIconifyBtn.cpp +++ b/ILS_Window_Plugin/IWCdeIconifyBtn.cpp @@ -19,6 +19,6 @@ void IWCdeIconifyBtn::DrawSymbol(CDC* pdc, CRect rect) icon.top = buttonFrame.top + buttonFrame.Width() * 0.4; icon.bottom = buttonFrame.bottom - buttonFrame.Width() * 0.4; - Draw3dRect(pdc, buttonFrame, 1, lightColor, darkColor); - Draw3dRect(pdc, icon, 1, lightColor, darkColor); + DrawThick3dRect(pdc, buttonFrame, 1, lightColor, darkColor); + DrawThick3dRect(pdc, icon, 1, lightColor, darkColor); } \ No newline at end of file diff --git a/ILS_Window_Plugin/IWCdeMenuBtn.cpp b/ILS_Window_Plugin/IWCdeMenuBtn.cpp index 1eb860d..09bc694 100644 --- a/ILS_Window_Plugin/IWCdeMenuBtn.cpp +++ b/ILS_Window_Plugin/IWCdeMenuBtn.cpp @@ -16,6 +16,6 @@ void IWCdeMenuBtn::DrawSymbol(CDC* pdc, CRect rect) barIcon.top = rect.top + rect.Height() / 2 - 2; barIcon.bottom = barIcon.top + 4; - Draw3dRect(pdc, rect, 1, lightColor, darkColor); - Draw3dRect(pdc, barIcon, 1, lightColor, darkColor); + DrawThick3dRect(pdc, rect, 1, lightColor, darkColor); + DrawThick3dRect(pdc, barIcon, 1, lightColor, darkColor); } diff --git a/ILS_Window_Plugin/IWCdeTitleBar.cpp b/ILS_Window_Plugin/IWCdeTitleBar.cpp index 376cb63..494732a 100644 --- a/ILS_Window_Plugin/IWCdeTitleBar.cpp +++ b/ILS_Window_Plugin/IWCdeTitleBar.cpp @@ -14,7 +14,7 @@ IWCdeTitleBar::IWCdeTitleBar(COLORREF backgroundColor, COLORREF textColor, COLOR this->iconifyButton = new IWCdeIconifyBtn(backgroundColor, lightColor, darkColor); this->menuButton = new IWCdeMenuBtn(backgroundColor, lightColor, darkColor); - this->resizeButton = new IWX11ResizeBtn(backgroundColor); + this->resizeButton = new IWX11ResizeBtn(backgroundColor, textColor); } void IWCdeTitleBar::PositionButtons(const CRect& rect) @@ -34,9 +34,9 @@ void IWCdeTitleBar::PositionButtons(const CRect& rect) void IWCdeTitleBar::DrawTitle(CDC* pdc, CRect rect, CString title) { - Draw3dRect(pdc, titleArea, 1, lightColor, darkColor); + DrawThick3dRect(pdc, titleArea, 1, lightColor, darkColor); - auto oldFont = pdc->SelectObject(this->font); + auto oldFont = pdc->SelectObject(this->mainFont); pdc->SetTextColor(this->textColor); pdc->SetBkMode(TRANSPARENT); pdc->DrawText(_T(title), -1, titleArea, DT_CENTER | DT_VCENTER | DT_SINGLELINE); diff --git a/ILS_Window_Plugin/IWCdeWindow.cpp b/ILS_Window_Plugin/IWCdeWindow.cpp index 703ad58..018af53 100644 --- a/ILS_Window_Plugin/IWCdeWindow.cpp +++ b/ILS_Window_Plugin/IWCdeWindow.cpp @@ -8,8 +8,8 @@ IWCdeWindow::IWCdeWindow(IWApproachDefinition selectedApproach, IWStyling stylin , lightColor(AdjustColorBrightness(styling.windowFrameColor, 1.4)) , darkColor(AdjustColorBrightness(styling.windowFrameColor, 0.4)) { - COLORREF textColor = RGB(styling.windowFrameTextColor.r, styling.windowFrameTextColor.g, styling.windowFrameTextColor.b); - this->titleBar = new IWCdeTitleBar(windowBorderColor, textColor, lightColor, darkColor, this); + this->titleBar = new IWCdeTitleBar(windowBorderColor, styling.windowFrameTextColor, lightColor, darkColor, this); + this->extraMenuItemWidth = 20; } int IWCdeWindow::GetEdgeCursorPosition(CPoint point) @@ -76,28 +76,28 @@ void IWCdeWindow::DrawBorder(CDC* pdc, CRect rect) leftBorderRect.left = leftBorderRect.left + 1; leftBorderRect.right = leftBorderRect.left + WINDOW_BORDER_THICKNESS - 1; leftBorderRect.bottom = leftBorderRect.bottom - TITLE_BAR_HEIGHT; - Draw3dRect(pdc, leftBorderRect, border3dSteps, lightColor, darkColor); + DrawThick3dRect(pdc, leftBorderRect, border3dSteps, lightColor, darkColor); CRect bottomBorderRect = rect; bottomBorderRect.top = bottomBorderRect.bottom - WINDOW_BORDER_THICKNESS; bottomBorderRect.left = bottomBorderRect.left + TITLE_BAR_HEIGHT; bottomBorderRect.right = bottomBorderRect.right - TITLE_BAR_HEIGHT; bottomBorderRect.bottom = bottomBorderRect.bottom - 1; - Draw3dRect(pdc, bottomBorderRect, border3dSteps, lightColor, darkColor); + DrawThick3dRect(pdc, bottomBorderRect, border3dSteps, lightColor, darkColor); CRect rightBorderRect = rect; rightBorderRect.top = rightBorderRect.top + TITLE_BAR_HEIGHT; rightBorderRect.left = rightBorderRect.right - WINDOW_BORDER_THICKNESS; rightBorderRect.right = rightBorderRect.right - 1; rightBorderRect.bottom = rightBorderRect.bottom - TITLE_BAR_HEIGHT; - Draw3dRect(pdc, rightBorderRect, border3dSteps, lightColor, darkColor); + DrawThick3dRect(pdc, rightBorderRect, border3dSteps, lightColor, darkColor); CRect topBorderRect = rect; topBorderRect.top = topBorderRect.top + 1; topBorderRect.left = topBorderRect.left + TITLE_BAR_HEIGHT; topBorderRect.right = topBorderRect.right - TITLE_BAR_HEIGHT; topBorderRect.bottom = topBorderRect.top + WINDOW_BORDER_THICKNESS - 1; - Draw3dRect(pdc, topBorderRect, border3dSteps, lightColor, darkColor); + DrawThick3dRect(pdc, topBorderRect, border3dSteps, lightColor, darkColor); // Corners @@ -106,36 +106,38 @@ void IWCdeWindow::DrawBorder(CDC* pdc, CRect rect) topLeftCornerRect.left = topLeftCornerRect.left + 1; topLeftCornerRect.right = topLeftCornerRect.left + TITLE_BAR_HEIGHT - 2; topLeftCornerRect.bottom = topLeftCornerRect.top + TITLE_BAR_HEIGHT - 2; - Draw3dCorner(pdc, topLeftCornerRect, WINDOW_BORDER_THICKNESS, border3dSteps, lightColor, darkColor, true, true); + DrawThick3dCorner(pdc, topLeftCornerRect, WINDOW_BORDER_THICKNESS, border3dSteps, lightColor, darkColor, true, true); CRect topRightCornerRect = rect; topRightCornerRect.top = topRightCornerRect.top + 1; topRightCornerRect.left = topRightCornerRect.right - TITLE_BAR_HEIGHT; topRightCornerRect.right = topRightCornerRect.right - 2; topRightCornerRect.bottom = topRightCornerRect.top + TITLE_BAR_HEIGHT - 2; - Draw3dCorner(pdc, topRightCornerRect, WINDOW_BORDER_THICKNESS, border3dSteps, lightColor, darkColor, true, false); + DrawThick3dCorner(pdc, topRightCornerRect, WINDOW_BORDER_THICKNESS, border3dSteps, lightColor, darkColor, true, false); CRect bottomRightCornerRect = rect; bottomRightCornerRect.top = bottomRightCornerRect.bottom - TITLE_BAR_HEIGHT; bottomRightCornerRect.left = bottomRightCornerRect.right - TITLE_BAR_HEIGHT; bottomRightCornerRect.right = bottomRightCornerRect.right - 2; bottomRightCornerRect.bottom = bottomRightCornerRect.bottom - 2; - Draw3dCorner(pdc, bottomRightCornerRect, WINDOW_BORDER_THICKNESS, border3dSteps, lightColor, darkColor, false, false); + DrawThick3dCorner(pdc, bottomRightCornerRect, WINDOW_BORDER_THICKNESS, border3dSteps, lightColor, darkColor, false, false); CRect bottomLeftCornerRect = rect; bottomLeftCornerRect.top = bottomLeftCornerRect.bottom - TITLE_BAR_HEIGHT; bottomLeftCornerRect.left = bottomLeftCornerRect.left + 1; bottomLeftCornerRect.right = bottomLeftCornerRect.left + TITLE_BAR_HEIGHT - 2; bottomLeftCornerRect.bottom = bottomLeftCornerRect.bottom - 2; - Draw3dCorner(pdc, bottomLeftCornerRect, WINDOW_BORDER_THICKNESS, border3dSteps, lightColor, darkColor, false, true); + DrawThick3dCorner(pdc, bottomLeftCornerRect, WINDOW_BORDER_THICKNESS, border3dSteps, lightColor, darkColor, false, true); } -COLORREF IWCdeWindow::AdjustColorBrightness(RGB color, double factor) +COLORREF IWCdeWindow::AdjustColorBrightness(COLORREF color, double factor) { + GetRValue(color); + // Adjust each component - int red = static_cast(color.r * factor); - int green = static_cast(color.g * factor); - int blue = static_cast(color.b * factor); + int red = GetRValue(color) * factor; + int green = GetGValue(color) * factor; + int blue = GetBValue(color) * factor; // Ensure the components are within the valid range red = max(0, min(255, red)); @@ -145,3 +147,32 @@ COLORREF IWCdeWindow::AdjustColorBrightness(RGB color, double factor) // Combine them back into a COLORREF return RGB(red, green, blue); } + +void IWCdeWindow::DrawMenuItem(CDC* pdc, CRect bounds, CString text, bool isHovered, bool isChecked) +{ + COLORREF bgColor = isHovered ? RGB(130, 130, 130) : RGB(152, 152, 152); + COLORREF textColor = RGB(255, 255, 255); // White text + + std::string fullText = isChecked ? "¤ " : " "; + fullText += text; + + CBrush brush(bgColor); + pdc->FillRect(&bounds, &brush); + + if (isHovered) { + COLORREF darkened = AdjustColorBrightness(bgColor, 0.6); + COLORREF lightened = AdjustColorBrightness(bgColor, 1.4); + DrawThick3dRect(pdc, bounds, 2, darkened, lightened); + } + + // Draw text + pdc->SetTextColor(textColor); + pdc->SetBkMode(TRANSPARENT); + + CRect textArea = bounds; + textArea.left += 10; + + CFont* oldFont = pdc->SelectObject(&mainFont); + pdc->DrawText(fullText.c_str(), &textArea, DT_LEFT | DT_VCENTER | DT_SINGLELINE); + pdc->SelectObject(&oldFont); +} diff --git a/ILS_Window_Plugin/IWCdeWindow.h b/ILS_Window_Plugin/IWCdeWindow.h index fb8b443..4c2ca1c 100644 --- a/ILS_Window_Plugin/IWCdeWindow.h +++ b/ILS_Window_Plugin/IWCdeWindow.h @@ -9,9 +9,11 @@ class IWCdeWindow : private: void DrawBorder(CDC* pdc, CRect rect) override; - COLORREF AdjustColorBrightness(RGB color, double factor); + void DrawMenuItem(CDC* pdc, CRect bounds, CString text, bool isHovered, bool isChecked) override; virtual int GetEdgeCursorPosition(CPoint point) override; + COLORREF AdjustColorBrightness(COLORREF color, double factor); + const COLORREF lightColor; const COLORREF darkColor; }; diff --git a/ILS_Window_Plugin/IWContextMenu.cpp b/ILS_Window_Plugin/IWContextMenu.cpp new file mode 100644 index 0000000..9e726b2 --- /dev/null +++ b/ILS_Window_Plugin/IWContextMenu.cpp @@ -0,0 +1,33 @@ +#include "pch.h" +#include "IWContextMenu.h" + +BEGIN_MESSAGE_MAP(IWContextMenu, CWnd) + ON_WM_PAINT() +END_MESSAGE_MAP() + +void IWContextMenu::SubclassMenu(HWND hMenuWnd) +{ + // Attach to the existing menu window + Attach(hMenuWnd); +} + +void IWContextMenu::OnPaint() +{ + CPaintDC dc(this); // Get device context + + // Get the menu window's size + CRect rect; + GetClientRect(&rect); + + // Draw the default menu first + DefWindowProc(WM_PAINT, (WPARAM)dc.m_hDC, 0); + + // Draw custom border + COLORREF borderColor = RGB(255, 0, 0); // Red border + int borderWidth = 2; + + CBrush borderBrush(borderColor); + dc.FrameRect(&rect, &borderBrush); + + // You can also use dc.DrawEdge() for different styles +} diff --git a/ILS_Window_Plugin/IWContextMenu.h b/ILS_Window_Plugin/IWContextMenu.h new file mode 100644 index 0000000..aeec1ab --- /dev/null +++ b/ILS_Window_Plugin/IWContextMenu.h @@ -0,0 +1,12 @@ +#pragma once +#include + +class IWContextMenu : public CWnd +{ +public: + void SubclassMenu(HWND hMenuWnd); + +protected: + afx_msg void OnPaint(); + DECLARE_MESSAGE_MAP() +}; diff --git a/ILS_Window_Plugin/IWDataTypes.h b/ILS_Window_Plugin/IWDataTypes.h index 80c8cc0..fa82819 100644 --- a/ILS_Window_Plugin/IWDataTypes.h +++ b/ILS_Window_Plugin/IWDataTypes.h @@ -7,10 +7,6 @@ class IWWindow; -struct RGB { - int r, g, b; -}; - struct IWTargetPosition { int trueAltitude; double latitude; @@ -49,17 +45,22 @@ enum IWTagMode { Callsign }; +enum IWTheme { + CDE, + X11 +}; + struct IWStyling { - RGB windowFrameColor; - RGB windowFrameTextColor; - RGB windowOuterFrameColor; - RGB backgroundColor; - RGB glideslopeColor; - RGB localizerColor; - RGB radarTargetColor; - RGB historyTrailColor; - RGB targetLabelColor; - RGB rangeStatusTextColor; + COLORREF windowFrameColor; + COLORREF windowFrameTextColor; + COLORREF windowOuterFrameColor; + COLORREF backgroundColor; + COLORREF glideslopeColor; + COLORREF localizerColor; + COLORREF radarTargetColor; + COLORREF historyTrailColor; + COLORREF targetLabelColor; + COLORREF rangeStatusTextColor; unsigned int fontSize; bool showTagByDefault; IWTagMode defaultTagMode; @@ -68,4 +69,14 @@ struct IWStyling { struct IWBehaviourSettings { bool openWindowsBasedOnActiveRunways; std::string windowStyle; +}; + +struct IWConfig { + IWStyling styling; + IWBehaviourSettings behaviour; +}; + +struct IWActiveRunway { + std::string airport; + std::string runway; }; \ No newline at end of file diff --git a/ILS_Window_Plugin/IWPlugin.cpp b/ILS_Window_Plugin/IWPlugin.cpp index 9de3e29..54f9fd1 100644 --- a/ILS_Window_Plugin/IWPlugin.cpp +++ b/ILS_Window_Plugin/IWPlugin.cpp @@ -1,125 +1,24 @@ #include "pch.h" #include "IWPlugin.h" -#include #include #include "IWUtils.h" #include #include "IWCdeWindow.h" #include "IWX11Window.h" -using json = nlohmann::json; - -IWPlugin::IWPlugin(void) : CPlugIn(EuroScopePlugIn::COMPATIBILITY_CODE, MY_PLUGIN_NAME, MY_PLUGIN_VERSION, MY_PLUGIN_DEVELOPER, MY_PLUGIN_COPYRIGHT) { - - AFX_MANAGE_STATE(AfxGetStaticModuleState()); // Manage the module state for MFC - - // Read configuration file - std::string jsonFilePath = GetPluginDirectory() + "\\" + CONFIG_FILE_NAME; - availableApproaches = ReadApproachDefinitions(jsonFilePath); - windowStyling = ReadStyling(jsonFilePath); - behaviourSettings = ReadBehaviourSettings(jsonFilePath); - - // Register a custom window class - WNDCLASS wndClass = { 0 }; - wndClass.lpfnWndProc = ::DefWindowProc; - wndClass.hInstance = AfxGetInstanceHandle(); - wndClass.hCursor = ::LoadCursor(nullptr, IDC_ARROW); - wndClass.hbrBackground = (HBRUSH)::GetStockObject(WHITE_BRUSH); - wndClass.lpszClassName = WINDOW_CLASS_NAME; - - if (!AfxRegisterClass(&wndClass)) - return; - - LoadSavedWindowPositions(); - SyncWithActiveRunways(); -} - -IWPlugin::~IWPlugin() +IWPlugin::IWPlugin(void) : + CPlugIn(EuroScopePlugIn::COMPATIBILITY_CODE, MY_PLUGIN_NAME, MY_PLUGIN_VERSION, MY_PLUGIN_DEVELOPER, MY_PLUGIN_COPYRIGHT), + settings(this), + windowManager(&settings) { - for (const auto& window : windows) { - if (window) { - delete window; - } - } + this->windowManager.SyncWithActiveRunways(this->CollectActiveRunways(true)); } -void IWPlugin::ShowWindow(IWApproachDefinition* approach) +IWPlugin::~IWPlugin() { - AFX_MANAGE_STATE(AfxGetStaticModuleState()); - - // Check if a window with the same title is already open - IWWindow* windowWithSameTitle = nullptr; - for (const auto& window : windows) { - if (window->GetActiveApproachName() == approach->title) { - windowWithSameTitle = window; - break; - } - } - - if (windowWithSameTitle) { - // Restore the window and bring it to the front - windowWithSameTitle->ShowWindow(SW_RESTORE); - windowWithSameTitle->SetForegroundWindow(); - return; - } - - // Calculate the spawning point for the new window - CPoint spawningPoint = CPoint(int(windows.size()) * 50, int(windows.size()) * 50 + 100); - CSize windowSize = CSize(300, 200); - - // Use the saved position if there is one - auto savedPosition = savedWindowPositions.find(approach->title); - if (savedPosition != savedWindowPositions.end()) { - windowSize = savedPosition->second.Size(); - spawningPoint = savedPosition->second.TopLeft(); - } - else { - // Use the same size as the newest window if there is one. - IWWindow* newestWindow = windows.size() > 0 ? windows.back() : nullptr; - if (newestWindow) { - CRect rect; - newestWindow->GetWindowRect(&rect); - windowSize = rect.Size(); - spawningPoint = CPoint(rect.left + 50, rect.top + 50); - } - } - - IWWindow* newWindow = nullptr; - if (this->behaviourSettings.windowStyle == "X11") { - newWindow = new IWX11Window(*approach, windowStyling); - } - else { - newWindow = new IWCdeWindow(*approach, windowStyling); - } - - auto hwndPopup = newWindow->CreateEx( - WS_EX_TOPMOST | WS_EX_APPWINDOW | WS_EX_NOACTIVATE, - WINDOW_CLASS_NAME, - _T(approach->title.c_str()), - WS_POPUP, - spawningPoint.x, - spawningPoint.y, - windowSize.cx, - windowSize.cy, - nullptr, - nullptr - ); - - if (!hwndPopup) { - delete newWindow; - return; - } - - newWindow->SetMenu(NULL); - newWindow->ShowWindow(SW_SHOWNOACTIVATE); // Show but don't steal focus - newWindow->UpdateWindow(); - newWindow->SetListener(this); - newWindow->SetAvailableApproaches(availableApproaches); - - windows.push_back(newWindow); + } - void IWPlugin::OnTimer(int seconds) { IWLiveData liveData; @@ -173,14 +72,12 @@ void IWPlugin::OnTimer(int seconds) } } - for (auto& window : windows) { - window->SendMessage(WM_UPDATE_DATA, reinterpret_cast(&liveData)); - } + windowManager.HandleLiveData(liveData); } void IWPlugin::OnAirportRunwayActivityChanged() { - SyncWithActiveRunways(); + this->windowManager.SyncWithActiveRunways(CollectActiveRunways(true)); } void IWPlugin::OnNewMetarReceived(const char* sStation, const char* sFullMetar) @@ -201,17 +98,9 @@ void IWPlugin::OnNewMetarReceived(const char* sStation, const char* sFullMetar) } } -void IWPlugin::SyncWithActiveRunways() +std::vector IWPlugin::CollectActiveRunways(bool forArrival) { - if (!behaviourSettings.openWindowsBasedOnActiveRunways) { - return; - } - - AFX_MANAGE_STATE(AfxGetStaticModuleState()); - - bool forArrival = true; - - std::vector approachesThatShouldBeOpen; + std::vector activeRunways; for (auto airport = this->SectorFileElementSelectFirst(EuroScopePlugIn::SECTOR_ELEMENT_AIRPORT); airport.IsValid(); airport = this->SectorFileElementSelectNext(airport, EuroScopePlugIn::SECTOR_ELEMENT_AIRPORT)) { if (airport.IsElementActive(!forArrival)) { @@ -222,14 +111,11 @@ void IWPlugin::SyncWithActiveRunways() for (int runwayDirection = 0; runwayDirection < 2; runwayDirection++) { if (runway.IsElementActive(!forArrival, runwayDirection) && runwayAirportName == activeAirportIcao) { auto runwayName = trimString(std::string(runway.GetRunwayName(runwayDirection))); - auto approach = std::find_if(this->availableApproaches.begin(), this->availableApproaches.end(), - [&runwayAirportName, runwayName](const IWApproachDefinition& approach) { - return approach.airport == runwayAirportName && approach.runway == runwayName; - }); - - if (approach != this->availableApproaches.end()) { - approachesThatShouldBeOpen.push_back(&(*approach)); - } + + activeRunways.push_back({ + runwayAirportName, + runwayName + }); } } } @@ -237,248 +123,9 @@ void IWPlugin::SyncWithActiveRunways() } } - // Find open windows that should be closed - for (auto& window : windows) { - bool shouldBeClosed = true; - for (auto& approach : approachesThatShouldBeOpen) { - if (window->GetActiveApproachName() == approach->title) { - shouldBeClosed = false; - } - } - if (shouldBeClosed) { - window->DestroyWindow(); - } - } - - // Open windows that should be open - for (auto& approach : approachesThatShouldBeOpen) { - bool alreadyOpen = std::any_of(windows.begin(), windows.end(), [&approach](const IWWindow* window) { - return window->GetActiveApproachName() == approach->title; - }); - - if (!alreadyOpen) { - this->ShowWindow(approach); - } - } + return activeRunways; } -std::vector IWPlugin::ReadApproachDefinitions(const std::string& jsonFilePath) { - const std::string generalErrorMessage = "Could not load approach definitions"; - - std::vector approaches; - - // Open the JSON file - std::ifstream file(jsonFilePath); - if (!file.is_open()) { - this->ShowErrorMessage(generalErrorMessage, "Unable to open JSON file '" + jsonFilePath + "'"); - return approaches; - } - - // Parse the JSON file - nlohmann::json jsonData; - try { - file >> jsonData; - } - catch (const nlohmann::json::parse_error& e) { - this->ShowErrorMessage(generalErrorMessage, std::string(e.what())); - return approaches; - } - - // Check if "approaches" key exists - if (!jsonData.contains("approaches") || !jsonData["approaches"].is_array()) { - this->ShowErrorMessage(generalErrorMessage, "'approaches' key not found or is not an array."); - return approaches; - } - - // Iterate over the approaches - for (const auto& approachJson : jsonData["approaches"]) { - try { - IWApproachDefinition approach; - - // Parse individual fields - approach.title = approachJson.at("title").get(); - approach.airport = approachJson.at("airport").get(); - approach.runway = approachJson.at("runway").get(); - approach.localizerCourse = approachJson.at("localizerCourse").get(); - approach.glideslopeAngle = approachJson.at("glideslopeAngle").get(); - approach.defaultRange = approachJson.at("defaultRange").get(); - approach.thresholdAltitude = approachJson.at("thresholdAltitude").get(); - approach.thresholdLatitude = approachJson.at("thresholdLatitude").get(); - approach.thresholdLongitude = approachJson.at("thresholdLongitude").get(); - approach.maxOffsetLeft = approachJson.at("maxOffsetLeft").get(); - approach.maxOffsetRight = approachJson.at("maxOffsetRight").get(); - - - // Add to the list - approaches.push_back(approach); - } - catch (const nlohmann::json::exception& e) { - this->ShowErrorMessage(generalErrorMessage, "Error parsing approach data: " + std::string(e.what())); - } - } - - return approaches; -} - -IWStyling IWPlugin::ReadStyling(const std::string& jsonFilePath) { - const std::string generalErrorMessage = "Could not load style settings"; - - std::ifstream file(jsonFilePath); - if (!file.is_open()) { - this->ShowErrorMessage(generalErrorMessage, "Unable to open JSON file: " + jsonFilePath); - } - - nlohmann::json jsonData; - file >> jsonData; - - auto readColor = [&jsonData, &generalErrorMessage, this](const std::string& key) -> RGB { - if (jsonData.contains("styling") && jsonData["styling"].contains(key)) { - return HexToRGB(jsonData["styling"][key].get()); - } - this->ShowErrorMessage(generalErrorMessage, ("'" + key + "' key not found.").c_str()); - }; - - auto readUnsignedInt = [&jsonData, &generalErrorMessage, this](const std::string& key) -> unsigned int { - if (jsonData.contains("styling") && jsonData["styling"].contains(key)) { - return jsonData["styling"][key].get(); - } - this->ShowErrorMessage(generalErrorMessage, ("'" + key + "' key not found.").c_str()); - return 0; // Default value when key is not found or is not an unsigned integer - }; - - auto readUIntWithDefault = [&jsonData, this](const std::string& key, unsigned int defaultValue) -> unsigned int { - if (jsonData.contains("styling") && jsonData["styling"].contains(key)) { - return jsonData["styling"][key].get(); - } - return defaultValue; - }; - - auto readStringWithDefault = [&jsonData, this](const std::string& key, const std::string& defaultValue) -> std::string { - if (jsonData.contains("styling") && jsonData["styling"].contains(key)) { - return jsonData["styling"][key].get(); - } - return defaultValue; - }; - - auto stringToTagMode = [](const std::string& value) -> IWTagMode { - if (value == "squawk") { - return IWTagMode::Squawk; - } - return IWTagMode::Callsign; - }; - - auto readBoolWithDefault = [&jsonData, this](const std::string& key, bool defaultValue) -> bool { - if (jsonData.contains("styling") && jsonData["styling"].contains(key)) { - return jsonData["styling"][key].get(); - } - return defaultValue; - }; - - return IWStyling{ - readColor("windowFrameColor"), - readColor("windowFrameTextColor"), - readColor("windowOuterFrameColor"), - readColor("backgroundColor"), - readColor("glideslopeColor"), - readColor("localizerColor"), - readColor("radarTargetColor"), - readColor("historyTrailColor"), - readColor("targetLabelColor"), - readColor("rangeStatusTextColor"), - readUIntWithDefault("fontSize", 12), - readBoolWithDefault("showTagByDefault", true), - stringToTagMode(readStringWithDefault("defaultTagMode", "callsign")) - }; -} - -IWBehaviourSettings IWPlugin::ReadBehaviourSettings(const std::string& jsonFilePath) { - std::ifstream file(jsonFilePath); - if (!file.is_open()) { - this->ShowErrorMessage("Could not load behaviour options", "Unable to open JSON file '" + jsonFilePath + "'"); - } - - nlohmann::json jsonData; - file >> jsonData; - - nlohmann::json jsonObject = jsonData["behaviour"]; - - auto readStringWithDefault = [&jsonData, this](const std::string& key, const std::string& defaultValue) -> std::string { - if (jsonData.contains("behaviour") && jsonData["behaviour"].contains(key)) { - return jsonData["behaviour"][key].get(); - } - return defaultValue; - }; - - return IWBehaviourSettings{ - jsonObject.at("openWindowsBasedOnActiveRunways").get(), - readStringWithDefault("windowStyle", "X11") - }; -} - -std::string IWPlugin::GetPluginDirectory() { - char modulePath[MAX_PATH]; - GetModuleFileNameA((HINSTANCE)&__ImageBase, modulePath, sizeof(modulePath)); - std::string pluginDirectory = std::string(modulePath).substr(0, std::string(modulePath).find_last_of("\\/")); - return pluginDirectory; -} - -void IWPlugin::OnWindowClosed(IWWindow* window) -{ - auto it = std::find(windows.begin(), windows.end(), window); - if (it != windows.end()) { - windows.erase(it); - } -} - -void IWPlugin::OnWindowMenuOpenNew(std::string approachTitle) -{ - auto selectedApproach = std::find_if(availableApproaches.begin(), availableApproaches.end(), - [&approachTitle](const IWApproachDefinition& approach) { - return approach.title == approachTitle; - }); - - if (selectedApproach != availableApproaches.end()) { - ShowWindow(&(*selectedApproach)); - } - else { - ShowWindow(&availableApproaches[0]); - } -} - -void IWPlugin::OnWindowRectangleChanged(IWWindow* window) -{ - CRect windowRect; - window->GetWindowRect(&windowRect); - std::string name = window->GetActiveApproachName(); - std::string description = name + " pos."; - std::string rect = std::to_string(windowRect.left) + "," + std::to_string(windowRect.top) + "," + std::to_string(windowRect.right) + "," + std::to_string(windowRect.bottom); - SaveDataToSettings(name.c_str(), description.c_str(), rect.c_str()); - - savedWindowPositions[name] = windowRect; -} - -void IWPlugin::LoadSavedWindowPositions() -{ - // For every available approach, check if there is a saved position - for (auto& approach : availableApproaches) { - const char* settings = GetDataFromSettings(approach.title.c_str()); - if (settings) { - std::string settingsString = std::string(settings); - std::regex regex("([0-9]+),([0-9]+),([0-9]+),([0-9]+)"); - std::smatch match; - if (std::regex_search(settingsString, match, regex)) { - CRect rect; - rect.left = std::stoi(match[1]); - rect.top = std::stoi(match[2]); - rect.right = std::stoi(match[3]); - rect.bottom = std::stoi(match[4]); - savedWindowPositions[approach.title] = rect; - } - } - } -} - - bool IWPlugin::OnCompileCommand(const char* sCommandLine) { const std::string generalError = "Could not open ILS window"; @@ -490,28 +137,20 @@ bool IWPlugin::OnCompileCommand(const char* sCommandLine) } // Extract the argument after ".ils " - std::string argument = stringToUpper(trimString(command.substr(prefix.length()))); - - if (argument.empty()) { + std::string approachName = stringToUpper(trimString(command.substr(prefix.length()))); + if (approachName.empty()) { this->ShowErrorMessage(generalError, "No approach name specified after '.ils'."); return false; } - // Find the approach by title - auto it = std::find_if(this->availableApproaches.begin(), this->availableApproaches.end(), - [&argument](const IWApproachDefinition& approach) { - return approach.title == argument; - }); + bool success = this->windowManager.Open(approachName); - if (it != this->availableApproaches.end()) { - // Approach found: Open the approach window - this->ShowWindow(&(*it)); - return true; // Command handled + if (!success) { + this->ShowErrorMessage(generalError, "Could not find approach with name '" + approachName + "'."); + return false; } else { - // Approach not found - this->ShowErrorMessage(generalError, "Approach '" + argument + "' not found."); - return false; // Command not handled + return true; // Command was handled } } diff --git a/ILS_Window_Plugin/IWPlugin.h b/ILS_Window_Plugin/IWPlugin.h index cc1eadd..ce2017e 100644 --- a/ILS_Window_Plugin/IWPlugin.h +++ b/ILS_Window_Plugin/IWPlugin.h @@ -2,51 +2,28 @@ #include #include -#include "IWWindow.h" #include "IWDataTypes.h" #include +#include "IWSettings.h" +#include "IWWindowManager.h" -#define WINDOW_CLASS_NAME _T("IWWindow") - -#define MY_PLUGIN_NAME "ILS Window Plugin" -#define MY_PLUGIN_VERSION PLUGIN_VERSION -#define MY_PLUGIN_DEVELOPER CONTRIBUTORS -#define MY_PLUGIN_COPYRIGHT "GPL v3" - -#define CONFIG_FILE_NAME "ILS_window_plugin-config.json" +class IWPlugin : public EuroScopePlugIn::CPlugIn { +public: + IWPlugin(); + ~IWPlugin(); -class IWPlugin : public EuroScopePlugIn::CPlugIn, IIWWndEventListener { private: - std::vector windows; - std::vector availableApproaches; - IWStyling windowStyling; - IWBehaviourSettings behaviourSettings; - std::map savedWindowPositions; + IWWindowManager windowManager; std::map airportTemperatures; + IWSettings settings; - void ShowWindow(IWApproachDefinition* approach); - void SyncWithActiveRunways(); - void LoadSavedWindowPositions(); - void ShowErrorMessage(std::string consequence, std::string details); - - bool autoOpenWhenRunwaysChanges = true; - - // Euroscope API + // Euroscope callbacks bool OnCompileCommand(const char* sCommandLine) override; void OnTimer(int seconds) override; void OnAirportRunwayActivityChanged() override; void OnNewMetarReceived(const char* sStation, const char* sFullMetar) override; - // Events from a window - void OnWindowClosed(IWWindow* window) override; - void OnWindowMenuOpenNew(std::string approachTitle) override; - void OnWindowRectangleChanged(IWWindow* window) override; -public: - IWPlugin(); - ~IWPlugin(); - - std::vector ReadApproachDefinitions(const std::string& jsonFilePath); - IWStyling ReadStyling(const std::string& iniFilePath); - IWBehaviourSettings ReadBehaviourSettings(const std::string& jsonFilePath); - std::string GetPluginDirectory(); + // Helper functions for the Euroscope API + std::vector CollectActiveRunways(bool forArrival); + void ShowErrorMessage(std::string consequence, std::string details); }; diff --git a/ILS_Window_Plugin/IWSettings.cpp b/ILS_Window_Plugin/IWSettings.cpp new file mode 100644 index 0000000..8646ad4 --- /dev/null +++ b/ILS_Window_Plugin/IWSettings.cpp @@ -0,0 +1,240 @@ +#include "pch.h" +#include "IWSettings.h" +#include +#include +#include +#include "IWUtils.h" + +using json = nlohmann::json; + +IWSettings::IWSettings(EuroScopePlugIn::CPlugIn* plugin) +{ + this->euroscopePluginRef = plugin; + + auto configFilePath = this->GetPluginDirectory() + "\\" + CONFIG_FILE_NAME; + this->availableApproaches = this->ReadApproachDefinitions(configFilePath); + this->windowStyling = this->ReadStyling(configFilePath); + this->behaviourSettings = this->ReadBehaviourSettings(configFilePath); + + this->LoadWindowPositionSettings(); +} + +void IWSettings::StoreWindowPositon(const std::string& approachName, CRect windowRect) +{ + std::string description = approachName + " pos."; + std::string rect = std::to_string(windowRect.left) + "," + std::to_string(windowRect.top) + "," + std::to_string(windowRect.right) + "," + std::to_string(windowRect.bottom); + + + this->euroscopePluginRef->SaveDataToSettings(approachName.c_str(), description.c_str(), rect.c_str()); + cachedWindowPositions[approachName] = windowRect; +} + +CRect* IWSettings::GetWindowPositon(const std::string& approachName) +{ + // Check if the position is cached + if (cachedWindowPositions.find(approachName) == cachedWindowPositions.end()) { + return NULL; + } + else { + return &cachedWindowPositions[approachName]; + } +} + +std::vector IWSettings::GetAvailableApproaches() +{ + return this->availableApproaches; +} + +IWConfig IWSettings::GetConfig() +{ + return IWConfig{ + this->windowStyling, + this->behaviourSettings + }; +} + +void IWSettings::LoadWindowPositionSettings() +{ + // For every available approach, check if there is a saved position + for (auto& approach : availableApproaches) { + const char* settings = this->euroscopePluginRef->GetDataFromSettings(approach.title.c_str()); + if (settings) { + std::string settingsString = std::string(settings); + std::regex regex("([0-9]+),([0-9]+),([0-9]+),([0-9]+)"); + std::smatch match; + if (std::regex_search(settingsString, match, regex)) { + CRect rect; + rect.left = std::stoi(match[1]); + rect.top = std::stoi(match[2]); + rect.right = std::stoi(match[3]); + rect.bottom = std::stoi(match[4]); + cachedWindowPositions[approach.title] = rect; + } + } + } +} + +std::vector IWSettings::ReadApproachDefinitions(const std::string& jsonFilePath) { + const std::string generalErrorMessage = "Could not load approach definitions"; + + std::vector approaches; + + // Open the JSON file + std::ifstream file(jsonFilePath); + if (!file.is_open()) { + this->ShowErrorMessage(generalErrorMessage, "Unable to open JSON file '" + jsonFilePath + "'"); + return approaches; + } + + // Parse the JSON file + nlohmann::json jsonData; + try { + file >> jsonData; + } + catch (const nlohmann::json::parse_error& e) { + this->ShowErrorMessage(generalErrorMessage, std::string(e.what())); + return approaches; + } + + // Check if "approaches" key exists + if (!jsonData.contains("approaches") || !jsonData["approaches"].is_array()) { + this->ShowErrorMessage(generalErrorMessage, "'approaches' key not found or is not an array."); + return approaches; + } + + // Iterate over the approaches + for (const auto& approachJson : jsonData["approaches"]) { + try { + IWApproachDefinition approach; + + // Parse individual fields + approach.title = approachJson.at("title").get(); + approach.airport = approachJson.at("airport").get(); + approach.runway = approachJson.at("runway").get(); + approach.localizerCourse = approachJson.at("localizerCourse").get(); + approach.glideslopeAngle = approachJson.at("glideslopeAngle").get(); + approach.defaultRange = approachJson.at("defaultRange").get(); + approach.thresholdAltitude = approachJson.at("thresholdAltitude").get(); + approach.thresholdLatitude = approachJson.at("thresholdLatitude").get(); + approach.thresholdLongitude = approachJson.at("thresholdLongitude").get(); + approach.maxOffsetLeft = approachJson.at("maxOffsetLeft").get(); + approach.maxOffsetRight = approachJson.at("maxOffsetRight").get(); + + + // Add to the list + approaches.push_back(approach); + } + catch (const nlohmann::json::exception& e) { + this->ShowErrorMessage(generalErrorMessage, "Error parsing approach data: " + std::string(e.what())); + } + } + + return approaches; +} + +IWStyling IWSettings::ReadStyling(const std::string& jsonFilePath) { + const std::string generalErrorMessage = "Could not load style settings"; + + std::ifstream file(jsonFilePath); + if (!file.is_open()) { + this->ShowErrorMessage(generalErrorMessage, "Unable to open JSON file: " + jsonFilePath); + } + + nlohmann::json jsonData; + file >> jsonData; + + auto readColor = [&jsonData, &generalErrorMessage, this](const std::string& key) -> COLORREF { + if (jsonData.contains("styling") && jsonData["styling"].contains(key)) { + return HexToRGB(jsonData["styling"][key].get()); + } + this->ShowErrorMessage(generalErrorMessage, ("'" + key + "' key not found.").c_str()); + }; + + auto readUnsignedInt = [&jsonData, &generalErrorMessage, this](const std::string& key) -> unsigned int { + if (jsonData.contains("styling") && jsonData["styling"].contains(key)) { + return jsonData["styling"][key].get(); + } + this->ShowErrorMessage(generalErrorMessage, ("'" + key + "' key not found.").c_str()); + return 0; // Default value when key is not found or is not an unsigned integer + }; + + auto readUIntWithDefault = [&jsonData, this](const std::string& key, unsigned int defaultValue) -> unsigned int { + if (jsonData.contains("styling") && jsonData["styling"].contains(key)) { + return jsonData["styling"][key].get(); + } + return defaultValue; + }; + + auto readStringWithDefault = [&jsonData, this](const std::string& key, const std::string& defaultValue) -> std::string { + if (jsonData.contains("styling") && jsonData["styling"].contains(key)) { + return jsonData["styling"][key].get(); + } + return defaultValue; + }; + + auto stringToTagMode = [](const std::string& value) -> IWTagMode { + if (value == "squawk") { + return IWTagMode::Squawk; + } + return IWTagMode::Callsign; + }; + + auto readBoolWithDefault = [&jsonData, this](const std::string& key, bool defaultValue) -> bool { + if (jsonData.contains("styling") && jsonData["styling"].contains(key)) { + return jsonData["styling"][key].get(); + } + return defaultValue; + }; + + return IWStyling{ + readColor("windowFrameColor"), + readColor("windowFrameTextColor"), + readColor("windowOuterFrameColor"), + readColor("backgroundColor"), + readColor("glideslopeColor"), + readColor("localizerColor"), + readColor("radarTargetColor"), + readColor("historyTrailColor"), + readColor("targetLabelColor"), + readColor("rangeStatusTextColor"), + readUIntWithDefault("fontSize", 12), + readBoolWithDefault("showTagByDefault", true), + stringToTagMode(readStringWithDefault("defaultTagMode", "callsign")) + }; +} + +IWBehaviourSettings IWSettings::ReadBehaviourSettings(const std::string& jsonFilePath) { + std::ifstream file(jsonFilePath); + if (!file.is_open()) { + this->ShowErrorMessage("Could not load behaviour options", "Unable to open JSON file '" + jsonFilePath + "'"); + } + + nlohmann::json jsonData; + file >> jsonData; + + nlohmann::json jsonObject = jsonData["behaviour"]; + + auto readStringWithDefault = [&jsonData, this](const std::string& key, const std::string& defaultValue) -> std::string { + if (jsonData.contains("behaviour") && jsonData["behaviour"].contains(key)) { + return jsonData["behaviour"][key].get(); + } + return defaultValue; + }; + + return IWBehaviourSettings{ + jsonObject.at("openWindowsBasedOnActiveRunways").get(), + readStringWithDefault("windowStyle", "X11") + }; +} + +void IWSettings::ShowErrorMessage(std::string consequence, std::string details) +{ + this->euroscopePluginRef->DisplayUserMessage(MY_PLUGIN_NAME, consequence.c_str(), details.c_str(), false, true, false, true, false); +} + +std::string IWSettings::GetPluginDirectory() { + char modulePath[MAX_PATH]; + GetModuleFileNameA((HINSTANCE)&__ImageBase, modulePath, sizeof(modulePath)); + std::string pluginDirectory = std::string(modulePath).substr(0, std::string(modulePath).find_last_of("\\/")); + return pluginDirectory; +} \ No newline at end of file diff --git a/ILS_Window_Plugin/IWSettings.h b/ILS_Window_Plugin/IWSettings.h new file mode 100644 index 0000000..8149aa1 --- /dev/null +++ b/ILS_Window_Plugin/IWSettings.h @@ -0,0 +1,43 @@ +#pragma once + +#include "IWDataTypes.h" +#include "EuroScopePlugIn.h" +#include +#include +#include + +#define MY_PLUGIN_NAME "ILS Window Plugin" +#define MY_PLUGIN_VERSION PLUGIN_VERSION +#define MY_PLUGIN_DEVELOPER CONTRIBUTORS +#define MY_PLUGIN_COPYRIGHT "GPL v3" + +#define CONFIG_FILE_NAME "ILS_window_plugin-config.json" + +class IWSettings +{ +public: + IWSettings(EuroScopePlugIn::CPlugIn* plugin); + + void StoreWindowPositon(const std::string& approachName, CRect windowRect); + CRect* GetWindowPositon(const std::string& approachName); + std::vector GetAvailableApproaches(); + IWConfig GetConfig(); + + +private: + EuroScopePlugIn::CPlugIn* euroscopePluginRef; + std::vector availableApproaches; + std::map cachedWindowPositions; + + std::vector ReadApproachDefinitions(const std::string& jsonFilePath); + IWBehaviourSettings ReadBehaviourSettings(const std::string& jsonFilePath); + IWStyling ReadStyling(const std::string& jsonFilePath); + void LoadWindowPositionSettings(); + + void ShowErrorMessage(std::string consequence, std::string details); + + std::string GetPluginDirectory(); + + IWStyling windowStyling; + IWBehaviourSettings behaviourSettings; +}; diff --git a/ILS_Window_Plugin/IWTitleBar.cpp b/ILS_Window_Plugin/IWTitleBar.cpp index 1afd40d..58030c1 100644 --- a/ILS_Window_Plugin/IWTitleBar.cpp +++ b/ILS_Window_Plugin/IWTitleBar.cpp @@ -18,7 +18,7 @@ IWTitleBar::IWTitleBar(COLORREF backgroundColor, int fontSize, IWTitleBarEventLi this->backgroundColor = backgroundColor; float fontPointsSize = fontSize * 72 / 96; - this->font.CreatePointFont(int(fontPointsSize * 10), _T("EuroScope")); + this->mainFont.CreatePointFont(int(fontPointsSize * 10), _T("EuroScope")); this->eventListener = listener; } diff --git a/ILS_Window_Plugin/IWTitleBar.h b/ILS_Window_Plugin/IWTitleBar.h index f082a03..8504519 100644 --- a/ILS_Window_Plugin/IWTitleBar.h +++ b/ILS_Window_Plugin/IWTitleBar.h @@ -34,7 +34,7 @@ class IWTitleBar : public CStatic IWTitleBarBtn* iconifyButton; IWTitleBarBtn* resizeButton; - CFont font; + CFont mainFont; private: COLORREF backgroundColor; diff --git a/ILS_Window_Plugin/IWTitleBarBtn.cpp b/ILS_Window_Plugin/IWTitleBarBtn.cpp index efac844..9322ec8 100644 --- a/ILS_Window_Plugin/IWTitleBarBtn.cpp +++ b/ILS_Window_Plugin/IWTitleBarBtn.cpp @@ -36,13 +36,6 @@ void IWTitleBarBtn::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct) { pDC->FillSolidRect(&rect, this->backgroundColor); // White background } - // Draw border (optional: use a different color on hover) - CPen pen(PS_SOLID, 1, RGB(0, 0, 0)); // Black pen for the border - CPen* oldPen = pDC->SelectObject(&pen); - pDC->SelectStockObject(HOLLOW_BRUSH); // No fill for the rectangle - pDC->Rectangle(&rect); - pDC->SelectObject(oldPen); - // Call DrawSymbol to draw the custom symbol inside DrawSymbol(pDC, rect); } diff --git a/ILS_Window_Plugin/IWUtils.h b/ILS_Window_Plugin/IWUtils.h index eff492a..2fa9bd5 100644 --- a/ILS_Window_Plugin/IWUtils.h +++ b/ILS_Window_Plugin/IWUtils.h @@ -7,11 +7,11 @@ #include "IWDataTypes.h" #include -std::string trimString(const std::string& value) { +inline std::string trimString(const std::string& value) { return std::regex_replace(value, std::regex("^ +| +$|( ) +"), "$1"); } -std::string stringToUpper(std::string s) +inline std::string stringToUpper(std::string s) { std::transform(s.begin(), s.end(), s.begin(), [](unsigned char c) { return std::toupper(c); } // correct @@ -19,23 +19,23 @@ std::string stringToUpper(std::string s) return s; } -RGB HexToRGB(const std::string& hexColor) { - RGB color = { 0, 0, 0 }; +inline COLORREF HexToRGB(const std::string& hexColor) { + int red, green, blue; if (hexColor.size() == 7 && hexColor[0] == '#') { // Extract each color component from the hex string std::stringstream ss; ss << std::hex << hexColor.substr(1, 2); // Red - ss >> color.r; + ss >> red; ss.clear(); ss << std::hex << hexColor.substr(3, 2); // Green - ss >> color.g; + ss >> green; ss.clear(); ss << std::hex << hexColor.substr(5, 2); // Blue - ss >> color.b; + ss >> blue; } - return color; + return RGB(red, green, blue); } diff --git a/ILS_Window_Plugin/IWVisualization.cpp b/ILS_Window_Plugin/IWVisualization.cpp index 4c5ebbb..8da41ff 100644 --- a/ILS_Window_Plugin/IWVisualization.cpp +++ b/ILS_Window_Plugin/IWVisualization.cpp @@ -12,7 +12,7 @@ BEGIN_MESSAGE_MAP(IWVisualization, CWnd) ON_WM_TIMER() END_MESSAGE_MAP() -IWVisualization::IWVisualization(IWApproachDefinition selectedApproach, IWStyling styling, CFont* font) +IWVisualization::IWVisualization(IWApproachDefinition selectedApproach, IWStyling styling, CFont* mainFont) { this->selectedApproach = selectedApproach; this->approachLength = selectedApproach.defaultRange; @@ -22,15 +22,15 @@ IWVisualization::IWVisualization(IWApproachDefinition selectedApproach, IWStylin this->applyTemperatureCorrection = true; this->tagMode = styling.defaultTagMode; - this->rangeStatusTextColor = RGB(styling.rangeStatusTextColor.r, styling.rangeStatusTextColor.g, styling.rangeStatusTextColor.b); - this->windowBackground = RGB(styling.backgroundColor.r, styling.backgroundColor.g, styling.backgroundColor.b); - this->targetLabelColor = RGB(styling.targetLabelColor.r, styling.targetLabelColor.g, styling.targetLabelColor.b); - this->glideSlopePen.CreatePen(PS_SOLID, 1, RGB(styling.glideslopeColor.r, styling.glideslopeColor.g, styling.glideslopeColor.b)); - this->localizerBrush.CreateSolidBrush(RGB(styling.localizerColor.r, styling.localizerColor.g, styling.localizerColor.b)); - this->radarTargetPen.CreatePen(PS_SOLID, 1, RGB(styling.radarTargetColor.r, styling.radarTargetColor.g, styling.radarTargetColor.b)); - this->historyTrailPen.CreatePen(PS_SOLID, 1, RGB(styling.historyTrailColor.r, styling.historyTrailColor.g, styling.historyTrailColor.b)); + this->rangeStatusTextColor = styling.rangeStatusTextColor; + this->windowBackground = styling.backgroundColor; + this->targetLabelColor = styling.targetLabelColor; + this->glideSlopePen.CreatePen(PS_SOLID, 1, styling.glideslopeColor); + this->localizerBrush.CreateSolidBrush(styling.localizerColor); + this->radarTargetPen.CreatePen(PS_SOLID, 1, styling.radarTargetColor); + this->historyTrailPen.CreatePen(PS_SOLID, 1, styling.historyTrailColor); - this->font = font; + this->mainFont = mainFont; } @@ -52,7 +52,7 @@ void IWVisualization::OnPaint() memDC.FillSolidRect(rect, windowBackground); // Select font - CFont* oldFont = memDC.SelectObject(font); + CFont* oldFont = memDC.SelectObject(mainFont); // Perform all drawing operations on memDC instead of dc DrawGlideslopeAndLocalizer(memDC); @@ -231,7 +231,7 @@ BOOL IWVisualization::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt) { this->approachLength -= 1; } - else if (zDelta < 0 && this->approachLength < 50) + else if (zDelta < 0 && this->approachLength < MAX_RANGE) { this->approachLength += 1; } diff --git a/ILS_Window_Plugin/IWVisualization.h b/ILS_Window_Plugin/IWVisualization.h index f28223b..c576759 100644 --- a/ILS_Window_Plugin/IWVisualization.h +++ b/ILS_Window_Plugin/IWVisualization.h @@ -7,6 +7,7 @@ #define APP_LINE_MARGIN_TOP 0.08 #define APP_LINE_MARGIN_SIDES 0.08 #define APP_LINE_MARGIN_BOTTOM 0.35 +#define MAX_RANGE 30 // Nautical miles #define LABEL_OFFSET 15 #define TARGET_RADIUS 5 #define HISTORY_TRAIL_RADIUS 5 @@ -17,7 +18,7 @@ class IWVisualization : public CWnd { public: - IWVisualization(IWApproachDefinition selectedApproach, IWStyling styling, CFont* font); + IWVisualization(IWApproachDefinition selectedApproach, IWStyling styling, CFont* mainFont); void DrawGlideslopeAndLocalizer(CDC& dc); void DrawRadarTargets(CDC& dc); void DrawCurrentZoomValue(CDC& dc); @@ -64,7 +65,7 @@ class IWVisualization : public CWnd CPen radarTargetPen; CPen historyTrailPen; IWTagMode tagMode; - CFont* font; + CFont* mainFont; std::set clickedTargets; diff --git a/ILS_Window_Plugin/IWWindow.cpp b/ILS_Window_Plugin/IWWindow.cpp index 3ec2ca4..110dcee 100644 --- a/ILS_Window_Plugin/IWWindow.cpp +++ b/ILS_Window_Plugin/IWWindow.cpp @@ -4,6 +4,7 @@ #include "RenderUtils.h" #include "IWX11TitleBar.h" #include "IWCdeTitleBar.h" +#include #define MAX_PROCEDURES 100 @@ -11,6 +12,7 @@ #define MENU_ITEM_SHOW_LABELS 10001 #define MENU_ITEM_CORRECT_FOR_TEMPERATURE 10002 #define MENU_ITEM_CLOSE 10003 +#define MENU_ITEM_TOGGLE_THEME 10004 #define MENU_ITEM_PROCEDURES_SEL_START 20000 #define MENU_ITEM_PROCEDURES_NEW_START 30000 @@ -27,27 +29,30 @@ BEGIN_MESSAGE_MAP(IWWindow, CWnd) ON_WM_ERASEBKGND() ON_WM_NCACTIVATE() ON_WM_GETMINMAXINFO() + ON_WM_INITMENUPOPUP() + ON_WM_MEASUREITEM() + ON_WM_DRAWITEM() ON_MESSAGE(WM_EXITSIZEMOVE, &IWWindow::OnExitSizeMove) ON_COMMAND_EX(MENU_ITEM_FLIP, &IWWindow::OnMenuOptionSelected) ON_COMMAND_EX(MENU_ITEM_SHOW_LABELS, &IWWindow::OnMenuOptionSelected) ON_COMMAND_EX(MENU_ITEM_CORRECT_FOR_TEMPERATURE, &IWWindow::OnMenuOptionSelected) ON_COMMAND_EX(MENU_ITEM_CLOSE, &IWWindow::OnMenuOptionSelected) + ON_COMMAND_EX(MENU_ITEM_TOGGLE_THEME, &IWWindow::OnMenuOptionSelected) ON_COMMAND_RANGE(MENU_ITEM_PROCEDURES_SEL_START, MENU_ITEM_PROCEDURES_SEL_START + MAX_PROCEDURES, &IWWindow::OnProcedureSelected) ON_COMMAND_RANGE(MENU_ITEM_PROCEDURES_NEW_START, MENU_ITEM_PROCEDURES_NEW_START + MAX_PROCEDURES, &IWWindow::OnProcedureSelected) END_MESSAGE_MAP() IWWindow::IWWindow(IWApproachDefinition selectedApproach, IWStyling styling, int titleBarHeight, int windowBorderThickness, int windowOuterBorderThickness) - : ilsVisualization(selectedApproach, styling, &this->font) + : ilsVisualization(selectedApproach, styling, &this->mainFont) , TITLE_BAR_HEIGHT(titleBarHeight) , WINDOW_BORDER_THICKNESS(windowBorderThickness) , WINDOW_OUTER_BORDER_WIDTH(windowOuterBorderThickness) - , textColor(RGB(styling.windowFrameTextColor.r, styling.windowFrameTextColor.g, styling.windowFrameTextColor.b)) - , windowBorderColor(RGB(styling.windowFrameColor.r, styling.windowFrameColor.g, styling.windowFrameColor.b)) - , windowOuterBorderColor(RGB(styling.windowOuterFrameColor.r, styling.windowOuterFrameColor.g, styling.windowOuterFrameColor.b)) + , textColor(styling.windowFrameTextColor) + , windowBorderColor(styling.windowFrameColor) + , windowOuterBorderColor(styling.windowOuterFrameColor) { float fontPointsSize = styling.fontSize * 72 / 96; - this->font.CreatePointFont(int(fontPointsSize * 10), _T("EuroScope")); - + this->mainFont.CreatePointFont(int(fontPointsSize * 10), _T("EuroScope")); this->selectedApproach = selectedApproach; } @@ -353,35 +358,40 @@ void IWWindow::SetActiveApproach(const IWApproachDefinition& selectedApproach) void IWWindow::CreatePopupMenu(CPoint point) { - // Create the main popup menu - CMenu menu; - menu.CreatePopupMenu(); + // Dynamically allocate menu to persist beyond this function + popupMenu = std::make_unique(); + popupMenu->CreatePopupMenu(); - // Create the submenu with the available approaches - CMenu subMenuSelect; - CMenu subMenuOpenNew; - subMenuSelect.CreatePopupMenu(); - subMenuOpenNew.CreatePopupMenu(); + // Submenus + auto subMenuSelect = std::make_unique(); + auto subMenuOpenNew = std::make_unique(); + subMenuSelect->CreatePopupMenu(); + subMenuOpenNew->CreatePopupMenu(); int idCounter = 0; for (const IWApproachDefinition& approach : availableApproaches) { - bool isActive = approach.title == this->selectedApproach.title; - int menuItemID = idCounter++; - if (isActive) { - subMenuSelect.AppendMenu(MF_STRING | MF_CHECKED, MENU_ITEM_PROCEDURES_SEL_START + menuItemID, CString(approach.title.c_str())); - } - else { - subMenuSelect.AppendMenu(MF_STRING, MENU_ITEM_PROCEDURES_SEL_START + menuItemID, CString(approach.title.c_str())); - } - subMenuOpenNew.AppendMenu(MF_STRING, MENU_ITEM_PROCEDURES_NEW_START + menuItemID, CString(approach.title.c_str())); + bool isActive = (approach.title == this->selectedApproach.title); + + subMenuSelect->AppendMenu( + MF_STRING | (isActive ? MF_CHECKED : 0), + MENU_ITEM_PROCEDURES_SEL_START + idCounter, + CString(approach.title.c_str()) + ); + subMenuOpenNew->AppendMenu( + MF_STRING, + MENU_ITEM_PROCEDURES_NEW_START + idCounter, + CString(approach.title.c_str()) + ); + + idCounter++; } - menu.AppendMenu(MF_POPUP, (UINT_PTR)subMenuSelect.m_hMenu, _T("View")); - menu.AppendMenu(MF_POPUP, (UINT_PTR)subMenuOpenNew.m_hMenu, _T("Open")); + popupMenu->AppendMenu(MF_POPUP, (UINT_PTR)subMenuSelect->Detach(), _T("View")); + popupMenu->AppendMenu(MF_POPUP, (UINT_PTR)subMenuOpenNew->Detach(), _T("New window")); // Add static menu items - menu.AppendMenu( + popupMenu->AppendMenu( MF_STRING | (ilsVisualization.GetShowTagsByDefault() ? MF_CHECKED : MF_UNCHECKED), MENU_ITEM_SHOW_LABELS, _T("Show labels by default") @@ -389,32 +399,27 @@ void IWWindow::CreatePopupMenu(CPoint point) auto airportTemperature = m_latestLiveData.airportTemperatures.find(selectedApproach.airport); auto airportTemperatureMenuText = - "Apply temperature correction (" - + selectedApproach.airport + ": " - + (airportTemperature != m_latestLiveData.airportTemperatures.end() ? std::to_string(airportTemperature->second) + "°C" : "N/A") + "Apply temperature correction (" + + selectedApproach.airport + ": " + + (airportTemperature != m_latestLiveData.airportTemperatures.end() ? std::to_string(airportTemperature->second) + "°C" : "N/A") + ")"; - menu.AppendMenu( + popupMenu->AppendMenu( MF_STRING | (ilsVisualization.GetApplyTemperatureCorrection() ? MF_CHECKED : MF_UNCHECKED), MENU_ITEM_CORRECT_FOR_TEMPERATURE, - _T(airportTemperatureMenuText.c_str()) - ); - menu.AppendMenu( - MF_STRING, - MENU_ITEM_FLIP, - _T("Change orientation") + airportTemperatureMenuText.c_str() ); + popupMenu->AppendMenu(MF_STRING, MENU_ITEM_FLIP, _T("Change orientation")); + popupMenu->AppendMenu(MF_STRING, MENU_ITEM_TOGGLE_THEME, _T("Toggle window style")); + popupMenu->AppendMenu(MF_STRING | MF_REMOVE, MENU_ITEM_CLOSE, _T("Close")); - menu.AppendMenu( - MF_STRING | MF_REMOVE, - MENU_ITEM_CLOSE, - _T("Close") - ); - - // Display the menu - menu.TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y, this); + popupHMenu = popupMenu->GetSafeHmenu(); + + // Show the menu + popupMenu->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y, this); } + BOOL IWWindow::OnMenuOptionSelected(UINT nID) { if (nID == MENU_ITEM_FLIP) @@ -436,6 +441,10 @@ BOOL IWWindow::OnMenuOptionSelected(UINT nID) { this->DestroyWindow(); } + else if (nID == MENU_ITEM_TOGGLE_THEME) + { + this->m_listener->OnToggleThemeClicked(this); + } return TRUE; } @@ -461,3 +470,61 @@ void IWWindow::OnProcedureSelected(UINT nID) Invalidate(); } +void IWWindow::OnInitMenuPopup(CMenu* pPopupMenu, UINT nIndex, BOOL bSysMenu) +{ + CWnd::OnInitMenuPopup(pPopupMenu, nIndex, bSysMenu); + + if (!bSysMenu) // Apply only to application menus + { + for (int i = 0; i < pPopupMenu->GetMenuItemCount(); ++i) + { + MENUITEMINFO mii = { sizeof(MENUITEMINFO) }; + mii.fMask = MIIM_FTYPE; + pPopupMenu->GetMenuItemInfo(i, &mii, TRUE); + mii.fType |= MFT_OWNERDRAW; + pPopupMenu->SetMenuItemInfo(i, &mii, TRUE); + } + } +} + +void IWWindow::OnMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct) +{ + if (!popupHMenu) return; + + CMenu* pMenu = CMenu::FromHandle(popupHMenu); + if (!pMenu) return; + + CString menuText; + pMenu->GetMenuString(lpMeasureItemStruct->itemID, menuText, MF_BYCOMMAND); + + CDC* pDC = GetDC(); + CFont* pOldFont = pDC->SelectObject(&mainFont); + + CSize textSize = pDC->GetTextExtent(menuText); + lpMeasureItemStruct->itemWidth = textSize.cx + extraMenuItemWidth; + lpMeasureItemStruct->itemHeight = 24; + + pDC->SelectObject(pOldFont); + ReleaseDC(pDC); +} + +void IWWindow::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct) +{ + if (lpDrawItemStruct->CtlType == ODT_MENU) // Check if it's a menu item + { + CDC* pDC = CDC::FromHandle(lpDrawItemStruct->hDC); + CRect rect = lpDrawItemStruct->rcItem; + bool isHovered = lpDrawItemStruct->itemState & ODS_SELECTED; + bool isChecked = lpDrawItemStruct->itemState & ODS_CHECKED; + + CString menuText; + ::GetMenuString((HMENU)lpDrawItemStruct->hwndItem, lpDrawItemStruct->itemID, menuText.GetBuffer(256), 256, MF_BYCOMMAND); + menuText.ReleaseBuffer(); + + this->DrawMenuItem(pDC, rect, menuText, isHovered, isChecked); + } + else + { + CWnd::OnDrawItem(nIDCtl, lpDrawItemStruct); + } +} \ No newline at end of file diff --git a/ILS_Window_Plugin/IWWindow.h b/ILS_Window_Plugin/IWWindow.h index 38c0b45..e6c2c18 100644 --- a/ILS_Window_Plugin/IWWindow.h +++ b/ILS_Window_Plugin/IWWindow.h @@ -20,6 +20,7 @@ class IIWWndEventListener { virtual void OnWindowClosed(IWWindow* window) = 0; virtual void OnWindowMenuOpenNew(std::string title) = 0; virtual void OnWindowRectangleChanged(IWWindow* window) = 0; + virtual void OnToggleThemeClicked(IWWindow* window) = 0; }; class IWWindow : public CWnd, public IWTitleBarEventListener { @@ -36,6 +37,7 @@ class IWWindow : public CWnd, public IWTitleBarEventListener { protected: virtual void DrawBorder(CDC* pdc, CRect windowRect) = 0; virtual int GetEdgeCursorPosition(CPoint point) = 0; + virtual void DrawMenuItem(CDC* pdc, CRect bounds, CString text, bool isHovered, bool isChecked) = 0; const int TITLE_BAR_HEIGHT; const int WINDOW_BORDER_THICKNESS; @@ -45,11 +47,13 @@ class IWWindow : public CWnd, public IWTitleBarEventListener { const COLORREF windowOuterBorderColor; IWTitleBar* titleBar; + CFont mainFont; + + int extraMenuItemWidth = 0; private: IWVisualization ilsVisualization; - CFont font; void CreatePopupMenu(CPoint point); @@ -80,8 +84,16 @@ class IWWindow : public CWnd, public IWTitleBarEventListener { afx_msg void OnGetMinMaxInfo(MINMAXINFO* lpMMI); afx_msg void OnLButtonDown(UINT nFlags, CPoint point); afx_msg BOOL OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message); + afx_msg void OnInitMenuPopup(CMenu* pPopupMenu, UINT nIndex, BOOL bSysMenu); + afx_msg void OnMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct); + afx_msg void OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct); + // Custom menu handling BOOL OnMenuOptionSelected(UINT nID); void OnProcedureSelected(UINT nID); + CRect GetClientRectBelowTitleBar(); + + std::shared_ptr popupMenu; + HMENU popupHMenu; }; diff --git a/ILS_Window_Plugin/IWWindowManager.cpp b/ILS_Window_Plugin/IWWindowManager.cpp new file mode 100644 index 0000000..dd855a6 --- /dev/null +++ b/ILS_Window_Plugin/IWWindowManager.cpp @@ -0,0 +1,266 @@ +#include "pch.h" +#include "IWWindowManager.h" +#include "IWX11Window.h" +#include "IWCdeWindow.h" +#include + +IWWindowManager::IWWindowManager(IWSettings* settings) +{ + this->settings = settings; + + AFX_MANAGE_STATE(AfxGetStaticModuleState()); // Manage the module state for MFC + + // Register a custom window class + WNDCLASS wndClass = { 0 }; + wndClass.lpfnWndProc = ::DefWindowProc; + wndClass.hInstance = AfxGetInstanceHandle(); + wndClass.hCursor = ::LoadCursor(nullptr, IDC_ARROW); + wndClass.hbrBackground = (HBRUSH)::GetStockObject(WHITE_BRUSH); + wndClass.lpszClassName = WINDOW_CLASS_NAME; + + if (!AfxRegisterClass(&wndClass)) + return; +} + +void IWWindowManager::OnWindowClosed(IWWindow* window) +{ + auto it = std::find(windows.begin(), windows.end(), window); + if (it != windows.end()) { + windows.erase(it); + } +} + +void IWWindowManager::OnWindowMenuOpenNew(std::string approachTitle) +{ + auto availableApproaches = this->settings->GetAvailableApproaches(); + auto selectedApproach = std::find_if(availableApproaches.begin(), availableApproaches.end(), + [&approachTitle](const IWApproachDefinition& approach) { + return approach.title == approachTitle; + }); + + if (selectedApproach != availableApproaches.end()) { + OpenApproachView(*selectedApproach); + } + else { + OpenApproachView(availableApproaches[0]); + } +} + +void IWWindowManager::OnWindowRectangleChanged(IWWindow* window) +{ + CRect windowRect; + window->GetWindowRect(&windowRect); + std::string name = window->GetActiveApproachName(); + std::string description = name + " pos."; + std::string rect = std::to_string(windowRect.left) + "," + std::to_string(windowRect.top) + "," + std::to_string(windowRect.right) + "," + std::to_string(windowRect.bottom); + + this->settings->StoreWindowPositon(name, windowRect); +} + +void IWWindowManager::OnToggleThemeClicked(IWWindow* window) +{ + // Close and repoen the window with the other theme + auto it = std::find(windows.begin(), windows.end(), window); + + if (it != windows.end()) { + auto activeApproachName = window->GetActiveApproachName(); + auto activeApproach = LookupApproach(activeApproachName); + + auto isX11 = dynamic_cast(window) != nullptr; + window->DestroyWindow(); + + if (isX11) { + OpenApproachViewWithTheme(activeApproach, IWTheme::CDE); + } + else { + OpenApproachViewWithTheme(activeApproach, IWTheme::X11); + } + } +} + +IWApproachDefinition IWWindowManager::LookupApproach(std::string approachName) +{ + auto availableApproaches = this->settings->GetAvailableApproaches(); + auto it = std::find_if(availableApproaches.begin(), availableApproaches.end(), + [&approachName](const IWApproachDefinition& approach) { + return approach.title == approachName; + }); + if (it != availableApproaches.end()) { + return *it; + } + return availableApproaches[0]; +} + +void IWWindowManager::OpenApproachView(IWApproachDefinition approach) +{ + std::string windowStyle = this->settings->GetConfig().behaviour.windowStyle; + + if (windowStyle == "X11") { + OpenApproachViewWithTheme(approach, IWTheme::X11); + } + else { + OpenApproachViewWithTheme(approach, IWTheme::CDE); + } +} + +void IWWindowManager::OpenApproachViewWithTheme(IWApproachDefinition approach, IWTheme theme) +{ + AFX_MANAGE_STATE(AfxGetStaticModuleState()); + + // Check if a window with the same title is already open + IWWindow* windowWithSameTitle = nullptr; + for (const auto& window : windows) { + if (window->GetActiveApproachName() == approach.title) { + windowWithSameTitle = window; + break; + } + } + + if (windowWithSameTitle) { + // Restore the window and bring it to the front + windowWithSameTitle->ShowWindow(SW_RESTORE); + windowWithSameTitle->SetForegroundWindow(); + return; + } + + // Calculate the spawning point for the new window + CPoint spawningPoint = CPoint(int(windows.size()) * 50, int(windows.size()) * 50 + 100); + CSize windowSize = CSize(300, 200); + + // Use the saved position if there is one + auto savedPosition = this->settings->GetWindowPositon(approach.title); + if (savedPosition) { + windowSize = savedPosition->Size(); + spawningPoint = savedPosition->TopLeft(); + } + else { + // Use the same size as the newest window if there is one. + IWWindow* newestWindow = windows.size() > 0 ? windows.back() : nullptr; + if (newestWindow) { + CRect rect; + newestWindow->GetWindowRect(&rect); + windowSize = rect.Size(); + spawningPoint = CPoint(rect.left + 50, rect.top + 50); + } + } + + IWWindow* newWindow = nullptr; + + switch (theme) { + case IWTheme::X11: + newWindow = new IWX11Window(approach, this->settings->GetConfig().styling); + break; + case IWTheme::CDE: + newWindow = new IWCdeWindow(approach, this->settings->GetConfig().styling); + break; + } + + auto hwndPopup = newWindow->CreateEx( + WS_EX_TOPMOST | WS_EX_APPWINDOW | WS_EX_NOACTIVATE, + WINDOW_CLASS_NAME, + _T(approach.title.c_str()), + WS_POPUP, + spawningPoint.x, + spawningPoint.y, + windowSize.cx, + windowSize.cy, + nullptr, + nullptr + ); + + if (!hwndPopup) { + delete newWindow; + return; + } + + newWindow->SetMenu(NULL); + newWindow->ShowWindow(SW_SHOWNOACTIVATE); // Show but don't steal focus + newWindow->UpdateWindow(); + newWindow->SetListener(this); + newWindow->SetAvailableApproaches(this->settings->GetAvailableApproaches()); + + windows.push_back(newWindow); +} + +void IWWindowManager::SyncWithActiveRunways(std::vector activeRunways) +{ + if (!this->settings->GetConfig().behaviour.openWindowsBasedOnActiveRunways) { + return; + } + + std::vector approachesThatShouldBeOpen; + + AFX_MANAGE_STATE(AfxGetStaticModuleState()); + + // Find approaches that should be open + for (auto& approach : this->settings->GetAvailableApproaches()) { + auto activeRunway = std::find_if(activeRunways.begin(), activeRunways.end(), + [&approach](const IWActiveRunway& activeRunway) { + return activeRunway.airport == approach.airport && activeRunway.runway == approach.runway; + }); + if (activeRunway != activeRunways.end()) { + approachesThatShouldBeOpen.push_back(approach); + } + } + + + // Find open windows that should be closed + for (auto& window : windows) { + bool shouldBeClosed = true; + for (auto& approach : approachesThatShouldBeOpen) { + if (window->GetActiveApproachName() == approach.title) { + shouldBeClosed = false; + } + } + if (shouldBeClosed) { + window->DestroyWindow(); + } + } + + // Open windows that should be open + for (auto& approach : approachesThatShouldBeOpen) { + bool alreadyOpen = std::any_of(windows.begin(), windows.end(), [&approach](const IWWindow* window) { + return window->GetActiveApproachName() == approach.title; + }); + + if (!alreadyOpen) { + this->OpenApproachView(approach); + } + } +} + +bool IWWindowManager::Open(std::string approachTitle) +{ + auto availableApproaches = this->settings->GetAvailableApproaches(); + + // Find the approach by title + auto it = std::find_if(availableApproaches.begin(), availableApproaches.end(), + [&approachTitle](const IWApproachDefinition& approach) { + return approach.title == approachTitle; + }); + + if (it != availableApproaches.end()) { + // Approach found: Open the approach window + this->OpenApproachView(*it); + return true; // Command handled + } + else { + return false; // Command not handled + } +} + +void IWWindowManager::HandleLiveData(IWLiveData liveData) +{ + for (auto& window : windows) { + window->SendMessage(WM_UPDATE_DATA, reinterpret_cast(&liveData)); + } +} + +IWWindowManager::~IWWindowManager() +{ + for (const auto& window : windows) { + if (window) { + delete window; + } + } +} diff --git a/ILS_Window_Plugin/IWWindowManager.h b/ILS_Window_Plugin/IWWindowManager.h new file mode 100644 index 0000000..50a861f --- /dev/null +++ b/ILS_Window_Plugin/IWWindowManager.h @@ -0,0 +1,35 @@ +#pragma once +#include "IWDataTypes.h" +#include "IWWindow.h" +#include "IWSettings.h" + +#define WINDOW_CLASS_NAME _T("IWWindow") + +class IWWindowManager : IIWWndEventListener +{ +public: + IWWindowManager(IWSettings* settings); + void SyncWithActiveRunways(std::vector activeRunways); + bool Open(std::string approachTitle); + void HandleLiveData(IWLiveData liveData); + + // Destructor + ~IWWindowManager(); + +private: + std::vector windows; + IWSettings* settings; + + void OpenApproachView(IWApproachDefinition approach); + void OpenApproachViewWithTheme(IWApproachDefinition approach, IWTheme theme); + + // Callbacks from a window + void OnWindowClosed(IWWindow* window) override; + void OnWindowMenuOpenNew(std::string approachTitle) override; + void OnWindowRectangleChanged(IWWindow* window) override; + void OnToggleThemeClicked(IWWindow* window) override; + + // Utils + IWApproachDefinition LookupApproach(std::string approachName); +}; + diff --git a/ILS_Window_Plugin/IWX11IconifyBtn.cpp b/ILS_Window_Plugin/IWX11IconifyBtn.cpp index db8893a..4ec83ab 100644 --- a/ILS_Window_Plugin/IWX11IconifyBtn.cpp +++ b/ILS_Window_Plugin/IWX11IconifyBtn.cpp @@ -1,11 +1,13 @@ #include "pch.h" #include "IWX11IconifyBtn.h" -void IWX11IconifyBtn::DrawSymbol(CDC* pDC, CRect rect) +void IWX11IconifyBtn::DrawIcon(CDC* pDC, CRect rect) { // Draw a black circle in the center - CBrush brush(RGB(0, 0, 0)); // Solid black brush for the circle + CBrush brush(this->iconColor); // Solid black brush for the circle CBrush* oldBrush = pDC->SelectObject(&brush); + CPen pen(PS_SOLID, 1, this->iconColor); + CPen* oldPen = pDC->SelectObject(&pen); int radius = min(rect.Width(), rect.Height()) / 3; // Circle radius CPoint center = rect.CenterPoint(); @@ -13,4 +15,5 @@ void IWX11IconifyBtn::DrawSymbol(CDC* pDC, CRect rect) // Restore the old GDI objects pDC->SelectObject(oldBrush); + pDC->SelectObject(oldPen); } diff --git a/ILS_Window_Plugin/IWX11IconifyBtn.h b/ILS_Window_Plugin/IWX11IconifyBtn.h index c4331a3..4492ed7 100644 --- a/ILS_Window_Plugin/IWX11IconifyBtn.h +++ b/ILS_Window_Plugin/IWX11IconifyBtn.h @@ -1,10 +1,11 @@ #pragma once -#include "IWTitleBarBtn.h" +#include "IWX11TitleBarBtnBase.h" -class IWX11IconifyBtn : public IWTitleBarBtn +class IWX11IconifyBtn : public IWX11TitleBarBtnBase { - using IWTitleBarBtn::IWTitleBarBtn; - void DrawSymbol(CDC* pDC, CRect rect) override; + using IWX11TitleBarBtnBase::IWX11TitleBarBtnBase; +private: + void DrawIcon(CDC* pDC, CRect rect) override; }; diff --git a/ILS_Window_Plugin/IWX11MenuBtn.cpp b/ILS_Window_Plugin/IWX11MenuBtn.cpp index 1a86cb9..50f7682 100644 --- a/ILS_Window_Plugin/IWX11MenuBtn.cpp +++ b/ILS_Window_Plugin/IWX11MenuBtn.cpp @@ -1,8 +1,7 @@ #include "pch.h" #include "IWX11MenuBtn.h" - -void IWX11MenuBtn::DrawSymbol(CDC* pDC, CRect rect) +void IWX11MenuBtn::DrawIcon(CDC* pDC, CRect rect) { // Calculate the center of the rectangle CPoint center = rect.CenterPoint(); @@ -17,13 +16,16 @@ void IWX11MenuBtn::DrawSymbol(CDC* pDC, CRect rect) points[1] = CPoint(center.x - triangleBase / 2, center.y - triangleHeight / 2); // Bottom left vertex points[2] = CPoint(center.x + triangleBase / 2, center.y - triangleHeight / 2); // Bottom right vertex - // Select a brush to fill the triangle - CBrush brush(RGB(0, 0, 0)); // Black color for the triangle + // Select a brush and pen + CBrush brush(this->iconColor); CBrush* oldBrush = pDC->SelectObject(&brush); + CPen pen(PS_SOLID, 1, this->iconColor); + CPen* oldPen = pDC->SelectObject(&pen); // Draw the filled triangle pDC->Polygon(points, 3); - // Restore the previous brush + // Restore the previous brush and pen pDC->SelectObject(oldBrush); + pDC->SelectObject(oldPen); } diff --git a/ILS_Window_Plugin/IWX11MenuBtn.h b/ILS_Window_Plugin/IWX11MenuBtn.h index 588bdd1..027d918 100644 --- a/ILS_Window_Plugin/IWX11MenuBtn.h +++ b/ILS_Window_Plugin/IWX11MenuBtn.h @@ -1,9 +1,11 @@ #pragma once -#include "IWTitleBarBtn.h" +#include "IWX11TitleBarBtnBase.h" -class IWX11MenuBtn : public IWTitleBarBtn +class IWX11MenuBtn : public IWX11TitleBarBtnBase { - using IWTitleBarBtn::IWTitleBarBtn; - void DrawSymbol(CDC* pDC, CRect rect) override; + using IWX11TitleBarBtnBase::IWX11TitleBarBtnBase; + +private: + void DrawIcon(CDC* pDC, CRect rect) override; }; diff --git a/ILS_Window_Plugin/IWX11ResizeBtn.cpp b/ILS_Window_Plugin/IWX11ResizeBtn.cpp index 471ad02..8152636 100644 --- a/ILS_Window_Plugin/IWX11ResizeBtn.cpp +++ b/ILS_Window_Plugin/IWX11ResizeBtn.cpp @@ -1,9 +1,9 @@ #include "pch.h" #include "IWX11ResizeBtn.h" -void IWX11ResizeBtn::DrawSymbol(CDC* pDC, CRect rect) { +void IWX11ResizeBtn::DrawIcon(CDC* pDC, CRect rect) { // Create a 1px black pen - CPen pen(PS_SOLID, 1, RGB(0, 0, 0)); + CPen pen(PS_SOLID, 1, this->iconColor); CPen* oldPen = pDC->SelectObject(&pen); // Draw first small rectangle (1/3 size) diff --git a/ILS_Window_Plugin/IWX11ResizeBtn.h b/ILS_Window_Plugin/IWX11ResizeBtn.h index 61f44a9..8dfdd4f 100644 --- a/ILS_Window_Plugin/IWX11ResizeBtn.h +++ b/ILS_Window_Plugin/IWX11ResizeBtn.h @@ -1,8 +1,10 @@ #pragma once -#include "IWTitleBarBtn.h" +#include "IWX11TitleBarBtnBase.h" -class IWX11ResizeBtn : public IWTitleBarBtn +class IWX11ResizeBtn : public IWX11TitleBarBtnBase { - using IWTitleBarBtn::IWTitleBarBtn; - void DrawSymbol(CDC* pDC, CRect rect) override; + using IWX11TitleBarBtnBase::IWX11TitleBarBtnBase; + +private: + void DrawIcon(CDC* pDC, CRect rect) override; }; diff --git a/ILS_Window_Plugin/IWX11TitleBar.cpp b/ILS_Window_Plugin/IWX11TitleBar.cpp index f983189..f118259 100644 --- a/ILS_Window_Plugin/IWX11TitleBar.cpp +++ b/ILS_Window_Plugin/IWX11TitleBar.cpp @@ -8,9 +8,10 @@ IWX11TitleBar::IWX11TitleBar(COLORREF backgroundColor, COLORREF textColor, IWTitleBarEventListener* listener) : IWTitleBar(backgroundColor, 14, listener) { - this->iconifyButton = new IWX11IconifyBtn(backgroundColor); - this->menuButton = new IWX11MenuBtn(backgroundColor); - this->resizeButton = new IWX11ResizeBtn(backgroundColor); + this->iconifyButton = new IWX11IconifyBtn(backgroundColor, textColor); + this->menuButton = new IWX11MenuBtn(backgroundColor, textColor); + this->resizeButton = new IWX11ResizeBtn(backgroundColor, textColor); + this->textColor = textColor; } void IWX11TitleBar::PositionButtons(const CRect& rect) @@ -52,7 +53,7 @@ void IWX11TitleBar::DrawTitle(CDC* pdc, CRect rect, CString title) { CRect textArea = rect; textArea.left += 5; - auto oldFont = pdc->SelectObject(this->font); + auto oldFont = pdc->SelectObject(this->mainFont); pdc->SetTextColor(this->textColor); pdc->SetBkMode(TRANSPARENT); pdc->DrawText(_T(title), -1, textArea, DT_LEFT | DT_VCENTER | DT_SINGLELINE); diff --git a/ILS_Window_Plugin/IWX11TitleBarBtnBase.cpp b/ILS_Window_Plugin/IWX11TitleBarBtnBase.cpp new file mode 100644 index 0000000..fe2ec36 --- /dev/null +++ b/ILS_Window_Plugin/IWX11TitleBarBtnBase.cpp @@ -0,0 +1,19 @@ +#include "pch.h" +#include "IWX11TitleBarBtnBase.h" + +IWX11TitleBarBtnBase::IWX11TitleBarBtnBase(COLORREF backgroundColor, COLORREF iconColor) : IWTitleBarBtn(backgroundColor) +{ + this->iconColor = iconColor; +} + +void IWX11TitleBarBtnBase::DrawSymbol(CDC* pDC, CRect rect) +{ + // Draw border (optional: use a different color on hover) + CPen pen(PS_SOLID, 1, this->iconColor); // Black pen for the border + CPen* oldPen = pDC->SelectObject(&pen); + pDC->SelectStockObject(HOLLOW_BRUSH); // No fill for the rectangle + pDC->Rectangle(&rect); + pDC->SelectObject(oldPen); + + this->DrawIcon(pDC, rect); +} diff --git a/ILS_Window_Plugin/IWX11TitleBarBtnBase.h b/ILS_Window_Plugin/IWX11TitleBarBtnBase.h new file mode 100644 index 0000000..ab902d5 --- /dev/null +++ b/ILS_Window_Plugin/IWX11TitleBarBtnBase.h @@ -0,0 +1,17 @@ +#pragma once + +#include "IWTitleBarBtn.h" + +class IWX11TitleBarBtnBase : public IWTitleBarBtn +{ +public: + IWX11TitleBarBtnBase(COLORREF backgroundColor, COLORREF iconColor); + +private: + void DrawSymbol(CDC* pDC, CRect rect) override; + +protected: + virtual void DrawIcon(CDC* pdc, CRect rect) = 0; + COLORREF iconColor; +}; + diff --git a/ILS_Window_Plugin/IWX11Window.cpp b/ILS_Window_Plugin/IWX11Window.cpp index b8c5a72..09a634d 100644 --- a/ILS_Window_Plugin/IWX11Window.cpp +++ b/ILS_Window_Plugin/IWX11Window.cpp @@ -5,7 +5,10 @@ IWX11Window::IWX11Window(IWApproachDefinition selectedApproach, IWStyling styling) : IWWindow(selectedApproach, styling, 26, 3, 1) { - this->titleBar = new IWX11TitleBar(windowBorderColor, textColor, this); + this->titleBar = new IWX11TitleBar(windowBorderColor, styling.windowFrameTextColor, this); + this->menuBgColor = styling.windowFrameColor; + this->menuTextColor = styling.windowFrameTextColor; + this->extraMenuItemWidth = 20; } void IWX11Window::DrawBorder(CDC* pdc, CRect windowRect) @@ -22,3 +25,27 @@ int IWX11Window::GetEdgeCursorPosition(CPoint point) { return 0; } + +void IWX11Window::DrawMenuItem(CDC* pdc, CRect bounds, CString text, bool isHovered, bool isChecked) +{ + // Invert colors when hovered + COLORREF bgColor = isHovered ? this->menuTextColor : this->menuBgColor; + COLORREF textColor = isHovered ? this->menuBgColor : this->menuTextColor; + + std::string fullText = isChecked ? "¤ " : " "; + fullText += text; + + CBrush brush(bgColor); + pdc->FillRect(&bounds, &brush); + + // Draw text + pdc->SetTextColor(textColor); + pdc->SetBkMode(TRANSPARENT); + + CRect textArea = bounds; + textArea.left += 10; + + CFont* oldFont = pdc->SelectObject(&mainFont); + pdc->DrawText(fullText.c_str(), &textArea, DT_LEFT | DT_VCENTER | DT_SINGLELINE); + pdc->SelectObject(&oldFont); +} diff --git a/ILS_Window_Plugin/IWX11Window.h b/ILS_Window_Plugin/IWX11Window.h index 874be80..45c317d 100644 --- a/ILS_Window_Plugin/IWX11Window.h +++ b/ILS_Window_Plugin/IWX11Window.h @@ -9,5 +9,9 @@ class IWX11Window : private: void DrawBorder(CDC* pdc, CRect windowRect) override; int GetEdgeCursorPosition(CPoint point) override; + void DrawMenuItem(CDC* pdc, CRect bounds, CString text, bool isHovered, bool isChecked) override; + + COLORREF menuBgColor; + COLORREF menuTextColor; }; diff --git a/ILS_Window_Plugin/RenderUtils.h b/ILS_Window_Plugin/RenderUtils.h index a224c0b..29e3622 100644 --- a/ILS_Window_Plugin/RenderUtils.h +++ b/ILS_Window_Plugin/RenderUtils.h @@ -2,22 +2,15 @@ #include -inline void Draw3dRect(CDC* pDC, CRect rect, int steps, COLORREF lightColor, COLORREF darkColor) +inline void DrawThick3dRect(CDC* pDC, CRect rect, int steps, COLORREF lightColor, COLORREF darkColor) { for (int drawnSunkSteps = 0; drawnSunkSteps < steps; drawnSunkSteps++) { - // Draw the top-left border (light color) - pDC->FillSolidRect(rect.left, rect.top, rect.Width(), 1, lightColor); // Top - pDC->FillSolidRect(rect.left, rect.top, 1, rect.Height(), lightColor); // Left - - // Draw the bottom-right border (dark color) - pDC->FillSolidRect(rect.right - 1, rect.top, 1, rect.Height(), darkColor); // Right - pDC->FillSolidRect(rect.left, rect.bottom - 1, rect.Width(), 1, darkColor); // Bottom - + pDC->Draw3dRect(rect, lightColor, darkColor); rect.DeflateRect(1, 1); } } -inline void Draw3dCorner(CDC* pDC, CRect rect, int borderWidth, int steps, COLORREF lightColor, COLORREF darkColor, bool isTop, bool isLeft) +inline void DrawThick3dCorner(CDC* pDC, CRect rect, int borderWidth, int steps, COLORREF lightColor, COLORREF darkColor, bool isTop, bool isLeft) { CPen lightPen(PS_SOLID, 1, lightColor); CPen darkPen(PS_SOLID, 1, darkColor);