﻿//+------------------------------------------------------------------+
//|                                             nn_CrosshairSync.mq5 |
//|                         MT5 port by Claude (original: nicholishen)|
//|                            http://www.reddit.com/u/nicholishenFX |
//+------------------------------------------------------------------+
#property copyright "nicholishen"
#property link      "http://www.reddit.com/u/nicholishenFX"
#property version   "2.13"
#property indicator_chart_window
#property indicator_plots 0

//--- input parameters
input string           hot_key         = "t";               // Hot-key to toggle crosshairs
input color            cross_col       = clrSeaGreen;       // Crosshair color
input ENUM_BASE_CORNER button_corner   = CORNER_LEFT_LOWER; // Corner for toggle button
input ENUM_LINE_STYLE  line_style      = STYLE_SOLID;       // Line style
input bool             show_all_charts = true;              // Show on all charts

//+------------------------------------------------------------------+
//| Mode constants (mirrors original MQ4)                            |
//+------------------------------------------------------------------+
#define CROSS_MOVE_FIRST   0
#define CROSS_MOVE_SECOND  1
#define CROSS_LOCK_ALL     2

//+------------------------------------------------------------------+
//| CCrosshair — one crosshair instance attached to one chart        |
//+------------------------------------------------------------------+
class CCrosshair
{
private:
   long              m_chart;
   color             m_color;
   ENUM_LINE_STYLE   m_style;
   int               m_mode;

   string            m_hline1, m_hline2;
   string            m_vline1, m_vline2;
   string            m_trend;
   string            m_label;

   string            UniqueName(const string base)
   { return base + "_" + IntegerToString(m_chart) + "_" + IntegerToString(GetTickCount()); }

   void              SafeDelete(const string name)
   { if(name != "" && ObjectFind(m_chart, name) >= 0) ObjectDelete(m_chart, name); }

   double            ObjPrice(const string name)
   { return ObjectGetDouble(m_chart, name, OBJPROP_PRICE); }

   datetime          ObjTime(const string name)
   { return (datetime)ObjectGetInteger(m_chart, name, OBJPROP_TIME); }

   void              SetHLinePrice(const string name, double price)
   { ObjectSetDouble(m_chart, name, OBJPROP_PRICE, price); }

   void              SetVLineTime(const string name, datetime t)
   { ObjectSetInteger(m_chart, name, OBJPROP_TIME, t); }

   void              SetTooltip(const string name, const string tip)
   { ObjectSetString(m_chart, name, OBJPROP_TOOLTIP, tip); }

   void              CreateHLine(string &nameOut, double price)
   {
      nameOut = UniqueName("HLINE");
      ObjectCreate(m_chart, nameOut, OBJ_HLINE, 0, 0, price);
      ObjectSetInteger(m_chart, nameOut, OBJPROP_COLOR,      m_color);
      ObjectSetInteger(m_chart, nameOut, OBJPROP_STYLE,      m_style);
      ObjectSetInteger(m_chart, nameOut, OBJPROP_ZORDER,     2);
      ObjectSetInteger(m_chart, nameOut, OBJPROP_SELECTABLE, false);
   }

   void              CreateVLine(string &nameOut, datetime t)
   {
      nameOut = UniqueName("VLINE");
      ObjectCreate(m_chart, nameOut, OBJ_VLINE, 0, t, 0);
      ObjectSetInteger(m_chart, nameOut, OBJPROP_COLOR,      m_color);
      ObjectSetInteger(m_chart, nameOut, OBJPROP_STYLE,      m_style);
      ObjectSetInteger(m_chart, nameOut, OBJPROP_ZORDER,     2);
      ObjectSetInteger(m_chart, nameOut, OBJPROP_SELECTABLE, false);
   }

   void              UpdateConnector(datetime t1, double p1, datetime t2, double p2)
   {
      // Update trend line endpoints
      ObjectSetInteger(m_chart, m_trend, OBJPROP_TIME,  0, t1);
      ObjectSetDouble (m_chart, m_trend, OBJPROP_PRICE, 0, p1);
      ObjectSetInteger(m_chart, m_trend, OBJPROP_TIME,  1, t2);
      ObjectSetDouble (m_chart, m_trend, OBJPROP_PRICE, 1, p2);

      // Build label: "price: N bars, M points"
      ENUM_TIMEFRAMES tf     = (ENUM_TIMEFRAMES)ChartPeriod(m_chart);
      string          symbol = ChartSymbol(m_chart);
      int             digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS);
      double          point  = SymbolInfoDouble(symbol, SYMBOL_POINT);

