295 lines
9.3 KiB
MQL5
295 lines
9.3 KiB
MQL5
//+------------------------------------------------------------------+
|
|
//| ObjectSizeLabel.mq5 |
|
|
//| Copyright 2021, MetaQuotes Ltd. |
|
|
//| https://www.mql5.com |
|
|
//| |
|
|
//| The script slowly moves a label around the chart and |
|
|
//| changes anchor point on the label. |
|
|
//| Label dimensions are taking into account. |
|
|
//+------------------------------------------------------------------+
|
|
#property script_show_inputs
|
|
|
|
input ENUM_BASE_CORNER Corner = CORNER_LEFT_UPPER;
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Helper function to concatenate strings from given array |
|
|
//+------------------------------------------------------------------+
|
|
string StringImplode(const string &lines[], const string glue,
|
|
const int start = 0, const int stop = -1)
|
|
{
|
|
const int n = stop == -1 ? ArraySize(lines) : fmin(stop, ArraySize(lines));
|
|
string result = "";
|
|
for(int i = start; i < n; i++)
|
|
{
|
|
result += (i > start ? glue : "") + lines[i];
|
|
}
|
|
return result;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Neat stringifier function for enumeration element |
|
|
//+------------------------------------------------------------------+
|
|
template<typename E>
|
|
string GetEnumString(const E e)
|
|
{
|
|
string words[];
|
|
StringSplit(EnumToString(e), '_', words);
|
|
for(int i = 0; i < ArraySize(words); ++i) StringToLower(words[i]);
|
|
return StringImplode(words, " ", 1);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Coordinates of bounding rectangle relative to anchor point |
|
|
//+------------------------------------------------------------------+
|
|
struct Margins
|
|
{
|
|
int nearX; // padding between label sides and window edges
|
|
int nearY; // adjacent to the selected corner
|
|
int farX; // padding between label sides and window edges
|
|
int farY; // opposite to the selected corner
|
|
};
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Get bounding rectangle around anchor point according to axes |
|
|
//| directions specific to selected corner. |
|
|
//| For example X may increase from left to right, or vice versa, |
|
|
//| and Y may run from up to down or in reverse. |
|
|
//+------------------------------------------------------------------+
|
|
Margins GetMargins(const ENUM_BASE_CORNER corner, const ENUM_ANCHOR_POINT anchor,
|
|
int dx, int dy)
|
|
{
|
|
Margins margins = {}; // zero margins by default
|
|
|
|
#define LEFT 0x1
|
|
#define LOWER 0x2
|
|
#define RIGHT 0x4
|
|
#define UPPER 0x8
|
|
#define CENTER 0x16
|
|
|
|
const int corner_flags[] = // flags of ENUM_BASE_CORNER elements
|
|
{
|
|
LEFT | UPPER,
|
|
LEFT | LOWER,
|
|
RIGHT | LOWER,
|
|
RIGHT | UPPER
|
|
};
|
|
|
|
const int anchor_flags[] = // flags of ENUM_ANCHOR_POINT elements
|
|
{
|
|
LEFT | UPPER,
|
|
LEFT,
|
|
LEFT | LOWER,
|
|
LOWER,
|
|
RIGHT | LOWER,
|
|
RIGHT,
|
|
RIGHT | UPPER,
|
|
UPPER,
|
|
CENTER
|
|
};
|
|
|
|
if(anchor == ANCHOR_CENTER)
|
|
{
|
|
margins.nearX = margins.farX = dx / 2;
|
|
margins.nearY = margins.farY = dy / 2;
|
|
}
|
|
else
|
|
{
|
|
const int mask = corner_flags[corner] & anchor_flags[anchor];
|
|
|
|
if((mask & (LEFT | RIGHT)) != 0) // both corner and anchor are same-side
|
|
{
|
|
margins.farX = dx;
|
|
}
|
|
else // corner is left/right-side but anchor is right/left, or vice versa
|
|
{
|
|
if((anchor_flags[anchor] & (LEFT | RIGHT)) == 0)
|
|
{
|
|
margins.nearX = dx / 2;
|
|
margins.farX = dx / 2;
|
|
}
|
|
else
|
|
{
|
|
margins.nearX = dx;
|
|
}
|
|
}
|
|
|
|
if((mask & (UPPER | LOWER)) != 0) // both corner and anchor are same-side
|
|
{
|
|
margins.farY = dy;
|
|
}
|
|
else // corner is up/down-side but anchor is down/up, or vice versa
|
|
{
|
|
if((anchor_flags[anchor] & (UPPER | LOWER)) == 0)
|
|
{
|
|
margins.farY = dy / 2;
|
|
margins.nearY = dy / 2;
|
|
}
|
|
else
|
|
{
|
|
margins.nearY = dy;
|
|
}
|
|
}
|
|
}
|
|
|
|
#undef LEFT
|
|
#undef LOWER
|
|
#undef RIGHT
|
|
#undef UPPER
|
|
#undef CENTER
|
|
|
|
return margins;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Script program start function |
|
|
//+------------------------------------------------------------------+
|
|
void OnStart()
|
|
{
|
|
// characteristic colors for different anchor points
|
|
const color colors[] =
|
|
{
|
|
clrRed,
|
|
clrMagenta,
|
|
clrBlue,
|
|
clrLimeGreen,
|
|
clrOrange,
|
|
clrDarkTurquoise,
|
|
clrSienna,
|
|
clrLightSalmon,
|
|
clrGray,
|
|
};
|
|
|
|
// init
|
|
const int t = ChartWindowOnDropped();
|
|
Comment(EnumToString(Corner));
|
|
|
|
const string name = "ObjSizeLabel";
|
|
int h = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS, t) - 1;
|
|
int w = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS) - 1;
|
|
int x = w / 2;
|
|
int y = h / 2;
|
|
|
|
// create and setup the label
|
|
ObjectCreate(0, name, OBJ_LABEL, t, 0, 0);
|
|
ObjectSetInteger(0, name, OBJPROP_SELECTABLE, true);
|
|
ObjectSetInteger(0, name, OBJPROP_SELECTED, true);
|
|
ObjectSetInteger(0, name, OBJPROP_CORNER, Corner);
|
|
|
|
// choose monospace font to get stable length of string
|
|
// while changing coordinates
|
|
ObjectSetString(0, name, OBJPROP_FONT, "Consolas");
|
|
|
|
// variables
|
|
int px = 5, py = 5; // diagonal movement
|
|
int dx = 0, dy = 0; // label size will be stored here
|
|
int pass = 0; // animation cycles
|
|
ENUM_ANCHOR_POINT anchor = 0; // random anchor point
|
|
Margins m = {}; // will hold indents from window borders respecting label size
|
|
|
|
const int key = TerminalInfoInteger(TERMINAL_KEYSTATE_SCRLOCK);
|
|
|
|
for( ;!IsStopped(); ++pass)
|
|
{
|
|
if(TerminalInfoInteger(TERMINAL_KEYSTATE_SCRLOCK) != key)
|
|
{
|
|
Sleep(1000);
|
|
continue;
|
|
}
|
|
|
|
// once in a while change anchor point
|
|
if(pass % 75 == 0)
|
|
{
|
|
// ENUM_ANCHOR_POINT consists of 9 elements: get a random one
|
|
const int r = rand() * 8 / 32768 + 1;
|
|
anchor = (ENUM_ANCHOR_POINT)((anchor + r) % 9);
|
|
ObjectSetInteger(0, name, OBJPROP_ANCHOR, anchor);
|
|
ObjectSetInteger(0, name, OBJPROP_COLOR, colors[anchor]);
|
|
const string message = " " + GetEnumString(anchor)
|
|
+ StringFormat("[%3d,%3d] ", x, y);
|
|
ResetLastError();
|
|
ObjectSetString(0, name, OBJPROP_TEXT, message);
|
|
// wait for the changes to take effect
|
|
do
|
|
{
|
|
ChartRedraw();
|
|
Sleep(1);
|
|
}
|
|
while (ObjectGetString(0, name, OBJPROP_TEXT) != message
|
|
&& !IsStopped());
|
|
|
|
// This is not always working as expected, that is
|
|
// OBJPROP_TEXT is changed but OBJPROP_XSIZE/OBJPROP_YSIZE
|
|
// are still 0,0 or something smaller than should be,
|
|
// according to what we get in the next line
|
|
dx = (int)ObjectGetInteger(0, name, OBJPROP_XSIZE);
|
|
dy = (int)ObjectGetInteger(0, name, OBJPROP_YSIZE);
|
|
// This is why we read OBJPROP_XSIZE/OBJPROP_YSIZE
|
|
// once again at the end of the loop
|
|
|
|
m = GetMargins(Corner, anchor, dx, dy);
|
|
|
|
// keep label in current point during anchor change
|
|
x -= px;
|
|
y -= py;
|
|
}
|
|
|
|
// bouncing from window endges, prevent clipping by x
|
|
if(x + px >= w - m.farX)
|
|
{
|
|
x = w - m.farX + px - 1;
|
|
px = -px;
|
|
}
|
|
else if(x + px < m.nearX)
|
|
{
|
|
x = m.nearX + px;
|
|
px = -px;
|
|
}
|
|
|
|
// bouncing from window endges, prevent clipping by y
|
|
if(y + py >= h - m.farY)
|
|
{
|
|
y = h - m.farY + py - 1;
|
|
py = -py;
|
|
}
|
|
else if(y + py < m.nearY)
|
|
{
|
|
y = m.nearY + py;
|
|
py = -py;
|
|
}
|
|
|
|
// calculate label's new position
|
|
x += px;
|
|
y += py;
|
|
|
|
// update the label content
|
|
ObjectSetString(0, name, OBJPROP_TEXT, " " + GetEnumString(anchor)
|
|
+ StringFormat("[%3d,%3d] ", x, y));
|
|
|
|
// update the label position
|
|
ObjectSetInteger(0, name, OBJPROP_XDISTANCE, x);
|
|
ObjectSetInteger(0, name, OBJPROP_YDISTANCE, y);
|
|
|
|
// animation timeout
|
|
ChartRedraw();
|
|
Sleep(100);
|
|
|
|
// get actual window size for the case it's changed by user
|
|
h = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS, t) - 1;
|
|
w = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS) - 1;
|
|
|
|
// get new dimesions
|
|
dx = (int)ObjectGetInteger(0, name, OBJPROP_XSIZE);
|
|
dy = (int)ObjectGetInteger(0, name, OBJPROP_YSIZE);
|
|
m = GetMargins(Corner, anchor, dx, dy);
|
|
|
|
if(_LastError != 0) // object may be deleted by user
|
|
{
|
|
Print("Terminated: ", _LastError);
|
|
break;
|
|
}
|
|
}
|
|
|
|
ObjectDelete(0, name);
|
|
Comment("");
|
|
}
|
|
//+------------------------------------------------------------------+
|