//+------------------------------------------------------------------+
//|                                                      ToolTip.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 "..\Helpers\HintBase.mqh"
//+------------------------------------------------------------------+
//| Class of the base Hint object of the WForms controls             |
//+------------------------------------------------------------------+
class CToolTip : public CHintBase
  {
private:
   int               m_step;                             // Transparency change step
//--- Adjust a tooltip size according to a text size
   void              CorrectSizeByTexts(void);
protected:
   //--- Draw (1) tooltip, (2) icon, (3) Info, (4) Warning, (5) Error and (6) User
   virtual void      DrawHint(const int shift);
   void              DrawIcon(void);
   void              DrawIconInfo(void)                  { CGCnvElement::DrawIconInfo(3,1,this.Opacity());                       }
   void              DrawIconWarning(void)               { CGCnvElement::DrawIconWarning(3,1,this.Opacity());                    }
   void              DrawIconError(void)                 { CGCnvElement::DrawIconError(3,1,this.Opacity());                      }
   virtual void      DrawIconUser(void)                  { return; }
//--- Protected constructor with object type, chart ID and subwindow
                     CToolTip(const ENUM_GRAPH_ELEMENT_TYPE type,
                              CGCnvElement *main_obj,CGCnvElement *base_obj,
                              const long chart_id,
                              const int subwindow,
                              const string descript,
                              const int x,
                              const int y,
                              const int w,
                              const int h);
public:
//--- (1) Set and (2) return the tooltip display delay
   void              SetInitialDelay(const long delay)   { this.SetProperty(CANV_ELEMENT_PROP_TOOLTIP_INITIAL_DELAY,delay);      }
   ulong             InitialDelay(void)                  { return this.GetProperty(CANV_ELEMENT_PROP_TOOLTIP_INITIAL_DELAY);     }
   
//--- (1) Set and (2) return the tooltip display duration
   void              SetAutoPopDelay(const long delay)   { this.SetProperty(CANV_ELEMENT_PROP_TOOLTIP_AUTO_POP_DELAY,delay);     }
   ulong             AutoPopDelay(void)                  { return this.GetProperty(CANV_ELEMENT_PROP_TOOLTIP_AUTO_POP_DELAY);    }
   
//--- (1) Set and (2) return the delay in a display of a new tooltip of one element
   void              SetReshowDelay(const long delay)    { this.SetProperty(CANV_ELEMENT_PROP_TOOLTIP_RESHOW_DELAY,delay);       }
   ulong             ReshowDelay(void)                   { return this.GetProperty(CANV_ELEMENT_PROP_TOOLTIP_RESHOW_DELAY);      }
   
//--- (1) Set and (2) return the flag for displaying a tooltip in an inactive window
   void              SetShowAlways(const bool flag)      { this.SetProperty(CANV_ELEMENT_PROP_TOOLTIP_SHOW_ALWAYS,flag);         }
   bool              ShowAlways(void)                    { return (bool)this.GetProperty(CANV_ELEMENT_PROP_TOOLTIP_SHOW_ALWAYS); }
   
//--- (1) Set and (2) return the type of the icon displayed in a tooltip
   void              SetIcon(const ENUM_CANV_ELEMENT_TOOLTIP_ICON ico)
                       { this.SetProperty(CANV_ELEMENT_PROP_TOOLTIP_ICON,ico);   }
   ENUM_CANV_ELEMENT_TOOLTIP_ICON Icon(void)
                       { return (ENUM_CANV_ELEMENT_TOOLTIP_ICON)this.GetProperty(CANV_ELEMENT_PROP_TOOLTIP_ICON); }
   
//--- (1) Set and (2) return the cloud tooltip flag
   void              SetBalloon(const bool flag)         { this.SetProperty(CANV_ELEMENT_PROP_TOOLTIP_IS_BALLOON,flag);          }
   bool              Balloon(void)                       { return (bool)this.GetProperty(CANV_ELEMENT_PROP_TOOLTIP_IS_BALLOON);  }
   
//--- (1) Set and (2) return the flag of fading when showing/hiding a tooltip
   void              SetUseFading(const bool flag)       { this.SetProperty(CANV_ELEMENT_PROP_TOOLTIP_USE_FADING,flag);          }
   bool              UseFading(void)                     { return (bool)this.GetProperty(CANV_ELEMENT_PROP_TOOLTIP_USE_FADING);  }
   
//--- (1) Set and (2) return the Tooltip title
   void              SetTitle(const string header);
   string            Title(void)                         { return this.GetProperty(CANV_ELEMENT_PROP_TOOLTIP_TITLE);             }
   
//--- (1,2) Set the Tooltip text
   virtual void      SetTooltipText(const string text);
   virtual void      SetText(const string text)          { this.SetTooltipText(text);                                            }
   
//--- Constructor
                     CToolTip(CGCnvElement *main_obj,CGCnvElement *base_obj,
                              const long chart_id,
                              const int subwindow,
                              const string descript,
                              const int x,
                              const int y,
                              const int w,
                              const int h);
//--- Display the element
   virtual void      Show(void);
//--- Redraw the object
   virtual void      Redraw(bool redraw);
//--- Clear the element filling it with color and opacity
   virtual void      Erase(const color colour,const uchar opacity,const bool redraw=false);
//--- Clear the element with a gradient fill
   virtual void      Erase(color &colors[],const uchar opacity,const bool vgradient,const bool cycle,const bool redraw=false);
//--- Draw the hint frame
   virtual void      DrawFrame(void);
//--- Initialize the variables
   virtual void      Initialize(void);
//--- Timer
   virtual void      OnTimer(void);
  };
