2026-06-10 18:31:08 +02:00 | | | //+------------------------------------------------------------------+
|
| | | //| VisualSystem.mqh |
|
| | | //| Copyright 2025, MetaQuotes Ltd. |
|
| | | //+------------------------------------------------------------------+
|
| | | #property copyright "Copyright 2025, MetaQuotes Ltd."
|
| | | #property link "https://www.mql5.com"
|
| | | #property version "1.00"
|
| | |
|
| | | #include "VisualInterfaces.mqh"
|
| | | #include "ZoneLevel.mqh"
|
| | |
|
| | | //+------------------------------------------------------------------+
|
2026-06-10 23:03:10 +02:00 | | | //| |
|
2026-06-10 18:31:08 +02:00 | | | //+------------------------------------------------------------------+
|
| | | class VisualSystem : public ViewEvents
|
| | | {
|
| | | private:
|
| | | long m_chart_id;
|
| | | int m_width, m_height;
|
| | | long m_zoom;
|
| | |
|
| | | datetime m_from_time, m_to_time;
|
| | | double m_min_price, m_max_price;
|
| | |
|
| | | bool m_auto_update;
|
| | | long m_min_zoom_to_keep_texts;
|
| | | bool m_min_zoom_reached_already;
|
| | |
|
2026-06-10 23:03:10 +02:00 | | | // Manual dynamic array
|
| | | ZoneLevel* m_levels[];
|
| | | int m_count;
|
| | | int m_capacity;
|
| | |
|
| | | void ResizeArray(int newCapacity);
|
2026-06-10 18:31:08 +02:00 | | |
|
| | | public:
|
| | | VisualSystem();
|
| | | ~VisualSystem();
|
| | |
|
| | | void SetMinZoomForTexts(long zoomLevel) { m_min_zoom_to_keep_texts = zoomLevel; }
|
| | | void SetAutoUpdate(bool enable) { m_auto_update = enable; }
|
| | |
|
2026-06-10 23:03:10 +02:00 | | | long Zoom() const { return m_zoom; }
|
2026-06-10 18:31:08 +02:00 | | | int GetScaledFontSize(int baseSizePoints) const;
|
| | |
|
| | | // Level management
|
| | | void AddLevel(ZoneLevel *level);
|
2026-06-10 23:03:10 +02:00 | | | bool RemoveLevel(int index);
|
| | | bool RemoveLevel(ZoneLevel *level);
|
2026-06-23 21:44:37 +02:00 | | |
|
2026-06-10 23:03:10 +02:00 | | | ZoneLevel* GetLevel(int index) const;
|
2026-06-23 21:44:37 +02:00 | | | ZoneLevel* GetLevelByName(string levelName);
|
| | |
|
2026-06-10 23:03:10 +02:00 | | | int GetSize() const { return m_count; }
|
| | | int GetCapacity() const { return m_capacity; }
|
| | | void SetCapacity(int newCapacity);
|
2026-06-10 18:31:08 +02:00 | | | void ClearLevels();
|
2026-06-10 23:03:10 +02:00 | | | void DeleteLevel(int index);
|
| | |
|
| | | // Rendering – now uses dirty tracking
|
2026-06-10 18:31:08 +02:00 | | | void Render();
|
| | |
|
2026-06-10 23:03:10 +02:00 | | | // Event handlers
|
2026-06-10 18:31:08 +02:00 | | | void UpdateViewBounds();
|
| | | void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam);
|
| | |
|
| | | // Virtual overrides
|
2026-06-10 23:03:10 +02:00 | | | virtual void OnViewChanged() override;
|
| | | virtual void OnZoomChanged(long newZoom, long oldZoom) override;
|
2026-06-10 18:31:08 +02:00 | | | virtual void OnMinZoomReached() override { }
|
2026-06-10 23:03:10 +02:00 | | | virtual void OnBeforeRender() override { UpdateViewBounds(); }
|
2026-06-10 18:31:08 +02:00 | | | virtual void OnAfterRender() override { }
|
| | |
|
| | | private:
|
| | | void DeleteAllTextObjects();
|
2026-06-10 23:03:10 +02:00 | | | // DeleteAllVisualObjects no longer used in Render, kept for emergency
|
2026-06-10 18:31:08 +02:00 | | | void DeleteAllVisualObjects();
|
| | | };
|
| | |
|
| | | //+------------------------------------------------------------------+
|
2026-06-10 23:03:10 +02:00 | | | //| Constructor |
|
2026-06-10 18:31:08 +02:00 | | | //+------------------------------------------------------------------+
|
2026-06-10 23:03:10 +02:00 | | | VisualSystem::VisualSystem()
|
| | | : m_chart_id(ChartID()), m_width(0), m_height(0), m_zoom(0),
|
| | | m_auto_update(true), m_min_zoom_to_keep_texts(2), m_min_zoom_reached_already(false),
|
| | | m_count(0), m_capacity(0)
|
2026-06-10 18:31:08 +02:00 | | | {
|
| | | UpdateViewBounds();
|
| | | }
|
2026-06-10 23:03:10 +02:00 | | |
|
2026-06-10 18:31:08 +02:00 | | | //+------------------------------------------------------------------+
|
| | | //| |
|
| | | //+------------------------------------------------------------------+
|
2026-06-10 23:03:10 +02:00 | | | VisualSystem::~VisualSystem()
|
2026-06-10 18:31:08 +02:00 | | | {
|
| | | ClearLevels();
|
| | | }
|
2026-06-10 23:03:10 +02:00 | | |
|
2026-06-10 18:31:08 +02:00 | | | //+------------------------------------------------------------------+
|
2026-06-10 23:03:10 +02:00 | | | //| Array management |
|
2026-06-10 18:31:08 +02:00 | | | //+------------------------------------------------------------------+
|
2026-06-10 23:03:10 +02:00 | | | void VisualSystem::ResizeArray(int newCapacity)
|
2026-06-10 18:31:08 +02:00 | | | {
|
2026-06-10 23:03:10 +02:00 | | | if(newCapacity <= 0)
|
2026-06-10 18:31:08 +02:00 | | | {
|
2026-06-10 23:03:10 +02:00 | | | ArrayResize(m_levels, 0);
|
| | | m_capacity = 0;
|
| | | return;
|
2026-06-10 18:31:08 +02:00 | | | }
|
2026-06-10 23:03:10 +02:00 | | | ArrayResize(m_levels, newCapacity);
|
| | | m_capacity = newCapacity;
|
2026-06-10 18:31:08 +02:00 | | | }
|
2026-06-10 23:03:10 +02:00 | | |
|
2026-06-10 18:31:08 +02:00 | | | //+------------------------------------------------------------------+
|
| | | //| |
|
| | | //+------------------------------------------------------------------+
|
2026-06-10 23:03:10 +02:00 | | | void VisualSystem::SetCapacity(int newCapacity)
|
2026-06-10 18:31:08 +02:00 | | | {
|
2026-06-10 23:03:10 +02:00 | | | if(newCapacity <= m_count)
|
| | | return;
|
| | | ResizeArray(newCapacity);
|
2026-06-10 18:31:08 +02:00 | | | }
|
2026-06-10 23:03:10 +02:00 | | |
|
2026-06-10 18:31:08 +02:00 | | | //+------------------------------------------------------------------+
|
| | | //| |
|
| | | //+------------------------------------------------------------------+
|
| | | void VisualSystem::AddLevel(ZoneLevel *level)
|
| | | {
|
| | | if(CheckPointer(level) == POINTER_INVALID)
|
| | | return;
|
2026-06-10 23:03:10 +02:00 | | | if(m_count >= m_capacity)
|
| | | {
|
| | | int newCap = (m_capacity == 0) ? 4 : m_capacity * 2;
|
| | | ResizeArray(newCap);
|
| | | }
|
| | | m_levels[m_count] = level;
|
| | | m_count++;
|
2026-06-23 21:44:37 +02:00 | | |
|
2026-06-10 23:03:10 +02:00 | | | }
|
2026-06-10 18:31:08 +02:00 | | |
|
2026-06-10 23:03:10 +02:00 | | | //+------------------------------------------------------------------+
|
| | | //| |
|
| | | //+------------------------------------------------------------------+
|
| | | bool VisualSystem::RemoveLevel(int index)
|
| | | {
|
| | | if(index < 0 || index >= m_count)
|
| | | return false;
|
| | | for(int i = index; i < m_count - 1; i++)
|
| | | m_levels[i] = m_levels[i + 1];
|
| | | m_count--;
|
| | | if(m_count > 0)
|
| | | m_levels[m_count] = NULL;
|
| | | return true;
|
| | | }
|
2026-06-10 18:31:08 +02:00 | | |
|
2026-06-10 23:03:10 +02:00 | | | //+------------------------------------------------------------------+
|
| | | //| |
|
| | | //+------------------------------------------------------------------+
|
| | | bool VisualSystem::RemoveLevel(ZoneLevel *level)
|
| | | {
|
| | | if(level == NULL)
|
| | | return false;
|
| | | for(int i = 0; i < m_count; i++)
|
| | | if(m_levels[i] == level)
|
| | | return RemoveLevel(i);
|
| | | return false;
|
2026-06-10 18:31:08 +02:00 | | | }
|
2026-06-10 23:03:10 +02:00 | | |
|
2026-06-10 18:31:08 +02:00 | | | //+------------------------------------------------------------------+
|
| | | //| |
|
| | | //+------------------------------------------------------------------+
|
2026-06-23 21:44:37 +02:00 | | | ZoneLevel* VisualSystem::GetLevelByName(string levelName)
|
| | | {
|
| | | for(int i = 0; i < m_count; i++)
|
| | | if(m_levels[i].GetName() == levelName)
|
| | | return m_levels[i];
|
| | | return NULL;
|
| | | }
|
| | | //+------------------------------------------------------------------+
|
| | | //| |
|
| | | //+------------------------------------------------------------------+
|
2026-06-10 23:03:10 +02:00 | | | void VisualSystem::DeleteLevel(int index)
|
2026-06-10 18:31:08 +02:00 | | | {
|
2026-06-10 23:03:10 +02:00 | | | if(index < 0 || index >= m_count)
|
| | | return;
|
| | | ZoneLevel* p = m_levels[index];
|
| | | if(CheckPointer(p) != POINTER_INVALID)
|
| | | delete p;
|
| | | RemoveLevel(index);
|
2026-06-10 18:31:08 +02:00 | | | }
|
2026-06-10 23:03:10 +02:00 | | |
|
2026-06-10 18:31:08 +02:00 | | | //+------------------------------------------------------------------+
|
| | | //| |
|
| | | //+------------------------------------------------------------------+
|
2026-06-10 23:03:10 +02:00 | | | ZoneLevel* VisualSystem::GetLevel(int index) const
|
2026-06-10 18:31:08 +02:00 | | | {
|
2026-06-10 23:03:10 +02:00 | | | if(index < 0 || index >= m_count)
|
| | | return NULL;
|
| | | return m_levels[index];
|
2026-06-10 18:31:08 +02:00 | | | }
|
| | |
|
| | | //+------------------------------------------------------------------+
|
| | | //| |
|
| | | //+------------------------------------------------------------------+
|
| | | void VisualSystem::ClearLevels()
|
| | | {
|
2026-06-10 23:03:10 +02:00 | | | for(int i = 0; i < m_count; i++)
|
| | | {
|
| | | if(CheckPointer(m_levels[i]) != POINTER_INVALID)
|
| | | delete m_levels[i];
|
| | | m_levels[i] = NULL;
|
| | | }
|
| | | m_count = 0;
|
2026-06-10 18:31:08 +02:00 | | | }
|
| | |
|
| | | //+------------------------------------------------------------------+
|
2026-06-10 23:03:10 +02:00 | | | //| Optimized Render – uses dirty flags |
|
2026-06-10 18:31:08 +02:00 | | | //+------------------------------------------------------------------+
|
2026-06-23 21:44:37 +02:00 | | | //+------------------------------------------------------------------+
|
| | | //| Optimized Render – reverse iteration + early culling |
|
| | | //+------------------------------------------------------------------+
|
2026-06-10 18:31:08 +02:00 | | | void VisualSystem::Render()
|
| | | {
|
2026-06-23 21:44:37 +02:00 | | | OnBeforeRender(); // <-- updates m_from_time, m_to_time, m_min_price, m_max_price
|
2026-06-10 18:31:08 +02:00 | | |
|
2026-06-23 21:44:37 +02:00 | | | // If bounds are invalid (chart not ready), just update all levels without culling
|
| | | bool boundsValid = (m_from_time < m_to_time && m_min_price < m_max_price);
|
| | |
|
| | | for(int i = m_count - 1; i >= 0; i--) // reverse iteration: draw newest first
|
| | | {
|
2026-06-10 23:03:10 +02:00 | | | ZoneLevel* p = m_levels[i];
|
2026-06-23 21:44:37 +02:00 | | | if(p == NULL)
|
| | | continue;
|
| | |
|
| | |
|
| | | // Fast culling using level's stored time/price range
|
| | | /* datetime levelFrom = p.GetFromTime();
|
| | | datetime levelTo = p.GetToTime();
|
| | | double levelMin = p.GetMinPrice();
|
| | | double levelMax = p.GetMaxPrice();
|
| | |
|
| | | // Skip if completely outside visible time OR price range
|
| | | if(levelTo < m_from_time || levelFrom > m_to_time ||
|
| | | levelMax < m_min_price || levelMin > m_max_price)
|
| | | {
|
| | | // Level not visible at all – skip UpdateDraw entirely
|
| | | // (Objects will be deleted when they become invisible inside UpdateDraw later)
|
| | | continue;
|
| | | }
|
| | | //} if(boundsValid)
|
| | | //{*/
|
| | |
|
| | | // Level intersects view (or bounds invalid) – update its drawing
|
| | | p.UpdateDraw(GetPointer(this), m_from_time, m_to_time, m_min_price, m_max_price);
|
2026-06-10 23:03:10 +02:00 | | | }
|
2026-06-10 18:31:08 +02:00 | | |
|
2026-06-10 23:03:10 +02:00 | | | OnAfterRender();
|
| | | }
|
2026-06-10 18:31:08 +02:00 | | |
|
2026-06-10 23:03:10 +02:00 | | | //+------------------------------------------------------------------+
|
| | | //| View bounds update |
|
| | | //+------------------------------------------------------------------+
|
2026-06-23 21:44:37 +02:00 | | | void VisualSystem::UpdateViewBounds()
|
2026-06-10 23:03:10 +02:00 | | | {
|
| | | long old_zoom = m_zoom;
|
| | | datetime old_from = m_from_time, old_to = m_to_time;
|
| | | double old_minp = m_min_price, old_maxp = m_max_price;
|
2026-06-10 18:31:08 +02:00 | | |
|
2026-06-10 23:03:10 +02:00 | | | m_width = (int)ChartGetInteger(m_chart_id, CHART_WIDTH_IN_PIXELS);
|
| | | m_height = (int)ChartGetInteger(m_chart_id, CHART_HEIGHT_IN_PIXELS);
|
| | | m_zoom = ChartGetInteger(m_chart_id, CHART_SCALE);
|
| | |
|
| | | int sub_window = 0;
|
| | | ChartXYToTimePrice(m_chart_id, 0, 0, sub_window, m_from_time, m_max_price);
|
| | | ChartXYToTimePrice(m_chart_id, m_width, m_height, sub_window, m_to_time, m_min_price);
|
2026-06-23 21:44:37 +02:00 | | |
|
2026-06-10 23:03:10 +02:00 | | | if(m_zoom != old_zoom)
|
| | | {
|
| | | OnZoomChanged(m_zoom, old_zoom);
|
| | | bool below_threshold = (m_zoom < m_min_zoom_to_keep_texts);
|
2026-06-23 21:44:37 +02:00 | | |
|
| | |
|
2026-06-10 23:03:10 +02:00 | | | if(below_threshold && !m_min_zoom_reached_already)
|
2026-06-23 21:44:37 +02:00 | | | {
|
2026-06-10 23:03:10 +02:00 | | | DeleteAllTextObjects();
|
| | | OnMinZoomReached();
|
| | | m_min_zoom_reached_already = true;
|
2026-06-23 21:44:37 +02:00 | | |
|
2026-06-10 23:03:10 +02:00 | | | }
|
| | | else
|
| | | if(!below_threshold)
|
| | | {
|
| | | m_min_zoom_reached_already = false;
|
| | | }
|
| | | }
|
2026-06-10 18:31:08 +02:00 | | |
|
2026-06-10 23:03:10 +02:00 | | | if(m_from_time != old_from || m_to_time != old_to ||
|
| | | m_min_price != old_minp || m_max_price != old_maxp)
|
| | | {
|
| | | OnViewChanged();
|
2026-06-10 18:31:08 +02:00 | | | }
|
2026-06-10 23:03:10 +02:00 | | | }
|
| | | //+------------------------------------------------------------------+
|
| | | //| |
|
| | | //+------------------------------------------------------------------+
|
| | | int VisualSystem::GetScaledFontSize(int baseSizePoints) const
|
| | | {
|
| | | return (int)MathMax(6.0, baseSizePoints - (m_zoom - 2));
|
| | | }
|
2026-06-10 18:31:08 +02:00 | | |
|
2026-06-10 23:03:10 +02:00 | | | //+------------------------------------------------------------------+
|
| | | //| |
|
| | | //+------------------------------------------------------------------+
|
| | | void VisualSystem::DeleteAllTextObjects()
|
| | | {
|
| | | for(int i = 0; i < m_count; i++)
|
| | | if(CheckPointer(m_levels[i]) != POINTER_INVALID)
|
| | | m_levels[i].DeleteText();
|
2026-06-23 21:44:37 +02:00 | | |
|
| | |
|
2026-06-10 23:03:10 +02:00 | | | }
|
| | |
|
| | | // Emergency cleanup – not used in normal render but kept for completeness
|
| | | void VisualSystem::DeleteAllVisualObjects()
|
| | | {
|
| | | for(int i = 0; i < m_count; i++)
|
| | | if(CheckPointer(m_levels[i]) != POINTER_INVALID)
|
| | | m_levels[i].Delete();
|
2026-06-10 18:31:08 +02:00 | | | }
|
| | |
|
| | | //+------------------------------------------------------------------+
|
| | | //| |
|
| | | //+------------------------------------------------------------------+
|
| | | void VisualSystem::OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
|
| | | {
|
| | | if(id == CHARTEVENT_CHART_CHANGE)
|
| | | UpdateViewBounds();
|
| | | }
|
2026-06-10 23:03:10 +02:00 | | |
|
2026-06-10 18:31:08 +02:00 | | | //+------------------------------------------------------------------+
|
| | | //| |
|
| | | //+------------------------------------------------------------------+
|
| | | void VisualSystem::OnViewChanged(void)
|
| | | {
|
| | | Render();
|
2026-06-23 21:44:37 +02:00 | | | // Print("OnViewChanged");
|
2026-06-10 18:31:08 +02:00 | | | }
|
| | |
|
| | | //+------------------------------------------------------------------+
|
2026-06-10 23:03:10 +02:00 | | | //| |
|
| | | //+------------------------------------------------------------------+
|
2026-06-23 21:44:37 +02:00 | | | void VisualSystem::OnZoomChanged(long newZoom, long oldZoom)
|
2026-06-10 23:03:10 +02:00 | | | {
|
| | | if(!m_min_zoom_reached_already)
|
| | | for(int i = 0; i < m_count; i++)
|
| | | if(CheckPointer(m_levels[i]) != POINTER_INVALID)
|
| | | m_levels[i].SetFontSize(GetScaledFontSize(12));
|
| | | }
|
| | |
|
2026-06-23 21:44:37 +02:00 | | | //+------------------------------------------------------------------+
|