1768 lines
72 KiB
MQL5
//+------------------------------------------------------------------+
| |||
//| ForexSessionsClock.mq5 |
| |||
//| ساعت و نقشه سشنهای فارکس | Forex Sessions Clock & Map |
| |||
//+------------------------------------------------------------------+
| |||
#property copyright "AiTrader"
| |||
#property link ""
| |||
#property version "5.00"
| |||
#property description "ساعت ۲۴ ساعته سشنهای فارکس (سیدنی، توکیو، لندن، نیویورک) - فارسی و انگلیسی"
| |||
#property description "Forex sessions 24h clock & map (Sydney, Tokyo, London, New York) - Persian & English"
| |||
#property indicator_chart_window
| |||
#property indicator_buffers 0
| |||
#property indicator_plots 0
| |||
| |||
#include <Canvas\Canvas.mqh>
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Enumerations |
| |||
//+------------------------------------------------------------------+
| |||
enum ENUM_UI_LANG
| |||
{
| |||
UI_FA = 0, // فارسی | Persian
| |||
UI_EN = 1 // English | انگلیسی
| |||
};
| |||
| |||
enum ENUM_DIGIT_STYLE
| |||
{
| |||
DIG_AUTO = 0, // خودکار (مطابق زبان) | Auto (follow language)
| |||
DIG_LATIN = 1, // ارقام لاتین 0123456789 | Latin digits
| |||
DIG_PERSIAN = 2 // ارقام فارسی ۰۱۲۳۴۵۶۷۸۹ | Persian digits
| |||
};
| |||
| |||
enum ENUM_THEME_MODE
| |||
{
| |||
THEME_LIGHT = 0, // روشن | Light
| |||
THEME_DARK = 1 // تیره | Dark
| |||
};
| |||
| |||
enum ENUM_REF_TZ
| |||
{
| |||
TZ_TEHRAN = 0, // ایران – تهران (UTC+3:30) | Iran – Tehran
| |||
TZ_UTC = 1, // زمان جهانی UTC | UTC
| |||
TZ_BROKER = 2, // زمان سرور بروکر | Broker server time
| |||
TZ_LONDON = 3, // انگلیس – لندن | UK – London
| |||
TZ_NEWYORK = 4, // آمریکا – نیویورک | USA – New York
| |||
TZ_TOKYO = 5, // ژاپن – توکیو | Japan – Tokyo
| |||
TZ_SYDNEY = 6, // استرالیا – سیدنی | Australia – Sydney
| |||
TZ_DUBAI = 7, // امارات – دبی | UAE – Dubai
| |||
TZ_ISTANBUL = 8, // ترکیه – استانبول | Turkey – Istanbul
| |||
TZ_BERLIN = 9, // آلمان – برلین | Germany – Berlin
| |||
TZ_CUSTOM = 10 // دلخواه (انحراف دستی) | Custom offset
| |||
};
| |||
#define TZ_COUNT 11
| |||
| |||
enum ENUM_PANEL_CORNER
| |||
{
| |||
PC_TL = 0, // بالا-چپ | Top-Left
| |||
PC_TR = 1, // بالا-راست | Top-Right
| |||
PC_BL = 2, // پایین-چپ | Bottom-Left
| |||
PC_BR = 3 // پایین-راست | Bottom-Right
| |||
};
| |||
| |||
// ساعتها به صورت لیست کشویی (ComboBox) با گام ۱۵ دقیقه | Time list (ComboBox), 15-minute steps
| |||
enum ENUM_HM
| |||
{
| |||
HM_0000 = 0, // 00:00
| |||
HM_0015 = 15, // 00:15
| |||
HM_0030 = 30, // 00:30
| |||
HM_0045 = 45, // 00:45
| |||
HM_0100 = 60, // 01:00
| |||
HM_0115 = 75, // 01:15
| |||
HM_0130 = 90, // 01:30
| |||
HM_0145 = 105, // 01:45
| |||
HM_0200 = 120, // 02:00
| |||
HM_0215 = 135, // 02:15
| |||
HM_0230 = 150, // 02:30
| |||
HM_0245 = 165, // 02:45
| |||
HM_0300 = 180, // 03:00
| |||
HM_0315 = 195, // 03:15
| |||
HM_0330 = 210, // 03:30
| |||
HM_0345 = 225, // 03:45
| |||
HM_0400 = 240, // 04:00
| |||
HM_0415 = 255, // 04:15
| |||
HM_0430 = 270, // 04:30
| |||
HM_0445 = 285, // 04:45
| |||
HM_0500 = 300, // 05:00
| |||
HM_0515 = 315, // 05:15
| |||
HM_0530 = 330, // 05:30
| |||
HM_0545 = 345, // 05:45
| |||
HM_0600 = 360, // 06:00
| |||
HM_0615 = 375, // 06:15
| |||
HM_0630 = 390, // 06:30
| |||
HM_0645 = 405, // 06:45
| |||
HM_0700 = 420, // 07:00
| |||
HM_0715 = 435, // 07:15
| |||
HM_0730 = 450, // 07:30
| |||
HM_0745 = 465, // 07:45
| |||
HM_0800 = 480, // 08:00
| |||
HM_0815 = 495, // 08:15
| |||
HM_0830 = 510, // 08:30
| |||
HM_0845 = 525, // 08:45
| |||
HM_0900 = 540, // 09:00
| |||
HM_0915 = 555, // 09:15
| |||
HM_0930 = 570, // 09:30
| |||
HM_0945 = 585, // 09:45
| |||
HM_1000 = 600, // 10:00
| |||
HM_1015 = 615, // 10:15
| |||
HM_1030 = 630, // 10:30
| |||
HM_1045 = 645, // 10:45
| |||
HM_1100 = 660, // 11:00
| |||
HM_1115 = 675, // 11:15
| |||
HM_1130 = 690, // 11:30
| |||
HM_1145 = 705, // 11:45
| |||
HM_1200 = 720, // 12:00
| |||
HM_1215 = 735, // 12:15
| |||
HM_1230 = 750, // 12:30
| |||
HM_1245 = 765, // 12:45
| |||
HM_1300 = 780, // 13:00
| |||
HM_1315 = 795, // 13:15
| |||
HM_1330 = 810, // 13:30
| |||
HM_1345 = 825, // 13:45
| |||
HM_1400 = 840, // 14:00
| |||
HM_1415 = 855, // 14:15
| |||
HM_1430 = 870, // 14:30
| |||
HM_1445 = 885, // 14:45
| |||
HM_1500 = 900, // 15:00
| |||
HM_1515 = 915, // 15:15
| |||
HM_1530 = 930, // 15:30
| |||
HM_1545 = 945, // 15:45
| |||
HM_1600 = 960, // 16:00
| |||
HM_1615 = 975, // 16:15
| |||
HM_1630 = 990, // 16:30
| |||
HM_1645 = 1005, // 16:45
| |||
HM_1700 = 1020, // 17:00
| |||
HM_1715 = 1035, // 17:15
| |||
HM_1730 = 1050, // 17:30
| |||
HM_1745 = 1065, // 17:45
| |||
HM_1800 = 1080, // 18:00
| |||
HM_1815 = 1095, // 18:15
| |||
HM_1830 = 1110, // 18:30
| |||
HM_1845 = 1125, // 18:45
| |||
HM_1900 = 1140, // 19:00
| |||
HM_1915 = 1155, // 19:15
| |||
HM_1930 = 1170, // 19:30
| |||
HM_1945 = 1185, // 19:45
| |||
HM_2000 = 1200, // 20:00
| |||
HM_2015 = 1215, // 20:15
| |||
HM_2030 = 1230, // 20:30
| |||
HM_2045 = 1245, // 20:45
| |||
HM_2100 = 1260, // 21:00
| |||
HM_2115 = 1275, // 21:15
| |||
HM_2130 = 1290, // 21:30
| |||
HM_2145 = 1305, // 21:45
| |||
HM_2200 = 1320, // 22:00
| |||
HM_2215 = 1335, // 22:15
| |||
HM_2230 = 1350, // 22:30
| |||
HM_2245 = 1365, // 22:45
| |||
HM_2300 = 1380, // 23:00
| |||
HM_2315 = 1395, // 23:15
| |||
HM_2330 = 1410, // 23:30
| |||
HM_2345 = 1425 // 23:45
| |||
};
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Inputs |
| |||
//+------------------------------------------------------------------+
| |||
input group "■■■ ① تنظیمات عمومی ‖ GENERAL SETTINGS ■■■"
| |||
input ENUM_UI_LANG InpLang = UI_FA; // ◆ زبان پیشفرض پنل | Default panel language
| |||
input bool InpRemember = true; // ◆ بهخاطر سپردن زبان و منطقهی زمانی انتخابی (با کلیک روی پنل) | Remember language & time zone chosen on the panel
| |||
input ENUM_DIGIT_STYLE InpDigits = DIG_AUTO; // ◆ نوع ارقام | Digit style
| |||
input ENUM_THEME_MODE InpTheme = THEME_LIGHT; // ◆ پوستهی پیشفرض (روی خود پنل هم قابل تغییر است) | Default theme (can also be switched on the panel)
| |||
input bool InpFaFix = false; // ◆ اصلاح اتصال حروف فارسی (فقط اگر متن فارسی جدا یا برعکس دیده شد روشن کنید) | Persian letter-joining fix (turn ON only if Persian text looks broken)
| |||
input string InpFontFa = "Tahoma"; // ◆ فونت فارسی | Persian font
| |||
input string InpFontEn = "Segoe UI"; // ◆ فونت انگلیسی و ساعت دیجیتال | English / digital-clock font
| |||
input string InpBrand = "Forex Sessions Clock"; // ◆ عنوان بالای پنل (خالی = مخفی) | Header title (empty = hidden)
| |||
| |||
input group "■■■ ② منطقهی زمانی ‖ TIME ZONE ■■■"
| |||
input ENUM_REF_TZ InpRefTz = TZ_TEHRAN; // ◆ منطقهی زمانی مرجع (پیشفرض: تهران) | Reference time zone (default: Tehran)
| |||
input int InpCustomMin = 210; // ◆ انحراف دلخواه از UTC به دقیقه (فقط حالت «دلخواه») | Custom UTC offset in minutes (custom mode only)
| |||
input bool InpWeekend = true; // ◆ بازار در آخر هفته «بسته» نمایش داده شود | Show market as closed on weekends
| |||
| |||
input group "■■■ ③ جایگذاری و اندازهی پنل ‖ PANEL POSITION & SIZE ■■■"
| |||
input ENUM_PANEL_CORNER InpCorner = PC_TL; // ◆ گوشهی نمایش پنل | Panel corner
| |||
input int InpX = 10; // ◆ فاصله از لبهی افقی (پیکسل) | Horizontal margin (px)
| |||
input int InpY = 20; // ◆ فاصله از لبهی عمودی (پیکسل) | Vertical margin (px)
| |||
input int InpScale = 100; // ◆ حداکثر اندازهی پنل به درصد (۵۰ تا ۲۰۰) | Maximum panel scale in % (50-200)
| |||
input bool InpAutoFit = true; // ◆ تنظیم خودکار اندازهی پنل با اندازهی چارت | Auto-fit panel to chart size
| |||
| |||
input group "■■■ ④ بخشهای قابل نمایش ‖ VISIBLE SECTIONS ■■■"
| |||
input bool InpShowHeader = true; // ◆ سربرگ (منطقهی زمانی، دکمههای زبان و وضعیت) | Header (time zone, language buttons & status)
| |||
input bool InpShowClock = true; // ◆ ساعت ۲۴ ساعتهی حلقهای | 24-hour ring clock
| |||
input bool InpShowNumbers = true; // ◆ اعداد ساعت دور ساعت | Hour numbers around clock
| |||
input bool InpShowSecHand = true; // ◆ عقربهی ثانیهشمار | Seconds hand
| |||
input bool InpShowDigital = true; // ◆ ساعت دیجیتال، تاریخ شمسی و میلادی | Digital clock & dates
| |||
input bool InpShowInfo = true; // ◆ کارتهای «سشن بعدی» و «همپوشانی بعدی» | "Next session" & "Next overlap" cards
| |||
input bool InpShowOverlaps = true; // ◆ جدول همپوشانی همهی سشنها | Table of all session overlaps
| |||
input bool InpShowAlertBar = true; // ◆ نوار وضعیت هشدار | Alert status bar
| |||
input bool InpShowCards = true; // ◆ کارت جزئیات هر سشن | Session detail cards
| |||
input bool InpShowTimeline = true; // ◆ نقشهی ۲۴ ساعتهی بازار (همراه با همپوشانیها) | 24-hour market map (with overlaps)
| |||
| |||
input group "■■■ ⑤ سشن سیدنی ‖ SYDNEY SESSION ■■■"
| |||
input bool InpSydOn = true; // ◆ فعالسازی سشن سیدنی | Enable Sydney session
| |||
input ENUM_HM InpSydStart = HM_0700; // ◆ ساعت شروع (از لیست انتخاب کنید) - به وقت محلی سیدنی | Start time (pick from list) - Sydney local time
| |||
input ENUM_HM InpSydEnd = HM_1600; // ◆ ساعت پایان (از لیست انتخاب کنید) - به وقت محلی سیدنی | End time (pick from list) - Sydney local time
| |||
input bool InpSydDst = true; // ◆ اعمال خودکار ساعت تابستانی | Automatic daylight-saving time
| |||
input color InpSydClr = C'168,85,247'; // ◆ رنگ سیدنی | Sydney colour
| |||
| |||
input group "■■■ ⑥ سشن توکیو ‖ TOKYO SESSION ■■■"
| |||
input bool InpTokOn = true; // ◆ فعالسازی سشن توکیو | Enable Tokyo session
| |||
input ENUM_HM InpTokStart = HM_0900; // ◆ ساعت شروع (از لیست انتخاب کنید) - به وقت محلی توکیو | Start time (pick from list) - Tokyo local time
| |||
input ENUM_HM InpTokEnd = HM_1800; // ◆ ساعت پایان (از لیست انتخاب کنید) - به وقت محلی توکیو | End time (pick from list) - Tokyo local time
| |||
input color InpTokClr = C'245,165,36'; // ◆ رنگ توکیو | Tokyo colour
| |||
| |||
input group "■■■ ⑦ سشن لندن ‖ LONDON SESSION ■■■"
| |||
input bool InpLonOn = true; // ◆ فعالسازی سشن لندن | Enable London session
| |||
input ENUM_HM InpLonStart = HM_0800; // ◆ ساعت شروع (از لیست انتخاب کنید) - به وقت محلی لندن | Start time (pick from list) - London local time
| |||
input ENUM_HM InpLonEnd = HM_1700; // ◆ ساعت پایان (از لیست انتخاب کنید) - به وقت محلی لندن | End time (pick from list) - London local time
| |||
input bool InpLonDst = true; // ◆ اعمال خودکار ساعت تابستانی | Automatic daylight-saving time
| |||
input color InpLonClr = C'52,201,138'; // ◆ رنگ لندن | London colour
| |||
| |||
input group "■■■ ⑧ سشن نیویورک ‖ NEW YORK SESSION ■■■"
| |||
input bool InpNyOn = true; // ◆ فعالسازی سشن نیویورک | Enable New York session
| |||
input ENUM_HM InpNyStart = HM_0800; // ◆ ساعت شروع (از لیست انتخاب کنید) - به وقت محلی نیویورک | Start time (pick from list) - New York local time
| |||
input ENUM_HM InpNyEnd = HM_1700; // ◆ ساعت پایان (از لیست انتخاب کنید) - به وقت محلی نیویورک | End time (pick from list) - New York local time
| |||
input bool InpNyDst = true; // ◆ اعمال خودکار ساعت تابستانی | Automatic daylight-saving time
| |||
input color InpNyClr = C'37,99,235'; // ◆ رنگ نیویورک | New York colour
| |||
| |||
input group "■■■ ⑨ هشدارها ‖ ALERTS ■■■"
| |||
input bool InpAlerts = true; // ◆ فعالسازی هشدار سشنها | Enable session alerts
| |||
input bool InpAlertOpen = true; // ◆ هشدار هنگام باز شدن سشن | Alert when a session opens
| |||
input bool InpAlertClose = false; // ◆ هشدار هنگام بسته شدن سشن | Alert when a session closes
| |||
input int InpAlertPre = 15; // ◆ هشدار چند دقیقه قبل از شروع (۰ = خاموش) | Alert minutes before open (0 = off)
| |||
input bool InpPopup = true; // ◆ نمایش پنجرهی هشدار | Popup alert window
| |||
input bool InpSound = true; // ◆ پخش صدا | Play sound
| |||
input string InpSoundFile = "alert.wav"; // ◆ نام فایل صدا | Sound file name
| |||
input bool InpPush = false; // ◆ ارسال نوتیفیکیشن به موبایل | Push notification to mobile
| |||
input bool InpMail = false; // ◆ ارسال ایمیل | Send e-mail
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Globals |
| |||
//+------------------------------------------------------------------+
| |||
#define GV_LANG "FSC_LANG"
| |||
#define GV_TZ "FSC_TZ"
| |||
#define GV_THEME "FSC_THEME"
| |||
| |||
CCanvas g_cv; // main panel (redrawn once per minute)
| |||
CCanvas g_ck; // optional live overlay: seconds hand
| |||
string g_fn=""; int g_fp=-1; uint g_ff=0; // font cache
| |||
string g_tn=""; // native label for digital time
| |||
bool g_built=false,g_refit=false;
| |||
bool g_hasCk=false,g_hasDg=false;
| |||
int g_ckx=0,g_cky=0,g_ckw=0,g_dgx=0,g_dgy=0,g_dgw=0,g_dgh=0;
| |||
long g_lastMin=-1;
| |||
int g_cw=0,g_chh=0,g_key=0;
| |||
#define SH_HDR 0
| |||
#define SH_CLK 1
| |||
#define SH_DIG 2
| |||
#define SH_INF 3
| |||
#define SH_ALR 4
| |||
#define SH_CRD 5
| |||
#define SH_TL 6
| |||
#define SH_OVL 7
| |||
bool g_sh[8];
| |||
struct SessSt
| |||
{
| |||
bool active;
| |||
long st;
| |||
long en;
| |||
long left;
| |||
double prog;
| |||
};
| |||
SessSt g_st[4];
| |||
string g_name;
| |||
int g_W=0,g_H=0,g_px=0,g_py=0;
| |||
double g_S=1.0;
| |||
bool g_fa=true,g_pd=true,g_fix=true,g_wk=false;
| |||
int g_tz=0;
| |||
string g_fFa,g_fEn;
| |||
datetime g_last=0;
| |||
long g_u=0,g_refOff=0,g_refNow=0,g_refMid=0;
| |||
CCanvas g_pp; // time-zone combo-box popup
| |||
bool g_popOpen=false;
| |||
int g_ppx=0,g_ppy=0,g_ppw=0,g_pph=0;
| |||
int g_PWb=1340,g_colX0=12;
| |||
int g_np=0,g_pa[6],g_pb[6]; // overlapping pairs that can really overlap
| |||
uint g_lct=0; // last handled click time
| |||
bool g_dark=false;
| |||
int g_hit[4][4]; // 0=tz box, 1=Persian button, 2=English button (x,y,w,h)
| |||
| |||
color kBg,kCard,kBorder,kText,kSub,kTrack,kNavy,kGreen,kMid,kHand,kFace,kAccent;
| |||
| |||
struct SessCfg
| |||
{
| |||
bool on;
| |||
int zone;
| |||
int sMin;
| |||
int eMin;
| |||
bool dst;
| |||
color clr;
| |||
string fa;
| |||
string en;
| |||
};
| |||
SessCfg g_s[4];
| |||
| |||
| |||
// Arabic/Persian shaping table: code, isolated, final, initial, medial
| |||
ushort g_ar[]=
| |||
{
| |||
0x0621,0xFE80,0xFE80,0,0, 0x0622,0xFE81,0xFE82,0,0, 0x0623,0xFE83,0xFE84,0,0,
| |||
0x0624,0xFE85,0xFE86,0,0, 0x0625,0xFE87,0xFE88,0,0, 0x0626,0xFE89,0xFE8A,0xFE8B,0xFE8C,
| |||
0x0627,0xFE8D,0xFE8E,0,0, 0x0628,0xFE8F,0xFE90,0xFE91,0xFE92, 0x0629,0xFE93,0xFE94,0,0,
| |||
0x062A,0xFE95,0xFE96,0xFE97,0xFE98, 0x062B,0xFE99,0xFE9A,0xFE9B,0xFE9C, 0x062C,0xFE9D,0xFE9E,0xFE9F,0xFEA0,
| |||
0x062D,0xFEA1,0xFEA2,0xFEA3,0xFEA4, 0x062E,0xFEA5,0xFEA6,0xFEA7,0xFEA8, 0x062F,0xFEA9,0xFEAA,0,0,
| |||
0x0630,0xFEAB,0xFEAC,0,0, 0x0631,0xFEAD,0xFEAE,0,0, 0x0632,0xFEAF,0xFEB0,0,0,
| |||
0x0633,0xFEB1,0xFEB2,0xFEB3,0xFEB4, 0x0634,0xFEB5,0xFEB6,0xFEB7,0xFEB8, 0x0635,0xFEB9,0xFEBA,0xFEBB,0xFEBC,
| |||
0x0636,0xFEBD,0xFEBE,0xFEBF,0xFEC0, 0x0637,0xFEC1,0xFEC2,0xFEC3,0xFEC4, 0x0638,0xFEC5,0xFEC6,0xFEC7,0xFEC8,
| |||
0x0639,0xFEC9,0xFECA,0xFECB,0xFECC, 0x063A,0xFECD,0xFECE,0xFECF,0xFED0, 0x0641,0xFED1,0xFED2,0xFED3,0xFED4,
| |||
0x0642,0xFED5,0xFED6,0xFED7,0xFED8, 0x0643,0xFED9,0xFEDA,0xFEDB,0xFEDC, 0x0644,0xFEDD,0xFEDE,0xFEDF,0xFEE0,
| |||
0x0645,0xFEE1,0xFEE2,0xFEE3,0xFEE4, 0x0646,0xFEE5,0xFEE6,0xFEE7,0xFEE8, 0x0647,0xFEE9,0xFEEA,0xFEEB,0xFEEC,
| |||
0x0648,0xFEED,0xFEEE,0,0, 0x064A,0xFEF1,0xFEF2,0xFEF3,0xFEF4, 0x067E,0xFB56,0xFB57,0xFB58,0xFB59,
| |||
0x0686,0xFB7A,0xFB7B,0xFB7C,0xFB7D, 0x0698,0xFB8A,0xFB8B,0,0, 0x06A9,0xFB8E,0xFB8F,0xFB90,0xFB91,
| |||
0x06AF,0xFB92,0xFB93,0xFB94,0xFB95, 0x06CC,0xFBFC,0xFBFD,0xFBFE,0xFBFF
| |||
};
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Basic helpers |
| |||
//+------------------------------------------------------------------+
| |||
uint A(color c){ return ColorToARGB(c,255); }
| |||
int P(double v){ return (int)MathRound(v*g_S); }
| |||
| |||
color Mix(color a,color b,double t)
| |||
{
| |||
int ai=(int)a, bi=(int)b;
| |||
int ar=ai&0xFF, ag=(ai>>8)&0xFF, ab=(ai>>16)&0xFF;
| |||
int br=bi&0xFF, bg=(bi>>8)&0xFF, bb=(bi>>16)&0xFF;
| |||
int r=(int)MathRound(ar+(br-ar)*t);
| |||
int g=(int)MathRound(ag+(bg-ag)*t);
| |||
int l=(int)MathRound(ab+(bb-ab)*t);
| |||
return (color)(r|(g<<8)|(l<<16));
| |||
}
| |||
| |||
// accent colour for text on tinted backgrounds: darker on light theme, lighter on dark theme
| |||
color Dk(color c,double t){ return g_dark?Mix(c,clrWhite,0.62):Mix(c,clrBlack,t); }
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Persian text engine (digits + letter joining + RTL ordering) |
| |||
//+------------------------------------------------------------------+
| |||
int ArIdx(ushort c)
| |||
{
| |||
int n=ArraySize(g_ar)/5;
| |||
for(int i=0;i<n;i++) if(g_ar[i*5]==c) return i;
| |||
return -1;
| |||
}
| |||
bool IsLtrCh(ushort c)
| |||
{
| |||
return (c>='0'&&c<='9')||(c>='A'&&c<='Z')||(c>='a'&&c<='z')||(c>=0x06F0&&c<=0x06F9)||(c>=0x0660&&c<=0x0669);
| |||
}
| |||
bool IsLatinLetter(ushort c){ return (c>='A'&&c<='Z')||(c>='a'&&c<='z'); }
| |||
bool IsJoinCh(ushort c){ return c==':'||c=='.'||c=='/'||c=='-'||c=='+'||c=='%'||c==','||c=='_'; }
| |||
| |||
string FaShape(string s)
| |||
{
| |||
int n=StringLen(s);
| |||
if(n==0) return s;
| |||
ushort o[]; ArrayResize(o,n+2);
| |||
int m=0, prev=-1;
| |||
for(int i=0;i<n;i++)
| |||
{
| |||
ushort c=StringGetCharacter(s,i);
| |||
if(c==0x200C){ prev=-1; continue; } // ZWNJ
| |||
int row=ArIdx(c);
| |||
if(row<0){ o[m++]=c; prev=-1; continue; }
| |||
bool pj=(prev>=0 && g_ar[prev*5+3]!=0);
| |||
if(c==0x0644 && i+1<n && StringGetCharacter(s,i+1)==0x0627) // lam-alef
| |||
{ o[m++]=pj?0xFEFC:0xFEFB; i++; prev=-1; continue; }
| |||
bool nj=false;
| |||
if(g_ar[row*5+3]!=0 && i+1<n)
| |||
{
| |||
ushort nc=StringGetCharacter(s,i+1);
| |||
if(nc!=0x0621 && ArIdx(nc)>=0) nj=true;
| |||
}
| |||
ushort f;
| |||
if(pj&&nj) f=g_ar[row*5+4];
| |||
else if(nj) f=g_ar[row*5+3];
| |||
else if(pj) f=g_ar[row*5+2];
| |||
else f=g_ar[row*5+1];
| |||
o[m++]=f; prev=row;
| |||
}
| |||
// reverse whole string (RTL -> visual LTR)
| |||
for(int i=0;i<m/2;i++){ ushort t=o[i]; o[i]=o[m-1-i]; o[m-1-i]=t; }
| |||
// restore LTR runs (digits / latin) and mirror brackets
| |||
for(int i=0;i<m;i++)
| |||
{
| |||
if(IsLtrCh(o[i]))
| |||
{
| |||
int j=i;
| |||
while(j+1<m)
| |||
{
| |||
if(IsLtrCh(o[j+1])) j++;
| |||
else if(IsJoinCh(o[j+1]) && j+2<m && IsLtrCh(o[j+2])) j+=2;
| |||
else if(o[j+1]==' ' && IsLatinLetter(o[j]) && j+2<m && IsLatinLetter(o[j+2])) j+=2;
| |||
else break;
| |||
}
| |||
for(int a=i,b=j;a<b;a++,b--){ ushort t=o[a]; o[a]=o[b]; o[b]=t; }
| |||
i=j;
| |||
}
| |||
else if(o[i]=='(') o[i]=')';
| |||
else if(o[i]==')') o[i]='(';
| |||
}
| |||
string r="";
| |||
for(int i=0;i<m;i++) r+=ShortToString(o[i]);
| |||
return r;
| |||
}
| |||
| |||
string Dig(string s)
| |||
{
| |||
if(!g_pd) return s;
| |||
string r=""; int n=StringLen(s);
| |||
for(int i=0;i<n;i++)
| |||
{
| |||
ushort c=StringGetCharacter(s,i);
| |||
if(c>='0'&&c<='9') c=(ushort)(0x06F0+(c-'0'));
| |||
r+=ShortToString(c);
| |||
}
| |||
return r;
| |||
}
| |||
string Pre(string s,bool shape){ s=Dig(s); if(shape&&g_fix) s=FaShape(s); return s; }
| |||
string L(string fa,string en){ return g_fa ? Pre(fa,true) : Pre(en,false); }
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Time zones & daylight saving |
| |||
//+------------------------------------------------------------------+
| |||
datetime NthSun(int y,int mo,int n)
| |||
{
| |||
MqlDateTime d; d.year=y; d.mon=mo; d.day=1; d.hour=0; d.min=0; d.sec=0;
| |||
datetime f=StructToTime(d);
| |||
TimeToStruct(f,d);
| |||
int fs=1+(7-d.day_of_week)%7;
| |||
int day;
| |||
if(n>0) day=fs+(n-1)*7;
| |||
else day=fs+7*((31-fs)/7);
| |||
return f+(datetime)((day-1)*86400);
| |||
}
| |||
long g_yFrom=0,g_yTo=0;
| |||
datetime g_euS=0,g_euE=0,g_usS=0,g_usE=0,g_auS=0,g_auE=0;
| |||
void DstEnsure(datetime u)
| |||
{
| |||
if((long)u>=g_yFrom && (long)u<g_yTo) return;
| |||
MqlDateTime d; TimeToStruct(u,d);
| |||
int y=d.year;
| |||
d.mon=1; d.day=1; d.hour=0; d.min=0; d.sec=0;
| |||
g_yFrom=(long)StructToTime(d);
| |||
d.year=y+1; g_yTo=(long)StructToTime(d);
| |||
g_euS=NthSun(y,3,-1)+3600; g_euE=NthSun(y,10,-1)+3600;
| |||
g_usS=NthSun(y,3,2)+7*3600; g_usE=NthSun(y,11,1)+6*3600;
| |||
g_auE=NthSun(y,4,1)-28800; g_auS=NthSun(y,10,1)-28800;
| |||
}
| |||
bool EuDst(datetime u){ DstEnsure(u); return (u>=g_euS && u<g_euE); }
| |||
bool UsDst(datetime u){ DstEnsure(u); return (u>=g_usS && u<g_usE); }
| |||
bool AuDst(datetime u){ DstEnsure(u); return (u<g_auE || u>=g_auS); }
| |||
| |||
int ZoneOff(int z,datetime u,bool dst=true)
| |||
{
| |||
switch(z)
| |||
{
| |||
case TZ_TEHRAN: return 12600;
| |||
case TZ_UTC: return 0;
| |||
case TZ_BROKER: { long d=(long)TimeTradeServer()-(long)TimeGMT(); return (int)(MathRound(d/900.0)*900); }
| |||
case TZ_LONDON: return (dst&&EuDst(u))?3600:0;
| |||
case TZ_NEWYORK: return -18000+((dst&&UsDst(u))?3600:0);
| |||
case TZ_TOKYO: return 32400;
| |||
case TZ_SYDNEY: return 36000+((dst&&AuDst(u))?3600:0);
| |||
case TZ_DUBAI: return 14400;
| |||
case TZ_ISTANBUL: return 10800;
| |||
case TZ_BERLIN: return 3600+((dst&&EuDst(u))?3600:0);
| |||
case TZ_CUSTOM: return InpCustomMin*60;
| |||
}
| |||
return 0;
| |||
}
| |||
| |||
string TzLong(int z,bool fa)
| |||
{
| |||
string f[TZ_COUNT]={"ایران – تهران","زمان جهانی (UTC)","زمان سرور بروکر","انگلیس – لندن","آمریکا – نیویورک","ژاپن – توکیو","استرالیا – سیدنی","امارات – دبی","ترکیه – استانبول","آلمان – برلین","دلخواه (UTC±)"};
| |||
string e[TZ_COUNT]={"Iran – Tehran","UTC (Universal)","Broker server time","UK – London","USA – New York","Japan – Tokyo","Australia – Sydney","UAE – Dubai","Turkey – Istanbul","Germany – Berlin","Custom offset"};
| |||
return fa?f[z]:e[z];
| |||
}
| |||
string TzShort(int z,bool fa)
| |||
{
| |||
string f[TZ_COUNT]={"تهران","UTC","سرور","لندن","نیویورک","توکیو","سیدنی","دبی","استانبول","برلین","دلخواه"};
| |||
string e[TZ_COUNT]={"Tehran","UTC","Server","London","New York","Tokyo","Sydney","Dubai","Istanbul","Berlin","Custom"};
| |||
return fa?f[z]:e[z];
| |||
}
| |||
| |||
int ParseHM(string s,int def)
| |||
{
| |||
StringTrimLeft(s); StringTrimRight(s);
| |||
int p=StringFind(s,":");
| |||
if(p<0) return def;
| |||
int h=(int)StringToInteger(StringSubstr(s,0,p));
| |||
int m=(int)StringToInteger(StringSubstr(s,p+1));
| |||
if(h<0||h>24||m<0||m>59) return def;
| |||
return (h*60+m)%1440;
| |||
}
| |||
| |||
void SetupSessions()
| |||
{
| |||
g_s[0].on=InpSydOn; g_s[0].zone=TZ_SYDNEY; g_s[0].sMin=(int)InpSydStart; g_s[0].eMin=(int)InpSydEnd;
| |||
g_s[0].dst=InpSydDst; g_s[0].clr=InpSydClr; g_s[0].fa="سیدنی"; g_s[0].en="Sydney";
| |||
g_s[1].on=InpTokOn; g_s[1].zone=TZ_TOKYO; g_s[1].sMin=(int)InpTokStart; g_s[1].eMin=(int)InpTokEnd;
| |||
g_s[1].dst=false; g_s[1].clr=InpTokClr; g_s[1].fa="توکیو"; g_s[1].en="Tokyo";
| |||
g_s[2].on=InpLonOn; g_s[2].zone=TZ_LONDON; g_s[2].sMin=(int)InpLonStart; g_s[2].eMin=(int)InpLonEnd;
| |||
g_s[2].dst=InpLonDst; g_s[2].clr=InpLonClr; g_s[2].fa="لندن"; g_s[2].en="London";
| |||
g_s[3].on=InpNyOn; g_s[3].zone=TZ_NEWYORK; g_s[3].sMin=(int)InpNyStart; g_s[3].eMin=(int)InpNyEnd;
| |||
g_s[3].dst=InpNyDst; g_s[3].clr=InpNyClr; g_s[3].fa="نیویورک"; g_s[3].en="New York";
| |||
}
| |||
| |||
// five consecutive instances (UTC seconds) of session s around time u
| |||
void Inst(int s,long u,long &st[],long &en[])
| |||
{
| |||
int z=g_s[s].zone; bool dst=g_s[s].dst;
| |||
long lu=u+ZoneOff(z,(datetime)u,dst);
| |||
long ld=lu-(lu%86400);
| |||
int dur=g_s[s].eMin-g_s[s].sMin; if(dur<=0) dur+=1440;
| |||
for(int d=-2;d<=2;d++)
| |||
{
| |||
long ls=ld+(long)d*86400+(long)g_s[s].sMin*60;
| |||
long g=ls-ZoneOff(z,(datetime)ls,dst);
| |||
long su=ls-ZoneOff(z,(datetime)g,dst);
| |||
st[d+2]=su; en[d+2]=su+(long)dur*60;
| |||
}
| |||
}
| |||
| |||
bool WkClosed(datetime u)
| |||
{
| |||
MqlDateTime d; TimeToStruct(u,d);
| |||
if(d.day_of_week==6) return true;
| |||
if(d.day_of_week==5 && d.hour>=22) return true;
| |||
if(d.day_of_week==0 && d.hour<22) return true;
| |||
return false;
| |||
}
| |||
| |||
void GetState(int s,SessSt &r)
| |||
{
| |||
long st[5],en[5]; Inst(s,g_u,st,en);
| |||
int ai=-1,ni=-1;
| |||
for(int i=0;i<5;i++)
| |||
{
| |||
if(st[i]<=g_u && g_u<en[i]) ai=i;
| |||
if(st[i]>g_u && (ni<0 || st[i]<st[ni])) ni=i;
| |||
}
| |||
if(ni<0) ni=4;
| |||
if(ai>=0 && !g_wk)
| |||
{
| |||
r.active=true; r.st=st[ai]; r.en=en[ai]; r.left=en[ai]-g_u;
| |||
r.prog=(double)(g_u-st[ai])/(double)(en[ai]-st[ai]);
| |||
}
| |||
else
| |||
{
| |||
r.active=false; r.st=st[ni]; r.en=en[ni]; r.left=st[ni]-g_u; r.prog=0;
| |||
}
| |||
}
| |||
| |||
void RefreshStates(){ for(int s=0;s<4;s++) if(g_s[s].on) GetState(s,g_st[s]); }
| |||
| |||
int ClipDay(int s,long &a0[],long &a1[])
| |||
{
| |||
long st[5],en[5]; Inst(s,g_u,st,en);
| |||
int n=0;
| |||
for(int i=0;i<5;i++)
| |||
{
| |||
long a=st[i]+g_refOff-g_refMid, b=en[i]+g_refOff-g_refMid;
| |||
if(a<0) a=0;
| |||
if(b>86400) b=86400;
| |||
if(b>a){ a0[n]=a; a1[n]=b; n++; }
| |||
}
| |||
return n;
| |||
}
| |||
| |||
bool Overlap(long &a,long &b)
| |||
{
| |||
if(!g_s[2].on||!g_s[3].on) return false;
| |||
long s1[5],e1[5],s2[5],e2[5];
| |||
Inst(2,g_u,s1,e1); Inst(3,g_u,s2,e2);
| |||
bool f=false; long bs=0,be=0;
| |||
for(int i=0;i<5;i++)
| |||
for(int j=0;j<5;j++)
| |||
{
| |||
long lo=MathMax(s1[i],s2[j]), hi=MathMin(e1[i],e2[j]);
| |||
if(hi>lo && hi>g_u && (!f||lo<bs)){ bs=lo; be=hi; f=true; }
| |||
}
| |||
a=bs; b=be; return f;
| |||
}
| |||
| |||
string HM(long t)
| |||
{
| |||
long l=t+g_refOff; int m=(int)((l%86400)/60);
| |||
return StringFormat("%02d:%02d",m/60,m%60);
| |||
}
| |||
void DayW(long t,string &fa,string &en)
| |||
{
| |||
long l=t+g_refOff; long dd=(l-(l%86400)-g_refMid)/86400;
| |||
if(dd==0){ fa="امروز"; en="Today"; }
| |||
else if(dd==1){ fa="فردا"; en="Tomorrow"; }
| |||
else if(dd==-1){ fa="دیروز"; en="Yesterday"; }
| |||
else { fa=IntegerToString(dd)+" روز"; en=IntegerToString(dd)+" d"; }
| |||
}
| |||
string DurTxt(long s,bool fa)
| |||
{
| |||
if(s<0) s=0;
| |||
long h=s/3600, m=(s%3600)/60;
| |||
if(fa)
| |||
{
| |||
if(h>0) return IntegerToString(h)+" ساعت و "+IntegerToString(m)+" دقیقه";
| |||
if(m>0) return IntegerToString(m)+" دقیقه";
| |||
return "کمتر از ۱ دقیقه";
| |||
}
| |||
if(h>0) return IntegerToString(h)+"h "+IntegerToString(m)+"m";
| |||
if(m>0) return IntegerToString(m)+"m";
| |||
return "<1m";
| |||
}
| |||
| |||
// Gregorian -> Jalali
| |||
void G2J(int gy,int gm,int gd,int &jy,int &jm,int &jd)
| |||
{
| |||
int gdm[12]={0,31,59,90,120,151,181,212,243,273,304,334};
| |||
int gy2=(gm>2)?(gy+1):gy;
| |||
int days=355666+(365*gy)+((gy2+3)/4)-((gy2+99)/100)+((gy2+399)/400)+gd+gdm[gm-1];
| |||
jy=-1595+(33*(days/12053)); days%=12053;
| |||
jy+=4*(days/1461); days%=1461;
| |||
if(days>365){ jy+=(days-1)/365; days=(days-1)%365; }
| |||
if(days<186){ jm=1+(days/31); jd=1+(days%31); }
| |||
else { jm=7+((days-186)/30); jd=1+((days-186)%30); }
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Drawing primitives |
| |||
//+------------------------------------------------------------------+
| |||
void FillRRc(int x,int y,int w,int h,int r,uint clr)
| |||
{
| |||
if(w<=0||h<=0) return;
| |||
if(r>h/2) r=h/2;
| |||
if(r>w/2) r=w/2;
| |||
if(r<1){ g_cv.FillRectangle(x,y,x+w-1,y+h-1,clr); return; }
| |||
g_cv.FillRectangle(x+r,y,x+w-1-r,y+h-1,clr);
| |||
g_cv.FillRectangle(x,y+r,x+w-1,y+h-1-r,clr);
| |||
g_cv.FillCircle(x+r,y+r,r,clr); g_cv.FillCircle(x+w-1-r,y+r,r,clr);
| |||
g_cv.FillCircle(x+r,y+h-1-r,r,clr); g_cv.FillCircle(x+w-1-r,y+h-1-r,r,clr);
| |||
}
| |||
void RR(int x,int y,int w,int h,int r,color fill,color border,int t=1)
| |||
{
| |||
FillRRc(x,y,w,h,r,A(border));
| |||
FillRRc(x+t,y+t,w-2*t,h-2*t,MathMax(r-t,0),A(fill));
| |||
}
| |||
void CircAA(int cx,int cy,double r,color c)
| |||
{
| |||
int n=(int)MathMax(32,r*0.9);
| |||
int px=0,py=0;
| |||
for(int i=0;i<=n;i++)
| |||
{
| |||
double a=2*M_PI*i/n;
| |||
int x=(int)MathRound(cx+r*MathSin(a)), y=(int)MathRound(cy-r*MathCos(a));
| |||
if(i>0) g_cv.Line(px,py,x,y,A(c));
| |||
px=x; py=y;
| |||
}
| |||
}
| |||
void Disc(int cx,int cy,double r,color c,bool aa=false)
| |||
{
| |||
g_cv.FillCircle(cx,cy,(int)MathRound(r),A(c));
| |||
}
| |||
// ring segment; angles in radians, 0 = top, clockwise
| |||
void Seg(int cx,int cy,double r1,double r0,double a0,double a1,color c)
| |||
{
| |||
int steps=(int)MathMax(2,MathCeil((a1-a0)/0.12));
| |||
int n=(steps+1)*2;
| |||
int xs[],ys[]; ArrayResize(xs,n); ArrayResize(ys,n);
| |||
for(int k=0;k<=steps;k++)
| |||
{
| |||
double a=a0+(a1-a0)*k/steps;
| |||
xs[k]=(int)MathRound(cx+r1*MathSin(a)); ys[k]=(int)MathRound(cy-r1*MathCos(a));
| |||
double b=a1-(a1-a0)*k/steps;
| |||
xs[steps+1+k]=(int)MathRound(cx+r0*MathSin(b)); ys[steps+1+k]=(int)MathRound(cy-r0*MathCos(b));
| |||
}
| |||
g_cv.FillPolygon(xs,ys,A(c));
| |||
}
| |||
void Needle(CCanvas &cv,int cx,int cy,double ang,double len,double tail,double wid,color c)
| |||
{
| |||
double sx=MathSin(ang), sy=-MathCos(ang), qx=-sy, qy=sx;
| |||
int xs[4],ys[4];
| |||
xs[0]=(int)MathRound(cx+sx*len); ys[0]=(int)MathRound(cy+sy*len);
| |||
xs[1]=(int)MathRound(cx+qx*wid); ys[1]=(int)MathRound(cy+qy*wid);
| |||
xs[2]=(int)MathRound(cx-sx*tail); ys[2]=(int)MathRound(cy-sy*tail);
| |||
xs[3]=(int)MathRound(cx-qx*wid); ys[3]=(int)MathRound(cy-qy*wid);
| |||
cv.FillPolygon(xs,ys,A(c));
| |||
}
| |||
| |||
// soft drop-shadow (pre-blended with the panel background, so it needs no transparency)
| |||
void Shadow(int x,int y,int w,int h,int r,color sh,int blur)
| |||
{
| |||
int u=MathMax(1,P(1.2));
| |||
for(int k=blur;k>=1;k--)
| |||
{
| |||
double t=(g_dark?0.30:0.17)*(blur-k+1)/(double)blur;
| |||
int e=k*u;
| |||
FillRRc(x-e,y-e+2*u,w+2*e,h+2*e,r+e,A(Mix(kBg,sh,t)));
| |||
}
| |||
}
| |||
// raised card: shadow + body + top highlight / bottom shade (3-D look)
| |||
void Card(int x,int y,int w,int h,int r,color fill,color border,int t=1,int blur=3,color sh=clrNONE)
| |||
{
| |||
if(sh==clrNONE) sh=g_dark?clrBlack:C'40,55,100';
| |||
Shadow(x,y,w,h,r,sh,blur);
| |||
RR(x,y,w,h,r,fill,border,t);
| |||
color hi=g_dark?Mix(fill,clrWhite,0.10):Mix(fill,clrWhite,0.8);
| |||
color lo=g_dark?Mix(fill,clrBlack,0.30):Mix(fill,C'40,55,100',0.10);
| |||
g_cv.Line(x+r,y+t,x+w-r,y+t,A(hi));
| |||
g_cv.Line(x+r,y+h-t-1,x+w-r,y+h-t-1,A(lo));
| |||
}
| |||
void DiscShadow(int cx,int cy,int R)
| |||
{
| |||
color sh=g_dark?clrBlack:C'40,55,100';
| |||
for(int k=4;k>=1;k--)
| |||
Disc(cx,cy+P(3),R+P(4)+k*P(1.6),Mix(kCard,sh,(g_dark?0.34:0.18)*(5-k)/4.0));
| |||
}
| |||
// section title with an accent bar
| |||
void Title(int xa,int xb,int y,string s)
| |||
{
| |||
int off=P(16);
| |||
int bx=g_fa?xb-P(5):xa;
| |||
FillRRc(bx,y-P(13),P(5),P(26),P(2),A(kAccent));
| |||
TxS(g_fa?xa:xa+off,g_fa?xb-off:xb,y,s,kText,21,true);
| |||
}
| |||
| |||
void FS(string n,int p,uint f){ if(p==g_fp && f==g_ff && n==g_fn) return; g_cv.FontSet(n,p,f); g_fn=n; g_fp=p; g_ff=f; }
| |||
| |||
// text helpers (sizes are in base units, scaled by P)
| |||
void Tx(int x,int y,string s,color c,double sz,bool bold,uint al)
| |||
{
| |||
if(s=="") return;
| |||
FS(g_fa?g_fFa:g_fEn,P(sz),bold?FW_BOLD:FW_NORMAL);
| |||
g_cv.TextOut(x,y,s,A(c),al|TA_VCENTER);
| |||
}
| |||
void TxS(int xa,int xb,int y,string s,color c,double sz,bool bold) // text at the reading-start side
| |||
{ if(g_fa) Tx(xb,y,s,c,sz,bold,TA_RIGHT); else Tx(xa,y,s,c,sz,bold,TA_LEFT); }
| |||
void TxE(int xa,int xb,int y,string s,color c,double sz,bool bold) // text at the reading-end side
| |||
{ if(g_fa) Tx(xa,y,s,c,sz,bold,TA_LEFT); else Tx(xb,y,s,c,sz,bold,TA_RIGHT); }
| |||
int TW(string s,double sz,bool bold)
| |||
{
| |||
FS(g_fa?g_fFa:g_fEn,P(sz),bold?FW_BOLD:FW_NORMAL);
| |||
return g_cv.TextWidth(s);
| |||
}
| |||
int ColX(int k,int cw,int gap)
| |||
{
| |||
int left=P(10), right=left+cw+gap;
| |||
return ((k==0)==g_fa)?right:left;
| |||
}
| |||
color SC(int s,bool active){ return active?g_s[s].clr:Mix(g_s[s].clr,kCard,g_dark?0.28:0.55); }
| |||
// readable text colour on top of a coloured arc
| |||
color OnClr(color c)
| |||
{
| |||
int ci=(int)c;
| |||
double lum=(0.299*(ci&0xFF)+0.587*((ci>>8)&0xFF)+0.114*((ci>>16)&0xFF))/255.0;
| |||
return (lum>0.55)?C'31,42,68':clrWhite;
| |||
}
| |||
int OnCount(){ int n=0; for(int i=0;i<4;i++) if(g_s[i].on) n++; return n; }
| |||
| |||
string DurShort(long s,bool fa)
| |||
{
| |||
if(s<0) s=0;
| |||
long h=s/3600, m=(s%3600)/60;
| |||
if(fa)
| |||
{
| |||
if(m==0) return IntegerToString(h)+" ساعت";
| |||
if(h>0) return IntegerToString(h)+" ساعت و "+IntegerToString(m)+" دقیقه";
| |||
return IntegerToString(m)+" دقیقه";
| |||
}
| |||
if(m==0) return IntegerToString(h)+"h";
| |||
if(h>0) return IntegerToString(h)+"h "+IntegerToString(m)+"m";
| |||
return IntegerToString(m)+"m";
| |||
}
| |||
string UtcStr(int off)
| |||
{
| |||
int a=MathAbs(off)/60;
| |||
return StringFormat("UTC%s%02d:%02d",off<0?"-":"+",a/60,a%60);
| |||
}
| |||
| |||
// nearest (current or next) overlap of two sessions
| |||
bool PairNext(int a,int b,long &lo,long &hi,bool &now)
| |||
{
| |||
long s1[5],e1[5],s2[5],e2[5];
| |||
Inst(a,g_u,s1,e1); Inst(b,g_u,s2,e2);
| |||
bool f=false; lo=0; hi=0; now=false;
| |||
for(int i=0;i<5;i++)
| |||
for(int j=0;j<5;j++)
| |||
{
| |||
long l1=MathMax(s1[i],s2[j]), h1=MathMin(e1[i],e2[j]);
| |||
if(h1>l1 && h1>g_u && (!f || l1<lo)){ lo=l1; hi=h1; f=true; }
| |||
}
| |||
if(f) now=(!g_wk && lo<=g_u && g_u<hi);
| |||
return f;
| |||
}
| |||
bool NextOvAny(int &ba,int &bb,long &lo,long &hi,bool &now)
| |||
{
| |||
bool f=false; ba=-1; bb=-1; lo=0; hi=0; now=false;
| |||
for(int q=0;q<g_np;q++)
| |||
{
| |||
int a=g_pa[q], b=g_pb[q];
| |||
long l,h; bool nw;
| |||
if(!PairNext(a,b,l,h,nw)) continue;
| |||
if(!f || (nw&&!now) || (nw==now && l<lo)){ f=true; ba=a; bb=b; lo=l; hi=h; now=nw; }
| |||
}
| |||
return f;
| |||
}
| |||
// overlap segments of a pair inside today's 24h window (seconds from local midnight)
| |||
int PairClip(int a,int b,long &a0[],long &a1[])
| |||
{
| |||
long s1[5],e1[5],s2[5],e2[5];
| |||
Inst(a,g_u,s1,e1); Inst(b,g_u,s2,e2);
| |||
int n=0;
| |||
for(int i=0;i<5;i++)
| |||
for(int j=0;j<5;j++)
| |||
{
| |||
long lo=MathMax(s1[i],s2[j]), hi=MathMin(e1[i],e2[j]);
| |||
if(hi<=lo) continue;
| |||
long x=lo+g_refOff-g_refMid, y=hi+g_refOff-g_refMid;
| |||
if(x<0) x=0;
| |||
if(y>86400) y=86400;
| |||
if(y>x && n<25){ a0[n]=x; a1[n]=y; n++; }
| |||
}
| |||
return n;
| |||
}
| |||
| |||
// can these two sessions ever overlap (checked in winter and summer)?
| |||
bool PairEver(int a,int b)
| |||
{
| |||
MqlDateTime d; TimeToStruct(TimeGMT(),d);
| |||
for(int m=0;m<2;m++)
| |||
{
| |||
d.mon=(m==0)?1:7; d.day=15; d.hour=12; d.min=0; d.sec=0;
| |||
long u=(long)StructToTime(d);
| |||
long s1[5],e1[5],s2[5],e2[5];
| |||
Inst(a,u,s1,e1); Inst(b,u,s2,e2);
| |||
for(int i=0;i<5;i++)
| |||
for(int j=0;j<5;j++)
| |||
if(MathMin(e1[i],e2[j])-MathMax(s1[i],s2[j])>=900) return true;
| |||
}
| |||
return false;
| |||
}
| |||
void SetupPairs()
| |||
{
| |||
g_np=0;
| |||
for(int a=0;a<4;a++)
| |||
for(int b=a+1;b<4;b++)
| |||
if(g_s[a].on && g_s[b].on && PairEver(a,b) && g_np<6){ g_pa[g_np]=a; g_pb[g_np]=b; g_np++; }
| |||
}
| |||
| |||
// sort key: start of the pair's overlap measured from the first enabled session's open (market-day order)
| |||
long PairKey(int a,int b)
| |||
{
| |||
int f=0; for(int s=0;s<4;s++) if(g_s[s].on){ f=s; break; }
| |||
long s0[5],e0[5]; Inst(f,g_u,s0,e0);
| |||
long O=s0[2];
| |||
long s1[5],e1[5],s2[5],e2[5];
| |||
Inst(a,g_u,s1,e1); Inst(b,g_u,s2,e2);
| |||
long best=2000000000;
| |||
for(int i=0;i<5;i++)
| |||
for(int j=0;j<5;j++)
| |||
{
| |||
long lo=MathMax(s1[i],s2[j]), hi=MathMin(e1[i],e2[j]);
| |||
if(hi-lo<900) continue;
| |||
long k=((lo-O)%86400+86400)%86400;
| |||
if(k<best) best=k;
| |||
}
| |||
return best;
| |||
}
| |||
void SortPairs()
| |||
{
| |||
long key[6];
| |||
for(int q=0;q<g_np;q++) key[q]=PairKey(g_pa[q],g_pb[q]);
| |||
for(int i=1;i<g_np;i++)
| |||
{
| |||
long kk=key[i]; int pa=g_pa[i], pb=g_pb[i]; int j=i-1;
| |||
while(j>=0 && key[j]>kk){ key[j+1]=key[j]; g_pa[j+1]=g_pa[j]; g_pb[j+1]=g_pb[j]; j--; }
| |||
key[j+1]=kk; g_pa[j+1]=pa; g_pb[j+1]=pb;
| |||
}
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Horizontal layout (base units) |
| |||
//+------------------------------------------------------------------+
| |||
#define W_A 330
| |||
#define W_B 580
| |||
#define W_C 390
| |||
#define G_GAP 10
| |||
#define H_HDR 68
| |||
#define H_CLK 312
| |||
#define H_DIG 150
| |||
#define CARD_H 150
| |||
#define H_INF 74
| |||
#define H_ALR 54
| |||
#define ROW_OV 54
| |||
| |||
bool HasA(){ return g_sh[SH_CLK]||g_sh[SH_DIG]; }
| |||
bool HasB(){ return g_sh[SH_CRD]||g_sh[SH_INF]; }
| |||
bool HasC(){ return g_sh[SH_OVL]||g_sh[SH_ALR]; }
| |||
int NPairs(){ return g_np; }
| |||
int HCards(){ return 56+((OnCount()+1)/2)*(CARD_H+10); }
| |||
int HOvl(){ return 56+NPairs()*ROW_OV+10; }
| |||
int HTL(){ int n=OnCount(), np=NPairs(); return 56+(n+np)*26+(np>0?8:0)+36; }
| |||
int BodyH()
| |||
{
| |||
int a=0,b=0,c=0;
| |||
if(g_sh[SH_CLK]) a+=H_CLK;
| |||
if(g_sh[SH_DIG]) a+=H_DIG;
| |||
if(g_sh[SH_CRD]) b+=HCards();
| |||
if(g_sh[SH_INF]) b+=H_INF;
| |||
if(g_sh[SH_OVL]) c+=HOvl();
| |||
if(g_sh[SH_ALR]) c+=H_ALR;
| |||
return MathMax(a,MathMax(b,c));
| |||
}
| |||
// computes panel width (g_PWb) and returns panel height in base units
| |||
int Metrics()
| |||
{
| |||
int cw=0,cnt=0;
| |||
if(HasA()){ cw+=W_A; cnt++; }
| |||
if(HasB()){ cw+=W_B; cnt++; }
| |||
if(HasC()){ cw+=W_C; cnt++; }
| |||
if(cnt>1) cw+=(cnt-1)*G_GAP;
| |||
int w=cw+24;
| |||
if(g_sh[SH_HDR]) w=MathMax(w,1344);
| |||
if(g_sh[SH_TL]) w=MathMax(w,760);
| |||
if(w<400) w=400;
| |||
g_PWb=w;
| |||
g_colX0=12+(w-24-cw)/2;
| |||
int h=12;
| |||
if(g_sh[SH_HDR]) h+=H_HDR+10;
| |||
int bh=BodyH(); if(bh>0) h+=bh+10;
| |||
if(g_sh[SH_TL]) h+=HTL()+10;
| |||
return h+12;
| |||
}
| |||
int MX(int x,int w){ return P(g_fa?(g_PWb-x-w):x); } // block left pixel (mirrored for RTL)
| |||
| |||
string StatusText(color &pc,bool shortForm=false)
| |||
{
| |||
string af="",ae=""; int na=0; pc=kSub;
| |||
for(int s=0;s<4;s++)
| |||
{
| |||
if(!g_s[s].on || !g_st[s].active) continue;
| |||
if(na>0){ af+=" و "; ae+=" + "; }
| |||
af+=g_s[s].fa; ae+=g_s[s].en;
| |||
if(na==0) pc=g_s[s].clr;
| |||
na++;
| |||
}
| |||
if(g_wk){ pc=kSub; return shortForm?L("بازار بسته","Market closed"):L("بازار بسته است (آخر هفته)","Market closed (weekend)"); }
| |||
if(na==0) return shortForm?L("بدون سشن فعال","No active session"):L("اکنون هیچ سشنی فعال نیست","No session is active now");
| |||
if(shortForm) return L("فعال: "+af,"Active: "+ae);
| |||
return L("سشن "+af+" فعال است",ae+(na>1?" sessions":" session")+" active");
| |||
}
| |||
void Pill(int cx,int y,int h,string txt,color pc,double sz)
| |||
{
| |||
int tw=TW(txt,sz,true), pw=tw+P(40);
| |||
int px=cx-pw/2;
| |||
RR(px,y,pw,h,h/2,Mix(pc,kCard,0.85),Mix(pc,kCard,0.55));
| |||
Disc(g_fa?px+pw-P(14):px+P(14),y+h/2,P(4.5),pc);
| |||
Tx(g_fa?cx-P(6):cx+P(6),y+h/2,txt,Dk(pc,0.4),sz,true,TA_CENTER);
| |||
}
| |||
| |||
//--------------------------- header -------------------------------
| |||
void DrawHeader(int y)
| |||
{
| |||
int L0=P(12), R0=g_W-P(12), h=P(H_HDR);
| |||
Card(L0,y,R0-L0,h,P(14),kCard,kBorder,1,4);
| |||
int ia=L0+P(18), ib=R0-P(18);
| |||
if(InpBrand!="")
| |||
{
| |||
TxS(ia,ib,y+P(26),InpBrand,kText,25,true);
| |||
TxS(ia,ib,y+P(51),L("ساعت و نقشهی سشنهای فارکس","Forex sessions clock & map"),kSub,13.5,false);
| |||
}
| |||
color pc; string st=StatusText(pc);
| |||
int ctrlW=2*(P(86)+P(10))+P(120)+P(10)+P(300);
| |||
int brandW=0;
| |||
if(InpBrand!="") brandW=MathMax(TW(InpBrand,25,true),TW(L("ساعت و نقشهی سشنهای فارکس","Forex sessions clock & map"),12.5,false));
| |||
int zl,zr;
| |||
if(g_fa){ zl=ia+ctrlW+P(28); zr=ib-brandW-P(28); }
| |||
else { zl=ia+brandW+P(28); zr=ib-ctrlW-P(28); }
| |||
double psz=14;
| |||
while(psz>10 && TW(st,psz,true)+P(40)>zr-zl) psz-=1;
| |||
if(TW(st,psz,true)+P(40)>zr-zl){ st=StatusText(pc,true); psz=12; }
| |||
Pill((zl+zr)/2,y+(h-P(34))/2,P(34),st,pc,psz);
| |||
| |||
int bw=P(86), bh=P(40), gp=P(10), cwid=P(300), ch=P(50);
| |||
int by=y+(h-bh)/2, cy=y+(h-ch)/2;
| |||
int xP=g_fa?ia:ib-bw;
| |||
int xE=g_fa?ia+bw+gp:ib-2*bw-gp;
| |||
int tw2=P(120);
| |||
int xT=g_fa?ia+2*(bw+gp):ib-2*(bw+gp)-tw2;
| |||
int xC=g_fa?ia+2*(bw+gp)+tw2+gp:ib-2*(bw+gp)-tw2-gp-cwid;
| |||
Card(xP,by,bw,bh,P(10),g_fa?kNavy:kCard,g_fa?kNavy:kBorder,1,2);
| |||
Card(xE,by,bw,bh,P(10),g_fa?kCard:kNavy,g_fa?kBorder:kNavy,1,2);
| |||
g_cv.FontSet(g_fFa,P(14),FW_BOLD); g_fn="";
| |||
g_cv.TextOut(xP+bw/2,by+bh/2,Pre("فارسی",true),A(g_fa?clrWhite:kSub),TA_CENTER|TA_VCENTER);
| |||
g_cv.FontSet(g_fEn,P(14),FW_BOLD); g_fn="";
| |||
g_cv.TextOut(xE+bw/2,by+bh/2,"English",A(g_fa?kSub:clrWhite),TA_CENTER|TA_VCENTER);
| |||
g_hit[1][0]=xP; g_hit[1][1]=by; g_hit[1][2]=bw; g_hit[1][3]=bh;
| |||
g_hit[2][0]=xE; g_hit[2][1]=by; g_hit[2][2]=bw; g_hit[2][3]=bh;
| |||
| |||
// light / dark theme switch
| |||
Card(xT,by,tw2,bh,P(10),kCard,kBorder,1,2);
| |||
g_hit[3][0]=xT; g_hit[3][1]=by; g_hit[3][2]=tw2; g_hit[3][3]=bh;
| |||
int icx=g_fa?xT+tw2-P(26):xT+P(26), icy=by+bh/2;
| |||
if(g_dark){ Disc(icx,icy,P(9),C'250,204,21'); Disc(icx+P(5),icy-P(3),P(8),kCard); }
| |||
else
| |||
{
| |||
Disc(icx,icy,P(6),C'245,158,11');
| |||
for(int q=0;q<8;q++)
| |||
{
| |||
double aq=q*M_PI/4;
| |||
g_cv.Line((int)MathRound(icx+P(9)*MathSin(aq)),(int)MathRound(icy-P(9)*MathCos(aq)),
| |||
(int)MathRound(icx+P(13)*MathSin(aq)),(int)MathRound(icy-P(13)*MathCos(aq)),A(C'245,158,11'));
| |||
}
| |||
}
| |||
Tx(g_fa?xT+(tw2-P(44))/2:xT+P(44)+(tw2-P(44))/2,by+bh/2,g_dark?L("تیره","Dark"):L("روشن","Light"),kText,14,true,TA_CENTER);
| |||
| |||
// time-zone combo box
| |||
Card(xC,cy,cwid,ch,P(10),kCard,kAccent,2,3);
| |||
g_hit[0][0]=xC; g_hit[0][1]=cy; g_hit[0][2]=cwid; g_hit[0][3]=ch;
| |||
int ca=xC+P(14), cb=xC+cwid-P(14);
| |||
int ts=P(6), ax=g_fa?ca+P(6):cb-P(6), ay=cy+ch/2;
| |||
g_cv.FillTriangle(ax-ts,ay-ts/2,ax+ts,ay-ts/2,ax,ay+ts,A(kNavy));
| |||
int na=g_fa?ca+P(22):ca, nb=g_fa?cb:cb-P(22);
| |||
TxS(na,nb,cy+P(15),L("زمان مرجع (برای تغییر کلیک کنید)","Reference time (click to change)"),kSub,12.5,false);
| |||
TxS(na,nb,cy+P(35),Pre(TzLong(g_tz,g_fa),g_fa),kText,15,true);
| |||
}
| |||
| |||
//--------------------------- clock --------------------------------
| |||
void DrawClock(int x0,int w,int y)
| |||
{
| |||
double k=1.24;
| |||
int cx=x0+w/2, cy=y+P(142);
| |||
double R=P(100*k);
| |||
DiscShadow(cx,cy,(int)R);
| |||
Disc(cx,cy,R+P(4),kBorder);
| |||
Disc(cx,cy,R+P(2.5),kCard);
| |||
Disc(cx,cy,R,kFace);
| |||
for(int h=0;h<24;h++)
| |||
{
| |||
double a=2*M_PI*h/24.0;
| |||
double r1=P(98.5*k), r2=(h%6==0)?P(91*k):P(95*k);
| |||
g_cv.Line((int)MathRound(cx+r1*MathSin(a)),(int)MathRound(cy-r1*MathCos(a)),
| |||
(int)MathRound(cx+r2*MathSin(a)),(int)MathRound(cy-r2*MathCos(a)),A(h%6==0?kSub:kBorder));
| |||
if(InpShowNumbers)
| |||
{
| |||
double rn=P(84*k);
| |||
FS(g_fEn,P(12.5),(h%6==0)?FW_BOLD:FW_NORMAL);
| |||
g_cv.TextOut((int)MathRound(cx+rn*MathSin(a)),(int)MathRound(cy-rn*MathCos(a)),Dig(IntegerToString(h)),
| |||
A((h%6==0)?kText:kMid),TA_CENTER|TA_VCENTER);
| |||
}
| |||
}
| |||
double ro[4]={77,62.5,48,33.5};
| |||
double th=13.2;
| |||
for(int s=0;s<4;s++)
| |||
{
| |||
if(!g_s[s].on) continue;
| |||
double r1=P(ro[s]*k), r0=P((ro[s]-th)*k);
| |||
Seg(cx,cy,r1,r0,0.0,2*M_PI-0.0001,kTrack);
| |||
SessSt st=g_st[s];
| |||
long a0[5],a1[5]; int n=ClipDay(s,a0,a1);
| |||
color c=SC(s,st.active);
| |||
double best=0,bm=0;
| |||
for(int i=0;i<n;i++)
| |||
{
| |||
double f0=a0[i]/86400.0*2*M_PI, f1=a1[i]/86400.0*2*M_PI;
| |||
Seg(cx,cy,r1,r0,f0,f1,c);
| |||
if(f1-f0>best){ best=f1-f0; bm=(f0+f1)/2; }
| |||
}
| |||
if(best>0.6)
| |||
{
| |||
double rm=(r1+r0)/2;
| |||
FS(g_fa?g_fFa:g_fEn,P(12),FW_BOLD);
| |||
g_cv.TextOut((int)MathRound(cx+rm*MathSin(bm)),(int)MathRound(cy-rm*MathCos(bm)),
| |||
g_fa?Pre(g_s[s].fa,true):g_s[s].en,
| |||
A(st.active?OnClr(g_s[s].clr):Dk(g_s[s].clr,0.35)),TA_CENTER|TA_VCENTER);
| |||
}
| |||
}
| |||
Disc(cx,cy,P(18*k),kFace);
| |||
double ang=(double)(g_refNow%86400)/86400.0*2*M_PI;
| |||
Needle(g_cv,cx,cy,ang,P(80*k),P(12),P(3.2),kHand);
| |||
Disc(cx,cy,P(6.5),kHand);
| |||
Disc(cx,cy,P(2.5),kCard);
| |||
if(InpShowSecHand){ g_ckw=2*P(80*k)+4; g_ckx=cx-g_ckw/2; g_cky=cy-g_ckw/2; }
| |||
| |||
// legend
| |||
int ly=y+P(294);
| |||
int wsum=0,cnt=0,gp=P(18);
| |||
int wid[4];
| |||
for(int s=0;s<4;s++){ wid[s]=0; if(!g_s[s].on) continue; wid[s]=P(16)+TW(g_fa?Pre(g_s[s].fa,true):g_s[s].en,13.5,false); wsum+=wid[s]; cnt++; }
| |||
wsum+=gp*MathMax(cnt-1,0);
| |||
int x=cx-wsum/2;
| |||
for(int q=0;q<4;q++)
| |||
{
| |||
int s=g_fa?3-q:q;
| |||
if(!g_s[s].on) continue;
| |||
Disc(x+P(5),ly,P(5),g_s[s].clr);
| |||
Tx(x+P(16),ly,g_fa?Pre(g_s[s].fa,true):g_s[s].en,kMid,13.5,false,TA_LEFT);
| |||
x+=wid[s]+gp;
| |||
}
| |||
}
| |||
| |||
//--------------------------- digital + dates -----------------------
| |||
void DrawDigital(int x0,int w,int y)
| |||
{
| |||
int cx=x0+w/2;
| |||
color pc; string st=StatusText(pc);
| |||
Pill(cx,y,P(32),st,pc,13.5);
| |||
RR(cx-P(128),y+P(36),P(256),P(60),P(14),kFace,kBorder);
| |||
color ish=g_dark?clrBlack:C'40,55,100';
| |||
g_cv.Line(cx-P(112),y+P(37),cx+P(112),y+P(37),A(Mix(kFace,ish,g_dark?0.55:0.14)));
| |||
g_cv.Line(cx-P(112),y+P(38),cx+P(112),y+P(38),A(Mix(kFace,ish,g_dark?0.30:0.07)));
| |||
g_dgw=1; g_dgx=cx; g_dgy=y+P(66); // native label: HH:MM:SS
| |||
Tx(cx,y+P(106),L("ساعت فعلی به وقت "+TzShort(g_tz,true),"Current time in "+TzShort(g_tz,false)),kMid,13,false,TA_CENTER);
| |||
| |||
MqlDateTime d; TimeToStruct((datetime)g_refNow,d);
| |||
int jy,jm,jd; G2J(d.year,d.mon,d.day,jy,jm,jd);
| |||
string wf[7]={"یکشنبه","دوشنبه","سهشنبه","چهارشنبه","پنجشنبه","جمعه","شنبه"};
| |||
string we[7]={"Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"};
| |||
string mf[12]={"فروردین","اردیبهشت","خرداد","تیر","مرداد","شهریور","مهر","آبان","آذر","دی","بهمن","اسفند"};
| |||
string mj[12]={"Farvardin","Ordibehesht","Khordad","Tir","Mordad","Shahrivar","Mehr","Aban","Azar","Dey","Bahman","Esfand"};
| |||
string me[12]={"January","February","March","April","May","June","July","August","September","October","November","December"};
| |||
string greg=StringFormat("%02d %s %d",d.day,me[d.mon-1],d.year);
| |||
string jal=g_fa?(wf[d.day_of_week]+" "+IntegerToString(jd)+" "+mf[jm-1]+" "+IntegerToString(jy))
| |||
:(IntegerToString(jd)+" "+mj[jm-1]+" "+IntegerToString(jy));
| |||
string gS=Dig(greg), jS=Pre(jal,g_fa);
| |||
if(!g_fa) gS=Dig(we[d.day_of_week]+", "+greg);
| |||
int w1=TW(gS,13,false), w2=TW(jS,13,true), gp=P(22);
| |||
int xs=cx-(w1+w2+gp)/2;
| |||
int dy=y+P(130);
| |||
Tx(xs,dy,gS,kMid,13,false,TA_LEFT);
| |||
Disc(xs+w1+gp/2,dy,P(2),kMid);
| |||
Tx(xs+w1+gp,dy,jS,kText,13,true,TA_LEFT);
| |||
}
| |||
| |||
//--------------------------- session cards -------------------------
| |||
void SessCard(int s,int x,int y,int w,int h)
| |||
{
| |||
SessSt r=g_st[s];
| |||
color c=g_s[s].clr;
| |||
Card(x,y,w,h,P(12),r.active?Mix(c,kCard,0.9):kCard,r.active?c:kBorder,r.active?P(2):1,r.active?5:3,r.active?c:clrNONE);
| |||
int pad=P(14), xa=x+pad, xb=x+w-pad;
| |||
int off=P(18);
| |||
Disc(g_fa?xb-P(6):xa+P(6),y+P(24),P(6),c);
| |||
TxS(g_fa?xa:xa+off,g_fa?xb-off:xb,y+P(24),g_fa?Pre(g_s[s].fa,true):g_s[s].en,kText,17,true);
| |||
string stx=r.active?L("باز","Open"):L("بسته","Closed");
| |||
int pw=TW(stx,12.5,true)+P(24), ph=P(26);
| |||
int pxx=g_fa?xa:xb-pw;
| |||
RR(pxx,y+P(24)-ph/2,pw,ph,ph/2,r.active?Mix(c,kCard,0.78):kTrack,r.active?Mix(c,kCard,0.5):kBorder);
| |||
Tx(pxx+pw/2,y+P(24),stx,r.active?Dk(c,0.4):kSub,12.5,true,TA_CENTER);
| |||
| |||
string df,de;
| |||
string tf=TzShort(g_tz,true), te=TzShort(g_tz,false);
| |||
DayW(r.st,df,de);
| |||
TxS(xa,xb,y+P(62),L("شروع | "+tf,"Start | "+te),kSub,13.5,false);
| |||
TxE(xa,xb,y+P(62),L(df+" "+HM(r.st),de+" "+HM(r.st)),kText,14,true);
| |||
DayW(r.en,df,de);
| |||
TxS(xa,xb,y+P(88),L("پایان | "+tf,"End | "+te),kSub,13.5,false);
| |||
TxE(xa,xb,y+P(88),L(df+" "+HM(r.en),de+" "+HM(r.en)),kText,14,true);
| |||
| |||
string rem=r.active?L("تا پایان: "+DurTxt(r.left,true),"Ends in: "+DurTxt(r.left,false))
| |||
:L("تا شروع: "+DurTxt(r.left,true),"Opens in: "+DurTxt(r.left,false));
| |||
TxS(xa,xb,y+P(116),rem,r.active?Dk(c,0.35):kSub,12.5,r.active);
| |||
int by=y+h-P(15), bh=P(6), bw=xb-xa;
| |||
FillRRc(xa,by,bw,bh,bh/2,A(kTrack));
| |||
if(r.active)
| |||
{
| |||
int fw=(int)MathRound(bw*r.prog);
| |||
if(fw>0) FillRRc(g_fa?xb-fw:xa,by,fw,bh,bh/2,A(c));
| |||
}
| |||
}
| |||
void DrawCards(int x0,int w,int y)
| |||
{
| |||
Title(x0+P(4),x0+w-P(4),y+P(16),L("سشنهای اصلی فارکس","Main Forex Sessions"));
| |||
TxS(x0+P(4),x0+w-P(4),y+P(40),L("جزئیات سشنها به وقت "+TzShort(g_tz,true)+" نمایش داده میشود.","Session details are shown in "+TzShort(g_tz,false)+" time."),kSub,13.5,false);
| |||
int gap=P(10), cw=(w-gap)/2, ch=P(CARD_H);
| |||
int idx=0;
| |||
for(int s=0;s<4;s++)
| |||
{
| |||
if(!g_s[s].on) continue;
| |||
int col=idx%2, row=idx/2;
| |||
int x=((col==0)==g_fa)?x0+w-cw:x0;
| |||
SessCard(s,x,y+P(56)+row*(ch+gap),cw,ch);
| |||
idx++;
| |||
}
| |||
}
| |||
void DrawInfo(int x0,int w,int y)
| |||
{
| |||
int gap=P(10), cw=(w-gap)/2, ch=P(64);
| |||
// next session
| |||
{
| |||
int x=g_fa?x0+w-cw:x0;
| |||
Card(x,y,cw,ch,P(12),kCard,kBorder,1,3);
| |||
int ia=x+P(14), ib=x+cw-P(14);
| |||
int bs=-1; long bt=0;
| |||
for(int s=0;s<4;s++)
| |||
{
| |||
if(!g_s[s].on) continue;
| |||
long st[5],en[5]; Inst(s,g_u,st,en);
| |||
for(int i=0;i<5;i++) if(st[i]>g_u && (bs<0||st[i]<bt)){ bs=s; bt=st[i]; }
| |||
}
| |||
TxS(ia,ib,y+P(14),L("سشن بعدی","Next session"),kSub,13,false);
| |||
if(bs>=0)
| |||
{
| |||
int off=P(16);
| |||
Disc(g_fa?ib-P(5):ia+P(5),y+P(34),P(5),g_s[bs].clr);
| |||
TxS(g_fa?ia:ia+off,g_fa?ib-off:ib,y+P(34),L("سشن "+g_s[bs].fa,g_s[bs].en+" session"),kText,15.5,true);
| |||
TxS(ia,ib,y+P(53),L("شروع "+HM(bt)+" ("+DurTxt(bt-g_u,true)+" دیگر)",HM(bt)+" (in "+DurTxt(bt-g_u,false)+")"),kSub,12.5,false);
| |||
}
| |||
}
| |||
// next / current overlap
| |||
{
| |||
int x=g_fa?x0:x0+w-cw;
| |||
Card(x,y,cw,ch,P(12),kCard,kBorder,1,3);
| |||
int ia=x+P(14), ib=x+cw-P(14);
| |||
int a,b; long lo,hi; bool now;
| |||
TxS(ia,ib,y+P(14),L("همپوشانی (جاری / بعدی)","Overlap (current / next)"),kSub,13,false);
| |||
if(NextOvAny(a,b,lo,hi,now))
| |||
{
| |||
string df,de; DayW(lo,df,de);
| |||
TxS(ia,ib,y+P(34),L(g_s[a].fa+" + "+g_s[b].fa,g_s[a].en+" + "+g_s[b].en),now?kGreen:kText,15.5,true);
| |||
TxS(ia,ib,y+P(53),L(df+" "+HM(lo)+" – "+HM(hi),de+" "+HM(lo)+" – "+HM(hi)),kSub,12.5,false);
| |||
}
| |||
else TxS(ia,ib,y+P(38),"—",kSub,15,true);
| |||
}
| |||
}
| |||
| |||
//--------------------------- overlaps table -------------------------
| |||
void DrawOverlaps(int x0,int w,int y)
| |||
{
| |||
Title(x0+P(4),x0+w-P(4),y+P(16),L("همپوشانی سشنها","Session overlaps"));
| |||
TxS(x0+P(4),x0+w-P(4),y+P(40),L("بازهی همزمانی بازبودن هر دو سشن (به وقت "+TzShort(g_tz,true)+")","Both sessions open at once ("+TzShort(g_tz,false)+" time)"),kSub,13.5,false);
| |||
int ry=y+P(56);
| |||
for(int q=0;q<g_np;q++)
| |||
{
| |||
int a=g_pa[q], b=g_pb[q];
| |||
long lo,hi; bool now;
| |||
bool ok=PairNext(a,b,lo,hi,now);
| |||
color pc=Mix(g_s[a].clr,g_s[b].clr,0.5);
| |||
int rh=P(48);
| |||
Card(x0,ry,w,rh,P(11),now?Mix(pc,kCard,0.88):kCard,now?pc:kBorder,now?P(2):1,now?5:3,now?pc:clrNONE);
| |||
int ia=x0+P(14), ib=x0+w-P(14);
| |||
int dxa=g_fa?ib-P(5):ia+P(5), dir=g_fa?-1:1;
| |||
Disc(dxa,ry+P(16),P(5),g_s[a].clr);
| |||
Disc(dxa+dir*P(13),ry+P(16),P(5),g_s[b].clr);
| |||
int off=P(36);
| |||
TxS(g_fa?ia:ia+off,g_fa?ib-off:ib,ry+P(16),L(g_s[a].fa+" + "+g_s[b].fa,g_s[a].en+" + "+g_s[b].en),kText,14,true);
| |||
string ps=now?L("فعال","Active"):(ok?L("بعدی","Next"):"—");
| |||
int pw=TW(ps,11.5,true)+P(20), ph=P(22);
| |||
int pxx=g_fa?ia:ib-pw;
| |||
RR(pxx,ry+P(16)-ph/2,pw,ph,ph/2,now?Mix(kGreen,kCard,0.8):kTrack,now?Mix(kGreen,kCard,0.5):kBorder);
| |||
Tx(pxx+pw/2,ry+P(16),ps,now?Dk(kGreen,0.3):kSub,11.5,true,TA_CENTER);
| |||
if(ok)
| |||
{
| |||
string df,de; DayW(lo,df,de);
| |||
TxS(ia,ib,ry+P(36),L(df+" "+HM(lo)+" – "+HM(hi)+" ("+DurShort(hi-lo,true)+")",de+" "+HM(lo)+" – "+HM(hi)+" ("+DurShort(hi-lo,false)+")"),
| |||
now?Dk(pc,0.35):kSub,12.5,now);
| |||
}
| |||
else TxS(ia,ib,ry+P(36),L("همپوشانی ندارد","No overlap"),kSub,13.5,false);
| |||
ry+=P(ROW_OV);
| |||
}
| |||
}
| |||
| |||
//--------------------------- alert bar ------------------------------
| |||
void DrawAlertBar(int x0,int w,int y)
| |||
{
| |||
int h=P(44);
| |||
Card(x0,y,w,h,P(12),kCard,kBorder,1,3);
| |||
int ia=x0+P(14), ib=x0+w-P(14), cyy=y+h/2;
| |||
color dc=InpAlerts?kGreen:kSub;
| |||
Disc(g_fa?ib-P(5):ia+P(5),cyy,P(5),dc);
| |||
TxS(g_fa?ia:ia+P(18),g_fa?ib-P(18):ib,cyy,L("هشدار سشنها","Session alerts"),kText,14,true);
| |||
string st;
| |||
if(!InpAlerts) st=L("خاموش","Off");
| |||
else if(InpAlertPre>0) st=L("فعال ("+IntegerToString(InpAlertPre)+" دقیقه قبل)","On ("+IntegerToString(InpAlertPre)+" min before)");
| |||
else st=L("فعال","On");
| |||
TxE(ia,ib,cyy,st,dc,13,true);
| |||
}
| |||
| |||
//--------------------------- 24h timeline ---------------------------
| |||
void HBar(int x0,int x1,int y,int h,double f0,double f1,color c)
| |||
{
| |||
int a=x0+(int)MathRound((x1-x0)*f0), b=x0+(int)MathRound((x1-x0)*f1);
| |||
if(b-a<2) b=a+2;
| |||
FillRRc(a,y,b-a,h,MathMin(h/2,P(5)),A(c));
| |||
}
| |||
void DrawTimeline(int y)
| |||
{
| |||
int x0=P(12), w=g_W-P(24), n=OnCount(), np=NPairs();
| |||
int ch=P(HTL());
| |||
Card(x0,y,w,ch,P(14),kCard,kBorder,1,4);
| |||
int ia=x0+P(18), ib=x0+w-P(18);
| |||
Title(ia,ib,y+P(26),L("نقشهی ۲۴ ساعتهی بازار و همپوشانی سشنها","24-hour market map & session overlaps"));
| |||
long l=g_refNow%86400;
| |||
string hm=StringFormat("%02d:%02d",(int)(l/3600),(int)((l%3600)/60));
| |||
string bt=L("اکنون "+hm+" ("+TzShort(g_tz,true)+")","Now "+hm+" ("+TzShort(g_tz,false)+")");
| |||
int bw=TW(bt,12.5,true)+P(22), bh=P(26);
| |||
int bx=g_fa?ia:ib-bw;
| |||
RR(bx,y+P(26)-bh/2,bw,bh,bh/2,Mix(kNavy,kCard,0.88),Mix(kNavy,kCard,0.65));
| |||
Tx(bx+bw/2,y+P(26),bt,kNavy,12.5,true,TA_CENTER);
| |||
| |||
int nameW=P(220);
| |||
int bx0=g_fa?ia:ia+nameW, bx1=g_fa?ib-nameW:ib;
| |||
int rowH=P(26);
| |||
int ry=y+P(56);
| |||
int sep=(np>0)?P(8):0;
| |||
int gridBottom=ry+(n+np)*rowH+sep;
| |||
for(int h=0;h<=24;h+=2)
| |||
{
| |||
int gx=bx0+(int)MathRound((bx1-bx0)*h/24.0);
| |||
g_cv.Line(gx,ry-P(4),gx,gridBottom-P(2),A(kBorder));
| |||
FS(g_fEn,P(12.5),FW_NORMAL);
| |||
g_cv.TextOut(gx,gridBottom+P(14),Dig(StringFormat("%02d",h)),A(kMid),TA_CENTER|TA_VCENTER);
| |||
}
| |||
int idx=0;
| |||
for(int s=0;s<4;s++)
| |||
{
| |||
if(!g_s[s].on) continue;
| |||
int yy=ry+idx*rowH;
| |||
SessSt st=g_st[s];
| |||
Disc(g_fa?ib-P(5):ia+P(5),yy+P(10),P(5),g_s[s].clr);
| |||
TxS(g_fa?ia:ia+P(18),g_fa?ib-P(18):ib,yy+P(10),g_fa?Pre(g_s[s].fa,true):g_s[s].en,kText,14,st.active);
| |||
FillRRc(bx0,yy+P(3),bx1-bx0,P(14),P(5),A(kTrack));
| |||
long a0[5],a1[5]; int k=ClipDay(s,a0,a1);
| |||
for(int i=0;i<k;i++) HBar(bx0,bx1,yy+P(3),P(14),a0[i]/86400.0,a1[i]/86400.0,SC(s,st.active));
| |||
idx++;
| |||
}
| |||
if(np>0)
| |||
{
| |||
int sy=ry+n*rowH+P(3);
| |||
g_cv.Line(ia,sy,ib,sy,A(kBorder));
| |||
int j=0;
| |||
for(int q=0;q<g_np;q++)
| |||
{
| |||
int a=g_pa[q], b=g_pb[q];
| |||
int yy=ry+n*rowH+sep+j*rowH;
| |||
color pc=Mix(g_s[a].clr,g_s[b].clr,0.5);
| |||
long x0s[25],x1s[25]; int k=PairClip(a,b,x0s,x1s);
| |||
long lo,hi; bool now; PairNext(a,b,lo,hi,now);
| |||
int dxa=g_fa?ib-P(5):ia+P(5), dir=g_fa?-1:1;
| |||
Disc(dxa,yy+P(10),P(4),g_s[a].clr);
| |||
Disc(dxa+dir*P(11),yy+P(10),P(4),g_s[b].clr);
| |||
int off=P(30);
| |||
TxS(g_fa?ia:ia+off,g_fa?ib-off:ib,yy+P(10),L(g_s[a].fa+" + "+g_s[b].fa,g_s[a].en+" + "+g_s[b].en),now?kText:kSub,13.5,now);
| |||
FillRRc(bx0,yy+P(2),bx1-bx0,P(18),P(5),A(kTrack));
| |||
color ca=now?g_s[a].clr:Mix(g_s[a].clr,kCard,g_dark?0.28:0.5);
| |||
color cb=now?g_s[b].clr:Mix(g_s[b].clr,kCard,g_dark?0.28:0.5);
| |||
for(int i=0;i<k;i++)
| |||
{
| |||
HBar(bx0,bx1,yy+P(3),P(8),x0s[i]/86400.0,x1s[i]/86400.0,ca); // upper stripe = first session
| |||
HBar(bx0,bx1,yy+P(11),P(8),x0s[i]/86400.0,x1s[i]/86400.0,cb); // lower stripe = second session
| |||
}
| |||
j++;
| |||
}
| |||
}
| |||
int nx=bx0+(int)MathRound((bx1-bx0)*(double)l/86400.0);
| |||
g_cv.Line(nx,ry-P(4),nx,gridBottom-P(2),A(kHand));
| |||
g_cv.Line(nx+1,ry-P(4),nx+1,gridBottom-P(2),A(kHand));
| |||
Disc(nx,ry-P(5),P(4),kHand);
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Main redraw (once per minute / on click) |
| |||
//+------------------------------------------------------------------+
| |||
// keep the seconds hand and the digital-time label on top of their slots (they move when the layout is mirrored)
| |||
void PlaceOverlays()
| |||
{
| |||
if(g_hasCk){ ObjectSetInteger(0,g_name+"_C",OBJPROP_XDISTANCE,g_px+g_ckx); ObjectSetInteger(0,g_name+"_C",OBJPROP_YDISTANCE,g_py+g_cky); }
| |||
if(g_hasDg){ ObjectSetInteger(0,g_tn,OBJPROP_XDISTANCE,g_px+g_dgx); ObjectSetInteger(0,g_tn,OBJPROP_YDISTANCE,g_py+g_dgy); }
| |||
}
| |||
| |||
void FullRedraw()
| |||
{
| |||
datetime u=TimeGMT();
| |||
g_u=(long)u;
| |||
g_refOff=ZoneOff(g_tz,u);
| |||
g_refNow=g_u+g_refOff;
| |||
g_refMid=g_refNow-(g_refNow%86400);
| |||
g_wk=(InpWeekend && WkClosed(u));
| |||
RefreshStates();
| |||
SortPairs();
| |||
| |||
g_ckw=0; g_dgw=0;
| |||
g_cv.Erase(0);
| |||
RR(0,0,g_W,g_H,P(18),kBg,kBorder);
| |||
int y=P(12);
| |||
if(g_sh[SH_HDR]){ DrawHeader(y); y+=P(H_HDR+10); }
| |||
int bodyH=BodyH();
| |||
if(bodyH>0)
| |||
{
| |||
int x=g_colX0;
| |||
if(HasA())
| |||
{
| |||
int px=MX(x,W_A), yy=y;
| |||
Card(px,y,P(W_A),P((g_sh[SH_CLK]?H_CLK:0)+(g_sh[SH_DIG]?H_DIG:0)),P(16),kCard,kBorder,1,4);
| |||
if(g_sh[SH_CLK]){ DrawClock(px,P(W_A),yy); yy+=P(H_CLK); }
| |||
if(g_sh[SH_DIG]) DrawDigital(px,P(W_A),yy);
| |||
x+=W_A+G_GAP;
| |||
}
| |||
if(HasB())
| |||
{
| |||
int px=MX(x,W_B), yy=y;
| |||
if(g_sh[SH_CRD]){ DrawCards(px,P(W_B),yy); yy+=P(HCards()); }
| |||
if(g_sh[SH_INF]) DrawInfo(px,P(W_B),yy);
| |||
x+=W_B+G_GAP;
| |||
}
| |||
if(HasC())
| |||
{
| |||
int px=MX(x,W_C), yy=y;
| |||
if(g_sh[SH_OVL]){ DrawOverlaps(px,P(W_C),yy); yy+=P(HOvl()); }
| |||
if(g_sh[SH_ALR]) DrawAlertBar(px,P(W_C),yy);
| |||
}
| |||
y+=P(bodyH+10);
| |||
}
| |||
if(g_sh[SH_TL]) DrawTimeline(y);
| |||
g_cv.Update(false);
| |||
PlaceOverlays();
| |||
}
| |||
| |||
// light per-second update: digital time label (+ seconds hand)
| |||
void LiveDraw()
| |||
{
| |||
g_u=(long)TimeGMT();
| |||
g_refNow=g_u+g_refOff;
| |||
if(g_hasCk)
| |||
{
| |||
g_ck.Erase(0);
| |||
int c=g_ckw/2;
| |||
double sa=(double)(g_refNow%60)/60.0*2*M_PI;
| |||
g_ck.Line((int)MathRound(c-P(12)*MathSin(sa)),(int)MathRound(c+P(12)*MathCos(sa)),
| |||
(int)MathRound(c+P(98)*MathSin(sa)),(int)MathRound(c-P(98)*MathCos(sa)),A(C'239,68,68'));
| |||
g_ck.FillCircle(c,c,P(3.5),A(C'239,68,68'));
| |||
g_ck.Update(false);
| |||
}
| |||
if(g_hasDg)
| |||
{
| |||
long l=g_refNow%86400;
| |||
ObjectSetString(0,g_tn,OBJPROP_TEXT,Dig(StringFormat("%02d:%02d:%02d",(int)(l/3600),(int)((l%3600)/60),(int)(l%60))));
| |||
}
| |||
ChartRedraw();
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Alerts |
| |||
//+------------------------------------------------------------------+
| |||
void Notify(int s,int type)
| |||
{
| |||
string m;
| |||
if(g_fa)
| |||
{
| |||
if(type==0) m="سشن "+g_s[s].fa+" باز شد";
| |||
else if(type==1) m="سشن "+g_s[s].fa+" بسته شد";
| |||
else m=IntegerToString(InpAlertPre)+" دقیقه تا شروع سشن "+g_s[s].fa;
| |||
}
| |||
else
| |||
{
| |||
if(type==0) m=g_s[s].en+" session opened";
| |||
else if(type==1) m=g_s[s].en+" session closed";
| |||
else m=g_s[s].en+" session opens in "+IntegerToString(InpAlertPre)+" min";
| |||
}
| |||
if(InpPopup) Alert(m);
| |||
if(InpSound && InpSoundFile!="") PlaySound(InpSoundFile);
| |||
if(InpPush) SendNotification(m);
| |||
if(InpMail) SendMail("Forex Sessions",m);
| |||
}
| |||
void CheckAlerts(datetime u)
| |||
{
| |||
if(!InpAlerts || g_last==0){ g_last=u; return; }
| |||
long a=(long)g_last, b=(long)u;
| |||
for(int s=0;s<4;s++)
| |||
{
| |||
if(!g_s[s].on) continue;
| |||
long st[5],en[5]; Inst(s,b,st,en);
| |||
for(int i=0;i<5;i++)
| |||
{
| |||
if(InpWeekend && WkClosed((datetime)st[i])) continue;
| |||
if(InpAlertOpen && st[i]>a && st[i]<=b) Notify(s,0);
| |||
if(InpAlertClose && en[i]>a && en[i]<=b) Notify(s,1);
| |||
if(InpAlertPre>0){ long t=st[i]-(long)InpAlertPre*60; if(t>a && t<=b) Notify(s,2); }
| |||
}
| |||
}
| |||
g_last=u;
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Setup helpers |
| |||
//+------------------------------------------------------------------+
| |||
void SetupTheme()
| |||
{
| |||
if(g_dark)
| |||
{
| |||
kBg=C'15,18,28'; kCard=C'30,36,54'; kBorder=C'56,66,92'; kText=C'236,240,248';
| |||
kSub=C'160,171,194'; kTrack=C'38,45,64'; kNavy=C'96,125,255'; kGreen=C'52,211,120';
| |||
kMid=C'200,208,226'; kHand=C'245,247,252'; kFace=C'22,26,40'; kAccent=C'96,125,255';
| |||
}
| |||
else
| |||
{
| |||
kBg=C'238,241,248'; kCard=C'255,255,255'; kBorder=C'208,216,232'; kText=C'24,34,58';
| |||
kSub=C'96,108,132'; kTrack=C'238,241,247'; kNavy=C'27,42,74'; kGreen=C'34,166,100';
| |||
kMid=C'48,60,88'; kHand=C'27,42,74'; kFace=C'247,249,253'; kAccent=C'37,99,235';
| |||
}
| |||
}
| |||
void ApplyLang()
| |||
{
| |||
g_pd=(InpDigits==DIG_PERSIAN)||(InpDigits==DIG_AUTO && g_fa);
| |||
}
| |||
void PopupClose()
| |||
{
| |||
if(!g_popOpen) return;
| |||
g_pp.Destroy();
| |||
g_popOpen=false;
| |||
ChartRedraw();
| |||
}
| |||
void Persist()
| |||
{
| |||
if(InpRemember){ GlobalVariableSet(GV_LANG,g_fa?0:1); GlobalVariableSet(GV_TZ,g_tz); GlobalVariableSet(GV_THEME,g_dark?1:0); }
| |||
}
| |||
// time-zone drop-down list (combo box)
| |||
void PopupOpen()
| |||
{
| |||
int rh=P(36), pad=P(4);
| |||
g_ppw=MathMax(g_hit[0][2],P(360));
| |||
g_pph=TZ_COUNT*rh+2*pad;
| |||
g_ppx=g_px+g_hit[0][0];
| |||
g_ppy=g_py+g_hit[0][1]+g_hit[0][3]+P(2);
| |||
int cw=(int)ChartGetInteger(0,CHART_WIDTH_IN_PIXELS), chh=(int)ChartGetInteger(0,CHART_HEIGHT_IN_PIXELS);
| |||
if(g_ppx+g_ppw>cw) g_ppx=MathMax(0,cw-g_ppw);
| |||
if(g_ppy+g_pph>chh) g_ppy=MathMax(0,chh-g_pph);
| |||
if(!g_pp.CreateBitmapLabel(0,0,g_name+"_P",g_ppx,g_ppy,g_ppw,g_pph,COLOR_FORMAT_ARGB_NORMALIZE)) return;
| |||
ObjectSetInteger(0,g_name+"_P",OBJPROP_SELECTABLE,false);
| |||
ObjectSetInteger(0,g_name+"_P",OBJPROP_BACK,false);
| |||
ObjectSetInteger(0,g_name+"_P",OBJPROP_ZORDER,200);
| |||
g_popOpen=true;
| |||
g_pp.Erase(0);
| |||
g_pp.FillRectangle(0,0,g_ppw-1,g_pph-1,A(kNavy));
| |||
g_pp.FillRectangle(2,2,g_ppw-3,g_pph-3,A(kCard));
| |||
datetime u=TimeGMT();
| |||
for(int i=0;i<TZ_COUNT;i++)
| |||
{
| |||
int ry=pad+i*rh;
| |||
if(i==g_tz) g_pp.FillRectangle(3,ry,g_ppw-4,ry+rh-1,A(Mix(kNavy,kCard,0.86)));
| |||
g_pp.FontSet(g_fa?g_fFa:g_fEn,P(15),(i==g_tz)?FW_BOLD:FW_NORMAL);
| |||
g_pp.TextOut(g_fa?g_ppw-P(16):P(16),ry+rh/2,Pre(TzLong(i,g_fa),g_fa),A(kText),(g_fa?TA_RIGHT:TA_LEFT)|TA_VCENTER);
| |||
g_pp.FontSet(g_fEn,P(13),FW_NORMAL);
| |||
g_pp.TextOut(g_fa?P(16):g_ppw-P(16),ry+rh/2,UtcStr(ZoneOff(i,u)),A(kSub),(g_fa?TA_LEFT:TA_RIGHT)|TA_VCENTER);
| |||
}
| |||
g_pp.Update();
| |||
}
| |||
void PopupClick(int y)
| |||
{
| |||
int rh=P(36), pad=P(4);
| |||
int row=(y-pad)/rh;
| |||
if(y<pad || row<0 || row>=TZ_COUNT) return;
| |||
g_tz=row;
| |||
Persist();
| |||
PopupClose();
| |||
FullRedraw();
| |||
LiveDraw();
| |||
}
| |||
| |||
void Place()
| |||
{
| |||
int cw=(int)ChartGetInteger(0,CHART_WIDTH_IN_PIXELS);
| |||
int chh=(int)ChartGetInteger(0,CHART_HEIGHT_IN_PIXELS);
| |||
switch(InpCorner)
| |||
{
| |||
case PC_TL: g_px=InpX; g_py=InpY; break;
| |||
case PC_TR: g_px=cw-g_W-InpX; g_py=InpY; break;
| |||
case PC_BL: g_px=InpX; g_py=chh-g_H-InpY; break;
| |||
default: g_px=cw-g_W-InpX; g_py=chh-g_H-InpY; break;
| |||
}
| |||
if(g_px<0) g_px=0;
| |||
if(g_py<0) g_py=0;
| |||
ObjectSetInteger(0,g_name,OBJPROP_XDISTANCE,g_px);
| |||
ObjectSetInteger(0,g_name,OBJPROP_YDISTANCE,g_py);
| |||
if(g_hasCk){ ObjectSetInteger(0,g_name+"_C",OBJPROP_XDISTANCE,g_px+g_ckx); ObjectSetInteger(0,g_name+"_C",OBJPROP_YDISTANCE,g_py+g_cky); }
| |||
if(g_hasDg){ ObjectSetInteger(0,g_tn,OBJPROP_XDISTANCE,g_px+g_dgx); ObjectSetInteger(0,g_tn,OBJPROP_YDISTANCE,g_py+g_dgy); }
| |||
}
| |||
| |||
// choose scale / visible sections so the panel fits the chart
| |||
int Fit()
| |||
{
| |||
double req=MathMax(0.5,MathMin(2.0,InpScale/100.0));
| |||
g_sh[SH_HDR]=InpShowHeader; g_sh[SH_CLK]=InpShowClock; g_sh[SH_DIG]=InpShowDigital; g_sh[SH_INF]=InpShowInfo;
| |||
g_sh[SH_ALR]=InpShowAlertBar; g_sh[SH_CRD]=InpShowCards; g_sh[SH_TL]=InpShowTimeline; g_sh[SH_OVL]=InpShowOverlaps;
| |||
g_S=req;
| |||
int hb=Metrics();
| |||
if(InpAutoFit)
| |||
{
| |||
int chh=(int)ChartGetInteger(0,CHART_HEIGHT_IN_PIXELS);
| |||
int cw=(int)ChartGetInteger(0,CHART_WIDTH_IN_PIXELS);
| |||
if(chh>150 && cw>150)
| |||
{
| |||
double availH=chh-InpY-6, availW=cw-InpX-6;
| |||
int drop[4]={SH_TL,SH_INF,SH_ALR,SH_OVL};
| |||
double fs=req;
| |||
for(int k=0;k<=4;k++)
| |||
{
| |||
hb=Metrics();
| |||
fs=MathMin(req,MathMin(availH/(double)hb,availW/(double)g_PWb));
| |||
if(fs>=0.6 || k==4) break;
| |||
g_sh[drop[k]]=false;
| |||
}
| |||
g_S=MathMax(0.4,fs);
| |||
}
| |||
}
| |||
int key=(int)(g_S*1000);
| |||
for(int i=0;i<8;i++) if(g_sh[i]) key+=(1<<(i+12));
| |||
return key;
| |||
}
| |||
| |||
void Build()
| |||
{
| |||
PopupClose();
| |||
g_cv.Destroy(); g_ck.Destroy(); ObjectDelete(0,g_tn);
| |||
g_hasCk=false; g_hasDg=false; g_fn=""; g_fp=-1;
| |||
int hb=Metrics();
| |||
g_W=P(g_PWb); g_H=P(hb);
| |||
if(!g_cv.CreateBitmapLabel(0,0,g_name,0,0,g_W,g_H,COLOR_FORMAT_ARGB_NORMALIZE))
| |||
{ Print("Forex Sessions Clock: cannot create canvas"); return; }
| |||
ObjectSetInteger(0,g_name,OBJPROP_SELECTABLE,false);
| |||
ObjectSetInteger(0,g_name,OBJPROP_BACK,false);
| |||
ObjectSetInteger(0,g_name,OBJPROP_ZORDER,100);
| |||
FullRedraw();
| |||
if(g_ckw>0 && g_ck.CreateBitmapLabel(0,0,g_name+"_C",0,0,g_ckw,g_ckw,COLOR_FORMAT_ARGB_NORMALIZE))
| |||
{
| |||
g_hasCk=true;
| |||
ObjectSetInteger(0,g_name+"_C",OBJPROP_SELECTABLE,false);
| |||
ObjectSetInteger(0,g_name+"_C",OBJPROP_BACK,false);
| |||
}
| |||
if(g_dgw>0 && ObjectCreate(0,g_tn,OBJ_LABEL,0,0,0))
| |||
{
| |||
g_hasDg=true;
| |||
ObjectSetInteger(0,g_tn,OBJPROP_CORNER,CORNER_LEFT_UPPER);
| |||
ObjectSetInteger(0,g_tn,OBJPROP_ANCHOR,ANCHOR_CENTER);
| |||
ObjectSetString(0,g_tn,OBJPROP_FONT,g_fEn+" Bold");
| |||
ObjectSetInteger(0,g_tn,OBJPROP_FONTSIZE,(int)MathMax(9,MathRound(34*g_S)));
| |||
ObjectSetInteger(0,g_tn,OBJPROP_COLOR,kText);
| |||
ObjectSetInteger(0,g_tn,OBJPROP_SELECTABLE,false);
| |||
ObjectSetInteger(0,g_tn,OBJPROP_HIDDEN,true);
| |||
ObjectSetInteger(0,g_tn,OBJPROP_BACK,false);
| |||
}
| |||
Place();
| |||
LiveDraw();
| |||
}
| |||
| |||
bool InRect(int idx,int x,int y)
| |||
{
| |||
return (x>=g_hit[idx][0] && x<=g_hit[idx][0]+g_hit[idx][2] && y>=g_hit[idx][1] && y<=g_hit[idx][1]+g_hit[idx][3]);
| |||
}
| |||
void HandleClick(int x,int y)
| |||
{
| |||
bool wasOpen=g_popOpen;
| |||
if(wasOpen) PopupClose();
| |||
if(!g_sh[SH_HDR]) return;
| |||
if(InRect(0,x,y)){ if(!wasOpen) PopupOpen(); return; }
| |||
if(InRect(1,x,y)) g_fa=true;
| |||
else if(InRect(2,x,y)) g_fa=false;
| |||
else if(InRect(3,x,y)){ g_dark=!g_dark; SetupTheme(); if(g_hasDg) ObjectSetInteger(0,g_tn,OBJPROP_COLOR,kText); }
| |||
else return;
| |||
ApplyLang();
| |||
Persist();
| |||
FullRedraw();
| |||
LiveDraw();
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Indicator events |
| |||
//+------------------------------------------------------------------+
| |||
int OnInit()
| |||
{
| |||
g_fa=(InpLang==UI_FA);
| |||
g_tz=(int)InpRefTz;
| |||
g_fix=InpFaFix;
| |||
g_fFa=InpFontFa; g_fEn=InpFontEn;
| |||
g_dark=(InpTheme==THEME_DARK);
| |||
if(InpRemember)
| |||
{
| |||
if(GlobalVariableCheck(GV_THEME)) g_dark=(GlobalVariableGet(GV_THEME)>0.5);
| |||
if(GlobalVariableCheck(GV_LANG)) g_fa=(GlobalVariableGet(GV_LANG)<0.5);
| |||
if(GlobalVariableCheck(GV_TZ)){ int t=(int)GlobalVariableGet(GV_TZ); if(t>=0&&t<TZ_COUNT) g_tz=t; }
| |||
}
| |||
ApplyLang();
| |||
SetupTheme();
| |||
SetupSessions();
| |||
SetupPairs();
| |||
ArrayInitialize(g_hit,0);
| |||
g_name="FSC_Panel_"+IntegerToString(ChartID());
| |||
g_tn=g_name+"_T";
| |||
g_built=false;
| |||
EventSetMillisecondTimer(200); // first drawing happens in OnTimer, so loading is instant
| |||
return INIT_SUCCEEDED;
| |||
}
| |||
| |||
void OnDeinit(const int reason)
| |||
{
| |||
EventKillTimer();
| |||
g_pp.Destroy(); g_ck.Destroy(); g_cv.Destroy();
| |||
ObjectDelete(0,g_tn);
| |||
ChartRedraw();
| |||
}
| |||
| |||
void OnTimer()
| |||
{
| |||
if(!g_built)
| |||
{
| |||
EventKillTimer();
| |||
g_cw=(int)ChartGetInteger(0,CHART_WIDTH_IN_PIXELS);
| |||
g_chh=(int)ChartGetInteger(0,CHART_HEIGHT_IN_PIXELS);
| |||
g_key=Fit();
| |||
Build();
| |||
g_built=true;
| |||
g_lastMin=(long)TimeGMT()/60;
| |||
EventSetTimer(1);
| |||
return;
| |||
}
| |||
if(g_refit)
| |||
{
| |||
g_refit=false;
| |||
int k=Fit();
| |||
if(k!=g_key){ g_key=k; Build(); } else Place();
| |||
ChartRedraw();
| |||
}
| |||
datetime u=TimeGMT();
| |||
long mn=(long)u/60;
| |||
if(mn!=g_lastMin){ g_lastMin=mn; FullRedraw(); }
| |||
LiveDraw();
| |||
CheckAlerts(u);
| |||
}
| |||
| |||
void Click(int ax,int ay)
| |||
{
| |||
uint t=GetTickCount();
| |||
if(t-g_lct<300) return; // same physical click delivered twice
| |||
g_lct=t;
| |||
if(g_popOpen && ax>=g_ppx && ax<g_ppx+g_ppw && ay>=g_ppy && ay<g_ppy+g_pph){ PopupClick(ay-g_ppy); return; }
| |||
if(ax>=g_px && ax<g_px+g_W && ay>=g_py && ay<g_py+g_H){ HandleClick(ax-g_px,ay-g_py); return; }
| |||
PopupClose();
| |||
}
| |||
| |||
void OnChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
| |||
{
| |||
if(!g_built) return;
| |||
if(id==CHARTEVENT_CHART_CHANGE)
| |||
{
| |||
int cw=(int)ChartGetInteger(0,CHART_WIDTH_IN_PIXELS), chh=(int)ChartGetInteger(0,CHART_HEIGHT_IN_PIXELS);
| |||
if(cw!=g_cw || chh!=g_chh){ g_cw=cw; g_chh=chh; g_refit=true; } // handled later in OnTimer
| |||
return;
| |||
}
| |||
if(id==CHARTEVENT_OBJECT_CLICK)
| |||
{
| |||
if(StringFind(sparam,g_name)==0) Click((int)lparam,(int)dparam);
| |||
return;
| |||
}
| |||
if(id==CHARTEVENT_CLICK){ Click((int)lparam,(int)dparam); return; }
| |||
}
| |||
| |||
int OnCalculate(const int rates_total,const int prev_calculated,const datetime &time[],
| |||
const double &open[],const double &high[],const double &low[],const double &close[],
| |||
const long &tick_volume[],const long &volume[],const int &spread[])
| |||
{
| |||
return rates_total;
| |||
}
| |||
//+------------------------------------------------------------------+
|