//+------------------------------------------------------------------+
//| Protected constructor with an object type,                       |
//| chart ID and subwindow                                           |
//+------------------------------------------------------------------+
CToolTip::CToolTip(const ENUM_GRAPH_ELEMENT_TYPE type,
                   CGCnvElement *main_obj,CGCnvElement *base_obj,
                   const long chart_id,
                   const int subwindow,
                   const string descript,
                   const int x,
                   const int y,
                   const int w,
                   const int h) : CHintBase(type,main_obj,base_obj,chart_id,subwindow,descript,x,y,w,h)
  {
//--- Set the specified graphical element type for the object and assign the library object type to the current object
   this.SetTypeElement(type);
   this.m_type=OBJECT_DE_TYPE_GWF_COMMON;
   this.Initialize();
   this.m_step=(int)::floor(DEF_CONTROL_PROCESS_DURATION/COLLECTION_GRAPH_ELM_COUNTER_STEP);
  }
//+------------------------------------------------------------------+
//| Constructor indicating the main and base objects,                |
//| chart ID and subwindow                                           |
//+------------------------------------------------------------------+
CToolTip::CToolTip(CGCnvElement *main_obj,CGCnvElement *base_obj,
                   const long chart_id,
                   const int subwindow,
                   const string descript,
                   const int x,
                   const int y,
                   const int w,
                   const int h) : CHintBase(GRAPH_ELEMENT_TYPE_WF_TOOLTIP,main_obj,base_obj,chart_id,subwindow,descript,x,y,w,h)
  {
   this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_TOOLTIP);
   this.m_type=OBJECT_DE_TYPE_GWF_COMMON;
   this.Initialize();
   this.m_step=(int)::floor(DEF_CONTROL_PROCESS_DURATION/COLLECTION_GRAPH_ELM_COUNTER_STEP);
  }
//+------------------------------------------------------------------+
//| Initialize the variables                                         |
//+------------------------------------------------------------------+
void CToolTip::Initialize(void)
  {
   this.SetBackgroundColor(CLR_DEF_CONTROL_HINT_BACK_COLOR,true);
   this.SetBorderColor(CLR_DEF_CONTROL_HINT_BORDER_COLOR,true);
   this.SetForeColor(CLR_DEF_CONTROL_HINT_FORE_COLOR,true);
   this.SetDisplayed(false);
   this.SetBorderSizeAll(1);
   this.SetBorderStyle(FRAME_STYLE_SIMPLE);
   this.SetShadow(true);
   this.DrawShadow(2,2,CLR_DEF_SHADOW_COLOR,CLR_DEF_SHADOW_OPACITY,DEF_SHADOW_BLUR);
   this.SetOpacity(255,false);
   this.SetTitle("");
   this.SetTooltipText("");
   this.SetInitialDelay(DEF_CONTROL_TOOLTIP_INITIAL_DELAY);
   this.SetAutoPopDelay(DEF_CONTROL_TOOLTIP_AUTO_POP_DELAY);
   this.SetReshowDelay(DEF_CONTROL_TOOLTIP_RESHOW_DELAY);
   this.SetShowAlways(false);
   this.SetIcon(CANV_ELEMENT_TOOLTIP_ICON_NONE);
   this.SetBalloon(false);
   this.SetUseFading(true);
   this.SetTextAlign(ANCHOR_LEFT_UPPER);
   this.SetTextAnchor(FRAME_ANCHOR_LEFT_TOP);
   this.SetFont(DEF_FONT,DEF_FONT_SIZE,FW_NORMAL);
   this.SetDisplayed(false);
   this.Hide();
  }
