//+------------------------------------------------------------------+
//|                                                       JMACD4.mq5 |
//|                               Copyright © 2010, Nikolay Kositsin |
//|                              Khabarovsk,   farria@mail.redcom.ru |
//|                                                                  |
//|  4-Color Slope Gradient Histogram                                |
//|  2-Color Slope Signal Line with 5 Line Style Types               |
//|  On/Off toggle button framework                                  |
//+------------------------------------------------------------------+
#property copyright   "Copyright © 2010, Nikolay Kositsin / Mod by Banzai"
#property link        "farria@mail.redcom.ru"
#property version     "4.00"
#property description "JMACD 4-Color Slope Gradient Histogram with 2-Color Slope Signal Line and On/Off Button"
#property indicator_separate_window
#property indicator_buffers 3
#property indicator_plots   2

//+-----------------------------------+
//|  Indicator drawing parameters     |
//+-----------------------------------+
//---- plot 1: MACD Data (used for auto-scaling and DataWindow)
#property indicator_type1 DRAW_HISTOGRAM
#property indicator_color1 clrNONE
#property indicator_width1 1
#property indicator_label1 "JMACD"

//---- plot 2: Signal line (2-color slope line)
#property indicator_type2 DRAW_COLOR_LINE
#property indicator_color2 clrLime, clrMagenta
#property indicator_style2 STYLE_SOLID
#property indicator_width2 2
#property indicator_label2 "Signal Line"

//---- Zero level line
#property indicator_level1      0.0
#property indicator_levelcolor  clrDimGray
#property indicator_levelstyle  STYLE_DOT
#property indicator_levelwidth  1

//+------------------------------------------------------------------+
//|  Include libraries                                               |
//+------------------------------------------------------------------+
#include <SmoothAlgorithms.mqh>

//+------------------------------------------------------------------+
//|  Enumerations                                                    |
//+------------------------------------------------------------------+
enum Applied_price_
  {
   PRICE_CLOSE_ = 1,     // PRICE_CLOSE
   PRICE_OPEN_,          // PRICE_OPEN
   PRICE_HIGH_,          // PRICE_HIGH
   PRICE_LOW_,           // PRICE_LOW
   PRICE_MEDIAN_,        // PRICE_MEDIAN
   PRICE_TYPICAL_,       // PRICE_TYPICAL
   PRICE_WEIGHTED_,      // PRICE_WEIGHTED
   PRICE_SIMPL_,         // PRICE_SIMPL_
   PRICE_QUARTER_,       // PRICE_QUARTER_
   PRICE_TRENDFOLLOW0_,  // PRICE_TRENDFOLLOW0_
   PRICE_TRENDFOLLOW1_   // PRICE_TRENDFOLLOW1_
  };

//+------------------------------------------------------------------+
//|  Indicator input parameters                                      |
//+------------------------------------------------------------------+
input group "--- JMACD Settings ---"
input int            Fast_JMA     = 12;            // Period of the fast MA
input int            Slow_JMA     = 26;            // Depth of the SMMA smoothing
input int            Signal_JMA   = 9;             // Signal line period 
input int            Phase_       = 100;           // Smoothing parameter (-100 ... +100)
input Applied_price_ AppliedPrice = PRICE_CLOSE_;  // Price constant

input group "--- Outline Settings ---"
input bool                 InpAutoAroundLine = false;                 // Auto Draw Line Around Histogram
input int                  InpValueColor     = 4;                     // Level Draw Line Around Histogram (pixels)
input bool                 InpAroundLine     = true;                  // Draw Line Around Histogram

input group "--- 1: Above Zero & Rising (Bullish Acceleration) ---"
input color                InpLo0            = C'0,139,139';          // Top Gradient (DarkCyan)
input color                InpHi0            = clrWhiteSmoke;         // Bottom Gradient (WhiteSmoke)
input color                InpLn0            = clrLime;               // Outline Color (Lime)
input int                  InpOp0            = 160;                   // Start Opaque (0..255)
input int                  InpOP0            = 48;                    // End Opaque (0..255)

input group "--- 2: Above Zero & Falling (Bullish Deceleration) ---"
input color                InpLo1            = C'46,139,87';          // Top Gradient (SeaGreen)
input color                InpHi1            = clrWhiteSmoke;         // Bottom Gradient (WhiteSmoke)
input color                InpLn1            = clrMediumSeaGreen;     // Outline Color (MediumSeaGreen)
input int                  InpOp1            = 130;                   // Start Opaque (0..255)
input int                  InpOP1            = 40;                    // End Opaque (0..255)

input group "--- 3: Below Zero & Falling (Bearish Acceleration) ---"
input color                InpLo2            = C'236,0,130';          // Peak Gradient (Deep Pink)
input color                InpHi2            = clrWhiteSmoke;         // Zero Line Gradient (WhiteSmoke)
input color                InpLn2            = clrLightCoral;         // Outline Color (LightCoral)
input int                  InpOp2            = 144;                   // Start Opaque (0..255)
input int                  InpOP2            = 64;                    // End Opaque (0..255)

input group "--- 4: Below Zero & Rising (Bearish Deceleration) ---"
input color                InpLo3            = C'186,85,211';         // Peak Gradient (MediumOrchid)
input color                InpHi3            = clrWhiteSmoke;         // Zero Line Gradient (WhiteSmoke)
input color                InpLn3            = clrPlum;               // Outline Color (Plum)
input int                  InpOp3            = 130;                   // Start Opaque (0..255)
input int                  InpOP3            = 48;                    // End Opaque (0..255)

input group "--- Signal Line (2-Color Slope) Settings ---"
input bool                 InpShowSignalLine = true;                  // Show Signal Line
input ENUM_LINE_STYLE      InpSignalStyle    = STYLE_SOLID;           // Signal Line Style (Solid, Dash, Dot, Dash-Dot, Dash-Dot-Dot)
input int                  InpSignalWidth    = 2;                     // Signal Line Width (Styles other than Solid require Width = 1)
input color                InpSignalColorUp  = clrLime;               // Signal Line: Rising Color
input color                InpSignalColorDn  = clrMagenta;            // Signal Line: Falling Color

