forked from paulosalestd/MQL5Book
245 lines
8.8 KiB
MQL5
245 lines
8.8 KiB
MQL5
//+------------------------------------------------------------------+
|
|
//| Tableau.mqh |
|
|
//| Copyright (c) 2021-2022, Marketeer |
|
|
//| https://www.mql5.com/en/users/marketeer |
|
|
//| On-chart table with given number of columns and rows |
|
|
//+------------------------------------------------------------------+
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Defines |
|
|
//+------------------------------------------------------------------+
|
|
#define TBL_FLAG_ROW_0_HEADER 1 // bold text in top-most row
|
|
#define TBL_FLAG_COL_0_HEADER 2 // bold text in left-most column
|
|
#define TBL_CELL_HEIGHT_AUTO -1
|
|
#define TBL_CELL_WIDTH_AUTO 100 // always auto width!
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| UI table |
|
|
//+------------------------------------------------------------------+
|
|
class Tableau
|
|
{
|
|
private:
|
|
const string prefix;
|
|
const ENUM_BASE_CORNER corner;
|
|
const int nrows, ncols;
|
|
int cellHeight, cellWidth;
|
|
const int gap;
|
|
const int fontSize;
|
|
const int flags;
|
|
const string res;
|
|
|
|
const string defaultFont;
|
|
const string boldFont;
|
|
const color bgColor;
|
|
const uchar bgTrans;
|
|
|
|
public:
|
|
static ENUM_ANCHOR_POINT corner2anchor(const ENUM_BASE_CORNER corner)
|
|
{
|
|
return (ENUM_ANCHOR_POINT)(corner * 2);
|
|
};
|
|
|
|
static bool isBottomSide(const ENUM_BASE_CORNER corner)
|
|
{
|
|
return corner == CORNER_LEFT_LOWER || corner == CORNER_RIGHT_LOWER;
|
|
}
|
|
|
|
static bool isRightSide(const ENUM_BASE_CORNER corner)
|
|
{
|
|
return corner == CORNER_RIGHT_UPPER || corner == CORNER_RIGHT_LOWER;
|
|
}
|
|
|
|
Tableau(const string pref, const int rows, const int cols,
|
|
const int height = TBL_CELL_HEIGHT_AUTO, const int width = TBL_CELL_WIDTH_AUTO,
|
|
const ENUM_BASE_CORNER c = CORNER_RIGHT_LOWER, const int g = 8,
|
|
const int fsize = 8, const string font = "Consolas", const string bold = "Arial Black",
|
|
const int mask = TBL_FLAG_COL_0_HEADER,
|
|
const color bgc = 0x808080, const uchar bgt = 0xC0):
|
|
prefix(pref + (string)rows + "x" + (string)cols), corner(c), nrows(rows), ncols(cols),
|
|
cellHeight(height), cellWidth(width), gap(g),
|
|
fontSize(fabs(fsize)), flags(mask), defaultFont(font), boldFont(bold),
|
|
bgColor(bgc), bgTrans(bgt),
|
|
res("::LLET" + (string)ChartID())
|
|
{
|
|
if(height == TBL_CELL_HEIGHT_AUTO) cellHeight = fontSize * 2;
|
|
|
|
const ENUM_ANCHOR_POINT anchor = corner2anchor(corner);
|
|
const bool invertY = isBottomSide(corner);
|
|
const bool invertX = isRightSide(corner);
|
|
|
|
const int w = ncols * (width + gap);
|
|
const int h = nrows * (cellHeight + gap / 2) + gap / 2;
|
|
|
|
uint img[];
|
|
ArrayResize(img, w * h);
|
|
ArrayInitialize(img, (uint)ColorToARGB(bgColor, bgTrans));
|
|
ResourceCreate(res, img, w, h, 0, 0, w, COLOR_FORMAT_ARGB_NORMALIZE);
|
|
|
|
string name = prefix + "_";
|
|
ObjectCreate(0, name, OBJ_BITMAP_LABEL, 0, 0, 0);
|
|
ObjectSetString(0, name, OBJPROP_BMPFILE, 0, res);
|
|
ObjectSetString(0, name, OBJPROP_BMPFILE, 1, res);
|
|
ObjectSetInteger(0, name, OBJPROP_CORNER, corner);
|
|
ObjectSetInteger(0, name, OBJPROP_ANCHOR, anchor);
|
|
ObjectSetInteger(0, name, OBJPROP_XSIZE, w);
|
|
ObjectSetInteger(0, name, OBJPROP_YSIZE, h);
|
|
ObjectSetInteger(0, name, OBJPROP_XDISTANCE, gap / 2);
|
|
ObjectSetInteger(0, name, OBJPROP_YDISTANCE, gap / 2);
|
|
ObjectSetInteger(0, name, OBJPROP_BGCOLOR, ColorToARGB((color)ChartGetInteger(0, CHART_COLOR_GRID), 192));
|
|
ObjectSetInteger(0, name, OBJPROP_BORDER_TYPE, BORDER_FLAT);
|
|
|
|
for(int i = 0; i < nrows; i++)
|
|
{
|
|
int k = invertY ? nrows - i - 1 : i;
|
|
|
|
for(int j = 0; j < ncols; j++)
|
|
{
|
|
int m = invertX ? ncols - j - 1 : j;
|
|
|
|
name = prefix + "_" +(string)i + "_" + (string)j;
|
|
ObjectCreate(0, name, OBJ_LABEL, 0, 0, 0);
|
|
ObjectSetString(0, name, OBJPROP_TEXT, "...");
|
|
ObjectSetInteger(0, name, OBJPROP_CORNER, corner);
|
|
ObjectSetInteger(0, name, OBJPROP_ANCHOR, invertY ? ANCHOR_RIGHT_LOWER : ANCHOR_RIGHT_UPPER /*anchor*/);
|
|
ObjectSetInteger(0, name, OBJPROP_XSIZE, width);
|
|
ObjectSetInteger(0, name, OBJPROP_YSIZE, cellHeight);
|
|
ObjectSetString(0, name, OBJPROP_TEXT, name);
|
|
ObjectSetInteger(0, name, OBJPROP_XDISTANCE, m * width + m * gap + gap);
|
|
ObjectSetInteger(0, name, OBJPROP_YDISTANCE, k * (cellHeight + gap / 2) + gap);
|
|
ObjectSetInteger(0, name, OBJPROP_COLOR, ChartGetInteger(0, CHART_COLOR_FOREGROUND));
|
|
ObjectSetString(0, name, OBJPROP_FONT, defaultFont);
|
|
ObjectSetInteger(0, name, OBJPROP_FONTSIZE, fontSize);
|
|
|
|
if((flags & TBL_FLAG_COL_0_HEADER) != 0 && j == 0)
|
|
{
|
|
//if(!((flags & TBL_FLAG_ROW_0_HEADER) == 0 && i == 0))
|
|
{
|
|
ObjectSetString(0, name, OBJPROP_FONT, boldFont);
|
|
}
|
|
}
|
|
if((flags & TBL_FLAG_ROW_0_HEADER) != 0 && i == 0)
|
|
{
|
|
//if(!((flags & TBL_FLAG_COL_0_HEADER) == 0 && j == 0))
|
|
{
|
|
ObjectSetString(0, name, OBJPROP_FONT, boldFont);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
~Tableau()
|
|
{
|
|
ObjectsDeleteAll(0, prefix, 0);
|
|
}
|
|
|
|
int getRows() const
|
|
{
|
|
return nrows;
|
|
}
|
|
|
|
int getColumns() const
|
|
{
|
|
return ncols;
|
|
}
|
|
|
|
bool fill(const string &data[], const string &hint[]) const
|
|
{
|
|
if(!TextSetFont(defaultFont, fontSize * -10))
|
|
{
|
|
Print("TextSetFont failed: ", _LastError);
|
|
}
|
|
|
|
const bool invertY = isBottomSide(corner);
|
|
const bool invertX = isRightSide(corner);
|
|
|
|
const int n = ArraySize(data);
|
|
int cols[];
|
|
int offsets[];
|
|
ArrayResize(cols, ncols);
|
|
ArrayInitialize(cols, 0);
|
|
ArrayResize(offsets, ncols + 1);
|
|
ArrayInitialize(offsets, 0);
|
|
|
|
for(int i = 0; i < n; i++)
|
|
{
|
|
const int y = i / ncols; // row
|
|
const int x = i % ncols; // column
|
|
int w, h;
|
|
|
|
if(x < ncols && TextGetSize(data[i], w, h))
|
|
{
|
|
//if((int)(w * 1.5) > cols[x]) cols[x] = (int)(w * 1.5);
|
|
if(w > cols[x]) cols[x] = w;
|
|
}
|
|
else
|
|
{
|
|
Print(__FUNCSIG__, " failed: ", _LastError);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if(invertX)
|
|
{
|
|
ArrayReverse(cols);
|
|
}
|
|
|
|
int width = 0;
|
|
for(int i = 0; i < ncols; i++)
|
|
{
|
|
offsets[i] = (i > 0 ? offsets[i - 1] + cols[i - 1] : 0);
|
|
width += cols[i] + gap;
|
|
}
|
|
offsets[ncols] = offsets[ncols - 1] + cols[ncols - 1];
|
|
|
|
if(!invertX)
|
|
{
|
|
for(int k = 0; k < ncols; k++)
|
|
{
|
|
offsets[k] = offsets[k + 1] + gap;
|
|
}
|
|
}
|
|
|
|
const int h = ArraySize(hint);
|
|
|
|
for(int i = 0; i < n; i++)
|
|
{
|
|
const int y = i / ncols;
|
|
const int x = i % ncols;
|
|
if(x < ncols)
|
|
{
|
|
int k = invertY ? nrows - y - 1 : y;
|
|
int m = invertX ? ncols - x - 1 : x;
|
|
|
|
string name = prefix + "_" +(string)y + "_" + (string)x;
|
|
// NB: setting dimensions for OBJ_LABEL does nothing,
|
|
// cause it's automatically sized to text content
|
|
// this is a problem, but has minor impact
|
|
ObjectSetInteger(0, name, OBJPROP_XSIZE, cols[x]);
|
|
ObjectSetInteger(0, name, OBJPROP_YSIZE, cellHeight);
|
|
ObjectSetString(0, name, OBJPROP_TEXT, data[i]);
|
|
if(i < h && StringLen(hint[i]) > 0)
|
|
{
|
|
ObjectSetString(0, name, OBJPROP_TOOLTIP, hint[i]);
|
|
}
|
|
ObjectSetInteger(0, name, OBJPROP_XDISTANCE, offsets[m] + gap * m + gap);
|
|
ObjectSetInteger(0, name, OBJPROP_YDISTANCE, k * (cellHeight + gap / 2) + gap);
|
|
}
|
|
}
|
|
|
|
string name = prefix + "_";
|
|
ObjectSetInteger(0, name, OBJPROP_XSIZE, width + gap);
|
|
|
|
const int w = width + gap;
|
|
const int h1 = nrows * (cellHeight + gap / 2) + gap / 2;
|
|
|
|
uint img[];
|
|
ArrayResize(img, w * h1);
|
|
ArrayInitialize(img, (uint)ColorToARGB(bgColor, bgTrans));
|
|
ResourceCreate(res, img, w, h1, 0, 0, w, COLOR_FORMAT_ARGB_NORMALIZE);
|
|
|
|
ChartRedraw();
|
|
return true;
|
|
}
|
|
};
|
|
//+------------------------------------------------------------------+
|