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