//+------------------------------------------------------------------+
//|                                                  CDragHandle.mqh |
//|            https://www.mql5.com/en/users/alireza.saeedian/seller |
//+------------------------------------------------------------------+
#property link      "https://www.mql5.com/en/users/alireza.saeedian/seller"
//+------------------------------------------------------------------+
//| adding chart objects classes as an include file                  |
//+------------------------------------------------------------------+
#include <ChartObjects\ChartObjectsTxtControls.mqh> // chart objects text control module
//+------------------------------------------------------------------+
//| defines                                                          |
//+------------------------------------------------------------------+
#define MIN_OBJECT_WIDTH  200
#define MAX_OBJECT_WIDTH  400
#define MIN_OBJECT_HEIGHT 75
#define MAX_OBJECT_HEIGHT 150
#define MAX_DIGITS        8
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class CDragHandle
  {
private:
   CChartObjectRectLabel m_Background;   //background object
   CChartObjectLabel     m_Name;         //name object
   CChartObjectRectLabel m_Line;         //line object
   CChartObjectRectLabel m_Handle;       //handle object
   CChartObjectLabel     m_Minimum;      //minimum object
   CChartObjectLabel     m_Maximum;      //maximum object
   CChartObjectEdit      m_Current;      //current value object

   bool                  m_created;      //if the object is created or not

   int                   m_Last_X;       //last x coordinate of mouse

   int                   m_X;            //x coordinates
   int                   m_Y;            //y coordinates
   int                   m_W;            //width size
   int                   m_H;            //height size

   string                m_name;         //name of object value
   double                m_minimum;      //minimum value
   double                m_maximum;      //maximum value
   double                m_value;        //value of the object
   int                   m_digits;       //number of digits for showing min max and current value
   string                m_prefix;       //unique object name

   color                 m_BackColor;    //background of all objects
   color                 m_TextColor;    //color of all the texts
   color                 m_HandleColor;  //handle color
   color                 m_LineColor;    //line color
public:
                     CDragHandle(void);                                          //constructor
                    ~CDragHandle(void);                                          //destructor
   void              Dimensions(int x, int y, int w, int h);                     //setting dimensions
   void              SetPrefix(string str);                                      //setting prefix
   void              SetDigits(int digits);                                      //setting digits
   void              SetName(string str);                                        //setting object name
   void              SetRange(double min, double max);                           //setting min and max value
   void              SetValue(double val);                                       //set the current value
   void              SetColor(color back, color text, color line, color handle); //set colors
   double            GetValue(void);                                             //get last current value
   bool              Create(void);                                               //create dragging object on the chart
   bool              Destroy(void);                                              //destroying all the parts of the object
   void              RefreshValues(void);                                        //refresh the values based on class variables
   void              RefreshNames(void);                                         //refresh the names based on class variables
   void              RefreshColors(void);                                        //refresh the colors based on class variables
   void              RefreshSizes(void);                                         //refresh the sizes based on class variables
   bool              ValidateRange(double min, double max);                      //Checking whether the values are meaningful
   void              EventHandle(const int32_t id, const long &lparam,
                                 const double &dparam, const string &sparam);    //Event Handler Function
   void              UpdateHandlePosition(void);                                 //move the handle based on the current value
  };
//+------------------------------------------------------------------+
//| constructor                                                      |
//+------------------------------------------------------------------+
CDragHandle::CDragHandle(void)
  {
   m_created       = false;
   m_Last_X        = -1;
   m_X             = 100;
   m_Y             = 100;
   m_W             = 200;
   m_H             = 75;
   m_name          = "Drag Object";
   m_minimum       = 0;
   m_maximum       = 10;
   m_value         = 0;
   m_digits        = 0;
   m_prefix        = "001";
   m_BackColor     = clrBisque;
   m_TextColor     = clrBlack;
   m_HandleColor   = clrSilver;
   m_LineColor     = clrLightBlue;
  }