      int bar1  = iBarShift(symbol, tf, t1);
      int bar2  = iBarShift(symbol, tf, t2);
      int bars  = MathAbs(bar1 - bar2);
      int pts   = (int)MathRound((p2 - p1) / point);

      string des = DoubleToString(p2, digits) + ": " +
                   IntegerToString(bars) + " bars, " +
                   IntegerToString(pts)  + " points";

      ObjectSetString(m_chart, m_trend, OBJPROP_TOOLTIP, des);
      ObjectSetString(m_chart, m_label, OBJPROP_TEXT,    des);

      // Position label ahead of t2
      int zoom = (int)ChartGetInteger(m_chart, CHART_SCALE);
      int mult;
      switch(zoom)
      {
         case 0:  mult = 20; break;
         case 1:  mult = 10; break;
         case 2:  mult = 5;  break;
         case 3:  mult = 3;  break;
         default: mult = 2;  break;
      }
      datetime label_t = iTime(symbol, tf, bar2 - mult);
      if(label_t <= 0)
         label_t = t2 + (datetime)(PeriodSeconds(tf) * mult);

      ObjectSetInteger(m_chart, m_label, OBJPROP_TIME,  0, label_t);
      ObjectSetDouble (m_chart, m_label, OBJPROP_PRICE, 0, p2);
   }

   void              RemoveAll()
   {
      SafeDelete(m_hline1); m_hline1 = "";
      SafeDelete(m_hline2); m_hline2 = "";
      SafeDelete(m_vline1); m_vline1 = "";
      SafeDelete(m_vline2); m_vline2 = "";
      SafeDelete(m_trend);  m_trend  = "";
      SafeDelete(m_label);  m_label  = "";
   }

   void              CreateFirst()
   {
      string symbol = ChartSymbol(m_chart);
      double bid    = SymbolInfoDouble(symbol, SYMBOL_BID);
      if(bid <= 0) bid = iClose(symbol, PERIOD_CURRENT, 0);
      CreateHLine(m_hline1, bid);
      CreateVLine(m_vline1, TimeCurrent());
   }

   void              CreateSecond()
   {
      string   symbol = ChartSymbol(m_chart);
      double   bid    = SymbolInfoDouble(symbol, SYMBOL_BID);
      if(bid <= 0) bid = iClose(symbol, PERIOD_CURRENT, 0);
      datetime now    = TimeCurrent();

      CreateHLine(m_hline2, bid);
      CreateVLine(m_vline2, now);

      // Trend connector
      m_trend = UniqueName("Connector");
      datetime t1 = ObjTime(m_vline1);
      double   p1 = ObjPrice(m_hline1);
      ObjectCreate(m_chart, m_trend, OBJ_TREND, 0, t1, p1, now, bid);
      ObjectSetInteger(m_chart, m_trend, OBJPROP_COLOR,      m_color);
      ObjectSetInteger(m_chart, m_trend, OBJPROP_RAY_RIGHT,  false);
      ObjectSetInteger(m_chart, m_trend, OBJPROP_STYLE,      STYLE_DOT);
      ObjectSetInteger(m_chart, m_trend, OBJPROP_SELECTABLE, false);

      // Text label
      m_label = UniqueName("ConnectorLabel");
      ObjectCreate(m_chart, m_label, OBJ_TEXT, 0, now, bid);
      ObjectSetInteger(m_chart, m_label, OBJPROP_ANCHOR,    ANCHOR_LEFT_UPPER);
      ObjectSetInteger(m_chart, m_label, OBJPROP_COLOR,     m_color);
      ObjectSetInteger(m_chart, m_label, OBJPROP_FONTSIZE,  8);
      ObjectSetInteger(m_chart, m_label, OBJPROP_SELECTABLE,false);
   }