//+------------------------------------------------------------------+
//| Redraw the object                                                |
//+------------------------------------------------------------------+
void CToolTip::Redraw(bool redraw)
  {
//--- If the element should not be displayed (hidden inside another control), leave
   if(!this.Displayed() || this.Text()=="")
      return;
//--- Get the "Shadow" object
   CShadowObj *shadow=this.GetShadowObj();
//--- If the redraw flag is set,
   if(redraw)
     {
      //--- If the object has a shadow and the "Shadow" object exists, redraw it
      if(this.IsShadow() && shadow!=NULL)
        {
         //--- remove the previously drawn shadow,
         shadow.Erase();
         //--- save the relative shadow coordinates,
         int x=shadow.CoordXRelative();
         int y=shadow.CoordYRelative();
         //--- redraw the shadow,
         shadow.Draw(0,0,shadow.Blur(),true);
         //--- restore relative shadow coordinates
         shadow.SetCoordXRelative(x);
         shadow.SetCoordYRelative(y);
        }
      //--- completely redraw the object and save its new initial look
      this.Erase(this.m_array_colors_bg,this.Opacity(),this.m_gradient_v,this.m_gradient_c,true);
      this.Done();
     }
//--- otherwise, erase the object and its shadow
   else
     {
      CGCnvElement::Erase();
      if(this.IsShadow() && shadow!=NULL)
         shadow.Erase();
     }
  }
//+------------------------------------------------------------------+
//| Clear the element filling it with color and opacity              |
//+------------------------------------------------------------------+
void CToolTip::Erase(const color colour,const uchar opacity,const bool redraw=false)
  {
//--- Fill the element having the specified color and the redrawing flag
   CGCnvElement::EraseNoCrop(colour,opacity,false);
//--- If the object has a frame, draw it
   if(this.BorderStyle()!=FRAME_STYLE_NONE)
      this.DrawFrame();
//--- Draw a hint
   this.DrawHint(0);
//--- Update the element having the specified redrawing flag
   this.Update(redraw);
  }
//+------------------------------------------------------------------+
//| Clear the element with a gradient fill                           |
//+------------------------------------------------------------------+
void CToolTip::Erase(color &colors[],const uchar opacity,const bool vgradient,const bool cycle,const bool redraw=false)
  {
//--- Fill the element having the specified color array and the redrawing flag
   CGCnvElement::EraseNoCrop(colors,opacity,vgradient,cycle,false);
//--- If the object has a frame, draw it
   if(this.BorderStyle()!=FRAME_STYLE_NONE)
      this.DrawFrame();
//--- Draw a hint
   this.DrawHint(0);
//--- Update the element having the specified redrawing flag
   this.Update(redraw);
  }
//+------------------------------------------------------------------+
//| Draw the element border                                          |
//+------------------------------------------------------------------+
void CToolTip::DrawFrame(void)
  {
//--- If the element should not be displayed (hidden inside another control), leave
   if(!this.Displayed() || this.Text()=="")
      return;
//--- Draw a rectangle along the object edges
   this.DrawRectangle(0,0,this.Width()-1,this.Height()-1,this.BorderColor(),this.Opacity());
  }
//+------------------------------------------------------------------+
//| Shows the control                                                |
//+------------------------------------------------------------------+
void CToolTip::Show(void)
  {
//--- If the element should not be displayed (hidden inside another control), leave
   if(!this.Displayed() || this.TooltipText()=="")
      return;
//--- Get the "Shadow" object
   CShadowObj *shadow=this.GetShadowObj();
//--- If the object has a shadow and the "Shadow" object exists, display the shadow
   if(this.IsShadow() && shadow!=NULL)
     {
      shadow.Show();
      this.BringToTop();
     }
//--- Display the object
   CGCnvElement::Show();
  }
