ChartObjectsCopyPaste

Author: Copyright 2024, Marketeer
Miscellaneous
It issuies visual alerts to the screen
0 Views
0 Downloads
0 Favorites
ChartObjectsCopyPaste
//+------------------------------------------------------------------+
//|                                        ChartObjectsCopyPaste.mq5 |
//|                                        Copyright 2024, Marketeer |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, Marketeer"
#property link      "https://www.mql5.com/en/users/marketeer"
#property version   "1.0"
#property description "Copy && paste selected graphical objects between charts via Windows clipboard as text.\n\nUse Ctrl+Q on a source chart, then Ctrl+J on a target chart.\n"
#property description "Based on ObjectGroupEdit.mq5 from the algotrading book: https://www.mql5.com/en/book/applications/objects/objects_properties_get_set"

#property indicator_chart_window
#property indicator_buffers 0
#property indicator_plots   0

#include <MQL5Book/ObjectMonitorExt.mqh>

// !!! UNCOMMENT THIS #define BEFORE COMPILATION
//     (this is aguard to pass automatic check-up of the Codebase)
// #define DLL_LINK

#define PUSH(A,V) (A[ArrayResize(A, ArraySize(A) + 1) - 1] = V)


input bool MakeAllSelectable = false;
input bool LogDetails = false;


class ObjectMonitorClipboard: public ObjectMonitor
{
public:
   ObjectMonitorClipboard(const string objid, const int &flags[]): ObjectMonitor(objid, flags)
   {
   }
   
   string serialize() const
   {
      const long type = ((ObjectMonitorInterface *)&this).get(OBJPROP_TYPE);
      const string name = ((ObjectMonitorInterface *)&this).name();
      
      const string data = EnumToString((ENUM_OBJECT)type) + "\t" + name + "\t" + (string)type + "\t" + (string)ObjectFind(0, name) +
         ((ObjectMonitorBase<long,ENUM_OBJECT_PROPERTY_INTEGER> *)(m[0][])).serialize() +
         ((ObjectMonitorBase<double,ENUM_OBJECT_PROPERTY_DOUBLE> *)(m[1][])).serialize() +
         ((ObjectMonitorBase<string,ENUM_OBJECT_PROPERTY_STRING> *)(m[2][])).serialize();
      return data;
   }
   
   void deserialize(const string &lines[], const int start, const int stop)
   {
      string type2name[];
      for(int j = start; j < stop; j++)
      {
        if(StringSplit(lines[j], '\t', type2name) < 1) continue; // skip empty lines
        
        if(type2name[0] == "enum ENUM_OBJECT_PROPERTY_INTEGER")
        {
          j += ((ObjectMonitorBase<long,ENUM_OBJECT_PROPERTY_INTEGER> *)(m[0][])).deserialize(lines, j + 1, stop);
        }
        else if(type2name[0] == "enum ENUM_OBJECT_PROPERTY_DOUBLE")
        {
          j += ((ObjectMonitorBase<double,ENUM_OBJECT_PROPERTY_DOUBLE> *)(m[1][])).deserialize(lines, j + 1, stop);
        }
        else if(type2name[0] == "enum ENUM_OBJECT_PROPERTY_STRING")
        {
          j += ((ObjectMonitorBase<string,ENUM_OBJECT_PROPERTY_STRING> *)(m[2][])).deserialize(lines, j + 1, stop);
        }
      }
   }
   
};

int consts[2048];
string selected[];
ObjectMonitorClipboard *objects[];

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
void OnInit()
{
   for(int i = 0; i < ArraySize(consts); ++i)
   {
      consts[i] = i;
   }
   
   EventSetTimer(1);
}

//+------------------------------------------------------------------+
//| Monitor properties of objects selected on the chart              |
//+------------------------------------------------------------------+
void TrackSelectedObjects()
{
   for(int j = 0; j < ArraySize(objects); ++j)
   {
      delete objects[j];
   }
   
   ArrayResize(objects, 0, ArraySize(selected));

   for(int i = 0; i < ArraySize(selected); ++i)
   {
      PUSH(objects, new ObjectMonitorClipboard(selected[i], consts));
   }
}

//+------------------------------------------------------------------+
//| Timer event handler                                              |
//+------------------------------------------------------------------+
void OnTimer()
{
   // collect names of selected objects in the following array
   string updates[];
   const int n = ObjectsTotal(0);
   for(int i = 0; i < n; ++i)
   {
      const string name = ObjectName(0, i);
      
      if(MakeAllSelectable && !ObjectGetInteger(0, name, OBJPROP_SELECTABLE))
      {
         ObjectSetInteger(0, name, OBJPROP_SELECTABLE, true);
      }
      
      if(ObjectGetInteger(0, name, OBJPROP_SELECTED))
      {
         PUSH(updates, name);
      }
   }
   
   if(ArraySize(selected) != ArraySize(updates))
   {
      ArraySwap(selected, updates);
      Comment("Selected objects: ", ArraySize(selected));
      TrackSelectedObjects();
   }
}

