//+------------------------------------------------------------------+
//|                                                    Container.mqh |
//|                                  Copyright 2022, MetaQuotes Ltd. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2022, MetaQuotes Ltd."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
#property strict    // Necessary for mql4
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "..\..\WForms\WinFormBase.mqh"
#include "..\..\WForms\Common Controls\RadioButton.mqh"
#include "..\..\WForms\Common Controls\Button.mqh"
//+------------------------------------------------------------------+
//| Class of the base container object of WForms controls            |
//+------------------------------------------------------------------+
class CContainer : public CWinFormBase
  {
private:
//--- Create a new graphical object
   virtual CGCnvElement *CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type,
                                          const int element_num,
                                          const string name,
                                          const int x,
                                          const int y,
                                          const int w,
                                          const int h,
                                          const color colour,
                                          const uchar opacity,
                                          const bool movable,
                                          const bool activity) { return NULL; }

//--- Calculate Dock objects' binding coordinates
   void              CalculateCoords(CArrayObj *list);

protected:
//--- Adjust the element size to fit its content
   bool              AutoSizeProcess(const bool redraw);
//--- Set parameters for the attached object
   void              SetObjParams(CWinFormBase *obj,const color colour);
   
public:
//--- Return the size and coordinates of the working area
   int               GetWidthWorkspace(void)       const
                       {
                        return this.Width()-::fmax(this.BorderSizeLeft(),this.PaddingLeft())-::fmax(this.BorderSizeRight(),this.PaddingRight());
                       }
   int               GetHeightWorkspace(void)      const
                       {
                        return this.Height()-::fmax(this.BorderSizeTop(),this.PaddingTop())-::fmax(this.BorderSizeBottom(),this.PaddingBottom());
                       }
   int               GetCoordXWorkspace(void)      const
                       {
                        return this.CoordX()+::fmax(this.BorderSizeLeft(),this.PaddingLeft());
                       }
   int               GetCoordYWorkspace(void)      const
                       {
                        return this.CoordY()+::fmax(this.BorderSizeTop(),this.PaddingTop());
                       }
   int               GetRightEdgeWorkspace(void)   const
                       {
                        return this.RightEdge()-::fmax(this.BorderSizeRight(),this.PaddingRight());
                       }
   int               GetBottomEdgeWorkspace(void)  const
                       {
                        return this.BottomEdge()-::fmax(this.BorderSizeBottom(),this.PaddingBottom());
                       }

//--- Return the list of bound WinForms objects with (1) any and (2) specified WinForms object type (from the base one and higher)
   CArrayObj        *GetListWinFormsObj(void);
   CArrayObj        *GetListWinFormsObjByType(const ENUM_GRAPH_ELEMENT_TYPE type);
//--- Return the pointer to the specified WinForms object with the specified type by index
   CWinFormBase     *GetWinFormsObj(const ENUM_GRAPH_ELEMENT_TYPE type,const int index);
   
//--- Set the (1) X, (2) Y coordinates, (3) element width and (4) height
   virtual bool      SetCoordX(const int coord_x)              { return CGCnvElement::SetCoordX(coord_x);   }
   virtual bool      SetCoordY(const int coord_y)              { return CGCnvElement::SetCoordY(coord_y);   }
   virtual bool      SetWidth(const int width)                 { return CGCnvElement::SetWidth(width);      }
   virtual bool      SetHeight(const int height)               { return CGCnvElement::SetHeight(height);    }
   
//--- Create a new attached element
   virtual bool      CreateNewElement(const ENUM_GRAPH_ELEMENT_TYPE element_type,
                                      const int x,
                                      const int y,
                                      const int w,
                                      const int h,
                                      const color colour,
                                      const uchar opacity,
                                      const bool activity,
                                      const bool redraw);

//--- Redraw the object
   virtual void      Redraw(bool redraw)                             { CWinFormBase::Redraw(redraw);        }
   
//--- Reset the size of all bound objects to the initial ones
   bool              ResetSizeAllToInit(void);