//Forex-Station copy & paste code; Button code start 11
input group "=== Toggle Button Settings ==="
input string             button_note1          = "------------------------------"; // ------------------------------
input int                btn_Subwindow         = 0;                  // Window for the button (0=Chart, -1=Indicator)
input ENUM_BASE_CORNER   btn_corner            = CORNER_LEFT_UPPER;  // chart btn_corner for anchoring
input string             btn_text              = "JMACD: ON";        // Display label
input string             btn_Font              = "Arial";            // button font name
input int                btn_FontSize          = 9;                  // btn__font size
input color              btn_text_ON_color     = clrLime;            // ON color when the button is turned on
input color              btn_text_OFF_color    = clrRed;             // OFF color when the button is turned off
input color              btn_background_color  = clrDimGray;         // background color of the button
input color              btn_border_color      = clrBlack;           // border color the button
input int                button_x              = 100;                // Horizontal location
input int                button_y              = 100;                // Vertical location
input int                btn_Width             = 80;                 // btn__width
input int                btn_Height            = 20;                 // btn__height
input string             UniqueButtonID        = "JMACDBT";          // Unique Button ID                      
input string             button_note2          = "------------------------------"; // ------------------------------
//Forex-Station copy & paste code; Button code end 11

//+------------------------------------------------------------------+
//| Button State Tracking Variables                                  |
//+------------------------------------------------------------------+
bool    IndicatorEnabled = true;
bool    IndicatorHidden  = false;
int     g_btnWindow      = 0;
string  ToggleButtonName;
string  g_gvHidden;
string  IndicatorName, IndicatorObjPrefix, buttonId;

//+------------------------------------------------------------------+
//| Internal Gradient Structure                                      |
//+------------------------------------------------------------------+
struct Gradient
  {
   uint              R[2], G[2], B[2], O[2], Line;
  };

Gradient Storage[4];

//+------------------------------------------------------------------+
//| Global Variables                                                 |
//+------------------------------------------------------------------+
double   MACDBuffer[], SignBuffer[], ColorSignBuffer[];
int      start = 60, macd_start = 30;

int      Y               = 0;
int      X               = 0;
int      Scale           = 0;
bool     g_initialized   = false;
long     g_chart_id      = 0;
int      g_subwin        = -1;
string   g_prefix        = "";
int      g_max_drawn_bar = 0;
int      g_rates_total   = 0;

//+------------------------------------------------------------------+
//| Helper to resolve button text based on state                     |
//+------------------------------------------------------------------+
string GetButtonText(bool isEnabled)
  {
   string t = btn_text;
   if(isEnabled)
     {
      if(StringFind(t, "OFF") >= 0)      StringReplace(t, "OFF", "ON");
      else if(StringFind(t, "Off") >= 0) StringReplace(t, "Off", "On");
      else if(StringFind(t, "off") >= 0) StringReplace(t, "off", "on");
     }
   else
     {
      if(StringFind(t, "ON") >= 0)       StringReplace(t, "ON", "OFF");
      else if(StringFind(t, "On") >= 0)  StringReplace(t, "On", "Off");
      else if(StringFind(t, "on") >= 0)  StringReplace(t, "on", "off");
     }
   return t;
  }

//+------------------------------------------------------------------+
//| Get Timeframe Name String                                        |
//+------------------------------------------------------------------+
string GetStringTimeframe(ENUM_TIMEFRAMES timeframe)
{
   return(StringSubstr(EnumToString(timeframe), 7));
}
//+------------------------------------------------------------------+
//| Helper: Reliably find indicator subwindow number                 |
//+------------------------------------------------------------------+
int GetIndicatorSubwindow()
  {
   int sub = ChartWindowFind();
   if(sub >= 0)
      return sub;
   if(IndicatorName != "")
     {
      sub = ChartWindowFind(0, IndicatorName);
      if(sub >= 0)
         return sub;
     }
   string shortname;
   StringConcatenate(shortname, "JMACD ( ", Fast_JMA, ", ", Slow_JMA, ", ", Signal_JMA, " )");
   sub = ChartWindowFind(0, shortname);
   if(sub >= 0)
      return sub;
   int total = (int)ChartGetInteger(0, CHART_WINDOWS_TOTAL);
   for(int w = 1; w < total; w++)
     {
      int total_ind = ChartIndicatorsTotal(0, w);
      for(int i = 0; i < total_ind; i++)
        {
         string indName = ChartIndicatorName(0, w, i);
         if(StringFind(indName, "JMACD") >= 0)
            return w;
        }
     }
   return -1;
  }

