forked from rosh/MQL5Book
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("");
| |||
}
| |||
//+------------------------------------------------------------------+
|