//--- Place bound objects in the order of their Dock binding
   virtual bool      ArrangeObjects(const bool redraw);
   
//--- Set the (1) field width, (2) height, (3) the height of all fields around the control during auto scrolling
   void              SetAutoScrollMarginWidth(const int value)       { this.SetProperty(CANV_ELEMENT_PROP_AUTOSCROLL_MARGIN_W,value);  }
   void              SetAutoScrollMarginHeight(const int value)      { this.SetProperty(CANV_ELEMENT_PROP_AUTOSCROLL_MARGIN_H,value);  }
   void              SetAutoScrollMarginAll(const int value)
                       {
                        this.SetAutoScrollMarginWidth(value); this.SetAutoScrollMarginHeight(value);
                       }
   void              SetAutoScrollMargin(const int width,const int height)
                       {
                        this.SetAutoScrollMarginWidth(width); this.SetAutoScrollMarginHeight(height);
                       }
//--- Return the (1) field width and (2) height around the control during auto scrolling
   int               AutoScrollMarginWidth(void)               const { return (int)this.GetProperty(CANV_ELEMENT_PROP_AUTOSCROLL_MARGIN_W); }
   int               AutoScrollMarginHeight(void)              const { return (int)this.GetProperty(CANV_ELEMENT_PROP_AUTOSCROLL_MARGIN_H); }
  
//--- Set the flag of the element auto resizing depending on the content
   virtual void      SetAutoSize(const bool flag,const bool redraw)
                       {
                        bool prev=this.AutoSize();
                        if(prev==flag)
                           return;
                        CWinFormBase::SetAutoSize(flag,redraw);
                        if(prev!=this.AutoSize() && this.ElementsTotal()>0)
                           this.AutoSizeProcess(redraw);
                       }
//--- (1) Set and (2) return the mode of the element auto resizing depending on the content
   void              SetAutoSizeMode(const ENUM_CANV_ELEMENT_AUTO_SIZE_MODE mode,const bool redraw)
                       {
                        ENUM_CANV_ELEMENT_AUTO_SIZE_MODE prev=this.AutoSizeMode();
                        if(prev==mode)
                           return;
                        this.SetProperty(CANV_ELEMENT_PROP_AUTOSIZE_MODE,mode);
                        if(prev!=this.AutoSizeMode() && this.ElementsTotal()>0)
                           this.AutoSizeProcess(redraw);
                       }
   ENUM_CANV_ELEMENT_AUTO_SIZE_MODE AutoSizeMode(void)   const { return (ENUM_CANV_ELEMENT_AUTO_SIZE_MODE)this.GetProperty(CANV_ELEMENT_PROP_AUTOSIZE_MODE); }
   
//--- (1) Set and (2) return the mode of binding element borders to the container
   virtual void      SetDockMode(const ENUM_CANV_ELEMENT_DOCK_MODE mode,const bool redraw)
                       {
                        if(this.DockMode()==mode)
                           return;
                        CWinFormBase::SetDockMode(mode,redraw);
                        CContainer *base=this.GetBase();
                        if(base!=NULL)
                           base.ArrangeObjects(redraw);
                       }

//--- Set the width of the form frame (1) to the left, (2) at the top, (3) to the right, (4) at the bottom and (5) on all sides of the control
   virtual void      SetBorderSizeLeft(const uint value)
                       {
                        CForm::SetBorderSizeLeft(value);
                        if(this.PaddingLeft()<this.BorderSizeLeft())
                           this.SetPaddingLeft(this.BorderSizeLeft());
                       }
   virtual void      SetBorderSizeTop(const uint value)
                       {
                        CForm::SetBorderSizeTop(value);
                        if(this.PaddingTop()<this.BorderSizeTop())
                           this.SetPaddingTop(this.BorderSizeTop());
                       }
   virtual void      SetBorderSizeRight(const uint value)
                       {
                        CForm::SetBorderSizeRight(value);
                        if(this.PaddingRight()<this.BorderSizeRight())
                           this.SetPaddingRight(this.BorderSizeRight());
                       }
   virtual void      SetBorderSizeBottom(const uint value)
                       {
                        CForm::SetBorderSizeBottom(value);
                        if(this.PaddingBottom()<this.BorderSizeBottom())
                           this.SetPaddingBottom(this.BorderSizeBottom());
                       }