//+------------------------------------------------------------------+
//| Set the Tooltip text                                             |
//+------------------------------------------------------------------+
void CToolTip::SetTooltipText(const string text)
  {
   CGCnvElement::SetTooltipText(text);
   CWinFormBase::SetText(text);
   this.CorrectSizeByTexts();
  }
//+------------------------------------------------------------------+
//| Set the title text for Tooltip                                   |
//+------------------------------------------------------------------+
void CToolTip::SetTitle(const string header)
  {
   this.SetProperty(CANV_ELEMENT_PROP_TOOLTIP_TITLE,header);
   this.CorrectSizeByTexts();
  }
//+------------------------------------------------------------------+
//| Adjust tooltip size to fit texts                                 |
//+------------------------------------------------------------------+
void CToolTip::CorrectSizeByTexts(void)
  {
//--- If the tooltip text is not set for the object, leave
   if(this.TooltipText()=="" || this.TooltipText()==NULL)
      return;
//--- Declare variables for the width and height of the object
   int w=this.Width();
   int h=this.Height();
//--- Declare variables for the width and height of the title and tooltip texts
   int w1=0;
   int h1=0;
   int w2=w;
   int h2=h;
//--- If the header text is set, get and adjust its size
   if(this.Title()!="" && this.Title()!=NULL)
     {
      this.TextSize(this.Title(),w1,h1);
      if(w1<6)
         w1=6;
      if(h1<19)
         h1=19;
     }
//--- If the tooltip text is set, get and adjust its size
   this.TextSize(this.Text(),w2,h2);
   if(w2<6)
      w2=6;
   if(h2<19)
      h2=19;
//--- Calculate the total size of the tooltip
   w=fmax(w1,w2);
   h=h1+h2;
//--- Set the size of the object in accordance with the calculated ones
   this.Resize(w+12+(this.Icon()>CANV_ELEMENT_TOOLTIP_ICON_NONE && this.Icon()<=CANV_ELEMENT_TOOLTIP_ICON_USER ? 16 : 0),h,false);
  }
//+------------------------------------------------------------------+
//| Draw a hint                                                      |
//+------------------------------------------------------------------+
void CToolTip::DrawHint(const int shift)
  {
   int y=3;
   int x=6+(this.Icon()>CANV_ELEMENT_TOOLTIP_ICON_NONE ? 16 : 0);
   this.DrawIcon();
   if(this.Title()!="" && this.Title()!=NULL)
     {
      this.SetFont(DEF_FONT,DEF_FONT_SIZE,FW_BLACK);
      this.Text(x,y,this.Title(),this.ForeColor(),this.Opacity(),this.TextAnchor());
      this.SetFont(DEF_FONT,DEF_FONT_SIZE,FW_NORMAL);
      y+=this.TextHeight(this.Title())+4;
     }
   this.Text(x,y,this.Text(),this.ForeColor(),this.Opacity(),this.TextAnchor());
   this.Update();
  }
//+------------------------------------------------------------------+
//| Draw an icon                                                     |
//+------------------------------------------------------------------+
void CToolTip::DrawIcon(void)
  {
   switch(this.Icon())
     {
      case CANV_ELEMENT_TOOLTIP_ICON_INFO    :  this.DrawIconInfo();    break;
      case CANV_ELEMENT_TOOLTIP_ICON_WARNING :  this.DrawIconWarning(); break;
      case CANV_ELEMENT_TOOLTIP_ICON_ERROR   :  this.DrawIconError();   break;
      case CANV_ELEMENT_TOOLTIP_ICON_USER    :  this.DrawIconUser();    break;
      //--- Icon None
      default: break;
     }
  }