public:
   CCrosshair *next;

   CCrosshair(long chart, color col = clrWhite, ENUM_LINE_STYLE style = STYLE_SOLID)
      : m_chart(chart), m_color(col), m_style(style), m_mode(-1), next(NULL),
        m_hline1(""), m_hline2(""), m_vline1(""), m_vline2(""),
        m_trend(""), m_label("")
   {
      DoubleClick(); // enter CROSS_MOVE_FIRST immediately
   }

  ~CCrosshair() { RemoveAll(); }

   long Chart() { return m_chart; }

   void DoubleClick()
   {
      m_mode = (m_mode + 1 > CROSS_LOCK_ALL) ? CROSS_MOVE_FIRST : m_mode + 1;
      if(m_mode == CROSS_MOVE_FIRST)
      {
         RemoveAll();
         CreateFirst();
      }
      if(m_mode == CROSS_MOVE_SECOND)
         CreateSecond();
      // CROSS_LOCK_ALL: lines stay, no further updates
   }

   void Mouse(datetime time, double price)
   {
      if(time <= 0 || price <= 0) return;

      string symbol = ChartSymbol(m_chart);
      int    digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS);
      string tip    = TimeToString(time) + "\n" + DoubleToString(price, digits);

      if(m_mode == CROSS_MOVE_FIRST)
      {
         SetVLineTime(m_vline1, time);
         SetHLinePrice(m_hline1, price);
         SetTooltip(m_vline1, tip);
         SetTooltip(m_hline1, tip);
      }
      else if(m_mode == CROSS_MOVE_SECOND)
      {
         SetVLineTime(m_vline2, time);
         SetHLinePrice(m_hline2, price);
         SetTooltip(m_vline2, tip);
         SetTooltip(m_hline2, tip);
         UpdateConnector(ObjTime(m_vline1), ObjPrice(m_hline1), time, price);
      }
      // CROSS_LOCK_ALL: do nothing

      ChartRedraw(m_chart);
   }
};

//+------------------------------------------------------------------+
//| CCrossManager                                                     |
//+------------------------------------------------------------------+
class CCrossManager
{
private:
   color             m_color;
   ENUM_LINE_STYLE   m_style;
   ENUM_BASE_CORNER  m_corner;
   string            m_hotkey;
   bool              m_all_charts;
   bool              m_btn_state;
   int               m_clicks;
   string            m_btn_name;
   CCrosshair       *m_head;

   void AddCross(CCrosshair *c) { c.next = m_head; m_head = c; }

   void ClearAll()
   {
      CCrosshair *cur = m_head;
      while(cur != NULL) { CCrosshair *n = cur.next; delete cur; cur = n; }
      m_head = NULL;
   }

   void ForEachDoubleClick()
   { for(CCrosshair *c = m_head; c != NULL; c = c.next) c.DoubleClick(); }

   void ForEachMouse(datetime t, double p)
   { for(CCrosshair *c = m_head; c != NULL; c = c.next) c.Mouse(t, p); }

   void CreateButton()
   {
      ObjectDelete(0, m_btn_name);
      ObjectCreate(0, m_btn_name, OBJ_BUTTON, 0, 0, 0);
      ObjectSetInteger(0, m_btn_name, OBJPROP_CORNER,     m_corner);
      ObjectSetInteger(0, m_btn_name, OBJPROP_XSIZE,      90);
      ObjectSetInteger(0, m_btn_name, OBJPROP_YSIZE,      22);
      ObjectSetInteger(0, m_btn_name, OBJPROP_FONTSIZE,   8);
      ObjectSetString (0, m_btn_name, OBJPROP_FONT,       "Arial");
      ObjectSetInteger(0, m_btn_name, OBJPROP_SELECTABLE, false);
      ObjectSetInteger(0, m_btn_name, OBJPROP_ZORDER,     50);
      switch(m_corner)
      {
         case CORNER_RIGHT_LOWER: ObjectSetInteger(0,m_btn_name,OBJPROP_XDISTANCE,25); ObjectSetInteger(0,m_btn_name,OBJPROP_YDISTANCE,25); break;
         case CORNER_LEFT_LOWER:  ObjectSetInteger(0,m_btn_name,OBJPROP_XDISTANCE,0);  ObjectSetInteger(0,m_btn_name,OBJPROP_YDISTANCE,25); break;
         case CORNER_RIGHT_UPPER: ObjectSetInteger(0,m_btn_name,OBJPROP_XDISTANCE,25); ObjectSetInteger(0,m_btn_name,OBJPROP_YDISTANCE,0);  break;
         default:                 ObjectSetInteger(0,m_btn_name,OBJPROP_XDISTANCE,0);  ObjectSetInteger(0,m_btn_name,OBJPROP_YDISTANCE,0);  break;
      }
      UpdateButton();
   }