//--- Constructors
                     CContainer(const long chart_id,
                                const int subwindow,
                                const string name,
                                const int x,
                                const int y,
                                const int w,
                                const int h);
                     CContainer(const string name) : CWinFormBase(::ChartID(),0,name,0,0,0,0)
                       {
                        CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_WF_CONTAINER);
                        CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_CONTAINER);
                        this.m_type=OBJECT_DE_TYPE_GWF_CONTAINER; 
                        this.SetForeColor(CLR_DEF_FORE_COLOR,true);
                        this.SetFontBoldType(FW_TYPE_NORMAL);
                        this.SetMarginAll(3);
                        this.SetPaddingAll(0);
                        this.SetDockMode(CANV_ELEMENT_DOCK_MODE_NONE,false);
                        this.SetBorderStyle(FRAME_STYLE_NONE);
                        this.SetAutoScroll(false,false);
                        this.SetAutoScrollMarginAll(0);
                        this.SetAutoSize(false,false);
                        this.SetAutoSizeMode(CANV_ELEMENT_AUTO_SIZE_MODE_GROW,false);
                        this.Initialize();
                       }
//--- Destructor
                    ~CContainer();
  };
//+------------------------------------------------------------------+
//| Constructor indicating the chart and subwindow ID                |
//+------------------------------------------------------------------+
CContainer::CContainer(const long chart_id,
                       const int subwindow,
                       const string name,
                       const int x,
                       const int y,
                       const int w,
                       const int h) : CWinFormBase(chart_id,subwindow,name,x,y,w,h)
  {
   CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_WF_CONTAINER);
   CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_CONTAINER);
   this.m_type=OBJECT_DE_TYPE_GWF_CONTAINER;
   this.SetForeColor(CLR_DEF_FORE_COLOR,true);
   this.SetFontBoldType(FW_TYPE_NORMAL);
   this.SetMarginAll(3);
   this.SetPaddingAll(0);
   this.SetDockMode(CANV_ELEMENT_DOCK_MODE_NONE,false);
   this.SetBorderStyle(FRAME_STYLE_NONE);
   this.SetAutoScroll(false,false);
   this.SetAutoScrollMarginAll(0);
   this.SetAutoSize(false,false);
   this.SetAutoSizeMode(CANV_ELEMENT_AUTO_SIZE_MODE_GROW,false);
   this.Initialize();
   this.SetCoordXInit(x);
   this.SetCoordYInit(y);
   this.SetWidthInit(w);
   this.SetHeightInit(h);
  }
//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CContainer::~CContainer()
  {
   CForm::Deinitialize();
  }
//+------------------------------------------------------------------+
//| Create a new attached element                                    |
//+------------------------------------------------------------------+
bool CContainer::CreateNewElement(const ENUM_GRAPH_ELEMENT_TYPE element_type,
                                  const int x,
                                  const int y,
                                  const int w,
                                  const int h,
                                  const color colour,
                                  const uchar opacity,
                                  const bool activity,
                                  const bool redraw)
  {
//--- If the object type is less than the base WinForms object
   if(element_type<GRAPH_ELEMENT_TYPE_WF_BASE)
     {
      //--- report the error and return 'false'
      CMessage::ToLog(DFUN,MSG_PANEL_OBJECT_ERR_OBJ_MUST_BE_WFBASE);
      return false;
     }
//--- If failed to create a new graphical element, return 'false'
   CWinFormBase *obj=CForm::CreateAndAddNewElement(element_type,x,y,w,h,colour,opacity,activity);
   if(obj==NULL)
      return false;
//--- Set parameters for the created object
   this.SetObjParams(obj,colour);
//--- If the panel has auto resize enabled and features bound objects, call the resize method
   if(this.AutoSize() && this.ElementsTotal()>0)
      this.AutoSizeProcess(redraw);
//--- Redraw the panel and all added objects, and return 'true'
   this.Redraw(redraw);
   return true;
  }
