//+------------------------------------------------------------------+ //| 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); }