360 lines
12 KiB
MQL5
360 lines
12 KiB
MQL5
//+------------------------------------------------------------------+
| |
//| 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"
| |
| |
//+------------------------------------------------------------------+
| |
//| |
| |
//+------------------------------------------------------------------+
| |
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;
| |
| |
// Manual dynamic array
| |
ZoneLevel* m_levels[];
| |
int m_count;
| |
int m_capacity;
| |
| |
void ResizeArray(int newCapacity);
| |
| |
public:
| |
VisualSystem();
| |
~VisualSystem();
| |
| |
void SetMinZoomForTexts(long zoomLevel) { m_min_zoom_to_keep_texts = zoomLevel; }
| |
void SetAutoUpdate(bool enable) { m_auto_update = enable; }
| |
| |
long Zoom() const { return m_zoom; }
| |
int GetScaledFontSize(int baseSizePoints) const;
| |
| |
// Level management
| |
void AddLevel(ZoneLevel *level);
| |
bool RemoveLevel(int index);
| |
bool RemoveLevel(ZoneLevel *level);
| |
| |
ZoneLevel* GetLevel(int index) const;
| |
ZoneLevel* GetLevelByName(string levelName);
| |
| |
int GetSize() const { return m_count; }
| |
int GetCapacity() const { return m_capacity; }
| |
void SetCapacity(int newCapacity);
| |
void ClearLevels();
| |
void DeleteLevel(int index);
| |
| |
// Rendering – now uses dirty tracking
| |
void Render();
| |
| |
// Event handlers
| |
void UpdateViewBounds();
| |
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam);
| |
| |
// Virtual overrides
| |
virtual void OnViewChanged() override;
| |
virtual void OnZoomChanged(long newZoom, long oldZoom) override;
| |
virtual void OnMinZoomReached() override { }
| |
virtual void OnBeforeRender() override { UpdateViewBounds(); }
| |
virtual void OnAfterRender() override { }
| |
| |
private:
| |
void DeleteAllTextObjects();
| |
// DeleteAllVisualObjects no longer used in Render, kept for emergency
| |
void DeleteAllVisualObjects();
| |
};
| |
| |
//+------------------------------------------------------------------+
| |
//| Constructor |
| |
//+------------------------------------------------------------------+
| |
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)
| |
{
| |
UpdateViewBounds();
| |
}
| |
| |
//+------------------------------------------------------------------+
| |
//| |
| |
//+------------------------------------------------------------------+
| |
VisualSystem::~VisualSystem()
| |
{
| |
ClearLevels();
| |
}
| |
| |
//+------------------------------------------------------------------+
| |
//| Array management |
| |
//+------------------------------------------------------------------+
| |
void VisualSystem::ResizeArray(int newCapacity)
| |
{
| |
if(newCapacity <= 0)
| |
{
| |
ArrayResize(m_levels, 0);
| |
m_capacity = 0;
| |
return;
| |
}
| |
ArrayResize(m_levels, newCapacity);
| |
m_capacity = newCapacity;
| |
}
| |
| |
//+------------------------------------------------------------------+
| |
//| |
| |
//+------------------------------------------------------------------+
| |
void VisualSystem::SetCapacity(int newCapacity)
| |
{
| |
if(newCapacity <= m_count)
| |
return;
| |
ResizeArray(newCapacity);
| |
}
| |
| |
//+------------------------------------------------------------------+
| |
//| |
| |
//+------------------------------------------------------------------+
| |
void VisualSystem::AddLevel(ZoneLevel *level)
| |
{
| |
if(CheckPointer(level) == POINTER_INVALID)
| |
return;
| |
if(m_count >= m_capacity)
| |
{
| |
int newCap = (m_capacity == 0) ? 4 : m_capacity * 2;
| |
ResizeArray(newCap);
| |
}
| |
m_levels[m_count] = level;
| |
m_count++;
| |
| |
}
| |
| |
//+------------------------------------------------------------------+
| |
//| |
| |
//+------------------------------------------------------------------+
| |
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;
| |
}
| |
| |
//+------------------------------------------------------------------+
| |
//| |
| |
//+------------------------------------------------------------------+
| |
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;
| |
}
| |
| |
//+------------------------------------------------------------------+
| |
//| |
| |
//+------------------------------------------------------------------+
| |
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;
| |
}
| |
//+------------------------------------------------------------------+
| |
//| |
| |
//+------------------------------------------------------------------+
| |
void VisualSystem::DeleteLevel(int index)
| |
{
| |
if(index < 0 || index >= m_count)
| |
return;
| |
ZoneLevel* p = m_levels[index];
| |
if(CheckPointer(p) != POINTER_INVALID)
| |
delete p;
| |
RemoveLevel(index);
| |
}
| |
| |
//+------------------------------------------------------------------+
| |
//| |
| |
//+------------------------------------------------------------------+
| |
ZoneLevel* VisualSystem::GetLevel(int index) const
| |
{
| |
if(index < 0 || index >= m_count)
| |
return NULL;
| |
return m_levels[index];
| |
}
| |
| |
//+------------------------------------------------------------------+
| |
//| |
| |
//+------------------------------------------------------------------+
| |
void VisualSystem::ClearLevels()
| |
{
| |
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;
| |
}
| |
| |
//+------------------------------------------------------------------+
| |
//| Optimized Render – uses dirty flags |
| |
//+------------------------------------------------------------------+
| |
//+------------------------------------------------------------------+
| |
//| Optimized Render – reverse iteration + early culling |
| |
//+------------------------------------------------------------------+
| |
void VisualSystem::Render()
| |
{
| |
OnBeforeRender(); // <-- updates m_from_time, m_to_time, m_min_price, m_max_price
| |
| |
// 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
| |
{
| |
ZoneLevel* p = m_levels[i];
| |
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);
| |
}
| |
| |
OnAfterRender();
| |
}
| |
| |
//+------------------------------------------------------------------+
| |
//| View bounds update |
| |
//+------------------------------------------------------------------+
| |
void VisualSystem::UpdateViewBounds()
| |
{
| |
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;
| |
| |
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);
| |
| |
if(m_zoom != old_zoom)
| |
{
| |
OnZoomChanged(m_zoom, old_zoom);
| |
bool below_threshold = (m_zoom < m_min_zoom_to_keep_texts);
| |
| |
| |
if(below_threshold && !m_min_zoom_reached_already)
| |
{
| |
DeleteAllTextObjects();
| |
OnMinZoomReached();
| |
m_min_zoom_reached_already = true;
| |
| |
}
| |
else
| |
if(!below_threshold)
| |
{
| |
m_min_zoom_reached_already = false;
| |
}
| |
}
| |
| |
if(m_from_time != old_from || m_to_time != old_to ||
| |
m_min_price != old_minp || m_max_price != old_maxp)
| |
{
| |
OnViewChanged();
| |
}
| |
}
| |
//+------------------------------------------------------------------+
| |
//| |
| |
//+------------------------------------------------------------------+
| |
int VisualSystem::GetScaledFontSize(int baseSizePoints) const
| |
{
| |
return (int)MathMax(6.0, baseSizePoints - (m_zoom - 2));
| |
}
| |
| |
//+------------------------------------------------------------------+
| |
//| |
| |
//+------------------------------------------------------------------+
| |
void VisualSystem::DeleteAllTextObjects()
| |
{
| |
for(int i = 0; i < m_count; i++)
| |
if(CheckPointer(m_levels[i]) != POINTER_INVALID)
| |
m_levels[i].DeleteText();
| |
| |
| |
}
| |
| |
// 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();
| |
}
| |
| |
//+------------------------------------------------------------------+
| |
//| |
| |
//+------------------------------------------------------------------+
| |
void VisualSystem::OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
| |
{
| |
if(id == CHARTEVENT_CHART_CHANGE)
| |
UpdateViewBounds();
| |
}
| |
| |
//+------------------------------------------------------------------+
| |
//| |
| |
//+------------------------------------------------------------------+
| |
void VisualSystem::OnViewChanged(void)
| |
{
| |
Render();
| |
// Print("OnViewChanged");
| |
}
| |
| |
//+------------------------------------------------------------------+
| |
//| |
| |
//+------------------------------------------------------------------+
| |
void VisualSystem::OnZoomChanged(long newZoom, long oldZoom)
| |
{
| |
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));
| |
}
| |
| |
//+------------------------------------------------------------------+
|