//+------------------------------------------------------------------+
//| Create Toggle Button                                             |
//+------------------------------------------------------------------+
void CreateToggleButton()
  {
   g_btnWindow = (btn_Subwindow < 0) ? GetIndicatorSubwindow() : btn_Subwindow;
   if(g_btnWindow < 0) g_btnWindow = 0;

   if(ObjectFind(0, buttonId) < 0)
     {
      int y_pos = button_y;
      int obj_total = ObjectsTotal(0, -1, OBJ_BUTTON);
      bool collision = true;
      while(collision)
        {
         collision = false;
         for(int i = 0; i < obj_total; i++)
           {
            string name = ObjectName(0, i, -1, OBJ_BUTTON);
            if(name == buttonId)
               continue;
            if((ENUM_BASE_CORNER)ObjectGetInteger(0, name, OBJPROP_CORNER) != btn_corner)
               continue;
            if(ObjectGetInteger(0, name, OBJPROP_XDISTANCE) == button_x &&
               ObjectGetInteger(0, name, OBJPROP_YDISTANCE) == y_pos)
              {
               y_pos += btn_Height + 4;
               collision = true;
               break;
              }
           }
        }

      ObjectCreate(0, buttonId, OBJ_BUTTON, g_btnWindow, 0, 0);

      ObjectSetInteger(0, buttonId, OBJPROP_CORNER,       btn_corner);
      ObjectSetInteger(0, buttonId, OBJPROP_XDISTANCE,    button_x);
      ObjectSetInteger(0, buttonId, OBJPROP_YDISTANCE,    y_pos);
      ObjectSetInteger(0, buttonId, OBJPROP_XSIZE,        btn_Width);
      ObjectSetInteger(0, buttonId, OBJPROP_YSIZE,        btn_Height);
      ObjectSetString (0, buttonId, OBJPROP_FONT,         btn_Font);
      ObjectSetInteger(0, buttonId, OBJPROP_FONTSIZE,     btn_FontSize);
      ObjectSetInteger(0, buttonId, OBJPROP_BGCOLOR,      btn_background_color);
      ObjectSetInteger(0, buttonId, OBJPROP_BORDER_COLOR, btn_border_color);
      ObjectSetInteger(0, buttonId, OBJPROP_BORDER_TYPE,  BORDER_RAISED);
      ObjectSetInteger(0, buttonId, OBJPROP_SELECTABLE,   false);
      ObjectSetInteger(0, buttonId, OBJPROP_HIDDEN,       true);
      ObjectSetInteger(0, buttonId, OBJPROP_TIMEFRAMES,   OBJ_ALL_PERIODS);
      ObjectSetInteger(0, buttonId, OBJPROP_ZORDER,       100);
     }

   UpdateToggleButton();
  }

//+------------------------------------------------------------------+
//| Update Toggle Button Appearance                                  |
//+------------------------------------------------------------------+
void UpdateToggleButton()
  {
   if(ObjectFind(0, buttonId) < 0)
      return;

   IndicatorEnabled = !IndicatorHidden;

   string labelText = GetButtonText(IndicatorEnabled);
   color  textColor = IndicatorEnabled ? btn_text_ON_color : btn_text_OFF_color;

   ObjectSetString (0, buttonId, OBJPROP_TEXT,         labelText);
   ObjectSetString (0, buttonId, OBJPROP_FONT,         btn_Font);
   ObjectSetInteger(0, buttonId, OBJPROP_FONTSIZE,     btn_FontSize);
   ObjectSetInteger(0, buttonId, OBJPROP_COLOR,        textColor);
   ObjectSetInteger(0, buttonId, OBJPROP_BGCOLOR,      btn_background_color);
   ObjectSetInteger(0, buttonId, OBJPROP_BORDER_COLOR, btn_border_color);
   ObjectSetInteger(0, buttonId, OBJPROP_CORNER,       btn_corner);
   ObjectSetInteger(0, buttonId, OBJPROP_XDISTANCE,    button_x);
   ObjectSetInteger(0, buttonId, OBJPROP_XSIZE,        btn_Width);
   ObjectSetInteger(0, buttonId, OBJPROP_YSIZE,        btn_Height);
   ObjectSetInteger(0, buttonId, OBJPROP_STATE,        IndicatorEnabled);
   ObjectSetInteger(0, buttonId, OBJPROP_ZORDER,       100);

   ChartRedraw(0);
  }

//+------------------------------------------------------------------+
//| Ensure all buttons on chart have higher ZORDER                   |
//+------------------------------------------------------------------+
void BringButtonsToFront()
  {
   int total = ObjectsTotal(0, -1, OBJ_BUTTON);
   for(int i = 0; i < total; i++)
     {
      string name = ObjectName(0, i, -1, OBJ_BUTTON);
      if(name != "")
        {
         if(ObjectGetInteger(0, name, OBJPROP_ZORDER) < 100)
            ObjectSetInteger(0, name, OBJPROP_ZORDER, 100);
        }
     }
  }

//+------------------------------------------------------------------+
//| Delete all gradient histogram objects and free resources         |
//+------------------------------------------------------------------+
void DeleteIndicatorObjects()
  {
   ObjectsDeleteAll(0, g_prefix);
   for(int i = 0; i <= g_max_drawn_bar; i++)
     {
      string MasName = "::" + IntegerToString(g_chart_id) + "_" + UniqueButtonID + "_Maps_" + IntegerToString(i);
      ResourceFree(MasName);
     }
   g_max_drawn_bar = 0;
  }

//+------------------------------------------------------------------+
//| Toggle Indicator Visibility                                      |
//+------------------------------------------------------------------+
void ToggleIndicatorVisibility()
  {
   IndicatorHidden  = !IndicatorHidden;
   IndicatorEnabled = !IndicatorHidden;

   if(g_gvHidden != "")
      GlobalVariableSet(g_gvHidden, IndicatorHidden ? 1.0 : 0.0);

   if(IndicatorHidden)
     {
      PlotIndexSetInteger(0, PLOT_DRAW_TYPE, DRAW_NONE);
      PlotIndexSetInteger(1, PLOT_DRAW_TYPE, DRAW_NONE);
      DeleteIndicatorObjects();
     }
   else
     {
      PlotIndexSetInteger(0, PLOT_DRAW_TYPE, DRAW_HISTOGRAM);
      PlotIndexSetInteger(0, PLOT_LINE_COLOR, clrNONE);
      PlotIndexSetInteger(1, PLOT_DRAW_TYPE, InpShowSignalLine ? DRAW_COLOR_LINE : DRAW_NONE);
      PlotIndexSetInteger(1, PLOT_LINE_COLOR, 0, InpSignalColorUp);
      PlotIndexSetInteger(1, PLOT_LINE_COLOR, 1, InpSignalColorDn);
      DrawFull();
     }

   UpdateToggleButton();
   BringButtonsToFront();
   ChartRedraw(0);
  }

//+------------------------------------------------------------------+
//| Helper: Get chart first visible bar                              |
//+------------------------------------------------------------------+
int ChartFirstVisibleBar(const long chart_ID = 0)
  {
   return((int)ChartGetInteger(chart_ID, CHART_FIRST_VISIBLE_BAR));
  }