//+------------------------------------------------------------------+
//| Timer                                                            |
//+------------------------------------------------------------------+
void CToolTip::OnTimer(void)
  {
//--- If the object is in the normal state (hidden)
   if(this.DisplayState()==CANV_ELEMENT_DISPLAY_STATE_NORMAL)
     {
      //--- set the state of waiting for the object to fade in
      //--- set the waiting duration and set the countdown time
      this.SetDisplayState(CANV_ELEMENT_DISPLAY_STATE_WAITING_FADE_IN);
      this.m_pause.SetWaitingMSC(this.InitialDelay());
      this.m_pause.SetTimeBegin();
      return;
     }
//--- If the object is in the state of waiting for fading in
   if(this.DisplayState()==CANV_ELEMENT_DISPLAY_STATE_WAITING_FADE_IN)
     {
      //--- If the waiting time has not yet passed, leave
      if(this.m_pause.Passed()<this.InitialDelay())
         return;
      //--- Set the state of the object fading in and
      //--- the process start countdown time
      this.SetDisplayState(CANV_ELEMENT_DISPLAY_STATE_PROCESS_FADE_IN);
      this.m_pause.SetTimeBegin();
      return;
     }
//--- If the object is in the state of fading in
   if(this.DisplayState()==CANV_ELEMENT_DISPLAY_STATE_PROCESS_FADE_IN)
     {
      //--- If the object is not completely opaque yet
      if(this.Opacity()<255)
        {
         //--- set the display flag and show the object,
         //--- calculate the required opacity value at this timer step,
         //--- set the calculated opacity to the properties and redraw the object
         this.SetDisplayed(true);
         if(this.Opacity()==0)
            this.Show();
         uchar value=(this.Opacity()+(uchar)this.m_step<255 ? this.Opacity()+(uchar)this.m_step : 255);
         this.SetOpacity(value);
         this.Redraw(true);
         return;
        }
      //--- Set the end state of fading in
      this.SetDisplayState(CANV_ELEMENT_DISPLAY_STATE_COMPLETED_FADE_IN);
     }
//--- If the object is in the state of completion of fading in
   if(this.DisplayState()==CANV_ELEMENT_DISPLAY_STATE_COMPLETED_FADE_IN)
     {
      //--- set the state of waiting for object fading out,
      //--- set the waiting duration and set the countdown time
      this.SetDisplayState(CANV_ELEMENT_DISPLAY_STATE_WAITING_FADE_OUT);
      this.m_pause.SetWaitingMSC(this.AutoPopDelay());
      this.m_pause.SetTimeBegin();
      return;
     }
//--- If the object is in the state of waiting for fading out
   if(this.DisplayState()==CANV_ELEMENT_DISPLAY_STATE_WAITING_FADE_OUT)
     {
      //--- If the waiting time has not yet passed, leave
      if(this.m_pause.Passed()<this.AutoPopDelay())
         return;
      //--- Set the state of the object fading out and
      //--- the process start countdown time
      this.SetDisplayState(CANV_ELEMENT_DISPLAY_STATE_PROCESS_FADE_OUT);
      this.m_pause.SetTimeBegin();
      return;
     }
//--- If the object is in the state of fading out
   if(this.DisplayState()==CANV_ELEMENT_DISPLAY_STATE_PROCESS_FADE_OUT)
     {
      //--- If the object is not completely transparent yet
      if(this.Opacity()>0)
        {
         //--- set the display flag,
         //--- calculate the required opacity value at this timer step,
         //--- set the calculated opacity to the properties and redraw the object
         uchar value=(this.Opacity()-(uchar)this.m_step>0 ? this.Opacity()-(uchar)this.m_step : 0);
         this.SetOpacity(value);
         this.Redraw(true);
         return;
        }
      //--- Set the end state of fading out,
      //--- set the properties to full transparency and redraw the object
      this.SetDisplayState(CANV_ELEMENT_DISPLAY_STATE_COMPLETED_FADE_OUT);
      this.SetOpacity(0);
      this.Redraw(true);
     }
//--- If the object is in the state of completion of fading out
   if(this.DisplayState()==CANV_ELEMENT_DISPLAY_STATE_COMPLETED_FADE_OUT)
     {
      //--- Set the flag for not displaying, set the full transparency,
      //--- hide the object and set the state of the completion of the entire cycle of changes
      this.SetDisplayed(false);
      this.SetOpacity(0);
      this.Hide();
      this.SetDisplayState(CANV_ELEMENT_DISPLAY_STATE_COMPLETED);
     }
  }
//+------------------------------------------------------------------+