//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//| (dummy here, required for indicator)                             |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const int begin,
                const double &price[])
{
   return rates_total;
}

//+------------------------------------------------------------------+
//| Chart event handler                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
   if(id == CHARTEVENT_KEYDOWN)
   {
      const ulong c = (ulong)TerminalInfoInteger(TERMINAL_KEYSTATE_CONTROL);
      // PrintFormat("%X %X", c, lparam); // Q 0x51, J 0x4A
      if(c > 1)
      {
        if(lparam == 'Q')
        {
          string content;
          for(int i = 0; i < ArraySize(selected); ++i)
          {
             Print("Copy " + objects[i].name());
             content += objects[i].serialize() + "\n";
          }
          if(LogDetails) Print("Writing to clipboard...\n", content);
          WriteClipboard(content);
          Comment(StringFormat("%d objects sent to clipboard", ArraySize(selected)));
        }
        else if(lparam == 'J')
        {
          const string text = ReadClipboard();
          if(LogDetails) Print("Pasted from clipboard: \n" + text);
          const int n = ParseClipboardObjects(text);
          Comment(StringFormat("%d objects received from clipboard", n));
        }
      }
   }
}

//+------------------------------------------------------------------+
//| Finalization handler                                             |
//+------------------------------------------------------------------+
void OnDeinit(const int)
{
   for(int j = 0; j < ArraySize(objects); ++j)
   {
      delete objects[j];
   }
   Comment("");
}

//+------------------------------------------------------------------+
//| Read clipboard text and convert it to chart objects              |
//+------------------------------------------------------------------+
int ParseClipboardObjects(const string text)
{
   string lines[];
   int objs[];
   
   const int n = StringSplit(text, '\n', lines);
   for(int i = 0; i < n; i++)
   {
     StringTrimRight(lines[i]); // remove trailing '\r'
     if(StringFind(lines[i], "OBJ_") == 0)
     {
       PUSH(objs, i);
     }
   }
   
   const int m = ArraySize(objs);
   
   for(int i = 0; i < m; i++)
   {
     string type2name[];
     if(StringSplit(lines[objs[i]], '\t', type2name) == 4)
     {
        if(ObjectFind(0, type2name[1]) >= 0)
        {
           PrintFormat("Object '%s' already exists, skipping", type2name[1]);
           continue;
        }
        Print("Paste ", type2name[1]);
        ObjectCreate(0, type2name[1], (ENUM_OBJECT)type2name[2], (int)type2name[3] /* !subwindow is not adjustable in MQL5! */, 0, 0);
        ObjectMonitorClipboard temp(type2name[1], consts);
        temp.deserialize(lines, objs[i] + 1, i < m - 1 ? objs[i + 1] : n);
     }
   }
   ChartRedraw();
   return m;
}

//+------------------------------------------------------------------+

#ifdef DLL_LINK
//+------------------------------------------------------------------+
//| DLL-related permissions are required!                            |
//| Clipboard reading is the task implemented by DLLs.               |
//+------------------------------------------------------------------+
#include <WinApi/winuser.mqh>
#include <WinApi/winbase.mqh>

//+------------------------------------------------------------------+
//| We need thess defines and import for accessing Windows clipboard |
//+------------------------------------------------------------------+
#define CF_UNICODETEXT 13 // one of standard clipboard formats
#define GMEM_MOVEABLE 0x0002
#define GMEM_ZEROINIT 0x0040

#import "kernel32.dll"
string lstrcatW(PVOID string1, const string string2);
#import

//+------------------------------------------------------------------+
//| Example function to use DLL for reading Windows clipboard        |
//+------------------------------------------------------------------+
string ReadClipboard()
{
   string text;
   if(OpenClipboard(NULL))
   {
      HANDLE h = GetClipboardData(CF_UNICODETEXT);
      PVOID p = GlobalLock(h);
      if(p != 0)
      {
         text = lstrcatW(p, "");
         GlobalUnlock(h);
      }
      CloseClipboard();
   }
   return text;
}

//+------------------------------------------------------------------+
//| Example function to use DLL for writing Windows clipboard        |
//+------------------------------------------------------------------+
void WriteClipboard(const string text)
{
   if(OpenClipboard(NULL))
   {
      EmptyClipboard();

      HANDLE h = GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, (StringLen(text) + 1) * 2);
      
      PVOID p = GlobalLock(h);
      if(p != 0)
      {
         /**/lstrcatW(p, text);
         SetClipboardData(CF_UNICODETEXT, h);
         GlobalUnlock(h);
      }
      CloseClipboard();
   }
}

#else

string ReadClipboard()
{
  Alert("ReadClipboard stub: Please uncomment #define DLL_LINK and recompile");
  return "";
}

void WriteClipboard(const string text)
{
  Alert("WriteClipboard stub: Please uncomment #define DLL_LINK and recompile");
}

#endif
//+------------------------------------------------------------------+

Comments