//+------------------------------------------------------------------+
//| Helper: Get chart visible bars count                             |
//+------------------------------------------------------------------+
int ChartVisibleBars(const long chart_ID = 0)
  {
   return((int)ChartGetInteger(chart_ID, CHART_VISIBLE_BARS));
  }

//+------------------------------------------------------------------+
//| Helper: Get chart scale                                          |
//+------------------------------------------------------------------+
int ChartScaleGet(const long chart_ID = 0)
  {
   return((int)ChartGetInteger(chart_ID, CHART_SCALE));
  }

//+------------------------------------------------------------------+
//| Update dimensions                                                |
//+------------------------------------------------------------------+
void UpdateDimensions()
  {
   if(g_subwin < 0)
      g_subwin = GetIndicatorSubwindow();
   if(g_subwin >= 0)
      {
       Y = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS, g_subwin);
       X = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS, g_subwin);
      }
  }

//+------------------------------------------------------------------+
//| Update Scale value based on chart zoom                           |
//+------------------------------------------------------------------+
void UpdateScale()
  {
   int chartScale = ChartScaleGet(0);
   switch(chartScale)
     {
      case 5:  Scale = 3; break;
      case 4:  Scale = 2; break;
      case 3:  Scale = 1; break;
      case 2:  Scale = 1; break;
      default: Scale = 0; break;
     }
  }

//+------------------------------------------------------------------+
//| Helper: Convert price to pixel Y coordinate in subwindow         |
//+------------------------------------------------------------------+
int PriceToY(const double price, const double pMin, const double pMax, const int height)
  {
   if(pMax <= pMin || height <= 0)
      return 0;
   double ratio = (pMax - price) / (pMax - pMin);
   return (int)(ratio * (double)height);
  }