//+------------------------------------------------------------------+
//| Set parameters for the attached object                           |
//+------------------------------------------------------------------+
void CContainer::SetObjParams(CWinFormBase *obj,const color colour)
  {
//--- Set the text color of the object to be the same as that of the base container
   obj.SetForeColor(this.ForeColor(),true);
//--- If the created object is not a container, set the same group for it as the one for its base object
   if(obj.TypeGraphElement()<GRAPH_ELEMENT_TYPE_WF_CONTAINER || obj.TypeGraphElement()>GRAPH_ELEMENT_TYPE_WF_GROUPBOX)
      obj.SetGroup(this.Group());
//--- Depending on the object type
   switch(obj.TypeGraphElement())
     {
      //--- For the Container, Panel and GroupBox WinForms objects
      case GRAPH_ELEMENT_TYPE_WF_CONTAINER         :
      case GRAPH_ELEMENT_TYPE_WF_PANEL             :
      case GRAPH_ELEMENT_TYPE_WF_GROUPBOX          :
        //--- set the frame color equal to the background color 
        obj.SetBorderColor(obj.BackgroundColor(),true);
        break;
      //--- For "Label", "CheckBox" and "RadioButton" WinForms objects
      case GRAPH_ELEMENT_TYPE_WF_LABEL             :
      case GRAPH_ELEMENT_TYPE_WF_CHECKBOX          :
      case GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON       :
        //--- set the object text color depending on the one passed to the method:
        //--- either the container text color, or the one passed to the method.
        //--- The frame color is set equal to the text color
        //--- Set the background color to transparent
        obj.SetForeColor(colour==clrNONE ? this.ForeColor() : colour,true);
        obj.SetBorderColor(obj.ForeColor(),true);
        obj.SetBackgroundColor(CLR_CANV_NULL,true);
        obj.SetOpacity(0,false);
        break;
      //--- For the Button WinForms object
      case GRAPH_ELEMENT_TYPE_WF_BUTTON            :
        //--- set the object text color as a container text color depending on the one passed to the method:
        //--- set the background color depending on the one passed to the method:
        //--- either the default standard control background color, or the one passed to the method.
        //--- The frame color is set equal to the text color
        obj.SetForeColor(this.ForeColor(),true);
        obj.SetBackgroundColor(colour==clrNONE ? CLR_DEF_CONTROL_STD_BACK_COLOR : colour,true);
        obj.SetBorderColor(obj.ForeColor(),true);
        obj.SetBorderStyle(FRAME_STYLE_SIMPLE);
        break;
      //--- For "ListBox", "CheckedListBox" and "ButtonListBox" WinForms object
      case GRAPH_ELEMENT_TYPE_WF_LIST_BOX          :
      case GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX  :
      case GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX   :
        //--- set the object text color as a container text color depending on the one passed to the method:
        //--- set the background color depending on the one passed to the method:
        //--- either the default standard control background color, or the one passed to the method.
        //--- The frame color is set equal to the text color
        obj.SetBackgroundColor(colour==clrNONE ? CLR_DEF_CONTROL_STD_BACK_COLOR : colour,true);
        obj.SetBorderColor(CLR_DEF_BORDER_COLOR,true);
        obj.SetForeColor(CLR_DEF_FORE_COLOR,true);
        break;
      default:
        break;
     }
  }
//+------------------------------------------------------------------+
//| Reset the size of all bound objects to the initial ones          |
//+------------------------------------------------------------------+
bool CContainer::ResetSizeAllToInit(void)
  {
   bool res=true;
   CArrayObj *list=this.GetListWinFormsObj();
   if(list==NULL)
      return false;
   for(int i=0;i<list.Total();i++)
     {
      CWinFormBase *obj=list.At(i);
      if(obj==NULL)
        {
         res &=false;
         continue;
        }
      res &=obj.Resize(i,obj.GetWidthInit(),obj.GetHeightInit());
     }
   return res;
  }
