forked from animatedread/Warrior_EA
203 lines
9.9 KiB
MQL5
203 lines
9.9 KiB
MQL5
//+------------------------------------------------------------------+
|
|
//| StatusLabel.mqh |
|
|
//| AnimateDread |
|
|
//| https://www.mql5.com |
|
|
//+------------------------------------------------------------------+
|
|
#property copyright "AnimateDread"
|
|
#property link "https://www.mql5.com"
|
|
//+------------------------------------------------------------------+
|
|
//| Replaces Comment() for on-chart status text. Comment() draws |
|
|
//| directly onto the chart surface with no background, so status text |
|
|
//| becomes unreadable over candles. OBJ_LABEL also does not render |
|
|
//| embedded '\n' as line breaks (it printed as one truncated line), |
|
|
//| so each line of the status text gets its own OBJ_LABEL, and each |
|
|
//| label gets its own OBJ_RECTANGLE_LABEL background sized to that |
|
|
//| line's actual rendered pixel width via TextGetSize() - not a fixed |
|
|
//| per-character guess, which was oversized on this system's DPI/font |
|
|
//| metrics and left one big box behind the whole block. Some source |
|
|
//| lines are wider than the chart pane itself, which clipped the |
|
|
//| OBJ_LABEL text at the chart edge while its background - sized off |
|
|
//| the full, unclipped string - kept extending to the true width; |
|
|
//| word-wrapping every line to the chart's own pixel width keeps what |
|
|
//| is drawn and what is measured the same string. Separately, |
|
|
//| ObjectSetString(OBJPROP_TEXT) itself silently truncates a label's |
|
|
//| text at ~63 characters regardless of chart width or the pixel wrap |
|
|
//| above - confirmed by several status lines all cutting off almost |
|
|
//| exactly at 63 chars even on a wide chart - so wrapping also |
|
|
//| enforces a hard character cap well under that limit, which is what |
|
|
//| actually keeps every line fully visible. |
|
|
//+------------------------------------------------------------------+
|
|
#define STATUS_LABEL_PREFIX "WarriorStatusLine"
|
|
#define STATUS_LABEL_FONTSIZE 9
|
|
#define STATUS_LABEL_FONT "Consolas"
|
|
#define STATUS_LABEL_PAD_X 3
|
|
#define STATUS_LABEL_PAD_Y 1
|
|
#define STATUS_LABEL_LINE_GAP 2 // extra pixels between successive lines' backgrounds
|
|
#define STATUS_LABEL_RIGHT_MARGIN 20
|
|
#define STATUS_LABEL_MIN_WRAP_WIDTH 150
|
|
#define STATUS_LABEL_MAX_CHARS 55 // safe margin under OBJPROP_TEXT's observed ~63-char truncation
|
|
#define STATUS_LABEL_MAX_LINES 64
|
|
int g_statusLabelLineCount = 0;
|
|
void EnsureStatusLine(int idx)
|
|
{
|
|
string bg = STATUS_LABEL_PREFIX + (string)idx + "BG";
|
|
string txt = STATUS_LABEL_PREFIX + (string)idx + "Text";
|
|
if(ObjectFind(0, bg) < 0)
|
|
{
|
|
ObjectCreate(0, bg, OBJ_RECTANGLE_LABEL, 0, 0, 0);
|
|
ObjectSetInteger(0, bg, OBJPROP_CORNER, CORNER_LEFT_UPPER);
|
|
ObjectSetInteger(0, bg, OBJPROP_BGCOLOR, (color)ColorToARGB(clrBlack, 150));
|
|
ObjectSetInteger(0, bg, OBJPROP_BORDER_TYPE, BORDER_FLAT);
|
|
ObjectSetInteger(0, bg, OBJPROP_COLOR, clrNONE);
|
|
ObjectSetInteger(0, bg, OBJPROP_BACK, false);
|
|
ObjectSetInteger(0, bg, OBJPROP_SELECTABLE, false);
|
|
ObjectSetInteger(0, bg, OBJPROP_SELECTED, false);
|
|
ObjectSetInteger(0, bg, OBJPROP_HIDDEN, true);
|
|
}
|
|
if(ObjectFind(0, txt) < 0)
|
|
{
|
|
ObjectCreate(0, txt, OBJ_LABEL, 0, 0, 0);
|
|
ObjectSetInteger(0, txt, OBJPROP_CORNER, CORNER_LEFT_UPPER);
|
|
ObjectSetInteger(0, txt, OBJPROP_ANCHOR, ANCHOR_LEFT_UPPER);
|
|
ObjectSetInteger(0, txt, OBJPROP_FONTSIZE, STATUS_LABEL_FONTSIZE);
|
|
ObjectSetString(0, txt, OBJPROP_FONT, STATUS_LABEL_FONT);
|
|
ObjectSetInteger(0, txt, OBJPROP_COLOR, clrWhite);
|
|
ObjectSetInteger(0, txt, OBJPROP_BACK, false);
|
|
ObjectSetInteger(0, txt, OBJPROP_SELECTABLE, false);
|
|
ObjectSetInteger(0, txt, OBJPROP_SELECTED, false);
|
|
ObjectSetInteger(0, txt, OBJPROP_HIDDEN, true);
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Appends one already-short-enough chunk to outLines/outCount, |
|
|
//| force-splitting it further if it still exceeds |
|
|
//| STATUS_LABEL_MAX_CHARS (a single word longer than the cap, with |
|
|
//| no spaces to break on). |
|
|
//+------------------------------------------------------------------+
|
|
void AppendStatusChunk(string chunk, string &outLines[], int &outCount)
|
|
{
|
|
int pos = 0;
|
|
int len = StringLen(chunk);
|
|
while(len - pos > STATUS_LABEL_MAX_CHARS)
|
|
{
|
|
ArrayResize(outLines, outCount + 1);
|
|
outLines[outCount++] = StringSubstr(chunk, pos, STATUS_LABEL_MAX_CHARS);
|
|
pos += STATUS_LABEL_MAX_CHARS;
|
|
}
|
|
ArrayResize(outLines, outCount + 1);
|
|
outLines[outCount++] = StringSubstr(chunk, pos);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Greedily breaks one logical line into as many visual lines as |
|
|
//| needed to keep each one's measured TextGetSize() width within |
|
|
//| maxWidth AND its character count within STATUS_LABEL_MAX_CHARS - |
|
|
//| the latter is what actually matters (see this file's header |
|
|
//| comment on ObjectSetString's ~63-char truncation), the pixel width |
|
|
//| check just wraps sooner on a narrow chart. Requires TextSetFont() |
|
|
//| to already have been called by the caller so TextGetSize() |
|
|
//| measures against the same font actually rendered. |
|
|
//+------------------------------------------------------------------+
|
|
void WrapLineInto(string line, int maxWidth, string &outLines[], int &outCount)
|
|
{
|
|
uint dummyW = 0, dummyH = 0;
|
|
TextGetSize(line, dummyW, dummyH);
|
|
if(StringLen(line) == 0 || ((int)dummyW <= maxWidth && StringLen(line) <= STATUS_LABEL_MAX_CHARS))
|
|
{
|
|
ArrayResize(outLines, outCount + 1);
|
|
outLines[outCount++] = line;
|
|
return;
|
|
}
|
|
string words[];
|
|
int wordCount = StringSplit(line, ' ', words);
|
|
string current = "";
|
|
for(int w = 0; w < wordCount; w++)
|
|
{
|
|
string candidate = (StringLen(current) == 0) ? words[w] : current + " " + words[w];
|
|
uint cw = 0, ch = 0;
|
|
TextGetSize(candidate, cw, ch);
|
|
bool tooWide = ((int)cw > maxWidth || StringLen(candidate) > STATUS_LABEL_MAX_CHARS);
|
|
if(tooWide && StringLen(current) > 0)
|
|
{
|
|
AppendStatusChunk(current, outLines, outCount);
|
|
current = words[w];
|
|
}
|
|
else
|
|
current = candidate;
|
|
}
|
|
if(StringLen(current) > 0)
|
|
AppendStatusChunk(current, outLines, outCount);
|
|
}
|
|
void SetStatusLabel(string text, int x = 10, int y = 20)
|
|
{
|
|
//--- TextSetFont's size argument is tenths of a point when negative (matches OBJPROP_FONTSIZE's
|
|
//--- point-based units above) - must be set before any TextGetSize() call since it's global state
|
|
TextSetFont(STATUS_LABEL_FONT, -STATUS_LABEL_FONTSIZE * 10, 0);
|
|
long chartWidthPx = 0;
|
|
ChartGetInteger(0, CHART_WIDTH_IN_PIXELS, 0, chartWidthPx);
|
|
int maxWidth = (int)chartWidthPx - x - STATUS_LABEL_RIGHT_MARGIN;
|
|
if(maxWidth < STATUS_LABEL_MIN_WRAP_WIDTH)
|
|
maxWidth = STATUS_LABEL_MIN_WRAP_WIDTH;
|
|
string rawLines[];
|
|
int rawCount = StringSplit(text, '\n', rawLines);
|
|
string lines[];
|
|
int lineCount = 0;
|
|
for(int r = 0; r < rawCount && lineCount < STATUS_LABEL_MAX_LINES; r++)
|
|
WrapLineInto(rawLines[r], maxWidth, lines, lineCount);
|
|
if(lineCount > STATUS_LABEL_MAX_LINES)
|
|
lineCount = STATUS_LABEL_MAX_LINES;
|
|
int lineY = y;
|
|
for(int i = 0; i < lineCount; i++)
|
|
{
|
|
EnsureStatusLine(i);
|
|
string bg = STATUS_LABEL_PREFIX + (string)i + "BG";
|
|
string txt = STATUS_LABEL_PREFIX + (string)i + "Text";
|
|
uint textW = 0, textH = 0;
|
|
//--- an empty line measures 0x0, which would collapse its background - fall back to a single
|
|
//--- space so blank separator lines still reserve a row of height
|
|
TextGetSize(StringLen(lines[i]) > 0 ? lines[i] : " ", textW, textH);
|
|
ObjectSetInteger(0, bg, OBJPROP_XDISTANCE, x - STATUS_LABEL_PAD_X);
|
|
ObjectSetInteger(0, bg, OBJPROP_YDISTANCE, lineY - STATUS_LABEL_PAD_Y);
|
|
ObjectSetInteger(0, bg, OBJPROP_XSIZE, (int)textW + STATUS_LABEL_PAD_X * 2);
|
|
ObjectSetInteger(0, bg, OBJPROP_YSIZE, (int)textH + STATUS_LABEL_PAD_Y * 2);
|
|
ObjectSetInteger(0, txt, OBJPROP_XDISTANCE, x);
|
|
ObjectSetInteger(0, txt, OBJPROP_YDISTANCE, lineY);
|
|
ObjectSetString(0, txt, OBJPROP_TEXT, lines[i]);
|
|
lineY += (int)textH + STATUS_LABEL_LINE_GAP;
|
|
}
|
|
//--- fewer lines than last call - remove the now-unused trailing line objects rather than
|
|
//--- leaving stale text (e.g. a previous longer status block) hanging below the new one
|
|
for(int i = lineCount; i < g_statusLabelLineCount; i++)
|
|
{
|
|
string bg = STATUS_LABEL_PREFIX + (string)i + "BG";
|
|
string txt = STATUS_LABEL_PREFIX + (string)i + "Text";
|
|
if(ObjectFind(0, bg) >= 0)
|
|
ObjectDelete(0, bg);
|
|
if(ObjectFind(0, txt) >= 0)
|
|
ObjectDelete(0, txt);
|
|
}
|
|
g_statusLabelLineCount = lineCount;
|
|
//--- ObjectSet*() alone does not trigger a repaint - without this the new text/background only
|
|
//--- appears once something else forces a redraw (dragging the chart, clicking an object, a new
|
|
//--- bar), which made updates look stalled between those events
|
|
ChartRedraw(0);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Removes every status line object. Mirrors the old Comment("") |
|
|
//| clear on shutdown - ObjectsDeleteAll(0) elsewhere clears chart |
|
|
//| objects, but callers still need an explicit clear at points where |
|
|
//| only the status text (not the whole chart) should be reset. |
|
|
//+------------------------------------------------------------------+
|
|
void ClearStatusLabel(void)
|
|
{
|
|
for(int i = 0; i < g_statusLabelLineCount; i++)
|
|
{
|
|
string bg = STATUS_LABEL_PREFIX + (string)i + "BG";
|
|
string txt = STATUS_LABEL_PREFIX + (string)i + "Text";
|
|
if(ObjectFind(0, bg) >= 0)
|
|
ObjectDelete(0, bg);
|
|
if(ObjectFind(0, txt) >= 0)
|
|
ObjectDelete(0, txt);
|
|
}
|
|
g_statusLabelLineCount = 0;
|
|
ChartRedraw(0);
|
|
}
|