//+------------------------------------------------------------------+
//| Determine 4-color slope state:                                   |
//| 0: Above Zero & Rising  (Bullish Acceleration)                   |
//| 1: Above Zero & Falling (Bullish Deceleration)                   |
//| 2: Below Zero & Falling (Bearish Acceleration)                   |
//| 3: Below Zero & Rising  (Bearish Deceleration)                   |
//+------------------------------------------------------------------+
int GetHistState(const double jmacd, const double prevMacd)
  {
   if(jmacd >= 0.0)
     {
      return (jmacd >= prevMacd) ? 0 : 1;
     }
   else
     {
      return (jmacd < prevMacd) ? 2 : 3;
     }
  }

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
void OnInit()
  {
   g_chart_id = ChartID();
   g_subwin   = GetIndicatorSubwindow();

   //---- set MACDBuffer dynamic array as indicator buffer
   SetIndexBuffer(0, MACDBuffer, INDICATOR_DATA);
   PlotIndexSetInteger(0, PLOT_DRAW_BEGIN, macd_start + 1);
   PlotIndexSetString(0, PLOT_LABEL, "JMACD");
   PlotIndexSetDouble(0, PLOT_EMPTY_VALUE, EMPTY_VALUE);
   PlotIndexSetInteger(0, PLOT_DRAW_TYPE, DRAW_HISTOGRAM);
   PlotIndexSetInteger(0, PLOT_LINE_COLOR, clrNONE);

   //---- set SignBuffer dynamic array as indicator buffer
   SetIndexBuffer(1, SignBuffer, INDICATOR_DATA);
   //---- set ColorSignBuffer dynamic array as color index buffer
   SetIndexBuffer(2, ColorSignBuffer, INDICATOR_COLOR_INDEX);

   int sigWidth = InpSignalWidth;
   if(InpSignalStyle != STYLE_SOLID && sigWidth > 1)
      sigWidth = 1; // MT5 requires width 1 to render dash/dot styles

   PlotIndexSetInteger(1, PLOT_DRAW_BEGIN, start + 1);
   PlotIndexSetString(1, PLOT_LABEL, "Signal JMA");
   PlotIndexSetDouble(1, PLOT_EMPTY_VALUE, EMPTY_VALUE);
   PlotIndexSetInteger(1, PLOT_DRAW_TYPE, InpShowSignalLine ? DRAW_COLOR_LINE : DRAW_NONE);
   PlotIndexSetInteger(1, PLOT_COLOR_INDEXES, 2);
   PlotIndexSetInteger(1, PLOT_LINE_COLOR, 0, InpSignalColorUp);
   PlotIndexSetInteger(1, PLOT_LINE_COLOR, 1, InpSignalColorDn);
   PlotIndexSetInteger(1, PLOT_LINE_STYLE, InpSignalStyle);
   PlotIndexSetInteger(1, PLOT_LINE_WIDTH, sigWidth);

   //---- initialization of a variable for indicator short name
   string shortname;
   StringConcatenate(shortname, "[" + Symbol() + "] [" + GetStringTimeframe(_Period) + "] JMACD ( ", Fast_JMA, ", ", Slow_JMA, ", ", Signal_JMA, " )");
   IndicatorSetString(INDICATOR_SHORTNAME, shortname);
   IndicatorSetInteger(INDICATOR_DIGITS, _Digits + 1);

   //---- gradient storage initialization for all 4 states
   // State 0: Above Zero & Rising (Bullish Acceleration)
   Storage[0].R[0] = InpLo0 & 0xFF;
   Storage[0].G[0] = (InpLo0 >> 8) & 0xFF;
   Storage[0].B[0] = (InpLo0 >> 16) & 0xFF;
   Storage[0].O[0] = (uint)MathMin(MathMax(InpOp0, 0), 255);

   Storage[0].R[1] = InpHi0 & 0xFF;
   Storage[0].G[1] = (InpHi0 >> 8) & 0xFF;
   Storage[0].B[1] = (InpHi0 >> 16) & 0xFF;
   Storage[0].O[1] = (uint)MathMin(MathMax(InpOP0, 0), 255);

   Storage[0].Line = (uint)InpLn0;

   // State 1: Above Zero & Falling (Bullish Deceleration)
   Storage[1].R[0] = InpLo1 & 0xFF;
   Storage[1].G[0] = (InpLo1 >> 8) & 0xFF;
   Storage[1].B[0] = (InpLo1 >> 16) & 0xFF;
   Storage[1].O[0] = (uint)MathMin(MathMax(InpOp1, 0), 255);

   Storage[1].R[1] = InpHi1 & 0xFF;
   Storage[1].G[1] = (InpHi1 >> 8) & 0xFF;
   Storage[1].B[1] = (InpHi1 >> 16) & 0xFF;
   Storage[1].O[1] = (uint)MathMin(MathMax(InpOP1, 0), 255);

   Storage[1].Line = (uint)InpLn1;

   // State 2: Below Zero & Falling (Bearish Acceleration)
   Storage[2].R[0] = InpLo2 & 0xFF;
   Storage[2].G[0] = (InpLo2 >> 8) & 0xFF;
   Storage[2].B[0] = (InpLo2 >> 16) & 0xFF;
   Storage[2].O[0] = (uint)MathMin(MathMax(InpOp2, 0), 255);

   Storage[2].R[1] = InpHi2 & 0xFF;
   Storage[2].G[1] = (InpHi2 >> 8) & 0xFF;
   Storage[2].B[1] = (InpHi2 >> 16) & 0xFF;
   Storage[2].O[1] = (uint)MathMin(MathMax(InpOP2, 0), 255);

   Storage[2].Line = (uint)InpLn2;

   // State 3: Below Zero & Rising (Bearish Deceleration)
   Storage[3].R[0] = InpLo3 & 0xFF;
   Storage[3].G[0] = (InpLo3 >> 8) & 0xFF;
   Storage[3].B[0] = (InpLo3 >> 16) & 0xFF;
   Storage[3].O[0] = (uint)MathMin(MathMax(InpOp3, 0), 255);

   Storage[3].R[1] = InpHi3 & 0xFF;
   Storage[3].G[1] = (InpHi3 >> 8) & 0xFF;
   Storage[3].B[1] = (InpHi3 >> 16) & 0xFF;
   Storage[3].O[1] = (uint)MathMin(MathMax(InpOP3, 0), 255);

   Storage[3].Line = (uint)InpLn3;

   UpdateDimensions();
   UpdateScale();

   //---- CJJMA validation
   CJJMA JMA;
   JMA.JJMALengthCheck("Fast_JMA", Fast_JMA);
   JMA.JJMALengthCheck("Slow_JMA", Slow_JMA);
   JMA.JJMALengthCheck("Signal_JMA", Signal_JMA);
   JMA.JJMAPhaseCheck("Phase_", Phase_);

   //--- Set up unique IDs and global variables for button state tracking
   IndicatorObjPrefix = "__" + btn_text + "__";
   buttonId           = IndicatorObjPrefix + UniqueButtonID;
   ToggleButtonName   = buttonId;
   IndicatorName      = shortname;
   g_prefix           = "JMACD4_BT_" + UniqueButtonID + "_" + IntegerToString(g_chart_id) + "_";
   g_gvHidden         = "JMACD4_BT_" + UniqueButtonID + "_" + IntegerToString(g_chart_id) + "_Hidden";

   //--- Clean up any old duplicate or phantom buttons
   int total_objs = ObjectsTotal(0, -1, OBJ_BUTTON);
   for(int i = total_objs - 1; i >= 0; i--)
     {
      string bName = ObjectName(0, i, -1, OBJ_BUTTON);
      if(StringFind(bName, UniqueButtonID) >= 0 || StringFind(bName, "JMACD4_BT") >= 0)
        {
         if(bName != buttonId)
            ObjectDelete(0, bName);
        }
     }

   bool isParamChange = (UninitializeReason() == REASON_PARAMETERS);
   if(isParamChange && g_gvHidden != "")
     {
      GlobalVariableDel(g_gvHidden);
     }

   bool hasSavedState = false;
   bool savedHidden   = false;

   if(!isParamChange)
     {
      if(ObjectFind(0, buttonId) >= 0)
        {
         string btnTxt = ObjectGetString(0, buttonId, OBJPROP_TEXT);
         long   btnSt  = ObjectGetInteger(0, buttonId, OBJPROP_STATE);

         if(StringFind(btnTxt, "OFF") >= 0 || StringFind(btnTxt, "Off") >= 0 || btnSt == 0)
            savedHidden = true;
         else if(StringFind(btnTxt, "ON") >= 0 || StringFind(btnTxt, "On") >= 0 || btnSt != 0)
            savedHidden = false;
         hasSavedState = true;
        }

      if(!hasSavedState && GlobalVariableCheck(g_gvHidden))
        {
         savedHidden   = (GlobalVariableGet(g_gvHidden) > 0.5);
         hasSavedState = true;
        }
     }

   if(hasSavedState)
     {
      IndicatorHidden = savedHidden;
     }
   else
     {
      if(StringFind(btn_text, "OFF") >= 0 || StringFind(btn_text, "Off") >= 0)
         IndicatorHidden = true;
      else
         IndicatorHidden = false;
     }

   IndicatorEnabled = !IndicatorHidden;

   if(g_gvHidden != "")
      GlobalVariableSet(g_gvHidden, IndicatorHidden ? 1.0 : 0.0);

   if(IndicatorHidden)
     {
      PlotIndexSetInteger(0, PLOT_DRAW_TYPE, DRAW_NONE);
      PlotIndexSetInteger(1, PLOT_DRAW_TYPE, DRAW_NONE);
     }

   CreateToggleButton();
   BringButtonsToFront();
  }

//+------------------------------------------------------------------+
//| Custom indicator deinitialization function                       |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   if(reason == REASON_REMOVE || reason == REASON_CHARTCLOSE || reason == REASON_PARAMETERS)
     {
      ObjectDelete(0, buttonId);
      ObjectDelete(0, ToggleButtonName);
      if(g_gvHidden != "")
         GlobalVariableDel(g_gvHidden);
     }

   DeleteIndicatorObjects();
   ChartRedraw(0);
  }