//+------------------------------------------------------------------+
//| destructor                                                       |
//+------------------------------------------------------------------+
CDragHandle::~CDragHandle(void)
  {
   Destroy();
  }
//+------------------------------------------------------------------+
//| setting dimensions based on inputs and defined limits            |
//+------------------------------------------------------------------+
void CDragHandle::Dimensions(int x, int y, int w, int h)
  {
//--- checking the chart height and width
   int Chart_Width  = (int)ChartGetInteger(ChartID(), CHART_WIDTH_IN_PIXELS);
   if(Chart_Width<=0)
      Chart_Width  = 1920;
   int Chart_Height = (int)ChartGetInteger(ChartID(), CHART_HEIGHT_IN_PIXELS);
   if(Chart_Height<=0)
      Chart_Height = 1080;
//---
   if(w<=MAX_OBJECT_WIDTH && w>=MIN_OBJECT_WIDTH)
      m_W = w;
   else
      m_W = MIN_OBJECT_WIDTH;
//---
   if(h<=MAX_OBJECT_HEIGHT && h>=MIN_OBJECT_HEIGHT)
      m_H = h;
   else
      m_H = MIN_OBJECT_HEIGHT;
//---
   if(x>=0 && x<=Chart_Width-m_W)
      m_X = x;
   else
      m_X = 0;
//---
   if(y>=0 && y<=Chart_Height-m_H)
      m_Y = y;
   else
      m_Y = 0;
//---
   RefreshSizes();
  }
//+------------------------------------------------------------------+
//| set the prefix                                                   |
//+------------------------------------------------------------------+
void CDragHandle::SetPrefix(string str)
  {
//--- setting the prefix variable
   if(str=="")
      m_prefix="001";
   else
      m_prefix=str;
//---
   RefreshNames();
  }
//+------------------------------------------------------------------+
//| set the digits                                                   |
//+------------------------------------------------------------------+
void CDragHandle::SetDigits(int digits)
  {
   if(digits<0)
      m_digits = 0;
   else
      if(digits>MAX_DIGITS)
         m_digits = MAX_DIGITS;
      else
         m_digits = digits;
//---
   RefreshValues();
  }
//+------------------------------------------------------------------+
//| set the object name                                              |
//+------------------------------------------------------------------+
void CDragHandle::SetName(string str)
  {
   if(str=="")
      m_name="Drag Object";
   else
      m_name=str;
//---
   if(m_created)
      m_Name.Description(m_name);
  }
//+------------------------------------------------------------------+
//| set the range - min - max                                        |
//+------------------------------------------------------------------+
void CDragHandle::SetRange(double min, double max)
  {
//---checking if the min and the max value are meaningful or not
   if(ValidateRange(min, max))
     {
      //--- setting the maximum and the minimum
      m_minimum = NormalizeDouble(min,m_digits);
      m_maximum = NormalizeDouble(max,m_digits);
      //--- the value should be
      if(m_value<m_minimum)
         m_value = m_minimum;
      if(m_value>m_maximum)
         m_value = m_maximum;
      //--- refresh texts and also handle position
      RefreshValues();
      UpdateHandlePosition();
     }
  }
//+------------------------------------------------------------------+
//| set the current value                                            |
//+------------------------------------------------------------------+
void CDragHandle::SetValue(double val)
  {
   if(ValidateRange(m_minimum, m_maximum))
     {
      //--- the value should be between minimum and maximum
      if(val<m_minimum)
         m_value = m_minimum;
      else
         if(val>m_maximum)
            m_value = m_maximum;
         else
            m_value = NormalizeDouble(val,m_digits);
      //--- refresh texts and also handle position
      RefreshValues();
      UpdateHandlePosition();
     }
  }
//+------------------------------------------------------------------+
//| set the colors                                                   |
//+------------------------------------------------------------------+
void CDragHandle::SetColor(color back, color text, color line, color handle)
  {
//--- changing colors
   m_BackColor   = back;
   m_TextColor   = text;
   m_LineColor   = line;
   m_HandleColor = handle;
//--- changing colors
   RefreshColors();
  }
