diff --git a/Aman/Aman.vcxproj b/Aman/Aman.vcxproj index 788fb4a..4aa1548 100644 --- a/Aman/Aman.vcxproj +++ b/Aman/Aman.vcxproj @@ -210,6 +210,7 @@ + diff --git a/Aman/Aman.vcxproj.filters b/Aman/Aman.vcxproj.filters index 24632dc..7b29c1a 100644 --- a/Aman/Aman.vcxproj.filters +++ b/Aman/Aman.vcxproj.filters @@ -115,6 +115,9 @@ Header Files + + Header Files + diff --git a/Aman/AmanAircraft.h b/Aman/AmanAircraft.h index d3015d9..a6d604b 100644 --- a/Aman/AmanAircraft.h +++ b/Aman/AmanAircraft.h @@ -9,6 +9,7 @@ class AmanAircraft { std::string arrivalRunway; std::string icaoType; std::string nextFix; + std::string scratchPad; int viaFixIndex; diff --git a/Aman/AmanController.cpp b/Aman/AmanController.cpp index 7c4e361..d6eb116 100644 --- a/Aman/AmanController.cpp +++ b/Aman/AmanController.cpp @@ -6,8 +6,6 @@ AmanController::AmanController(AmanPlugIn* plugin) { this->amanModel = plugin; - this->amanWindow = NULL; - this->amanWindow = std::make_shared(this); } AmanController::~AmanController() {} @@ -22,9 +20,7 @@ void AmanController::modelUpdated() { } void AmanController::toggleTimeline(const std::string& id) { - bool isActive = std::find(activeTimelines.begin(), activeTimelines.end(), id) != activeTimelines.end(); - - if (isActive) { + if (isTimelineActive(id)) { activeTimelines.erase( std::remove(activeTimelines.begin(), activeTimelines.end(), id), activeTimelines.end() @@ -35,11 +31,27 @@ void AmanController::toggleTimeline(const std::string& id) { modelUpdated(); } +bool AmanController::isTimelineActive(const std::string& id) { + return std::find(activeTimelines.begin(), activeTimelines.end(), id) != activeTimelines.end(); +} + void AmanController::reloadProfiles() { this->amanModel->requestReload(); } -void AmanController::setTimelineHorizon(const std::string& id, uint32_t minutes) { - this->amanWindow->setTimelineHorizon(id, minutes); +bool AmanController::openWindow() { + if (this->amanWindow == nullptr) { + this->amanWindow = std::make_shared(this); + return true; + } +} + +bool AmanController::closeWindow() { + if (this->amanWindow == nullptr) { + return false; + } else { + this->amanWindow.reset(); + return true; + } } diff --git a/Aman/AmanController.h b/Aman/AmanController.h index 7af9e89..af75ff0 100644 --- a/Aman/AmanController.h +++ b/Aman/AmanController.h @@ -12,8 +12,10 @@ class AmanController { AmanController(AmanPlugIn* model); void modelUpdated(); void toggleTimeline(const std::string& id); + bool isTimelineActive(const std::string& id); void reloadProfiles(); - void setTimelineHorizon(const std::string& id, uint32_t minutes); + bool openWindow(); + bool closeWindow(); ~AmanController(); diff --git a/Aman/AmanPlugIn.cpp b/Aman/AmanPlugIn.cpp index 3e7cffe..ebd555d 100644 --- a/Aman/AmanPlugIn.cpp +++ b/Aman/AmanPlugIn.cpp @@ -5,6 +5,7 @@ #include "AmanPlugIn.h" #include "AmanTimeline.h" #include "AmanWindow.h" +#include "AmanTagItem.h" #include "stdafx.h" #include "windows.h" #include "rapidjson/document.h" @@ -17,6 +18,7 @@ #include #include #include +#include #define TO_UPPERCASE(str) std::transform(str.begin(), str.end(), str.begin(), ::toupper); #define REMOVE_EMPTY(strVec, output) \ @@ -26,26 +28,24 @@ str.pop_back(); #define DISPLAY_WARNING(str) DisplayUserMessage("Aman", "Warning", str, true, true, true, true, false); -AmanPlugIn::AmanPlugIn() : CPlugIn(COMPATIBILITY_CODE, "Arrival Manager", "2.1.0", "https://git.io/Jt3S8", "Open source") { +AmanPlugIn::AmanPlugIn() : CPlugIn(COMPATIBILITY_CODE, "Arrival Manager", "3.0.1", "https://git.io/Jt3S8", "Open source") { // Find directory of this .dll char fullPluginPath[_MAX_PATH]; GetModuleFileNameA((HINSTANCE)&__ImageBase, fullPluginPath, sizeof(fullPluginPath)); std::string fullPluginPathStr(fullPluginPath); pluginDirectory = fullPluginPathStr.substr(0, fullPluginPathStr.find_last_of("\\")); - amanController = std::make_shared(this); - loadTimelines("aman-config.json"); - amanController->modelUpdated(); } AmanPlugIn::~AmanPlugIn() { + } std::set AmanPlugIn::getAvailableIds() { std::set set; - for (auto timeline : timelines) { + for (auto& timeline : timelines) { set.insert(timeline->getIdentifier()); } return set; @@ -115,6 +115,7 @@ std::vector AmanPlugIn::getInboundsForFix(const std::string& fixNa ac.eta = timeNow + timeToFix - rt.GetPosition().GetReceivedTime(); ac.distLeft = findRemainingDist(rt, route, targetFixIndex); ac.secondsBehindPreceeding = 0; // Updated in the for-loop below + ac.scratchPad = rt.GetCorrelatedFlightPlan().GetControllerAssignedData().GetScratchPadString(); aircraftList.push_back(ac); } } @@ -138,9 +139,19 @@ void AmanPlugIn::OnTimer(int Counter) { amanController->modelUpdated(); } +bool AmanPlugIn::OnCompileCommand(const char* sCommandLine) { + if (strcmp(sCommandLine, ".aman open") == 0) { + return this->amanController->openWindow(); + } else if (strcmp(sCommandLine, ".aman close") == 0) { + return this->amanController->closeWindow(); + } + return false; +} + void AmanPlugIn::loadTimelines(const std::string& filename) { std::ifstream file(pluginDirectory + "\\" + filename); std::string fileContent((std::istreambuf_iterator(file)), std::istreambuf_iterator()); + std::map>> tagLayouts; if (fileContent.empty()) { DISPLAY_WARNING((filename + ": the JSON-file was not found or is empty").c_str()); @@ -159,44 +170,75 @@ void AmanPlugIn::loadTimelines(const std::string& filename) { return; } - for (auto& v : document["timelines"].GetArray()) { - auto object = v.GetObjectA(); - - std::vector targetFixes; - for (auto& fix : object["targetFixes"].GetArray()) { - targetFixes.push_back(fix.GetString()); + if (document.HasMember("tagLayouts") && document["tagLayouts"].IsObject()) { + for (auto property = document["tagLayouts"].MemberBegin(); property != document["tagLayouts"].MemberEnd(); ++property) { + auto layoutId = property->name.GetString(); + std::vector> tagItems; + for (auto& tagItemObj : property->value.GetArray()) { + auto dataSource = tagItemObj.HasMember("source") ? tagItemObj["source"].GetString() : ""; + auto width = tagItemObj.HasMember("width") ? tagItemObj["width"].GetUint() : 1; + auto defaultValue = tagItemObj.HasMember("defaultValue") ? tagItemObj["defaultValue"].GetString() : ""; + auto alignRight = tagItemObj.HasMember("rightAligned") && tagItemObj["rightAligned"].GetBool(); + auto isViaFixIndicator = tagItemObj.HasMember("isViaFixIndicator") && tagItemObj["isViaFixIndicator"].GetBool(); + + tagItems.push_back(std::make_shared(dataSource, defaultValue, width, alignRight, isViaFixIndicator)); + } + tagLayouts[layoutId] = tagItems; } + } + + if (document.HasMember("timelines") && document["timelines"].IsObject()) { + for (auto property = document["timelines"].MemberBegin(); property != document["timelines"].MemberEnd(); ++property) { + auto object = property->value.GetObjectA(); - std::vector viaFixes; - if (object.HasMember("viaFixes") && object["viaFixes"].IsArray()) { - for (auto& fix : object["viaFixes"].GetArray()) { - viaFixes.push_back(fix.GetString()); + std::vector targetFixes; + for (auto& fix : object["targetFixes"].GetArray()) { + targetFixes.push_back(fix.GetString()); } - } - std::vector destinationAirports; - if (object.HasMember("destinationAirports") && object["destinationAirports"].IsArray()) { - for (auto& destination : object["destinationAirports"].GetArray()) { - destinationAirports.push_back(destination.GetString()); + std::vector viaFixes; + if (object.HasMember("viaFixes") && object["viaFixes"].IsArray()) { + for (auto& fix : object["viaFixes"].GetArray()) { + viaFixes.push_back(fix.GetString()); + } } - } - std::string alias; - if (object.HasMember("alias") && object["alias"].IsString()) { - alias = object["alias"].GetString(); - } else { - alias = ""; - for (const auto& piece : targetFixes) alias += piece + "/"; - alias = alias.substr(0, alias.size() - 1); - } + std::vector destinationAirports; + if (object.HasMember("destinationAirports") && object["destinationAirports"].IsArray()) { + for (auto& destination : object["destinationAirports"].GetArray()) { + destinationAirports.push_back(destination.GetString()); + } + } - uint32_t startHorizon; - if (object.HasMember("startHorizon") && object["startHorizon"].IsUint()) { - startHorizon = object["startHorizon"].GetUint(); - amanController->setTimelineHorizon(alias, startHorizon); + std::vector> tagItems; + if (object.HasMember("tagLayout") && object["tagLayout"].IsString()) { + tagItems = tagLayouts[object["tagLayout"].GetString()]; + } else { + tagItems = {}; + } + + uint32_t defaultTimeSpan; + if (object.HasMember("defaultTimeSpan") && object["defaultTimeSpan"].IsUint()) { + defaultTimeSpan = object["defaultTimeSpan"].GetUint(); + } else { + defaultTimeSpan = 30; + } + + timelines.push_back(std::make_shared(targetFixes, viaFixes, destinationAirports, tagItems, property->name.GetString(), defaultTimeSpan)); } + } - timelines.push_back(std::make_shared(targetFixes, viaFixes, destinationAirports, alias)); + auto makeSureWindowIsOpen = document.HasMember("openAutomatically") ? document["openAutomatically"].GetBool() : true; + if (makeSureWindowIsOpen) { + this->amanController->openWindow(); + } + + // If only one timeline, open it automatically: + if (timelines.size() == 1) { + auto onlyTimelineId = timelines.at(0)->getIdentifier(); + if (!this->amanController->isTimelineActive(onlyTimelineId)) { + this->amanController->toggleTimeline(onlyTimelineId); + } } } @@ -250,8 +292,8 @@ std::shared_ptr>> AmanPlugIn::getTimel pAircraftList->clear(); for each (auto finalFix in fixes) { - auto var = getInboundsForFix(finalFix, viaFixes, timeline->getDestinationAirports()); - pAircraftList->insert(pAircraftList->end(), var.begin(), var.end()); + auto inbounds = getInboundsForFix(finalFix, viaFixes, timeline->getDestinationAirports()); + pAircraftList->insert(pAircraftList->end(), inbounds.begin(), inbounds.end()); } result->push_back(timeline); diff --git a/Aman/AmanPlugIn.h b/Aman/AmanPlugIn.h index ebb5abc..6f29870 100644 --- a/Aman/AmanPlugIn.h +++ b/Aman/AmanPlugIn.h @@ -26,6 +26,7 @@ class AmanPlugIn : public CPlugIn { std::string pluginDirectory; virtual void OnTimer(int Counter); + virtual bool OnCompileCommand(const char* sCommandLine); bool hasCorrectDestination(CFlightPlanData fpd, std::vector destinationAirports); int getFixIndexByName(CFlightPlanExtractedRoute extractedRoute, const std::string& fixName); diff --git a/Aman/AmanTagItem.h b/Aman/AmanTagItem.h new file mode 100644 index 0000000..7c54396 --- /dev/null +++ b/Aman/AmanTagItem.h @@ -0,0 +1,24 @@ +#pragma once +#include + +class TagItem { +public: + TagItem(const std::string& source, const std::string& defaultValue, uint32_t width, bool rightAligned, bool isViaFixIndicator) { + this->source = source; + this->width = width; + this->rightAligned = rightAligned; + this->defaultValue = defaultValue; + this->isViaFixIndicator = isViaFixIndicator; + } + std::string getSource() { return source; }; + std::string getDefaultValue() { return defaultValue; }; + uint32_t getWidth() { return width; }; + bool isRightAligned() { return rightAligned; }; + bool getIsViaFixIndicator() { return isViaFixIndicator; }; +private: + std::string source; + std::string defaultValue; + bool isViaFixIndicator; + uint32_t width; + bool rightAligned; +}; \ No newline at end of file diff --git a/Aman/AmanTimeline.cpp b/Aman/AmanTimeline.cpp index 72cef19..f7e4b51 100644 --- a/Aman/AmanTimeline.cpp +++ b/Aman/AmanTimeline.cpp @@ -2,32 +2,33 @@ #include #include +#include #include "AmanAircraft.h" #include "AmanTimeline.h" - -AmanTimeline::AmanTimeline(std::vector fixes, std::vector viaFixes, std::vector destinations, const std::string& alias) { +#include "AmanTagItem.h" + +AmanTimeline::AmanTimeline( + std::vector fixes, + std::vector viaFixes, + std::vector destinations, + std::vector> tagItems, + const std::string& alias, + uint32_t defaultTimeSpan) { this->fixes = fixes; this->viaFixes = viaFixes; this->aircraftList = std::vector(); this->destinationAirports = destinations; + this->tagItems = tagItems; this->alias = alias; + this->defaultTimeSpan = defaultTimeSpan; + + auto addWidth = [](uint32_t acc, std::shared_ptr tagItem) { return acc + tagItem->getWidth(); }; + this->width = std::accumulate(tagItems.begin(), tagItems.end(), 0, addWidth); } std::string AmanTimeline::getIdentifier() { - if(!alias.empty()) return alias; - switch (fixes.size()) { - case 1: - return fixes.at(0); - case 2: - return fixes.at(0) + "/" + fixes.at(1); - default: - std::ostringstream fixesSs; - copy(fixes.begin(), fixes.end(), std::ostream_iterator(fixesSs, "/")); - std::string output = fixesSs.str(); - output.pop_back(); // Remove last '/' - return output; - } + return alias; } bool AmanTimeline::isDual() { return fixes.size() == 2; } diff --git a/Aman/AmanTimeline.h b/Aman/AmanTimeline.h index 2d8b90f..148a40a 100644 --- a/Aman/AmanTimeline.h +++ b/Aman/AmanTimeline.h @@ -4,10 +4,19 @@ #include class AmanAircraft; +class TagItem; class AmanTimeline { public: - AmanTimeline(std::vector fixes, std::vector viaFixes, std::vector destinationAirports, const std::string& alias); + AmanTimeline( + std::vector fixes, + std::vector viaFixes, + std::vector destinationAirports, + std::vector> tagItems, + const std::string& alias, + uint32_t defaultTimeSpan + ); + std::string getIdentifier(); bool isDual(); bool containsListForFix(std::string fixName); @@ -17,6 +26,9 @@ class AmanTimeline { std::vector getFixes() { return fixes; } std::vector getViaFixes() { return viaFixes; } std::vector getDestinationAirports() { return destinationAirports; } + std::vector> getTagItems() { return tagItems; } + uint32_t getWidth() { return width; } + uint32_t getDefaultTimeSpan() { return defaultTimeSpan; } ~AmanTimeline(); @@ -26,5 +38,8 @@ class AmanTimeline { std::vector viaFixes; std::vector fixes; std::vector destinationAirports; + std::vector> tagItems; + uint32_t width; + uint32_t defaultTimeSpan; }; diff --git a/Aman/AmanTimelineView.cpp b/Aman/AmanTimelineView.cpp index d6e2957..659d96e 100644 --- a/Aman/AmanTimelineView.cpp +++ b/Aman/AmanTimelineView.cpp @@ -8,15 +8,23 @@ #include "AmanAircraft.h" #include "AmanTimeline.h" #include "AmanTimelineView.h" +#include "AmanTagItem.h" #include "Constants.h" +#define max(a,b) (((a) > (b)) ? (a) : (b)) +#define min(a,b) (((a) < (b)) ? (a) : (b)) + +#define CHAR_WIDTH 7 +#define LANE_WIDTH(tagWidth) (tagWidth * CHAR_WIDTH + AMAN_TAG_SEP_FROM_TIMELINE + AMAN_TIMELINE_SEPARATION) + CRect AmanTimelineView::getArea(std::shared_ptr timeline, CRect clientRect, int xOffset) { int totalWidth; + int laneWidth = LANE_WIDTH(timeline->getWidth()); if (timeline->isDual()) { - totalWidth = AMAN_WIDTH * 2 - AMAN_TIMELINE_WIDTH; + totalWidth = laneWidth * 2 + AMAN_RULER_WITDH; } else { - totalWidth = AMAN_WIDTH; + totalWidth = laneWidth + AMAN_RULER_WITDH; } return CRect(xOffset, clientRect.top, xOffset + totalWidth, clientRect.bottom); @@ -30,7 +38,7 @@ CRect AmanTimelineView::render(HDC hdc, std::shared_ptr timeline, int timelineStartX = myTotalArea.left; if (timeline->isDual()) { - timelineStartX += AMAN_WIDTH - AMAN_TIMELINE_WIDTH; + timelineStartX += LANE_WIDTH(timeline->getWidth()); } int presentTimeStartY = myTotalArea.bottom - AMAN_TIMELINE_REALTIME_OFFSET; @@ -40,16 +48,16 @@ CRect AmanTimelineView::render(HDC hdc, std::shared_ptr timeline, // Timeline bar int secToNextMin = 60 - (unixTimestamp % 60); - CRect futureBackground = { timelineStartX, myTotalArea.top, timelineStartX + AMAN_TIMELINE_WIDTH, myTotalArea.bottom }; + CRect futureBackground = { timelineStartX, myTotalArea.top, timelineStartX + AMAN_RULER_WITDH, myTotalArea.bottom }; FillRect(hdc, &futureBackground, AMAN_BRUSH_TIMELINE_AHEAD); CRect pastBackground = { timelineStartX, myTotalArea.bottom - AMAN_TIMELINE_REALTIME_OFFSET, - timelineStartX + AMAN_TIMELINE_WIDTH, myTotalArea.bottom }; + timelineStartX + AMAN_RULER_WITDH, myTotalArea.bottom }; FillRect(hdc, &pastBackground, AMAN_BRUSH_TIMELINE_PAST); // Vertical white border(s) HPEN oldPen = (HPEN)SelectObject(hdc, AMAN_VERTICAL_LINE_PEN); - MoveToEx(hdc, timelineStartX + AMAN_TIMELINE_WIDTH, myTotalArea.bottom, NULL); - LineTo(hdc, timelineStartX + AMAN_TIMELINE_WIDTH, myTotalArea.top); + MoveToEx(hdc, timelineStartX + AMAN_RULER_WITDH, myTotalArea.bottom, NULL); + LineTo(hdc, timelineStartX + AMAN_RULER_WITDH, myTotalArea.top); if (timeline->isDual()) { MoveToEx(hdc, timelineStartX, myTotalArea.bottom, NULL); LineTo(hdc, timelineStartX, myTotalArea.top); @@ -74,21 +82,21 @@ CRect AmanTimelineView::render(HDC hdc, std::shared_ptr timeline, // 10 minute label int hoursAtLine = ((unixTimestampAsMinutes + minute) / 60) % 24; timeSs << std::setfill('0') << std::setw(2) << hoursAtLine << ":" << std::setw(2) << minutesAtTick; - } else if (minutesAtTick % 5 == 0 || pixelsPerMinuteY > AMAN_TIMELINE_MAX_TICK_DENSITY) { + } else if (minutesAtTick % 5 == 0 || pixelsPerMinuteY > AMAN_RULER_MAX_TICK_DENSITY) { // 5 and 1 minute label timeSs << std::setfill('0') << std::setw(2) << minutesAtTick; } auto timeString = timeSs.str(); if (!timeString.empty()) { - CRect rect = { timelineStartX, tickPosY - 6, timelineStartX + AMAN_TIMELINE_WIDTH, tickPosY + 6 }; + CRect rect = { timelineStartX, tickPosY - 6, timelineStartX + AMAN_RULER_WITDH, tickPosY + 6 }; DrawText(hdc, timeString.c_str(), strlen(timeString.c_str()), &rect, DT_CENTER); } - int tickLength = minutesAtTick % 5 == 0 ? AMAN_TIMELINE_TICK_WIDTH_5_MIN : AMAN_TIMELINE_TICK_WIDTH_1_MIN; + int tickLength = minutesAtTick % 5 == 0 ? AMAN_RULER_TICK_WIDTH_5_MIN : AMAN_RULER_TICK_WIDTH_1_MIN; - MoveToEx(hdc, timelineStartX + AMAN_TIMELINE_WIDTH, tickPosY, NULL); - LineTo(hdc, timelineStartX + AMAN_TIMELINE_WIDTH - tickLength, tickPosY); + MoveToEx(hdc, timelineStartX + AMAN_RULER_WITDH, tickPosY, NULL); + LineTo(hdc, timelineStartX + AMAN_RULER_WITDH - tickLength, tickPosY); if (timeline->isDual()) { MoveToEx(hdc, timelineStartX, tickPosY, NULL); LineTo(hdc, timelineStartX + tickLength, tickPosY); @@ -100,7 +108,7 @@ CRect AmanTimelineView::render(HDC hdc, std::shared_ptr timeline, Polygon(hdc, polygon, 4); }; oldPen = (HPEN)SelectObject(hdc, AMAN_WHITE_PEN); - drawHourglass(timelineStartX + AMAN_TIMELINE_WIDTH, presentTimeStartY); + drawHourglass(timelineStartX + AMAN_RULER_WITDH, presentTimeStartY); if (timeline->isDual()) { drawHourglass(timelineStartX, presentTimeStartY); } @@ -112,11 +120,11 @@ CRect AmanTimelineView::render(HDC hdc, std::shared_ptr timeline, auto fixes = timeline->getFixes(); auto inboundsLeft = timeline->getAircraftList({ fixes[0] }); auto inboundsRight = timeline->getAircraftList({ fixes[1] }); - drawAircraftChain(hdc, unixTimestamp, timelineStartX, presentTimeStartY, pixelsPerSecY, true, inboundsLeft); - drawAircraftChain(hdc, unixTimestamp, timelineStartX, presentTimeStartY, pixelsPerSecY, false, inboundsRight); + drawAircraftChain(hdc, unixTimestamp, timelineStartX, presentTimeStartY, pixelsPerSecY, true, inboundsLeft, timeline->getTagItems()); + drawAircraftChain(hdc, unixTimestamp, timelineStartX, presentTimeStartY, pixelsPerSecY, false, inboundsRight, timeline->getTagItems()); } else { drawAircraftChain(hdc, unixTimestamp, timelineStartX, presentTimeStartY, pixelsPerSecY, false, - *timeline->getAircraftList()); + *timeline->getAircraftList(), timeline->getTagItems()); } // Draw the fix id @@ -124,8 +132,8 @@ CRect AmanTimelineView::render(HDC hdc, std::shared_ptr timeline, SetBkMode(hdc, OPAQUE); SetTextColor(hdc, AMAN_COLOR_FIX_TEXT); SelectObject(hdc, AMAN_FIX_FONT); - CRect rect = { timelineStartX - AMAN_TIMELINE_WIDTH, myTotalArea.bottom - 20, - timelineStartX + 2 * AMAN_TIMELINE_WIDTH, myTotalArea.bottom }; + CRect rect = { timelineStartX - AMAN_RULER_WITDH, myTotalArea.bottom - 20, + timelineStartX + 2 * AMAN_RULER_WITDH, myTotalArea.bottom }; std::string text = timeline->getIdentifier(); DrawText(hdc, text.c_str(), text.length(), &rect, DT_CENTER); @@ -142,16 +150,18 @@ CRect AmanTimelineView::render(HDC hdc, std::shared_ptr timeline, return myTotalArea; } + void AmanTimelineView::drawAircraftChain(HDC hdc, int timeNow, int xStart, int yStart, float pixelsPerSec, bool left, - std::vector aircraftList) { + std::vector aircraftList, std::vector> tagItems) { std::sort(aircraftList.begin(), aircraftList.end()); - int prevTop = -1; + int maxLabelWidth = 0; + int previousLabelTop = -1; int offset = 0; for (int ac = 0; ac < aircraftList.size(); ac++) { AmanAircraft aircraft = aircraftList.at(ac); - int acPos = yStart - (aircraft.eta - timeNow) * pixelsPerSec; + int acPosY = yStart - (aircraft.eta - timeNow) * pixelsPerSec; COLORREF defaultColor = aircraft.trackedByMe ? AMAN_COLOR_TRACKED : AMAN_COLOR_UNTRACKED; HBRUSH brush = aircraft.trackedByMe ? AMAN_TRACKED_BRUSH : AMAN_UNTRACKED_BRUSH; @@ -160,83 +170,39 @@ void AmanTimelineView::drawAircraftChain(HDC hdc, int timeNow, int xStart, int y auto oldPen = SelectObject(hdc, pen); auto oldBrush = SelectObject(hdc, brush); auto oldTextColor = SetTextColor(hdc, defaultColor); + auto labelSegments = generateLabel(aircraft, tagItems, defaultColor); - CRect boundingBox; - bool hasKnownViaFix = aircraft.viaFixIndex > -1 && aircraft.viaFixIndex < N_VIA_FIX_COLORS; - - // Left side of timeline - if (left) { - int rectLeft = xStart - AMAN_WIDTH + AMAN_TIMELINE_WIDTH + AMAN_LABEL_SEP_FROM_TIMELINE; - int rectTop = acPos - AMAN_AIRCRAFT_LINE_HEIGHT / 2; - int rectRight = xStart - AMAN_LABEL_SEP_FROM_TIMELINE; - int rectBottom = acPos + AMAN_AIRCRAFT_LINE_HEIGHT / 2; - - if (prevTop >= 0 && rectBottom > prevTop) { - int diff = rectBottom - prevTop; - rectTop = rectTop - diff; - rectBottom = rectBottom - diff; - } - - Ellipse(hdc, xStart - AMAN_DOT_RADIUS, acPos - AMAN_DOT_RADIUS, xStart + AMAN_DOT_RADIUS, - acPos + AMAN_DOT_RADIUS); - boundingBox = { rectLeft, rectTop, rectRight, rectBottom }; - - MoveToEx(hdc, xStart, acPos, NULL); - LineTo(hdc, boundingBox.right, boundingBox.top + AMAN_AIRCRAFT_LINE_HEIGHT / 2); - - prevTop = rectTop; - } - // Right side of timeline - else { - int rectLeft = xStart + AMAN_TIMELINE_WIDTH + AMAN_LABEL_SEP_FROM_TIMELINE; - int rectTop = acPos - AMAN_AIRCRAFT_LINE_HEIGHT / 2 + offset; - int rectRight = xStart + AMAN_WIDTH - AMAN_LABEL_SEP_FROM_TIMELINE; - int rectBottom = acPos + AMAN_AIRCRAFT_LINE_HEIGHT / 2 + offset; - - if (prevTop >= 0 && rectBottom > prevTop) { - int diff = rectBottom - prevTop; - rectTop = rectTop - diff; - rectBottom = rectBottom - diff; - } - - Ellipse(hdc, xStart + AMAN_TIMELINE_WIDTH - AMAN_DOT_RADIUS, acPos - AMAN_DOT_RADIUS, - xStart + AMAN_TIMELINE_WIDTH + AMAN_DOT_RADIUS, acPos + AMAN_DOT_RADIUS); - boundingBox = { rectLeft, rectTop, rectRight, rectBottom }; - - MoveToEx(hdc, xStart + AMAN_TIMELINE_WIDTH, acPos, NULL); - LineTo(hdc, boundingBox.left, boundingBox.top + AMAN_AIRCRAFT_LINE_HEIGHT / 2); - - prevTop = rectTop; + if (maxLabelWidth == 0) { + // For the first label, do a test render outside the view to estimate the width + CRect boundingBox = drawMultiColorText(hdc, { -INFINITE, 0 }, labelSegments); + maxLabelWidth = boundingBox.Width(); } - std::string nextFix = aircraft.nextFix.size() > 0 ? aircraft.nextFix : "-----"; - int minutesBehindPreceeding = round(aircraft.secondsBehindPreceeding / 60); - int remainingDistance = round(aircraft.distLeft); - - drawMultiColorText(hdc, boundingBox.TopLeft(), { - {4, false, defaultColor, aircraft.arrivalRunway}, - {9, false, defaultColor, aircraft.callsign}, - {5, false, defaultColor, aircraft.icaoType}, - {2, false, defaultColor, {aircraft.wtc}}, - {7, false, defaultColor, nextFix}, - {3, false, defaultColor, std::to_string(minutesBehindPreceeding)}, - {4, true, defaultColor, std::to_string(remainingDistance)} - }); - - if (hasKnownViaFix) { - // Draw mark indicating via fix - int markPos = boundingBox.left + 85; - SelectObject(hdc, VIA_FIX_COLORS[aircraft.viaFixIndex]); - MoveToEx(hdc, markPos, boundingBox.top + 2, NULL); - LineTo(hdc, markPos, boundingBox.bottom - 3); + int labelPosX = left ? xStart - maxLabelWidth - AMAN_TAG_SEP_FROM_TIMELINE : xStart + AMAN_RULER_WITDH + AMAN_TAG_SEP_FROM_TIMELINE; + int labelTop = acPosY - AMAN_TAG_LINE_HEIGHT / 2; + int labelBottom = labelTop + AMAN_TAG_LINE_HEIGHT; + if (previousLabelTop >= 0 && labelBottom > previousLabelTop) { + int diff = labelBottom - previousLabelTop; + labelTop -= diff; + labelBottom -= diff; } + CRect boundingBox = drawMultiColorText(hdc, { labelPosX, labelTop }, labelSegments); + + int rulerBorderX = left ? xStart : xStart + AMAN_RULER_WITDH; + Ellipse(hdc, rulerBorderX - AMAN_DOT_RADIUS, acPosY - AMAN_DOT_RADIUS, rulerBorderX + AMAN_DOT_RADIUS, acPosY + AMAN_DOT_RADIUS); + MoveToEx(hdc, rulerBorderX, acPosY, NULL); + LineTo(hdc, left ? boundingBox.right : boundingBox.left, boundingBox.top + AMAN_TAG_LINE_HEIGHT / 2); + + previousLabelTop = boundingBox.top; + maxLabelWidth = max(maxLabelWidth, boundingBox.Width()); + if (aircraft.isSelected) { boundingBox.InflateRect(2, 2); FrameRect(hdc, boundingBox, brush); } - prevTop -= AMAN_AIRCRAFT_LINE_SEPARATION; + previousLabelTop -= AMAN_TAG_VERTICAL_SEPARATION; SetTextColor(hdc, oldTextColor); SelectObject(hdc, oldPen); @@ -244,11 +210,13 @@ void AmanTimelineView::drawAircraftChain(HDC hdc, int timeNow, int xStart, int y } } -void AmanTimelineView::drawMultiColorText(HDC hdc, CPoint pt, std::vector texts, bool vertical) { +CRect AmanTimelineView::drawMultiColorText(HDC hdc, CPoint pt, std::vector texts, bool vertical) { CRect startRect; startRect.MoveToXY(pt); COLORREF oldColor = GetTextColor(hdc); COLORREF prevColor = oldColor; + CRect accRect = { pt.x, pt.y, pt.x, pt.y }; + for each (auto item in texts) { std::stringstream ss; ss << (item.rightAligned ? std::right : std::left) << std::setw(item.width) << item.text; @@ -256,15 +224,28 @@ void AmanTimelineView::drawMultiColorText(HDC hdc, CPoint pt, std::vector timeline, CPoint position) { @@ -277,10 +258,60 @@ void AmanTimelineView::drawViafixColorLegend(HDC hdc, std::shared_ptrgetViaFixes().at(i) + " "; DrawText(hdc, viafix.c_str(), viafix.length(), &startRect, DT_LEFT | DT_CALCRECT); DrawText(hdc, viafix.c_str(), viafix.length(), &startRect, DT_LEFT); - SelectObject(hdc, VIA_FIX_COLORS[i]); + SelectObject(hdc, VIA_FIX_PENS[i]); MoveToEx(hdc, startRect.left + 5, startRect.top + 3, NULL); LineTo(hdc, startRect.left + 5, startRect.bottom - 5); startRect.MoveToY(startRect.bottom); } } } + +std::string AmanTimelineView::formatTime(uint32_t totalSeconds, bool minutesOnly = false) { + if (minutesOnly) { + int minutesRounded = round((float)totalSeconds / 60.0f); + return std::to_string(minutesRounded); + } else { + int minutes = totalSeconds / 60; + auto minutesString = std::to_string(minutes); + auto secondsString = std::to_string(totalSeconds - minutes * 60); + std::string mm = std::string(2 - minutesString.length(), '0') + minutesString; + std::string ss = std::string(2 - secondsString.length(), '0') + secondsString; + return mm + ":" + ss; + } +} + +std::vector AmanTimelineView::generateLabel(AmanAircraft aircraft, std::vector> tagItems, COLORREF defaultColor) { + int remainingDistance = round(aircraft.distLeft); + + bool hasKnownViaFix = aircraft.viaFixIndex > -1 && aircraft.viaFixIndex < N_VIA_FIX_COLORS; + + std::vector segments; + std::transform(tagItems.begin(), tagItems.end(), std::back_inserter(segments), [&](std::shared_ptr tagItem) -> TextSegment { + std::string sourceId = tagItem->getSource(); + std::string displayValue; + + bool timeRelevant = aircraft.secondsBehindPreceeding > 0; + + if (sourceId == "callsign") displayValue = aircraft.callsign; + else if (sourceId == "assignedRunway") displayValue = aircraft.arrivalRunway; + else if (sourceId == "aircraftType") displayValue = aircraft.icaoType; + else if (sourceId == "aircraftWtc") displayValue = { aircraft.wtc }; + else if (sourceId == "minutesBehindPreceedingRounded") displayValue = timeRelevant ? formatTime(aircraft.secondsBehindPreceeding, true) : ""; + else if (sourceId == "timeBehindPreceeding") displayValue = timeRelevant ? formatTime(aircraft.secondsBehindPreceeding) : ""; + else if (sourceId == "remainingDistance") displayValue = std::to_string(remainingDistance); + else if (sourceId == "directRouting") displayValue = aircraft.nextFix.size() > 0 ? aircraft.nextFix : tagItem->getDefaultValue(); + else if (sourceId == "scratchPad") displayValue = aircraft.scratchPad; + else if (sourceId == "static") displayValue = tagItem->getDefaultValue(); + else displayValue = "?"; + + uint32_t maxWidth = tagItem->getWidth(); + if (displayValue.length() > maxWidth) { + displayValue = displayValue.substr(0, maxWidth - 1) + "…"; + } + + COLORREF textColor = hasKnownViaFix && tagItem->getIsViaFixIndicator() ? VIA_FIX_COLORS[aircraft.viaFixIndex] : defaultColor; + return { tagItem->getWidth(), tagItem->isRightAligned(), textColor, displayValue }; + }); + + return segments; +} diff --git a/Aman/AmanTimelineView.h b/Aman/AmanTimelineView.h index 9703319..4d45bce 100644 --- a/Aman/AmanTimelineView.h +++ b/Aman/AmanTimelineView.h @@ -5,6 +5,7 @@ class AmanAircraft; class AmanTimeline; +class TagItem; class AmanTimelineView { public: @@ -19,7 +20,9 @@ class AmanTimelineView { std::string text; }; - static void drawAircraftChain(HDC hdc, int timeNow, int xStart, int yStart, float pixelsPerSec, bool left, std::vector aircraftList); - static void drawMultiColorText(HDC hdc, CPoint pt, std::vector texts, bool vertical = false); + static void drawAircraftChain(HDC hdc, int timeNow, int xStart, int yStart, float pixelsPerSec, bool left, std::vector aircraftList, std::vector> tagItems); + static CRect drawMultiColorText(HDC hdc, CPoint pt, std::vector texts, bool vertical = false); static void drawViafixColorLegend(HDC hdc, std::shared_ptr timeline, CPoint position); + static std::vector generateLabel(AmanAircraft aircraft, std::vector> tagItems, COLORREF defaultColor); + static std::string formatTime(uint32_t totalSeconds, bool minutesOnly); }; diff --git a/Aman/AmanWindow.cpp b/Aman/AmanWindow.cpp index 4d2fdd7..aab4476 100644 --- a/Aman/AmanWindow.cpp +++ b/Aman/AmanWindow.cpp @@ -10,20 +10,20 @@ #include // In minutes: -#define MAX_HORIZON 240 -#define MIN_HORIZON 5 -#define DEFAULT_HORIZON 30 +#define MAX_TIME_SPAN 240 +#define MIN_TIME_SPAN 5 -#define TIMELINES_RELOAD "[Reload aman-config.json] " +#define TIMELINES_RELOAD "[Reload config] " AmanWindow::AmanWindow(AmanController* controller) : Window("AmanWindow", "AMAN") { this->controller = controller; this->titleBar = std::make_shared(); this->menuBar = std::make_shared(); - static auto onSelection = [controller](const std::string& timelineId) { + static auto onSelection = [controller, this](const std::string& timelineId) { if (timelineId == TIMELINES_RELOAD) { controller->reloadProfiles(); + this->zoomLevels.clear(); } else { controller->toggleTimeline(timelineId); } @@ -77,10 +77,12 @@ void AmanWindow::drawContent(HDC hdc, CRect clientRect) { // This code runs on the window's thread, so we must make sure // the main thread is not currently writing to the shared AmanTimeline-vector renderTimelinesMutex.lock(); - for (auto& timeline : *timelinesToRender) { - auto zoomSec = getZoomLevel(timeline->getIdentifier()) * 60; - previousTimelineArea = AmanTimelineView::render(hdc, timeline, timelineView, zoomSec, previousTimelineArea.right); - timelineIds.push_back(timeline->getIdentifier()); + if (timelinesToRender != nullptr) { + for (auto& timeline : *timelinesToRender) { + auto zoomSec = getZoomLevel(timeline) * 60; + previousTimelineArea = AmanTimelineView::render(hdc, timeline, timelineView, zoomSec, previousTimelineArea.right); + timelineIds.push_back(timeline->getIdentifier()); + } } renderTimelinesMutex.unlock(); @@ -92,11 +94,13 @@ void AmanWindow::drawContent(HDC hdc, CRect clientRect) { this->menuBar->render(hdc, titleBarRect); } -uint32_t AmanWindow::getZoomLevel(const std::string& id) { +uint32_t AmanWindow::getZoomLevel(const std::shared_ptr& timeline) { + auto &id = timeline->getIdentifier(); + if (this->zoomLevels.count(id)) { return this->zoomLevels[id]; } else { - return DEFAULT_HORIZON; + return min(timeline->getDefaultTimeSpan(), MAX_TIME_SPAN); } } @@ -169,20 +173,19 @@ void AmanWindow::mouseWheelSrolled(CPoint cursorPosClient, short delta) { auto timelinePointedAt = getTimelineAt(timelinesToRender, cursorPosClient); if (timelinePointedAt) { - std::string id = timelinePointedAt->getIdentifier(); - auto currentRange = getZoomLevel(id); + auto currentRange = getZoomLevel(timelinePointedAt); auto newRange = currentRange - delta / 60; - auto limitReached = newRange < MIN_HORIZON || newRange > MAX_HORIZON; + auto limitReached = newRange < MIN_TIME_SPAN || newRange > MAX_TIME_SPAN; if (!limitReached) { - this->zoomLevels[id] = newRange; + this->zoomLevels[timelinePointedAt->getIdentifier()] = newRange; requestRepaint(); } } } -void AmanWindow::setTimelineHorizon(const std::string& id, uint32_t minutes) { - this->zoomLevels[id] = min(minutes, MAX_HORIZON); +void AmanWindow::closeRequested() { + controller->closeWindow(); } void AmanWindow::windowClosed() { diff --git a/Aman/AmanWindow.h b/Aman/AmanWindow.h index 418c65a..9e9597f 100644 --- a/Aman/AmanWindow.h +++ b/Aman/AmanWindow.h @@ -25,8 +25,6 @@ class AmanWindow : public Window { void update(timelineCollection timelines); - void setTimelineHorizon(const std::string& id, uint32_t minutes); - private: AmanController* controller; std::shared_ptr titleBar; @@ -56,7 +54,8 @@ class AmanWindow : public Window { void mouseReleased(CPoint cursorPosClient) override; void mouseMoved(CPoint cursorPosClient, CPoint cursorPosScreen) override; void mouseWheelSrolled(CPoint cursorPosClient, short delta) override; + void closeRequested() override; void windowClosed() override; void drawContent(HDC hdc, CRect clientRect) override; - uint32_t getZoomLevel(const std::string& id); + uint32_t getZoomLevel(const std::shared_ptr& timeline); }; diff --git a/Aman/Constants.h b/Aman/Constants.h index 161870c..de9a21e 100644 --- a/Aman/Constants.h +++ b/Aman/Constants.h @@ -13,7 +13,7 @@ const COLORREF AMAN_COLOR_MENU_TEXT = RGB(255, 255, 255); const COLORREF AMAN_COLOR_MENU_TEXT_BACKGROUND_HOVER = RGB(60, 60, 60); const int N_VIA_FIX_COLORS = 8; -const HPEN VIA_FIX_COLORS[N_VIA_FIX_COLORS] = { +const HPEN VIA_FIX_PENS[N_VIA_FIX_COLORS] = { CreatePen(PS_SOLID, 2, RGB(0, 255, 0)), CreatePen(PS_SOLID, 2, RGB(0, 255, 255)), CreatePen(PS_SOLID, 2, RGB(255, 128, 255)), @@ -23,6 +23,17 @@ const HPEN VIA_FIX_COLORS[N_VIA_FIX_COLORS] = { CreatePen(PS_SOLID, 2, RGB(204, 0, 102)) }; +const COLORREF VIA_FIX_COLORS[N_VIA_FIX_COLORS] = { + RGB(0, 255, 0), + RGB(0, 255, 255), + RGB(255, 128, 255), + RGB(204, 204, 0), + RGB(255, 128, 0), + RGB(102, 102, 255), + RGB(204, 0, 102) +}; + + static const HBRUSH AMAN_BRUSH_MAIN_BACKGROUND = CreateSolidBrush(RGB(13, 9, 13)); static const HBRUSH AMAN_BRUSH_TITLE_BAR_BACKGROUND = CreateSolidBrush(RGB(72, 72, 72)); static const HBRUSH AMAN_BRUSH_MENU_BACKGROUND = CreateSolidBrush(RGB(90, 90, 90)); @@ -46,16 +57,15 @@ const HFONT AMAN_LEGEND_FONT = CreateFont(13, 0, 0, 0, FW_NORMAL, 0, 0, 0, 0, 3, const HFONT AMAN_MENU_FONT = CreateFont(15, 0, 0, 0, FW_NORMAL, 0, 0, 0, 0, 3, 2, 1, 49, "EuroScope"); const int AMAN_TITLEBAR_HEIGHT = 20; -const int AMAN_TIMELINE_WIDTH = 50; -const int AMAN_TIMELINE_TICK_WIDTH_1_MIN = 4; -const int AMAN_TIMELINE_TICK_WIDTH_5_MIN = 8; -const int AMAN_TIMELINE_MAX_TICK_DENSITY = 15; -const int AMAN_TIMELINE_TIME_POS = 40; +const int AMAN_RULER_WITDH = 50; +const int AMAN_RULER_TICK_WIDTH_1_MIN = 4; +const int AMAN_RULER_TICK_WIDTH_5_MIN = 8; +const int AMAN_RULER_MAX_TICK_DENSITY = 15; const int AMAN_TIMELINE_REALTIME_OFFSET = 50; -const int AMAN_WIDTH = 330; -const int AMAN_AIRCRAFT_LINE_HEIGHT = 12; -const int AMAN_AIRCRAFT_LINE_SEPARATION = 5; -const int AMAN_LABEL_SEP_FROM_TIMELINE = 20; +const int AMAN_TAG_LINE_HEIGHT = 12; +const int AMAN_TAG_VERTICAL_SEPARATION = 5; +const int AMAN_TAG_SEP_FROM_TIMELINE = 20; const int AMAN_DOT_RADIUS = 3; +const int AMAN_TIMELINE_SEPARATION = 20; #endif diff --git a/Aman/PopupMenu.cpp b/Aman/PopupMenu.cpp index dc3add9..5becbfa 100644 --- a/Aman/PopupMenu.cpp +++ b/Aman/PopupMenu.cpp @@ -69,13 +69,13 @@ CRect PopupMenu::renderMenuItem(HDC hdc, const std::string& text, CRect area, in bool isActive = std::find(activeItems.begin(), activeItems.end(), text) != activeItems.end(); auto label = (isActive ? "x " : " ") + text; - int minWidth = area.Width(); + int width = area.Width(); bool isHovered = index == lastHoveredIndex; auto oldBkMode = SetBkMode(hdc, isHovered ? OPAQUE : TRANSPARENT); DrawText(hdc, label.c_str(), label.length(), &area, DT_LEFT | DT_CALCRECT); - area.right = area.left + minWidth; + area.right = area.left + width; DrawText(hdc, label.c_str(), label.length(), &area, DT_LEFT); clickAreas[index] = { area }; diff --git a/Aman/Window.cpp b/Aman/Window.cpp index 817eb5e..0383f73 100644 --- a/Aman/Window.cpp +++ b/Aman/Window.cpp @@ -124,8 +124,7 @@ LRESULT CALLBACK Window::handleMessage(UINT message, WPARAM wParam, LPARAM lPara switch (message) { case WM_CLOSE: { - // Should not close - return 0; + closeRequested(); } break; case WM_DESTROY: { windowClosed(); diff --git a/Aman/Window.h b/Aman/Window.h index d467ff5..146069f 100644 --- a/Aman/Window.h +++ b/Aman/Window.h @@ -19,6 +19,7 @@ class Window { virtual void mouseReleased(CPoint cursorPosClient) {}; virtual void mouseMoved(CPoint cursorPosClient, CPoint cursorPosScreen) {}; virtual void mouseWheelSrolled(CPoint cursorPosClient, short delta) {}; + virtual void closeRequested() {}; virtual void windowClosed() {}; virtual void drawContent(HDC hdc, CRect clientRect) {}; diff --git a/README.md b/README.md index d50c35f..34ee477 100644 --- a/README.md +++ b/README.md @@ -1,60 +1,87 @@ # Arrival manager for EuroScope A simple arrival manager plugin for EuroScope. Uses the position predictions provided by EuroScope to visualize the arrival flow for a given airport or waypoint. -## How to use -The AMAN-display will appear in a separate window once the plugin has been loaded. Timelines are loaded from `aman-config.json` which must be placed in the same directory as the plugin `dll`. The file content can be reloaded at run time through the menu. +## Download + +The plugin .dll-file can be found in the Release folder. [Direct link](https://github.com/EvenAR/euroscope-aman/raw/master/Release/Aman.dll). +The AMAN-display will appear in a separate window once the plugin has been loaded. + +## Configuration + +Timelines are loaded from `aman-config.json` which must be placed in the same directory as the plugin `dll`. The file content can be reloaded at run time through the menu. Example `aman-config.json`: ```json { - "timelines": [ - { - "alias": "19R/19L", + "openAutomatically": false, + "timelines": { + "19R/19L": { "targetFixes": [ "GSW40", "GME40" ], - "viaFixes": [ "ADOPI", "LUNIP", "ESEBA", "INREX", "BELGU", "RIPAM" ], - "startHorizon": 120, + "viaFixes": [ "ADOPI", "LUNIP", "ESEBA", "INREX", "RIPAM", "BELGU" ], + "tagLayout": "myLayout", "destinationAirports": [ "ENGM" ] }, - { - "alias": "...", + "...": { "targetFixes": [ "....", "...." ], "viaFixes": [ "..." ], - "startHorizon": 60, + "tagLayout": "simpleLayout", + "defaultTimeSpan": 60, "destinationAirports": [ "....", "...." ] } - ] + }, + "tagLayouts": { + "myLayout": [ + { "source": "assignedRunway", "width": 4 }, + { "source": "callsign", "width": 8 }, + { "source": "static", "defaultValue": "*", "width": 2, "isViaFixIndicator": true }, + { "source": "aircraftType", "width": 5 }, + { "source": "aircraftWtc", "width": 2 }, + { "source": "timeBehindPreceeding", "width": 5, "rightAligned": true }, + { "source": "remainingDistance", "width": 4, "rightAligned": true }, + { "source": "static", "width": 1 }, + { "source": "directRouting", "width": 5, "rightAligned": true, "defaultValue": "-----" }, + { "source": "static", "width": 1 }, + { "source": "scratchPad", "width": 4 } + ], + "simpleLayout": [ + { "source": "assignedRunway", "width": 4 }, + { "source": "callsign", "width": 8 } + ] + } } ``` +### Timelines + | Property | Description |------------------|--------------- | `targetFixes` | Based on the assigned route, any aircraft expected to pass one of these fixes are shown in the timeline. When exactly two fixes are specified, a dual timeline is shown with the first fix on the left side and the second on the right side. +| `tagLayout` | The id of the tag-layout that should be used for this timeline. | `viaFixes` | (optional) Each fix will be assigned a color, and aircraft with a route initially (any direct routings ignored) going through one of these fixes will be marked with the color. For example, this can give a better overview of which direction each aircraft is coming from. Only eight different colors are available at the moment. -| `alias` | (optional) If used, this will be the ID of the timeline. If not, the name will be generated from `targetFixes`. -| `startHorizon` | (optional) If used, this will be the initial time horizon (in minutes) when the timeline is loaded. +| `defaultTimeSpan`| (optional) If used, this will be the initial "zoom"-level (in minutes) when the timeline is loaded. | `destinationAirports` | (optional) If used, aircraft whose destination is not in `destinationAirports` will not be included. -The information displayed for each aircraft has the following layout: +### Tag layouts -![](https://i.gyazo.com/76f58bf5317288c11fdf2580356c913b.png) +A tag layout has a set of tag values, which will be drawn in the specified order. Each tag value is configured using the following properties: -``` - - -<"via fix" indicator> (when applicable) - - - - - -``` +| Property | Description +|---------------------|--------------- +| `source` | Where to get the value from. The following sources are available: `callsign`, `assignedRunway`, `aircraftType`, `aircraftWtc`, `minutesBehindPreceedingRounded`, `timeBehindPreceeding`, `remainingDistance`, `directRouting`, `scratchPad`, `static`. +| `width` | The number of characters that the value should "reserve". If the value is longer than `width` it will be truncated. +| `rightAligned` | (optional) Defaults to `false`. +| `isViaFixIndicator` | (optional) If true, the value will be colored based on the "via fix". Defaults to `false`. +| `defaultValue` | (optional) Can only be used if `source` is `directRouting` or `static`. Defaults to `""`. -## Download +## Available dot-commands -The plugin .dll-file can be found in the Release folder. [Direct link](https://github.com/EvenAR/euroscope-aman/raw/master/Release/Aman.dll). +| Command | Description +|---------------------|--------------- +| `.aman open` | Opens the window +| `.aman close` | Closes the window ![Window](https://i.gyazo.com/52cf2fbc1d6eb48f4a77b71784e7c61f.png) - +Search tags: > Vatsim, air traffic control, ATC, flight simulator diff --git a/Release/Aman.dll b/Release/Aman.dll index bc7e200..cd3ec45 100644 Binary files a/Release/Aman.dll and b/Release/Aman.dll differ diff --git a/Release/aman-config.json b/Release/aman-config.json index 4238090..e03a9a5 100644 --- a/Release/aman-config.json +++ b/Release/aman-config.json @@ -1,11 +1,29 @@ { - "timelines": [ - { - "alias": "19R/19L", + "openAutomatically": false, + "timelines": { + "19R/19L": { "targetFixes": [ "GSW40", "GME40" ], - "viaFixes": [ "ADOPI", "LUNIP", "ESEBA", "INREX", "BELGU", "RIPAM" ], - "startHorizon": 10, - "destinationAirports": [ "ENGM" ] + "viaFixes": [ "ADOPI", "LUNIP", "ESEBA", "INREX", "RIPAM", "BELGU" ], + "tagLayout": "myLayout" } - ] + }, + "tagLayouts": { + "myLayout": [ + { "source": "assignedRunway", "width": 4 }, + { "source": "callsign", "width": 8 }, + { "source": "static", "defaultValue": "*", "width": 2, "isViaFixIndicator": true }, + { "source": "aircraftType", "width": 5 }, + { "source": "aircraftWtc", "width": 2 }, + { "source": "timeBehindPreceeding", "width": 5, "rightAligned": true }, + { "source": "remainingDistance", "width": 4, "rightAligned": true }, + { "source": "static", "width": 1 }, + { "source": "directRouting", "width": 5, "rightAligned": true, "defaultValue": "-----" }, + { "source": "static", "width": 1 }, + { "source": "scratchPad", "width": 4 } + ], + "simpleLayout": [ + { "source": "assignedRunway", "width": 4, "isViaFixIndicator": true }, + { "source": "callsign", "width": 8 } + ] + } } \ No newline at end of file