//+------------------------------------------------------------------+
//| Adjust the element size to fit its content                       |
//+------------------------------------------------------------------+
bool CContainer::AutoSizeProcess(const bool redraw)
  {
//--- Get the list of bound objects with WinForms type basic and higher
   CArrayObj *list=this.GetListWinFormsObj();
   int maxcX=0;
   int maxcY=0;
//--- Calculate the maximum coordinate of the right and bottom edge from all bound objects
   for(int i=0;i<list.Total();i++)
     {
      CWinFormBase *obj=list.At(i);
      if(obj==NULL)
         continue;
      if(obj.RightEdge()>maxcX)
         maxcX=obj.RightEdge();
      if(obj.BottomEdge()>maxcY)
         maxcY=obj.BottomEdge();
     }
//--- Calculate the required width and height of the panel after adjusting its size to the content
   int w=maxcX-this.CoordX();
   int h=maxcY-this.CoordY();
//--- Calculate the number of pixels, by which we need to resize the container in width and height
   int excess_x=w-this.GetWidthWorkspace()-this.BorderSizeRight()-1;
   int excess_y=h-this.GetHeightWorkspace()-this.BorderSizeBottom()-1;
//--- If failed to change the container size, return 'true'
   if(excess_x==0 && excess_y==0)
      return true;
//--- Return the result of resizing the container
   return
     (
      //--- In case of size increase only
      this.AutoSizeMode()==CANV_ELEMENT_AUTO_SIZE_MODE_GROW ? 
      this.Resize(this.Width()+(excess_x>0  ? excess_x : 0),this.Height()+(excess_y>0  ? excess_y : 0),redraw) :
      //--- if both increase and decrease
      this.Resize(this.Width()+(excess_x!=0 ? excess_x : 0),this.Height()+(excess_y!=0 ? excess_y : 0),redraw)
     );
  }