//+------------------------------------------------------------------+
//| get the last current value                                       |
//+------------------------------------------------------------------+
double CDragHandle::GetValue(void)
  {
   return(m_value);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CDragHandle::Create(void)
  {
//--- return if objects are created before
   if(m_created)
      return(false);
   bool result = true;
//--- 1. create the background
   if(!m_Background.Create(ChartID(), m_prefix+"-DraggingHandle-Background", 0, m_X, m_Y, m_W, m_H))
      result = false;
   else
     {
      m_Background.Color(m_BackColor);
      m_Background.BackColor(m_BackColor);
     }
//--- 2. create the name object and properties
   if(!m_Name.Create(ChartID(), m_prefix+"-DraggingHandle-Name", 0, m_X+5, m_Y+5))
      result = false;
   else
     {
      m_Name.Color(m_TextColor);
      m_Name.Description(m_name);
     }
//--- 3. create the line object and properties
   if(!m_Line.Create(ChartID(), m_prefix+"-DraggingHandle-Line", 0, m_X+5, m_Y+5+20+10, m_W-10, 5))
      result = false;
   else
     {
      m_Line.BackColor(m_LineColor);
      m_Line.Color(m_BackColor);
     }
//--- 4. create the handle object and properties
   if(!m_Handle.Create(ChartID(), m_prefix+"-DraggingHandle-Handle", 0, m_X+5+((m_W-10)/2)-3, m_Y+30, 6, 15))
      result = false;
   else
     {
      m_Handle.BackColor(m_HandleColor);
      m_Handle.Color(m_BackColor);
     }
//--- 5. create the minimum object and properties
   if(!m_Minimum.Create(ChartID(), m_prefix+"-DraggingHandle-Minimum", 0, m_X+5, m_Y+50))
      result = false;
   else
     {
      m_Minimum.Color(m_TextColor);
      m_Minimum.Description(DoubleToString(m_minimum, m_digits));
     }
//--- 6. create the current value object and its properties
   if(!m_Current.Create(ChartID(), m_prefix+"-DraggingHandle-Current", 0, m_X+5+(1*(m_W-10)/3), m_Y+50, (m_W-10)/3, 20))
      result = false;
   else
     {
      m_Current.BackColor(m_BackColor);
      m_Current.BorderColor(m_BackColor);
      m_Current.Color(m_TextColor);
      m_Current.TextAlign(ALIGN_CENTER);
      m_Current.Description(DoubleToString(m_value, m_digits));
      m_Current.ReadOnly(false);
     }
//--- 7. create the maximum object and properties
   if(!m_Maximum.Create(ChartID(), m_prefix+"-DraggingHandle-Maximum", 0, m_X+5+(2*(m_W-10)/3)+50, m_Y+50))
      result = false;
   else
     {
      m_Maximum.Color(m_TextColor);
      m_Maximum.Description(DoubleToString(m_maximum, m_digits));
     }
//--- if all objects are created successfully
   if(result)
     {
      m_created=true;
      UpdateHandlePosition();
      return(true);
     }
//--- if all the objects aren't created successfully
   else
     {
      Destroy();
      return(false);
     }
  }
//+------------------------------------------------------------------+
//| Destroy all the objects                                          |
//+------------------------------------------------------------------+
bool CDragHandle::Destroy(void)
  {
   bool deleted = true;
//--- deleting all the objects one by one
   if(!m_Background.Delete())
      deleted = false;
   if(!m_Name.Delete())
      deleted = false;
   if(!m_Line.Delete())
      deleted = false;
   if(!m_Handle.Delete())
      deleted = false;
   if(!m_Minimum.Delete())
      deleted = false;
   if(!m_Current.Delete())
      deleted = false;
   if(!m_Maximum.Delete())
      deleted = false;
   m_created = false;
   m_Last_X   = -1;
//--- checking if all the objects are deleted
   if(deleted)
     {
      ChartSetInteger(ChartID(), CHART_MOUSE_SCROLL, true);
      return(true);
     }
   else
     {
      return(false);
     }
  }
//+------------------------------------------------------------------+
//| refresh the values                                               |
//+------------------------------------------------------------------+
void CDragHandle::RefreshValues(void)
  {
//--- if objects are created before
   if(m_created)
     {
      m_Minimum.Description(DoubleToString(m_minimum, m_digits)); //reset minimum
      m_Maximum.Description(DoubleToString(m_maximum, m_digits)); //reset maximum
      m_Current.Description(DoubleToString(m_value, m_digits));   //reset current value
      ChartRedraw();
     }
  }
//+------------------------------------------------------------------+
//| refresh the value of minimum, maximum and current value          |
//+------------------------------------------------------------------+
void CDragHandle::RefreshNames(void)
  {
   if(!m_created)
      return;
   string Name_Parts[];
//--- resetting the background object name
   StringSplit(m_Background.Name(), '-', Name_Parts);
   if(ArraySize(Name_Parts)>=3)
      m_Background.Name(m_prefix+"-"+Name_Parts[1]+"-"+Name_Parts[2]);
   ArrayFree(Name_Parts);
//--- resetting the Name object name
   StringSplit(m_Name.Name(), '-', Name_Parts);
   if(ArraySize(Name_Parts)>=3)
      m_Name.Name(m_prefix+"-"+Name_Parts[1]+"-"+Name_Parts[2]);
   ArrayFree(Name_Parts);
//--- resetting the line object name
   StringSplit(m_Line.Name(), '-', Name_Parts);
   if(ArraySize(Name_Parts)>=3)
      m_Line.Name(m_prefix+"-"+Name_Parts[1]+"-"+Name_Parts[2]);
   ArrayFree(Name_Parts);
//--- resetting the handle object name
   StringSplit(m_Handle.Name(), '-', Name_Parts);
   if(ArraySize(Name_Parts)>=3)
      m_Handle.Name(m_prefix+"-"+Name_Parts[1]+"-"+Name_Parts[2]);
   ArrayFree(Name_Parts);
//--- resetting the minimum object name
   StringSplit(m_Minimum.Name(), '-', Name_Parts);
   if(ArraySize(Name_Parts)>=3)
      m_Minimum.Name(m_prefix+"-"+Name_Parts[1]+"-"+Name_Parts[2]);
   ArrayFree(Name_Parts);
//--- resetting the maximum object name
   StringSplit(m_Maximum.Name(), '-', Name_Parts);
   if(ArraySize(Name_Parts)>=3)
      m_Maximum.Name(m_prefix+"-"+Name_Parts[1]+"-"+Name_Parts[2]);
   ArrayFree(Name_Parts);
//--- resetting the current object name
   StringSplit(m_Current.Name(), '-', Name_Parts);
   if(ArraySize(Name_Parts)>=3)
      m_Current.Name(m_prefix+"-"+Name_Parts[1]+"-"+Name_Parts[2]);
   ArrayFree(Name_Parts);
  }
//+------------------------------------------------------------------+
//| refresh the value of minimum, maximum and current value          |
//+------------------------------------------------------------------+
void CDragHandle::RefreshSizes(void)
  {
//--- return if objects are not created
   if(!m_created)
      return;
//---the background object
   m_Background.X_Distance(m_X);
   m_Background.Y_Distance(m_Y);
   m_Background.X_Size(m_W);
   m_Background.Y_Size(m_H);
//---the name object
   m_Name.X_Distance(m_X+5);
   m_Name.Y_Distance(m_Y+5);
//---the line object
   m_Line.X_Distance(m_X+5);
   m_Line.Y_Distance(m_Y+5+20+10);
   m_Line.X_Size(m_W-10);
   m_Line.Y_Size(5);
//---the handle object
   m_Handle.X_Distance(m_X+5);
   m_Handle.Y_Distance(m_Y+30);
   m_Handle.X_Size(6);
   m_Handle.Y_Size(15);
//---the minimum object
   m_Minimum.X_Distance(m_X+5);
   m_Minimum.Y_Distance(m_Y+50);
//---the current value object
   m_Current.X_Distance(m_X+5+(1*(m_W-10)/3));
   m_Current.Y_Distance(m_Y+50);
   m_Current.X_Size((m_W-10)/3);
   m_Current.Y_Size(20);
//---the maximum object
   m_Maximum.X_Distance(m_X+5+(2*(m_W-10)/3)+50);
   m_Maximum.Y_Distance(m_Y+50);
//---
   UpdateHandlePosition(); //we should update the handle position based on last sizes and values
  }
//+------------------------------------------------------------------+
//| Renewing colors based on class variables                         |
//+------------------------------------------------------------------+
void CDragHandle::RefreshColors(void)
  {
//---
   if(!m_created)
      return;
//--- the background object
   m_Background.Color(m_BackColor);
   m_Background.BackColor(m_BackColor);
//--- the name object
   m_Name.Color(m_TextColor);
//--- the line object
   m_Line.BackColor(m_LineColor);
   m_Line.Color(m_BackColor);
//--- the handle object
   m_Handle.BackColor(m_HandleColor);
   m_Handle.Color(m_BackColor);
//--- the minimum object
   m_Minimum.Color(m_TextColor);
//--- the current value object
   m_Current.BackColor(m_BackColor);
   m_Current.BorderColor(m_BackColor);
   m_Current.Color(m_TextColor);
//--- the maximum object
   m_Maximum.Color(m_TextColor);
   ChartRedraw(); //redraw the chart to reset the colors
  }
//+------------------------------------------------------------------+
//| Validating the minimum and maximum values                        |
//+------------------------------------------------------------------+
bool CDragHandle::ValidateRange(double min, double max)
  {
   if(min>=max)
     {
      Print("minimum is greater or equal to maximum");
      return(false);
     }
   else
     {
      return(true);
     }
  }
//+------------------------------------------------------------------+
//| Object Event Function                                            |
//+------------------------------------------------------------------+
void CDragHandle::EventHandle(const int id, const long &lparam, const double &dparam, const string &sparam)
  {
//--- check wether object is created before or not
   if(!m_created)
      return;
//--- no drag when we are on the object
   if(id==CHARTEVENT_MOUSE_MOVE) //if id is object move
     {
      //--- if the object is in the borders of background object
      if(lparam>=m_Background.X_Distance() && lparam<=m_Background.X_Distance()+m_Background.X_Size() &&
         dparam>=m_Background.Y_Distance() && dparam<=m_Background.Y_Distance()+m_Background.Y_Size())
        {
         ChartSetInteger(ChartID(), CHART_MOUSE_SCROLL, false);
        }
      else //we should turn on the scroll if the mouse is out of the object borders and we are not dragging it
         if(m_Last_X==-1)
           {
            ChartSetInteger(ChartID(), CHART_MOUSE_SCROLL, true);
           }
     }
//--- if we change the value of current value
   if(id==CHARTEVENT_OBJECT_ENDEDIT && sparam==m_Current.Name()) //if we end editing the current value object
     {
      //--- checking if the edit box is numeric value
      double value_1 = 0;
      if(!SetNumericValue(m_Current.Description(), value_1))
        {
         m_Current.Description(DoubleToString(m_value, m_digits));
         Print("enter a numeric value!");
         ChartRedraw();
         return;
        }
      //--- checking minimum and maximum
      if(!ValidateRange(m_minimum, m_maximum))
         return;
      //--- checking if the current value is between minimum and maximum
      if(value_1<=m_minimum)
         value_1 = m_minimum;
      if(value_1>=m_maximum)
         value_1 = m_maximum;
      m_value = NormalizeDouble(value_1, m_digits); //setting the current value variable again
      UpdateHandlePosition();
     }
//--- if drag the handle
   if(id==CHARTEVENT_MOUSE_MOVE) //if id is equal to object move
     {
      if(sparam=="1" && m_Last_X==-1) //clicked and holded
        {
         //--- if we clicked and holded the mouse in the borders of handle object
         if(lparam>=m_Handle.X_Distance() && lparam<=m_Handle.X_Distance()+m_Handle.X_Size() &&
            dparam>=m_Handle.Y_Distance() && dparam<=m_Handle.Y_Distance()+m_Handle.Y_Size())
           {
            m_Last_X = (int)lparam;
           }
        }
      //--- if we click and holded the left mouse click on the handle object
      if(m_Last_X!=-1)
        {
         int dif_m_X = (int)(lparam-m_Last_X); //finding the difference
         //--- limits of moving the handle object from left
         int new_x = m_Handle.X_Distance() + dif_m_X;
         int min_x = m_Line.X_Distance() - 3;
         int max_x = m_Line.X_Distance() + m_Line.X_Size() - 3;
         if(new_x < min_x)
            new_x = min_x;
         if(new_x > max_x)
            new_x = max_x;
         m_Handle.X_Distance(new_x); //moving handle object
         double percent_1 = (double)(m_Handle.X_Distance()+3-m_Line.X_Distance())/(double)(m_Line.X_Size()); //calculating the multiplier
         double value_1   = NormalizeDouble(m_minimum+((m_maximum-m_minimum)*percent_1), m_digits); //finding the current value
         //--- checking whether the value is between min and max
         if(value_1<=m_minimum)
            value_1 = m_minimum;
         if(value_1>=m_maximum)
            value_1 = m_maximum;
         m_Current.Description(DoubleToString(value_1, m_digits)); //resetting current object text
         m_value = value_1; //resetting the value
         m_Last_X = (int)lparam; //renew last mouse position for the new move
         ChartRedraw();
        }
      //if we released the left click of the mouse and if the handle was holded
      if(sparam=="0" && m_Last_X!=-1)
        {
         m_Last_X = -1; //release the handle movement
        }
     }
  }
//+------------------------------------------------------------------+
//| replace the coordinates of the handle                            |
//+------------------------------------------------------------------+
void CDragHandle::UpdateHandlePosition(void)
  {
   if(!m_created)
      return;
//--- minimum shouldn't be less than maximum
   if(!ValidateRange(m_minimum, m_maximum))
      return;
//--- checking if the value is between minimum and maximum
   double val = m_value;
   if(val < m_minimum)
      val = m_minimum;
   if(val > m_maximum)
      val = m_maximum;
   double multiplier = (val - m_minimum) / (m_maximum - m_minimum);
   int point = m_Line.X_Distance() + (int)MathRound(m_Line.X_Size() * multiplier) - 3;
   m_Handle.X_Distance(point); //setting the handler on the right place
   ChartRedraw();
  }
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| checking numeric value in a text                                 |
//+------------------------------------------------------------------+
bool SetNumericValue(string text, double &value)
  {
   int length = StringLen(text);
// Empty string
   if(length == 0)
      return false;
   bool has_digit = false;
   bool has_separator = false;
// Check every character
   for(int i = 0; i < length; i++)
     {
      ushort c = StringGetCharacter(text, i);
      // Digit: 0-9
      if(c >= '0' && c <= '9')
        {
         has_digit = true;
         continue;
        }
      // Decimal separator: dot or comma
      if(c == '.' || c == ',')
        {
         // Only one decimal separator is allowed
         if(has_separator)
            return false;
         has_separator = true;
         continue;
        }
      // Sign is allowed only at the beginning
      if(c == '-' || c == '+')
        {
         if(i != 0)
            return false;
         continue;
        }
      // Anything else is invalid
      return false;
     }
// There must be at least one digit
   if(!has_digit)
      return false;
// Convert comma to dot for StringToDouble()
   StringReplace(text, ",", ".");
// Convert the validated text to double
   value = StringToDouble(text);
   return true;
  }
//+------------------------------------------------------------------+