//+------------------------------------------------------------------+
//| Fill pixel memory buffer with gradient and optional outline      |
//+------------------------------------------------------------------+
void FillMap(uint &MapStorage[], const int iX, const int iY, const Gradient &gColor, const bool invert = false)
  {
   if(iX < 1 || iY < 1)
      return;

   ArrayResize(MapStorage, iX * iY);

   uint bStart = invert ? gColor.B[1] : gColor.B[0];
   uint bEnd   = invert ? gColor.B[0] : gColor.B[1];
   uint gStart = invert ? gColor.G[1] : gColor.G[0];
   uint gEnd   = invert ? gColor.G[0] : gColor.G[1];
   uint rStart = invert ? gColor.R[1] : gColor.R[0];
   uint rEnd   = invert ? gColor.R[0] : gColor.R[1];
   uint oStart = invert ? gColor.O[1] : gColor.O[0];
   uint oEnd   = invert ? gColor.O[0] : gColor.O[1];

   double dB = (bEnd != bStart && iY > 1) ? ((double)bEnd - (double)bStart) / (double)iY : 0.0;
   double dG = (gEnd != gStart && iY > 1) ? ((double)gEnd - (double)gStart) / (double)iY : 0.0;
   double dR = (rEnd != rStart && iY > 1) ? ((double)rEnd - (double)rStart) / (double)iY : 0.0;
   double dO = (oEnd != oStart && iY > 1) ? ((double)oEnd - (double)oStart) / (double)iY : 0.0;

   bool drawAroundLine = InpAroundLine;
   if(InpAutoAroundLine && iX < InpValueColor)
      drawAroundLine = false;

   if(drawAroundLine && iY >= 2 && iX >= 2)
     {
      for(int i = iY - 2; i > 0; i--)
        {
         uint  bVal = (uint)(bStart + (dB != 0.0 ? (double)i * dB : 0.0));
         uint  gVal = (uint)(gStart + (dG != 0.0 ? (double)i * dG : 0.0));
         uint  rVal = (uint)(rStart + (dR != 0.0 ? (double)i * dR : 0.0));
         uchar oVal = (uchar)(oStart + (dO != 0.0 ? (double)i * dO : 0.0));

         color bodyColor = (color)((bVal << 16) | (gVal << 8) | rVal);
         uint  bodyPixel = ColorToARGB(bodyColor, oVal);
         ArrayFill(MapStorage, iX * i, iX, bodyPixel);

         uint linePixel = ColorToARGB((color)gColor.Line, oVal);
         MapStorage[iX * i]          = linePixel;
         MapStorage[iX * i + iX - 1] = linePixel;
        }

      uint topLinePixel = ColorToARGB((color)gColor.Line, (uchar)oStart);
      ArrayFill(MapStorage, 0, iX, topLinePixel);

      uint botLinePixel = ColorToARGB((color)gColor.Line, (uchar)oEnd);
      ArrayFill(MapStorage, iX * (iY - 1), iX, botLinePixel);
     }
   else
     {
      for(int i = 0; i < iY; i++)
        {
         uint  bVal = (uint)(bStart + (dB != 0.0 ? (double)i * dB : 0.0));
         uint  gVal = (uint)(gStart + (dG != 0.0 ? (double)i * dG : 0.0));
         uint  rVal = (uint)(rStart + (dR != 0.0 ? (double)i * dR : 0.0));
         uchar oVal = (uchar)(oStart + (dO != 0.0 ? (double)i * dO : 0.0));

         color bodyColor = (color)((bVal << 16) | (gVal << 8) | rVal);
         uint  bodyPixel = ColorToARGB(bodyColor, oVal);
         ArrayFill(MapStorage, iX * i, iX, bodyPixel);
        }
     }
  }