//+------------------------------------------------------------------+
//| Place bound objects in the order of their Dock binding           |
//+------------------------------------------------------------------+
bool CContainer::ArrangeObjects(const bool redraw)
  {
//--- Get the list of bound objects with WinForms type basic and higher
   CArrayObj *list=this.GetListWinFormsObj();
   CWinFormBase *prev=NULL, *obj=NULL, *elm=NULL;
//--- In the loop by all bound objects,
   for(int i=0;i<list.Total();i++)
     {
      //--- Get the current and previous elements from the list
      obj=list.At(i);
      prev=list.At(i-1);
      //--- If the object is not received, move on
      if(obj==NULL)
         continue;
      int x=0, y=0; // Object binding coordinates
      //--- Depending on the current object binding mode...
      //--- Top
      if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_TOP)
        {
         //--- If failed to change the object size (for the entire working area width and by the initial object height), move on to the next one
         if(!obj.Resize(this.GetWidthWorkspace(),obj.GetHeightInit(),false))
            continue;
         //--- Get the object binding coordinates
         x=this.GetCoordXWorkspace();
         y=(prev!=NULL ? prev.BottomEdge()+1 : this.GetCoordYWorkspace());
         //--- If failed to move the object to the obtained coordinates, move on to the next one
         if(!obj.Move(x,y,false))
            continue;
        }
      //--- Bottom
      if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_BOTTOM)
        {
         //--- If failed to change the object size (for the entire working area width and by the initial object height), move on to the next one
         if(!obj.Resize(this.GetWidthWorkspace(),obj.GetHeightInit(),false))
            continue;
         //--- Get the object binding coordinates
         x=this.GetCoordXWorkspace();
         y=(prev!=NULL ? prev.CoordY()-obj.Height()-1 : this.GetBottomEdgeWorkspace()-obj.Height()-1);
         //--- If failed to move the object to the obtained coordinates, move on to the next one
         if(!obj.Move(x,y,false))
            continue;
        }
      //--- Left
      if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_LEFT)
        {
         //--- If failed to change the object size (for the initial object width and the entire working area height), move on to the next one
         if(!obj.Resize(obj.GetWidthInit(),this.GetHeightWorkspace(),false))
            continue;
         //--- Get the object binding coordinates
         x=(prev!=NULL ? prev.RightEdge()+1 : this.GetCoordXWorkspace());
         y=this.GetCoordYWorkspace();
         //--- If failed to move the object to the obtained coordinates, move on to the next one
         if(!obj.Move(x,y,false))
            continue;
        }
      //--- Right
      if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_RIGHT)
        {
         //--- If failed to change the object size (for the initial object width and the entire working area height), move on to the next one
         if(!obj.Resize(obj.GetWidthInit(),this.GetHeightWorkspace(),false))
            continue;
         //--- Get the object binding coordinates
         x=(prev!=NULL ? prev.CoordX()-obj.Width()-1 : this.GetRightEdgeWorkspace()-obj.Width());
         y=this.GetCoordYWorkspace();
         //--- If failed to move the object to the obtained coordinates, move on to the next one
         if(!obj.Move(x,y,false))
            continue;
        }
      //--- Binding with filling
      if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_FILL)
        {
         //--- If failed to change the object size (for the entire working area width and height), move on to the next one
         if(!obj.Resize(this.GetWidthWorkspace(),this.GetHeightWorkspace(),false))
            continue;
         //--- Get the object binding coordinates
         x=this.GetCoordXWorkspace();
         y=this.GetCoordYWorkspace();
         //--- If failed to move the object to the obtained coordinates, move on to the next one
         if(!obj.Move(x,y,false))
            continue;
        }
      //--- No binding
      if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_NONE)
        {
         //--- Reset the object size
         obj.Resize(obj.GetWidthInit(),obj.GetHeightInit(),false);
         //--- Get the initial object location coordinates
         x=this.GetCoordXWorkspace()+obj.CoordXRelativeInit();
         y=this.GetCoordYWorkspace()+obj.CoordYRelativeInit();
         //--- If failed to move the object to the obtained coordinates, move on to the next one
         if(!obj.Move(x,y,false))
            continue;
        }
      //--- Calculate and set the relative object coordinates
      obj.SetCoordXRelative(x-this.CoordX());
      obj.SetCoordYRelative(y-this.CoordY());
     }

//--- If auto resizing mode is enabled
   if(this.AutoSize())
      this.AutoSizeProcess(false);

//--- Redraw the object with the redraw flag and return 'true'
   this.Redraw(redraw); 
   return true;
  }
//+------------------------------------------------------------------+
//| Return the list of bound objects                                 |
//| of any WinForms base type and higher                             |
//+------------------------------------------------------------------+
CArrayObj *CContainer::GetListWinFormsObj(void)
  {
   return CSelect::ByGraphCanvElementProperty(this.GetListElements(),CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_BASE,EQUAL_OR_MORE);
  }
//+------------------------------------------------------------------+
//| Return the list of bound objects                                 |
//| with the specified WinForms object type                          |
//+------------------------------------------------------------------+
CArrayObj *CContainer::GetListWinFormsObjByType(const ENUM_GRAPH_ELEMENT_TYPE type)
  {
   return CSelect::ByGraphCanvElementProperty(this.GetListElements(),CANV_ELEMENT_PROP_TYPE,type,EQUAL);
  }
//+------------------------------------------------------------------+
//| Return the pointer to the specified WinForms object              |
//| with the specified type by index                                 |
//+------------------------------------------------------------------+
CWinFormBase *CContainer::GetWinFormsObj(const ENUM_GRAPH_ELEMENT_TYPE type,const int index)
  {
   CArrayObj *list=this.GetListWinFormsObjByType(type);
   return(list!=NULL ? list.At(index) : NULL);
  }
//+------------------------------------------------------------------+
//| Calculate Dock objects' binding coordinates                      |
//+------------------------------------------------------------------+
void CContainer::CalculateCoords(CArrayObj *list)
  {
   
  }
//+------------------------------------------------------------------+
