| //+------------------------------------------------------------------+
|
| //| AI Canvas Shell.mqh |
|
| //| Copyright 2026, Allan Munene Mutiiria. |
|
| //| https://t.me/Forex_Algo_Trader |
|
| //+------------------------------------------------------------------+
|
| #property copyright "Copyright 2026, Allan Munene Mutiiria."
|
| #property link "https://t.me/Forex_Algo_Trader"
|
|
|
| //--- Include guard
|
| #ifndef AI_CANVAS_SHELL_MQH
|
| #define AI_CANVAS_SHELL_MQH
|
|
|
| //--- Include required modules
|
| #include "AI Canvas State.mqh"
|
| #include "AI Canvas Primitives.mqh"
|
| #include "AI Canvas Render.mqh"
|
| #include "AI Canvas Editor.mqh"
|
| #include "AI Canvas Interact.mqh"
|
| #include "AI Logic.mqh"
|
|
|
| //--- Forward declare main EA inputs only when not compiled from main
|
| #ifndef AI_COMPILED_FROM_MAIN
|
| extern int MagicNumber; // Magic number for trade ops
|
| extern bool EnableAutoSignal; // Enable auto signal on new bar
|
| #endif
|
|
|
| //+------------------------------------------------------------------+
|
| //| Configure prompt and search editors with current layout |
|
| //+------------------------------------------------------------------+
|
| void Ai_ConfigureEditor()
|
| {
|
| //--- Compute prompt content width from current pane rect
|
| int pL, pT, pR, pB;
|
| Ai_GetPromptPaneRect(pL, pT, pR, pB);
|
| int contentW = (pR - pL) - 2 * AI_TEXT_PAD - 8;
|
| //--- Initialize main prompt editor
|
| g_ai_editor.Init("Arial", AI_FONT_BODY, AI_TEXT_PAD, AI_TEXT_PAD);
|
| g_ai_editor.SetWrapWidth(contentW);
|
| g_ai_editor.SetPlaceholder("Type your prompt here... (Enter for newline, Shift+Enter to send)");
|
| //--- Initialize search popup editor
|
| g_ai_searchEditor.Init("Arial", AI_FONT_BODY, 4, 4);
|
| g_ai_searchEditor.SetWrapWidth(2000);
|
| g_ai_searchEditor.SetPlaceholder("Search across chat titles and conversations...");
|
| }
|
|
|
| //+------------------------------------------------------------------+
|
| //| Recompute layout and reposition prompt overlay |
|
| //+------------------------------------------------------------------+
|
| void Ai_RecomputeLayout()
|
| {
|
| //--- Bail when main canvas does not exist
|
| if(!g_ai_canvMainExists)
|
| return;
|
| //--- Reconfigure editors with current dimensions
|
| Ai_ConfigureEditor();
|
| //--- Reposition prompt overlay to match new sidebar width
|
| if(g_ai_canvPromptExists)
|
| {
|
| int dpL, dpT, dpR, dpB;
|
| Ai_GetPromptPaneRect(dpL, dpT, dpR, dpB);
|
| ObjectSetInteger(0, AI_CANVAS_NAME_PROMPT, OBJPROP_XDISTANCE, AI_DASHBOARD_X + dpL);
|
| ObjectSetInteger(0, AI_CANVAS_NAME_PROMPT, OBJPROP_YDISTANCE, AI_DASHBOARD_Y + dpT);
|
| }
|
| }
|
|
|
| //+------------------------------------------------------------------+
|
| //| Create main dashboard canvas and prompt overlay |
|
| //+------------------------------------------------------------------+
|
| bool Ai_CreateMainCanvas()
|
| {
|
| //--- Skip if already created
|
| if(g_ai_canvMainExists)
|
| return true;
|
| //--- Compute full dashboard size
|
| const int dW = AI_SIDEBAR_W_EXPANDED + AI_MAIN_W;
|
| const int dH = AI_HEADER_H + AI_PAD + AI_DISPLAY_H + AI_PAD + AI_PROMPT_H + AI_PAD + AI_FOOTER_H;
|
| //--- Create main canvas as bitmap label
|
| if(!g_ai_canvMain.CreateBitmapLabel(AI_CANVAS_NAME_MAIN,
|
| AI_DASHBOARD_X, AI_DASHBOARD_Y,
|
| dW, dH, COLOR_FORMAT_ARGB_NORMALIZE))
|
| {
|
| Print("Ai_CreateMainCanvas: CreateBitmapLabel failed: ", GetLastError());
|
| return false;
|
| }
|
| //--- Configure main canvas object properties
|
| ObjectSetInteger(0, AI_CANVAS_NAME_MAIN, OBJPROP_BACK, false);
|
| ObjectSetInteger(0, AI_CANVAS_NAME_MAIN, OBJPROP_SELECTABLE, false);
|
| ObjectSetInteger(0, AI_CANVAS_NAME_MAIN, OBJPROP_HIDDEN, true);
|
| ObjectSetInteger(0, AI_CANVAS_NAME_MAIN, OBJPROP_ZORDER, 1000);
|
| g_ai_canvMainExists = true;
|
| //--- Compute prompt overlay screen position and size
|
| int ppL, ppT, ppR, ppB;
|
| Ai_GetPromptPaneRect(ppL, ppT, ppR, ppB);
|
| const int promptW = ppR - ppL;
|
| const int promptH = ppB - ppT;
|
| const int promptScreenX = AI_DASHBOARD_X + ppL;
|
| const int promptScreenY = AI_DASHBOARD_Y + ppT;
|
| //--- Try create prompt overlay - non-fatal if it fails
|
| if(g_ai_canvPrompt.CreateBitmapLabel(AI_CANVAS_NAME_PROMPT,
|
| promptScreenX, promptScreenY,
|
| promptW, promptH, COLOR_FORMAT_ARGB_NORMALIZE))
|
| {
|
| //--- Configure prompt overlay base properties
|
| ObjectSetInteger(0, AI_CANVAS_NAME_PROMPT, OBJPROP_BACK, false);
|
| ObjectSetInteger(0, AI_CANVAS_NAME_PROMPT, OBJPROP_SELECTABLE, false);
|
| ObjectSetInteger(0, AI_CANVAS_NAME_PROMPT, OBJPROP_HIDDEN, true);
|
| //--- Suppress default object-name tooltip
|
| ObjectSetString(0, AI_CANVAS_NAME_PROMPT, OBJPROP_TOOLTIP, "\n");
|
| //--- Always-visible flag - hide done via bitmap content not visibility
|
| ObjectSetInteger(0, AI_CANVAS_NAME_PROMPT, OBJPROP_TIMEFRAMES, OBJ_ALL_PERIODS);
|
| //--- High zorder for click event routing (visual stacking is by creation order)
|
| ObjectSetInteger(0, AI_CANVAS_NAME_PROMPT, OBJPROP_ZORDER, 1001);
|
| g_ai_canvPromptExists = true;
|
| }
|
| else
|
| {
|
| //--- Log failure and fall back to main-only renders
|
| Print("Ai_CreateMainCanvas: prompt overlay create failed (",
|
| GetLastError(), ") - falling back to main-only renders.");
|
| g_ai_canvPromptExists = false;
|
| }
|
| return true;
|
| }
|
|
|
| //+------------------------------------------------------------------+
|
| //| Destroy main canvas and prompt overlay |
|
| //+------------------------------------------------------------------+
|
| void Ai_DestroyMainCanvas()
|
| {
|
| //--- Bail when nothing to destroy
|
| if(!g_ai_canvMainExists)
|
| return;
|
| //--- Destroy prompt overlay first
|
| if(g_ai_canvPromptExists)
|
| {
|
| g_ai_canvPrompt.Destroy();
|
| ObjectDelete(0, AI_CANVAS_NAME_PROMPT);
|
| g_ai_canvPromptExists = false;
|
| }
|
| //--- Destroy main canvas
|
| g_ai_canvMain.Destroy();
|
| ObjectDelete(0, AI_CANVAS_NAME_MAIN);
|
| g_ai_canvMainExists = false;
|
| }
|
|
|
| //+------------------------------------------------------------------+
|
| //| Show dashboard - load images, create canvas, render |
|
| //+------------------------------------------------------------------+
|
| void Ai_ShowDashboard()
|
| {
|
| //--- Load images for icons and logos
|
| if(!Ai_LoadImages())
|
| Print("Ai_ShowDashboard: image load partial");
|
| //--- Create canvases and bail on failure
|
| if(!Ai_CreateMainCanvas())
|
| {
|
| Print("Ai_ShowDashboard: failed to create canvas");
|
| return;
|
| }
|
| //--- Configure editors and restore prompt text
|
| Ai_ConfigureEditor();
|
| g_ai_editor.SetText(g_ai_currentPrompt);
|
| //--- Enable mouse events on chart
|
| ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true);
|
| ChartSetInteger(0, CHART_EVENT_MOUSE_WHEEL, true);
|
| ChartSetInteger(0, CHART_MOUSE_SCROLL, true);
|
| //--- Mark visible and trigger first render
|
| g_ai_dashboardVisible = true;
|
| Ai_RenderAll();
|
| ChartRedraw();
|
| }
|
|
|
| //+------------------------------------------------------------------+
|
| //| Hide dashboard - persist state and destroy canvas |
|
| //+------------------------------------------------------------------+
|
| void Ai_HideDashboard()
|
| {
|
| //--- Save current chat state to disk
|
| AiUpdateCurrentHistory();
|
| //--- Release keyboard override
|
| Ai_EndKeyboardOverride();
|
| //--- Restore default chart scroll behavior
|
| ChartSetInteger(0, CHART_MOUSE_SCROLL, true);
|
| //--- Destroy canvases and mark hidden
|
| Ai_DestroyMainCanvas();
|
| g_ai_dashboardVisible = false;
|
| ChartRedraw();
|
| }
|
|
|
| //+------------------------------------------------------------------+
|
| //| Initialize EA - called from OnInit |
|
| //+------------------------------------------------------------------+
|
| bool Ai_Init()
|
| {
|
| //--- Apply default theme
|
| Ai_ApplyTheme(false);
|
| //--- Initialize all scroll states
|
| AiScrollInit(g_ai_chatScroll);
|
| AiScrollInit(g_ai_bigScroll);
|
| AiScrollInit(g_ai_searchScroll);
|
| //--- Configure trade helper magic number
|
| g_ai_trade.SetExpertMagicNumber(MagicNumber);
|
| //--- Open log file in append mode (organized under AI EA Data\Logs)
|
| g_ai_logFileHandle = FileOpen(AI_LOG_DIR + LogFileName, FILE_READ | FILE_WRITE | FILE_TXT);
|
| if(g_ai_logFileHandle == INVALID_HANDLE)
|
| {
|
| Print("Failed to open log file: ", GetLastError());
|
| }
|
| else
|
| {
|
| FileSeek(g_ai_logFileHandle, 0, SEEK_END);
|
| }
|
| //--- Load saved chats from disk and show dashboard
|
| AiLoadChats();
|
| Ai_ShowDashboard();
|
| return true;
|
| }
|
|
|
| //+------------------------------------------------------------------+
|
| //| Deinitialize EA - called from OnDeinit |
|
| //+------------------------------------------------------------------+
|
| void Ai_Deinit()
|
| {
|
| //--- Save current chat state and hide dashboard
|
| AiUpdateCurrentHistory();
|
| Ai_HideDashboard();
|
| //--- Close log file
|
| if(g_ai_logFileHandle != INVALID_HANDLE)
|
| {
|
| FileClose(g_ai_logFileHandle);
|
| g_ai_logFileHandle = INVALID_HANDLE;
|
| }
|
| //--- Destroy persistent chat scratch canvas
|
| if(g_ai_canvChatTmpReady)
|
| {
|
| g_ai_canvChatTmp.Destroy();
|
| g_ai_canvChatTmpReady = false;
|
| }
|
| //--- Destroy persistent search scratch canvas
|
| if(g_ai_canvSearchTmpReady)
|
| {
|
| g_ai_canvSearchTmp.Destroy();
|
| g_ai_canvSearchTmpReady = false;
|
| }
|
| //--- Destroy primitives high-res fill canvas
|
| if(g_ai_prim.m_hrFillReady)
|
| {
|
| g_ai_prim.m_hrFill.Destroy();
|
| g_ai_prim.m_hrFillReady = false;
|
| }
|
| //--- Destroy primitives high-res border canvas
|
| if(g_ai_prim.m_hrBorderReady)
|
| {
|
| g_ai_prim.m_hrBorder.Destroy();
|
| g_ai_prim.m_hrBorderReady = false;
|
| }
|
| //--- Destroy main editor scratch canvas
|
| if(g_ai_editor.tmpCanvasReady)
|
| {
|
| g_ai_editor.tmpCanvas.Destroy();
|
| g_ai_editor.tmpCanvasReady = false;
|
| }
|
| //--- Destroy search editor scratch canvas
|
| if(g_ai_searchEditor.tmpCanvasReady)
|
| {
|
| g_ai_searchEditor.tmpCanvas.Destroy();
|
| g_ai_searchEditor.tmpCanvasReady = false;
|
| }
|
| //--- Free image pixel buffers
|
| g_ai_imagesLoaded = false;
|
| }
|
|
|
| //+------------------------------------------------------------------+
|
| //| Process new bar tick for auto signal generation |
|
| //+------------------------------------------------------------------+
|
| void Ai_OnTick()
|
| {
|
| //--- Read latest bar timestamp
|
| datetime currentBarTime[1];
|
| if(CopyTime(Symbol(), Period(), 0, 1, currentBarTime) != 1)
|
| return;
|
| //--- Trigger auto signal on new bar when enabled
|
| if(currentBarTime[0] != g_ai_lastBarTime && EnableAutoSignal)
|
| {
|
| g_ai_lastBarTime = currentBarTime[0];
|
| Print("New bar detected at ", TimeToString(g_ai_lastBarTime, TIME_DATE | TIME_MINUTES),
|
| ". Running auto signal check...");
|
| AiGetTradeSignal(true);
|
| }
|
| }
|
|
|
| #endif // AI_CANVAS_SHELL_MQH
|
| //+------------------------------------------------------------------+
|