//+------------------------------------------------------------------+
//| Update the current forming bar (bar 0)                           |
//+------------------------------------------------------------------+
void DrawFirst(const datetime barTime, const datetime prevTime, const double jmacd)
  {
   if(IndicatorHidden)
      return;

   if(g_subwin < 0)
      g_subwin = GetIndicatorSubwindow();
   if(g_subwin < 0)
      return;

   int subwinHeight = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS, g_subwin);
   if(subwinHeight <= 0)
      return;

   double pMin = ChartGetDouble(0, CHART_PRICE_MIN, g_subwin);
   double pMax = ChartGetDouble(0, CHART_PRICE_MAX, g_subwin);
   if(pMax <= pMin)
     {
      double minV = 0.0, maxV = 0.0;
      int checkBars = MathMin(g_rates_total, 100);
      for(int s = 0; s < checkBars; s++)
        {
         int idx = g_rates_total - 1 - s;
         if(idx >= 0 && idx < ArraySize(MACDBuffer))
           {
            if(MACDBuffer[idx] != EMPTY_VALUE)
              {
               if(MACDBuffer[idx] < minV) minV = MACDBuffer[idx];
               if(MACDBuffer[idx] > maxV) maxV = MACDBuffer[idx];
              }
            if(SignBuffer[idx] != EMPTY_VALUE)
              {
               if(SignBuffer[idx] < minV) minV = SignBuffer[idx];
               if(SignBuffer[idx] > maxV) maxV = SignBuffer[idx];
              }
           }
        }
      if(maxV <= minV)
        {
         minV = -0.001;
         maxV =  0.001;
        }
      double pad = (maxV - minV) * 0.05;
      pMin = minV - pad;
      pMax = maxV + pad;
     }

   double chartMin = ChartGetDouble(0, CHART_PRICE_MIN, 0);
   double chartMax = ChartGetDouble(0, CHART_PRICE_MAX, 0);
   double midChartPrice = (chartMax > chartMin) ? (chartMin + chartMax) * 0.5 : 0.0;

   int Cx = 0, dummyY = 0, CxP = 0;
   if(!ChartTimePriceToXY(0, 0, barTime, midChartPrice, Cx, dummyY))
      return;
   if(!ChartTimePriceToXY(0, 0, prevTime, midChartPrice, CxP, dummyY))
      return;

   if(Cx <= 0 || CxP <= 0)
      return;

   int LengtOfBar = Cx - CxP;
   if(LengtOfBar <= 0)
      return;

   int barWidth = LengtOfBar - Scale;
   if(barWidth < 1)
      barWidth = 1;

   int CyZero = PriceToY(0.0, pMin, pMax, subwinHeight);
   int CyMacd = PriceToY(jmacd, pMin, pMax, subwinHeight);

   int yTop = MathMin(CyZero, CyMacd);
   int yBot = MathMax(CyZero, CyMacd);
   int barHeight = yBot - yTop;
   if(barHeight < 1)
      barHeight = 1;

   if(yTop < 0)
     {
      barHeight += yTop;
      yTop = 0;
     }
   if(barHeight > subwinHeight)
      barHeight = subwinHeight;
   if(barHeight < 1)
      return;

   int prevBarIdx = g_rates_total - 2;
   double prevMacd = (prevBarIdx >= 0 && prevBarIdx < ArraySize(MACDBuffer)) ? MACDBuffer[prevBarIdx] : jmacd;
   int state = GetHistState(jmacd, prevMacd);
   bool invertGrad = (jmacd < 0.0);

   uint MapStorage[];
   FillMap(MapStorage, barWidth, barHeight, Storage[state], invertGrad);

   string D       = g_prefix + "0";
   string MasName = "::" + IntegerToString(g_chart_id) + "_" + UniqueButtonID + "_Maps_0";

   ResourceFree(MasName);
   ResourceCreate(MasName, MapStorage, barWidth, barHeight, 0, 0, 0, COLOR_FORMAT_ARGB_NORMALIZE);

   if(ObjectFind(0, D) < 0)
      ObjectCreate(0, D, OBJ_BITMAP_LABEL, g_subwin, 0, 0);

   ObjectSetString(0, D, OBJPROP_BMPFILE, 0, MasName);
   ObjectSetInteger(0, D, OBJPROP_CORNER, CORNER_LEFT_UPPER);
   ObjectSetInteger(0, D, OBJPROP_XDISTANCE, Cx - barWidth / 2);
   ObjectSetInteger(0, D, OBJPROP_YDISTANCE, yTop);
   ObjectSetInteger(0, D, OBJPROP_BACK, true);
   ObjectSetInteger(0, D, OBJPROP_SELECTABLE, false);
   ObjectSetInteger(0, D, OBJPROP_SELECTED, false);
   ObjectSetInteger(0, D, OBJPROP_HIDDEN, true);

   ChartRedraw(0);
  }

//+------------------------------------------------------------------+
//| Draw all visible gradient histogram bars                         |
//+------------------------------------------------------------------+
bool DrawFull()
  {
   if(IndicatorHidden)
      return false;

   g_subwin = GetIndicatorSubwindow();
   if(g_subwin < 0)
      return false;

   UpdateDimensions();
   UpdateScale();

   int subwinHeight = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS, g_subwin);
   if(subwinHeight <= 0)
      return false;

   int FVB = ChartFirstVisibleBar(0);
   int VB  = ChartVisibleBars(0);
   if(FVB < 0 || VB <= 0)
      return false;

   int vB = FVB - VB;
   int startShift = (vB < 0) ? 0 : vB;
   int endShift   = FVB + 3;

   datetime times[];
   ArraySetAsSeries(times, true);
   int copied = CopyTime(_Symbol, _Period, 0, endShift + 3, times);
   if(copied <= 1)
      return false;

   if(endShift >= copied - 1)
      endShift = copied - 2;
   if(startShift > endShift)
      return false;

   // Get price bounds of subwindow
   double pMin = ChartGetDouble(0, CHART_PRICE_MIN, g_subwin);
   double pMax = ChartGetDouble(0, CHART_PRICE_MAX, g_subwin);
   if(pMax <= pMin)
     {
      double minV = 0.0, maxV = 0.0;
      for(int s = startShift; s <= endShift; s++)
        {
         int idx = g_rates_total - 1 - s;
         if(idx >= 0 && idx < ArraySize(MACDBuffer))
           {
            if(MACDBuffer[idx] != EMPTY_VALUE)
              {
               if(MACDBuffer[idx] < minV) minV = MACDBuffer[idx];
               if(MACDBuffer[idx] > maxV) maxV = MACDBuffer[idx];
              }
            if(SignBuffer[idx] != EMPTY_VALUE)
              {
               if(SignBuffer[idx] < minV) minV = SignBuffer[idx];
               if(SignBuffer[idx] > maxV) maxV = SignBuffer[idx];
              }
           }
        }
      if(maxV <= minV)
        {
         minV = -0.001;
         maxV =  0.001;
        }
      double pad = (maxV - minV) * 0.05;
      pMin = minV - pad;
      pMax = maxV + pad;
     }

   double chartMin = ChartGetDouble(0, CHART_PRICE_MIN, 0);
   double chartMax = ChartGetDouble(0, CHART_PRICE_MAX, 0);
   double midChartPrice = (chartMax > chartMin) ? (chartMin + chartMax) * 0.5 : 0.0;

   // 1. Delete previous objects and free old resources
   DeleteIndicatorObjects();

   // 2. Draw visible bars
   uint MapStorage[];
   int lastBarWidth = 0;
   int barsDrawn = 0;

   for(int shift = startShift; shift <= endShift; shift++)
     {
      int barIdx = g_rates_total - 1 - shift;
      if(barIdx < 0 || barIdx >= ArraySize(MACDBuffer))
         continue;

      double jmacd = MACDBuffer[barIdx];
      if(jmacd == EMPTY_VALUE)
         continue;

      int Cx = 0, CxP = 0, dummyY = 0;
      if(!ChartTimePriceToXY(0, 0, times[shift], midChartPrice, Cx, dummyY))
         continue;

      int LengtOfBar = 0;
      if(ChartTimePriceToXY(0, 0, times[shift + 1], midChartPrice, CxP, dummyY) && Cx > 0 && CxP > 0)
        {
         LengtOfBar = Cx - CxP;
         if(LengtOfBar > 0)
            lastBarWidth = LengtOfBar;
        }
      else if(Cx > 0 && lastBarWidth > 0)
        {
         LengtOfBar = lastBarWidth;
        }
      else
         continue;

      if(LengtOfBar <= 0)
         continue;

      int barWidth = LengtOfBar - Scale;
      if(barWidth < 1)
         barWidth = 1;

      int CyZero = PriceToY(0.0, pMin, pMax, subwinHeight);
      int CyMacd = PriceToY(jmacd, pMin, pMax, subwinHeight);

      int yTop = MathMin(CyZero, CyMacd);
      int yBot = MathMax(CyZero, CyMacd);
      int barHeight = yBot - yTop;
      if(barHeight < 1)
         barHeight = 1;

      if(yTop < 0)
        {
         barHeight += yTop;
         yTop = 0;
        }
      if(barHeight > subwinHeight)
         barHeight = subwinHeight;
      if(barHeight < 1)
         continue;

      double prevMacd = (barIdx > 0) ? MACDBuffer[barIdx - 1] : jmacd;
      int state = GetHistState(jmacd, prevMacd);
      bool invertGrad = (jmacd < 0.0);

      FillMap(MapStorage, barWidth, barHeight, Storage[state], invertGrad);

      string D       = g_prefix + IntegerToString(shift);
      string MasName = "::" + IntegerToString(g_chart_id) + "_" + UniqueButtonID + "_Maps_" + IntegerToString(shift);

      ResourceFree(MasName);
      ResourceCreate(MasName, MapStorage, barWidth, barHeight, 0, 0, 0, COLOR_FORMAT_ARGB_NORMALIZE);

      if(ObjectFind(0, D) < 0)
         ObjectCreate(0, D, OBJ_BITMAP_LABEL, g_subwin, 0, 0);

      ObjectSetString(0, D, OBJPROP_BMPFILE, 0, MasName);
      ObjectSetInteger(0, D, OBJPROP_CORNER, CORNER_LEFT_UPPER);
      ObjectSetInteger(0, D, OBJPROP_XDISTANCE, Cx - barWidth / 2);
      ObjectSetInteger(0, D, OBJPROP_YDISTANCE, yTop);
      ObjectSetInteger(0, D, OBJPROP_BACK, true);
      ObjectSetInteger(0, D, OBJPROP_SELECTABLE, false);
      ObjectSetInteger(0, D, OBJPROP_SELECTED, false);
      ObjectSetInteger(0, D, OBJPROP_HIDDEN, true);

      if(shift > g_max_drawn_bar)
         g_max_drawn_bar = shift;
      barsDrawn++;
     }

   ChartRedraw(0);
   return (barsDrawn > 0);
  }

