//+------------------------------------------------------------------+ //| Panel.mqh | //| Copyright 2026, Allan Niñal | //| https://www.allanninal.dev/mql5 | //+------------------------------------------------------------------+ //| A grid panel made of chart objects that all start with one | //| prefix: title bar, header row, one row label per row and one | //| button per cell. Only cells whose text or colours changed are | //| touched. Everything is removed by Destroy() (call it in | //| OnDeinit). If the chart refuses objects (e.g. in the tester) the | //| panel simply stays silent. | //+------------------------------------------------------------------+ // SPDX-License-Identifier: LicenseRef-PolyForm-Noncommercial-1.0.0 (see LICENSE) #property strict enum ENUM_SD_CORNER { SD_TOP_LEFT=0, // Top left SD_TOP_RIGHT=1, // Top right SD_BOTTOM_LEFT=2, // Bottom left SD_BOTTOM_RIGHT=3 // Bottom right }; class CSDPanel { private: string m_p; long m_chart; int m_corner,m_ox,m_oy,m_fs; bool m_collapsed,m_ok,m_built; int m_rows,m_cols; string m_title; string m_rowhead; // header of the row-label column ("Symbol" unless set) string m_heads[],m_labels[]; string m_txt[],m_tip[]; color m_fg[],m_bg[]; color m_c_panel,m_c_text,m_c_head; int m_lw,m_cw,m_rh,m_pad,m_W,m_H,m_x0,m_y0,m_cwpx,m_chpx; bool Put(const ENUM_OBJECT type,const string name,const int x,const int y,const int w,const int h, const string txt,const color fg,const color bg,const bool bold); void Size(); public: CSDPanel(): m_rowhead("Symbol"),m_chart(0),m_collapsed(false),m_ok(true),m_built(false),m_rows(0),m_cols(0) {} void Setup(const string prefix,const string title,const int corner,const int ox,const int oy,const int fontsize, const color c_panel,const color c_text,const color c_head); void Layout(const string &heads[],const string &labels[]); void Rebuild(); void SetTitle(const string t); void SetRowHead(const string h) { m_rowhead=h; } void SetCell(const int r,const int c,const string txt,const color fg,const color bg,const string tip); bool Click(const string name,int &row,int &col); bool ChartResized(); void Destroy(); string Prefix() const { return(m_p); } }; void CSDPanel::Setup(const string prefix,const string title,const int corner,const int ox,const int oy, const int fontsize,const color c_panel,const color c_text,const color c_head) { m_p=prefix; m_title=title; m_corner=corner; m_ox=ox; m_oy=oy; m_fs=MathMax(7,fontsize); m_c_panel=c_panel; m_c_text=c_text; m_c_head=c_head; m_chart=ChartID(); m_collapsed=false; m_ok=true; } void CSDPanel::Size() { m_rh=m_fs*2+6; m_pad=4; m_cwpx=(int)ChartGetInteger(m_chart,CHART_WIDTH_IN_PIXELS); m_chpx=(int)ChartGetInteger(m_chart,CHART_HEIGHT_IN_PIXELS); int maxl=StringLen(m_rowhead); for(int i=0; i=m_rows*m_cols) return; bool chg=(m_txt[i]!=txt || m_fg[i]!=fg || m_bg[i]!=bg); if(!chg && m_tip[i]==tip) return; m_txt[i]=txt; m_fg[i]=fg; m_bg[i]=bg; m_tip[i]=tip; if(!m_ok || !m_built || m_collapsed) return; string n=m_p+"c"+IntegerToString(r)+"_"+IntegerToString(c); if(chg) { ObjectSetString(m_chart,n,OBJPROP_TEXT,txt); ObjectSetInteger(m_chart,n,OBJPROP_COLOR,fg); ObjectSetInteger(m_chart,n,OBJPROP_BGCOLOR,bg); ObjectSetInteger(m_chart,n,OBJPROP_BORDER_COLOR,bg); } ObjectSetString(m_chart,n,OBJPROP_TOOLTIP,tip); } //--- a click on one of our objects: row/col of the cell, (row,-1) for a row label, (-2,0) when handled (collapse) bool CSDPanel::Click(const string name,int &row,int &col) { if(StringFind(name,m_p)!=0 || !m_ok) return(false); string rest=StringSubstr(name,StringLen(m_p)); ObjectSetInteger(m_chart,name,OBJPROP_STATE,false); if(rest=="min") { m_collapsed=!m_collapsed; Rebuild(); row=-2; col=0; return(true); } if(StringGetCharacter(rest,0)=='c') { string p[]; if(StringSplit(StringSubstr(rest,1),'_',p)==2) { row=(int)StringToInteger(p[0]); col=(int)StringToInteger(p[1]); ChartRedraw(m_chart); return(true); } } if(StringGetCharacter(rest,0)=='r') { row=(int)StringToInteger(StringSubstr(rest,1)); col=-1; ChartRedraw(m_chart); return(true); } return(false); } //--- true (and re-lays out) when the chart size changed and the panel is anchored to a right/bottom corner bool CSDPanel::ChartResized() { if(!m_ok || !m_built) return(false); int w=(int)ChartGetInteger(m_chart,CHART_WIDTH_IN_PIXELS),h=(int)ChartGetInteger(m_chart,CHART_HEIGHT_IN_PIXELS); if(w==m_cwpx && h==m_chpx) return(false); if(m_corner==SD_TOP_LEFT) { m_cwpx=w; m_chpx=h; return(false); } Rebuild(); return(true); } void CSDPanel::Destroy() { if(m_p!="") { ObjectsDeleteAll(m_chart,m_p); ChartRedraw(m_chart); } m_built=false; } //+------------------------------------------------------------------+