   void UpdateButton()
   {
      if(m_btn_state)
      {
         ObjectSetString (0, m_btn_name, OBJPROP_TEXT,   "X-Hair ON");
         ObjectSetInteger(0, m_btn_name, OBJPROP_BGCOLOR, m_color);
         ObjectSetInteger(0, m_btn_name, OBJPROP_COLOR,   clrWhite);
      }
      else
      {
         ObjectSetString (0, m_btn_name, OBJPROP_TEXT,   "X-Hair OFF");
         ObjectSetInteger(0, m_btn_name, OBJPROP_BGCOLOR, clrDimGray);
         ObjectSetInteger(0, m_btn_name, OBJPROP_COLOR,   clrWhite);
      }
      ObjectSetInteger(0, m_btn_name, OBJPROP_STATE, false);
      ChartRedraw(0);
   }

   void Show(bool mode)
   {
      if(mode)
      {
         long ch = ChartFirst();
         while(ch >= 0)
         {
            if(m_all_charts || ChartSymbol(ch) == Symbol())
            {
               AddCross(new CCrosshair(ch, m_color, m_style));
               ChartRedraw(ch);
            }
            long nxt = ChartNext(ch);
            if(nxt == ch || nxt < 0) break;
            ch = nxt;
         }
      }
      else
      {
         ClearAll();
         long ch = ChartFirst();
         while(ch >= 0)
         {
            ChartRedraw(ch);
            long nxt = ChartNext(ch);
            if(nxt == ch || nxt < 0) break;
            ch = nxt;
         }
      }
   }

   void HandleMouse(const long lparam, const double dparam)
   {
      datetime time  = 0;
      double   price = 0;
      int      sub   = 0;
      ChartXYToTimePrice(0, (int)lparam, (int)dparam, sub, time, price);
      ForEachMouse(time, price);
   }

public:
   CCrossManager(color col, ENUM_BASE_CORNER corner, ENUM_LINE_STYLE style,
                 string hotkey, bool all_charts)
      : m_color(col), m_corner(corner), m_style(style),
        m_hotkey(hotkey), m_all_charts(all_charts),
        m_btn_state(false), m_clicks(0), m_head(NULL)
   {
      m_btn_name = "__xhair_btn_" + IntegerToString(ChartID()) + "__";
      ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true);
      CreateButton();
   }

  ~CCrossManager()
   {
      ClearAll();
      ObjectDelete(0, m_btn_name);
      ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, false);
      ChartRedraw(0);
   }

   void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
   {
      // Hot-key toggle
      if(id == CHARTEVENT_KEYDOWN)
      {
         string key = ShortToString(TranslateKey((short)lparam));
         if(key == m_hotkey)
         {
            m_btn_state = !m_btn_state;
            UpdateButton();
            Show(m_btn_state);
         }
         return;
      }

      // Button click toggle
      if(id == CHARTEVENT_OBJECT_CLICK && sparam == m_btn_name)
      {
         m_btn_state = !m_btn_state;
         UpdateButton();
         Show(m_btn_state);
         return;
      }

      if(!m_btn_state) return;

      // Mouse move → track crosshair
      if(id == CHARTEVENT_MOUSE_MOVE)
      {
         HandleMouse(lparam, dparam);
         return;
      }

      // Chart click → double-click detection (same 200ms window as original)
      if(id == CHARTEVENT_CLICK)
      {
         m_clicks++;
         if(m_clicks >= 2)
            ForEachDoubleClick();
         EventSetMillisecondTimer(200);
         return;
      }
   }

   void OnTimer()
   {
      EventKillTimer();
      m_clicks = 0;
   }
};

//+------------------------------------------------------------------+
CCrossManager *cross_mgr = NULL;

int OnInit()
{
   cross_mgr = new CCrossManager(cross_col, button_corner, line_style, hot_key, show_all_charts);
   if(cross_mgr == NULL) return INIT_FAILED;
   return INIT_SUCCEEDED;
}

void OnDeinit(const int reason)
{
   if(cross_mgr != NULL) { delete cross_mgr; cross_mgr = NULL; }
}

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[])
{
   return rates_total;
}

void OnChartEvent(const int id, const long &lparam,
                  const double &dparam, const string &sparam)
{
   if(cross_mgr != NULL)
      cross_mgr.OnChartEvent(id, lparam, dparam, sparam);
}

void OnTimer()
{
   if(cross_mgr != NULL)
      cross_mgr.OnTimer();
}
//+------------------------------------------------------------------+