//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {
   if(rates_total < start)
      return(0);

   g_rates_total = rates_total;

   if(prev_calculated <= 0)
     {
      CreateToggleButton();
      BringButtonsToFront();
     }

   int first, bar;
   double price_, fast_jma, slow_jma, jmacd, sign_jma;

   if(prev_calculated > rates_total || prev_calculated <= 0)
     {
      first = 0;
      for(bar = 0; bar <= macd_start; bar++) MACDBuffer[bar] = 0.0;
      for(bar = 0; bar <= start;      bar++)
        {
         SignBuffer[bar] = 0.0;
         ColorSignBuffer[bar] = 0;
        }
     }
   else
      first = prev_calculated - 1;

   static CJJMA JMA1, JMA2, JMA3;

   for(bar = first; bar < rates_total; bar++)
     {
      price_ = PriceSeries(AppliedPrice, bar, open, low, high, close);

      fast_jma = JMA1.JJMASeries(0, prev_calculated, rates_total, 0, Phase_, Fast_JMA, price_, bar, false);
      slow_jma = JMA2.JJMASeries(0, prev_calculated, rates_total, 0, Phase_, Slow_JMA, price_, bar, false);

      jmacd = fast_jma - slow_jma;

      sign_jma = JMA3.JJMASeries(macd_start, prev_calculated, rates_total, 0, Phase_, Signal_JMA, jmacd, bar, false);

      MACDBuffer[bar] = jmacd;
      SignBuffer[bar] = sign_jma;

      if(bar > 0)
        {
         if(sign_jma >= SignBuffer[bar - 1])
            ColorSignBuffer[bar] = 0; // Rising
         else
            ColorSignBuffer[bar] = 1; // Falling
        }
      else
        {
         ColorSignBuffer[bar] = 0;
        }
     }

   static datetime tmpTime = 0;
   g_initialized = true;

   if(!IndicatorHidden)
     {
      datetime curBarTime = time[rates_total - 1];
      datetime prevBarTime = (rates_total >= 2) ? time[rates_total - 2] : 0;
      double curMacd = MACDBuffer[rates_total - 1];

      if(tmpTime != curBarTime || g_max_drawn_bar <= 0)
        {
         if(DrawFull())
            tmpTime = curBarTime;
        }
      else
        {
         DrawFirst(curBarTime, prevBarTime, curMacd);
        }
     }

   return(rates_total);
  }

//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
   if(id == CHARTEVENT_OBJECT_CLICK)
     {
      string altId = "_" + UniqueButtonID + IndicatorObjPrefix + "_BT_";
      if(sparam == buttonId || sparam == ToggleButtonName || sparam == altId)
        {
         ToggleIndicatorVisibility();
         return;
        }
     }

   if(id >= CHARTEVENT_CUSTOM)
      return;

   if(id == CHARTEVENT_CHART_CHANGE && g_initialized)
     {
      BringButtonsToFront();
      if(!IndicatorHidden)
        {
         DrawFull();
        }
     }
  }
//+------------------------------------------------------------------+
