//+------------------------------------------------------------------+
//|                                         PatternRecorderPanel.mqh |
//|                                  Copyright 2026, MetaQuotes Ltd. |
//|                          https://www.mql5.com/en/users/lynnchris |
//+------------------------------------------------------------------+
#property copyright "Copyright 2026, MetaQuotes Ltd."
#property link      "https://www.mql5.com/en/users/lynnchris"
#property version   "1.0"

#ifndef PART83_PATTERNRECORDERPANEL_MQH
#define PART83_PATTERNRECORDERPANEL_MQH


#include "PatternLibrary.mqh"
//--- Pattern extraction converts the selection rectangle into candle data
#include "PatternExtractor.mqh"

//+------------------------------------------------------------------+
//| Declares the chart-object recorder panel                         |
//+------------------------------------------------------------------+
class CRecorderPanel
  {
private:
   long              m_chart_id;
   int               m_subwin;
   int               m_x, m_y;
   int               m_width, m_height;
   string            m_prefix;

   //--- Base object names
   string            m_bg_name;
   string            m_title_name;
   string            m_pattern_name;
   string            m_status_name;
   string            m_select_info_name;
   string            m_notes_label_name;
   string            m_notes_edit_name;
   string            m_count_name;
   string            m_btn_select_name;
   string            m_btn_record_name;
   string            m_btn_cancel_name;
   string            m_btn_view_name;
   string            m_btn_delete_name;
   string            m_btn_close_name;
   //--- Name reserved for the chart selection rectangle
   string            m_rect_name;

   //--- Dynamic sample buttons (prefix "sample_")
   string            m_sample_prefix;
   ulong             m_selected_sample_id;

   //--- Library and state
   CPatternLibrary   *m_library;
   bool               m_isSelectionMode;
   bool               m_hasPending;
   SPatternSample     m_pendingSample;

public:
                     CRecorderPanel();
                    ~CRecorderPanel();

   bool              Create(CPatternLibrary *lib, long chart_id, int subwin, int x, int y);
   void              Destroy();
   void              OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam);

   void              UpdateUI();
   void              RefreshSampleList();
   void              ShowStatus(string msg, bool is_error = false);

private:
   void              CreateObject(string name, ENUM_OBJECT type, int x, int y, int w, int h, string text = "");
   void              SetObjectText(string name, string text);
   string            GetObjectText(string name);
   bool              ObjectExists(string name);
   void              DeleteObject(string name);
   void              DeleteObjectsWithPrefix(string prefix);

   void              OnSelectFormation();
   void              OnRecord();
   void              OnCancel();
   void              OnViewSamples();
   void              OnDeleteSample();
   void              OnClose();
   void              OnSampleClick(string name);

   void              ClearSelection();
   bool              FindActiveRectangle();
   bool              CaptureCurrentSelection();
   void              ShowPendingInfo();
   bool              IsMyObject(string name);
  };

//+------------------------------------------------------------------+
//| Initializes recorder-panel state and object names                |
//+------------------------------------------------------------------+
CRecorderPanel::CRecorderPanel()
   : m_chart_id(0), m_subwin(0), m_x(0), m_y(0), m_width(350), m_height(460),
     m_library(NULL), m_isSelectionMode(false), m_hasPending(false),
     m_selected_sample_id(0)
  {
   m_prefix = "PatternRecorder_";
   m_bg_name = m_prefix + "bg";
   m_title_name = m_prefix + "title";
   m_pattern_name = m_prefix + "pattern";
   m_status_name = m_prefix + "status";
   m_select_info_name = m_prefix + "info";
   m_notes_label_name = m_prefix + "notes_label";
   m_notes_edit_name = m_prefix + "notes_edit";
   m_count_name = m_prefix + "count";
   m_btn_select_name = m_prefix + "btn_select";
   m_btn_record_name = m_prefix + "btn_record";
   m_btn_cancel_name = m_prefix + "btn_cancel";
   m_btn_view_name = m_prefix + "btn_view";
   m_btn_delete_name = m_prefix + "btn_delete";
   m_btn_close_name = m_prefix + "btn_close";
   m_rect_name = "PatternRect";
   m_sample_prefix = m_prefix + "sample_";
  }

//+------------------------------------------------------------------+
//| Removes recorder-panel chart objects during destruction          |
//+------------------------------------------------------------------+
CRecorderPanel::~CRecorderPanel()
  {
   Destroy();
  }

//+------------------------------------------------------------------+
//| Creates all recorder-panel chart objects                         |
//+------------------------------------------------------------------+
bool CRecorderPanel::Create(CPatternLibrary *lib, long chart_id, int subwin, int x, int y)
  {
   m_library = lib;
   m_chart_id = chart_id;
   m_subwin = subwin;
   m_x = x;
   m_y = y;

//--- Create a border-only rectangle as the panel background
   CreateObject(m_bg_name, OBJ_RECTANGLE_LABEL, x, y, m_width, m_height, "");
   ObjectSetInteger(m_chart_id, m_bg_name, OBJPROP_BORDER_COLOR, clrBlack);
   ObjectSetInteger(m_chart_id, m_bg_name, OBJPROP_WIDTH, 1);

//--- Create the panel title
   CreateObject(m_title_name, OBJ_LABEL, x+10, y+5, m_width-20, 30, "  PATTERN RECORDER");
   ObjectSetInteger(m_chart_id, m_title_name, OBJPROP_COLOR, clrBlack);
   ObjectSetInteger(m_chart_id, m_title_name, OBJPROP_FONTSIZE, 12);
   ObjectSetInteger(m_chart_id, m_title_name, OBJPROP_ALIGN, ALIGN_LEFT);

//--- Identify the pattern category recorded by this EA
   CreateObject(m_pattern_name, OBJ_LABEL, x+10, y+40, m_width-20, 25, "Label: DOUBLE BOTTOM CANDIDATE");
   ObjectSetInteger(m_chart_id, m_pattern_name, OBJPROP_COLOR, clrBlack);
   ObjectSetInteger(m_chart_id, m_pattern_name, OBJPROP_FONTSIZE, 10);

//--- Reserve a status line for success and error feedback
   CreateObject(m_status_name, OBJ_LABEL, x+10, y+70, m_width-20, 25, "Ready.");
   ObjectSetInteger(m_chart_id, m_status_name, OBJPROP_COLOR, clrDarkGreen);
   ObjectSetInteger(m_chart_id, m_status_name, OBJPROP_FONTSIZE, 10);

//--- Create the button that starts rectangle selection
   CreateObject(m_btn_select_name, OBJ_BUTTON, x+10, y+100, m_width-20, 28, "SELECT FORMATION");
   ObjectSetInteger(m_chart_id, m_btn_select_name, OBJPROP_COLOR, clrBlack);
   ObjectSetInteger(m_chart_id, m_btn_select_name, OBJPROP_BORDER_COLOR, clrBlack);
   ObjectSetInteger(m_chart_id, m_btn_select_name, OBJPROP_FONTSIZE, 10);

//--- Create the record and cancel controls on one row
   int bw = (m_width-30)/2;
   CreateObject(m_btn_record_name, OBJ_BUTTON, x+10, y+135, bw, 28, "RECORD");
   ObjectSetInteger(m_chart_id, m_btn_record_name, OBJPROP_COLOR, clrBlack);
   ObjectSetInteger(m_chart_id, m_btn_record_name, OBJPROP_BORDER_COLOR, clrBlack);
   ObjectSetInteger(m_chart_id, m_btn_record_name, OBJPROP_FONTSIZE, 10);
   CreateObject(m_btn_cancel_name, OBJ_BUTTON, x+20+bw, y+135, bw, 28, "CANCEL");
   ObjectSetInteger(m_chart_id, m_btn_cancel_name, OBJPROP_COLOR, clrBlack);
   ObjectSetInteger(m_chart_id, m_btn_cancel_name, OBJPROP_BORDER_COLOR, clrBlack);
   ObjectSetInteger(m_chart_id, m_btn_cancel_name, OBJPROP_FONTSIZE, 10);

//--- Display the time range and bar count for the active selection
   CreateObject(m_select_info_name, OBJ_LABEL, x+10, y+170, m_width-20, 50, "No formation selected.");
   ObjectSetInteger(m_chart_id, m_select_info_name, OBJPROP_COLOR, clrBlack);
   ObjectSetInteger(m_chart_id, m_select_info_name, OBJPROP_FONTSIZE, 10);

//--- Provide a free-text field for the trader's observations
   CreateObject(m_notes_label_name, OBJ_LABEL, x+10, y+225, 40, 25, "Notes:");
   ObjectSetInteger(m_chart_id, m_notes_label_name, OBJPROP_COLOR, clrBlack);
   CreateObject(m_notes_edit_name, OBJ_EDIT, x+55, y+225, m_width-65, 25, "");
   ObjectSetInteger(m_chart_id, m_notes_edit_name, OBJPROP_COLOR, clrBlack);
   ObjectSetInteger(m_chart_id, m_notes_edit_name, OBJPROP_BORDER_COLOR, clrBlack);

//--- Display the number of samples currently in the library
   CreateObject(m_count_name, OBJ_LABEL, x+10, y+255, m_width-20, 25, "Recorded Samples: 0");
   ObjectSetInteger(m_chart_id, m_count_name, OBJPROP_COLOR, clrBlack);
   ObjectSetInteger(m_chart_id, m_count_name, OBJPROP_FONTSIZE, 10);

//--- Create sample-list and deletion controls
   CreateObject(m_btn_view_name, OBJ_BUTTON, x+10, y+285, bw, 28, "VIEW SAMPLES");
   ObjectSetInteger(m_chart_id, m_btn_view_name, OBJPROP_COLOR, clrBlack);
   ObjectSetInteger(m_chart_id, m_btn_view_name, OBJPROP_BORDER_COLOR, clrBlack);
   ObjectSetInteger(m_chart_id, m_btn_view_name, OBJPROP_FONTSIZE, 10);
   CreateObject(m_btn_delete_name, OBJ_BUTTON, x+20+bw, y+285, bw, 28, "DELETE SELECTED");
   ObjectSetInteger(m_chart_id, m_btn_delete_name, OBJPROP_COLOR, clrBlack);
   ObjectSetInteger(m_chart_id, m_btn_delete_name, OBJPROP_BORDER_COLOR, clrBlack);
   ObjectSetInteger(m_chart_id, m_btn_delete_name, OBJPROP_FONTSIZE, 10);

//--- Create the close button at the bottom of the panel
   CreateObject(m_btn_close_name, OBJ_BUTTON, x+10, y+425, m_width-20, 28, "CLOSE");
   ObjectSetInteger(m_chart_id, m_btn_close_name, OBJPROP_COLOR, clrBlack);
   ObjectSetInteger(m_chart_id, m_btn_close_name, OBJPROP_BORDER_COLOR, clrBlack);
   ObjectSetInteger(m_chart_id, m_btn_close_name, OBJPROP_FONTSIZE, 10);

//--- Synchronize all dynamic labels and redraw the chart
   UpdateUI();
   RefreshSampleList();
   ShowStatus("Ready.", false);
   ChartRedraw(m_chart_id);
   return(true);
  }

//+------------------------------------------------------------------+
//| Destroy all objects                                              |
//+------------------------------------------------------------------+
void CRecorderPanel::Destroy()
  {
   DeleteObject(m_bg_name);
   DeleteObject(m_title_name);
   DeleteObject(m_pattern_name);
   DeleteObject(m_status_name);
   DeleteObject(m_select_info_name);
   DeleteObject(m_notes_label_name);
   DeleteObject(m_notes_edit_name);
   DeleteObject(m_count_name);
   DeleteObject(m_btn_select_name);
   DeleteObject(m_btn_record_name);
   DeleteObject(m_btn_cancel_name);
   DeleteObject(m_btn_view_name);
   DeleteObject(m_btn_delete_name);
   DeleteObject(m_btn_close_name);
   DeleteObjectsWithPrefix(m_sample_prefix);
   ObjectDelete(m_chart_id, m_rect_name);
   ChartRedraw(m_chart_id);
  }

//+------------------------------------------------------------------+
//| Helper: Create an object                                         |
//+------------------------------------------------------------------+
void CRecorderPanel::CreateObject(string name, ENUM_OBJECT type, int x, int y, int w, int h, string text = "")
  {
   if(!ObjectCreate(m_chart_id, name, type, m_subwin, 0, 0))
      return;
   ObjectSetInteger(m_chart_id, name, OBJPROP_XDISTANCE, x);
   ObjectSetInteger(m_chart_id, name, OBJPROP_YDISTANCE, y);
   ObjectSetInteger(m_chart_id, name, OBJPROP_XSIZE, w);
   ObjectSetInteger(m_chart_id, name, OBJPROP_YSIZE, h);
   if(text != "")
      ObjectSetString(m_chart_id, name, OBJPROP_TEXT, text);
   if(type == OBJ_BUTTON)
     {
      ObjectSetInteger(m_chart_id, name, OBJPROP_STATE, false);
      ObjectSetInteger(m_chart_id, name, OBJPROP_BORDER_TYPE, BORDER_FLAT);
      //--- Style sample buttons as transparent clickable text
      if(StringFind(name, m_sample_prefix) == 0)
        {
         //--- Remove both fill and border from sample-list buttons
         ObjectSetInteger(m_chart_id, name, OBJPROP_BGCOLOR, clrNONE);
         ObjectSetInteger(m_chart_id, name, OBJPROP_BORDER_COLOR, clrNONE);
        }
     }
   if(type == OBJ_EDIT)
      ObjectSetInteger(m_chart_id, name, OBJPROP_READONLY, false);
   if(type == OBJ_LABEL)
      ObjectSetInteger(m_chart_id, name, OBJPROP_HIDDEN, false);
  }

//+------------------------------------------------------------------+
//| Helper: Set object text                                          |
//+------------------------------------------------------------------+
void CRecorderPanel::SetObjectText(string name, string text)
  {
   if(ObjectExists(name))
      ObjectSetString(m_chart_id, name, OBJPROP_TEXT, text);
  }

//+------------------------------------------------------------------+
//| Helper: Get object text                                          |
//+------------------------------------------------------------------+
string CRecorderPanel::GetObjectText(string name)
  {
   if(ObjectExists(name))
      return(ObjectGetString(m_chart_id, name, OBJPROP_TEXT));
   return("");
  }

//+------------------------------------------------------------------+
//| Helper: Check object exists                                      |
//+------------------------------------------------------------------+
bool CRecorderPanel::ObjectExists(string name)
  {
   return(ObjectFind(m_chart_id, name) >= 0);
  }

//+------------------------------------------------------------------+
//| Helper: Delete an object                                         |
//+------------------------------------------------------------------+
void CRecorderPanel::DeleteObject(string name)
  {
   if(ObjectExists(name))
      ObjectDelete(m_chart_id, name);
  }

//+------------------------------------------------------------------+
//| Delete all objects with a given prefix                           |
//+------------------------------------------------------------------+
void CRecorderPanel::DeleteObjectsWithPrefix(string prefix)
  {
   string obj_name;
   for(int i = ObjectsTotal(m_chart_id, m_subwin) - 1; i >= 0; i--)
     {
      obj_name = ObjectName(m_chart_id, i, m_subwin);
      if(StringFind(obj_name, prefix) == 0)
         ObjectDelete(m_chart_id, obj_name);
     }
  }

//+------------------------------------------------------------------+
//| Check if object belongs to this panel                            |
//+------------------------------------------------------------------+
bool CRecorderPanel::IsMyObject(string name)
  {
   return((StringFind(name, m_prefix) == 0) || (name == m_rect_name));
  }

//+------------------------------------------------------------------+
//| Chart event handler                                              |
//+------------------------------------------------------------------+
void CRecorderPanel::OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
  {
   if(id == CHARTEVENT_OBJECT_CLICK)
     {
      string name = sparam;
      if(!IsMyObject(name))
         return;

      if(StringFind(name, m_sample_prefix) == 0)
         OnSampleClick(name);
      else
         if(name == m_btn_select_name)
            OnSelectFormation();
         else
            if(name == m_btn_record_name)
               OnRecord();
            else
               if(name == m_btn_cancel_name)
                  OnCancel();
               else
                  if(name == m_btn_view_name)
                     OnViewSamples();
                  else
                     if(name == m_btn_delete_name)
                        OnDeleteSample();
                     else
                        if(name == m_btn_close_name)
                           OnClose();
     }
  }

//+------------------------------------------------------------------+
//| Handle click on a sample button                                  |
//+------------------------------------------------------------------+
void CRecorderPanel::OnSampleClick(string name)
  {
   string text = ObjectGetString(m_chart_id, name, OBJPROP_TEXT);
   string id_str = text;
   StringReplace(id_str, "#", "");
   int space = StringFind(id_str, " ");
   if(space > 0)
      id_str = StringSubstr(id_str, 0, space);
   ulong id = (ulong)StringToInteger(id_str);
   if(id == 0)
      return;

   m_selected_sample_id = id;
   ShowStatus("Selected sample #" + IntegerToString(id), false);

//--- Highlight the selected sample and reset all other sample colors
   string obj_name;
   for(int i = ObjectsTotal(m_chart_id, m_subwin) - 1; i >= 0; i--)
     {
      obj_name = ObjectName(m_chart_id, i, m_subwin);
      if(StringFind(obj_name, m_sample_prefix) == 0)
        {
         if(obj_name == name)
            ObjectSetInteger(m_chart_id, obj_name, OBJPROP_COLOR, clrYellow);
         else
            ObjectSetInteger(m_chart_id, obj_name, OBJPROP_COLOR, clrBlack);
        }
     }
   ChartRedraw(m_chart_id);
  }

//+------------------------------------------------------------------+
//| Starts formation selection with a chart rectangle                |
//+------------------------------------------------------------------+
void CRecorderPanel::OnSelectFormation()
  {
   ClearSelection();
   if(!ObjectCreate(m_chart_id, m_rect_name, OBJ_RECTANGLE, m_subwin, 0, 0))
     {
      ShowStatus("Failed to create selection rectangle.", true);
      return;
     }
   ObjectSetInteger(m_chart_id, m_rect_name, OBJPROP_COLOR, clrDodgerBlue);
   ObjectSetInteger(m_chart_id, m_rect_name, OBJPROP_WIDTH, 2);
   ObjectSetInteger(m_chart_id, m_rect_name, OBJPROP_STYLE, STYLE_SOLID);
   ObjectSetInteger(m_chart_id, m_rect_name, OBJPROP_FILL, false);
   ObjectSetInteger(m_chart_id, m_rect_name, OBJPROP_BACK, false);
   ObjectSetInteger(m_chart_id, m_rect_name, OBJPROP_SELECTABLE, true);
   ObjectSetInteger(m_chart_id, m_rect_name, OBJPROP_SELECTED, true);
   ObjectSetInteger(m_chart_id, m_rect_name, OBJPROP_HIDDEN, false);
   datetime t1 = TimeCurrent() - PeriodSeconds() * 30;
   datetime t2 = TimeCurrent();
   double price_high = ChartGetDouble(m_chart_id, CHART_PRICE_MAX);
   double price_low = ChartGetDouble(m_chart_id, CHART_PRICE_MIN);
   ObjectSetDouble(m_chart_id, m_rect_name, OBJPROP_PRICE, 0, price_high);
   ObjectSetDouble(m_chart_id, m_rect_name, OBJPROP_PRICE, 1, price_low);
   ObjectSetInteger(m_chart_id, m_rect_name, OBJPROP_TIME, 0, t1);
   ObjectSetInteger(m_chart_id, m_rect_name, OBJPROP_TIME, 1, t2);
   ChartRedraw(m_chart_id);
   m_isSelectionMode = true;
   m_hasPending = false;
   SetObjectText(m_select_info_name, "Drag the blue rectangle to cover the formation.\nThen click RECORD.");
   ShowStatus("Select formation using the rectangle.", false);
   UpdateUI();
  }

//+------------------------------------------------------------------+
//| Captures and stores the selected formation                       |
//+------------------------------------------------------------------+
void CRecorderPanel::OnRecord()
  {
   if(!m_isSelectionMode)
     {
      ShowStatus("Please click SELECT FORMATION first.", true);
      return;
     }
   if(!FindActiveRectangle())
     {
      ShowStatus("Rectangle not found. Please redraw.", true);
      return;
     }
   if(!CaptureCurrentSelection())
     {
      ShowStatus("Failed to capture formation. Check rectangle covers at least 2 candles.", true);
      return;
     }
   m_pendingSample.notes = GetObjectText(m_notes_edit_name);
   if(StringLen(m_pendingSample.notes) > MAX_NOTES_LENGTH)
     {
      ShowStatus("Notes exceed the 256-character limit.", true);
      return;
     }
   ulong new_id = m_library.GetNewID();
   m_pendingSample.id = new_id;
   if(m_library.AddSample(m_pendingSample))
     {
      ShowStatus("✓ Pattern Sample #" + IntegerToString(new_id) + " recorded successfully!", false);
      m_hasPending = false;
      m_isSelectionMode = false;
      ClearSelection();
      UpdateUI();
      RefreshSampleList();
     }
   else
     {
      ShowStatus("Failed to save sample. Check storage folder permissions.", true);
     }
  }

//+------------------------------------------------------------------+
//| Cancels the active formation selection                           |
//+------------------------------------------------------------------+
void CRecorderPanel::OnCancel()
  {
   ClearSelection();
   m_isSelectionMode = false;
   m_hasPending = false;
   SetObjectText(m_select_info_name, "Selection cancelled.");
   ShowStatus("Cancelled.", false);
   UpdateUI();
  }

//+------------------------------------------------------------------+
//| Displays recent samples as clickable text buttons                |
//+------------------------------------------------------------------+
void CRecorderPanel::OnViewSamples()
  {
   DeleteObjectsWithPrefix(m_sample_prefix);

   if(m_library == NULL)
      return;
   SPatternSample samples[];
   m_library.GetAllSamples(samples);
   int count = ArraySize(samples);
   if(count == 0)
     {
      ShowStatus("No samples recorded yet.", false);
      return;
     }

   int x = m_x + 10;
   int y = m_y + 315;
   int w = m_width - 20;
   int h = 20;
   int max_visible = 5;
   int start = (count > max_visible) ? count - max_visible : 0;

   for(int i = start; i < count; i++)
     {
      string name = m_sample_prefix + IntegerToString(i);
      string text = "#" + IntegerToString(samples[i].id) + "  " +
                    IntegerToString(samples[i].bars) + " bars  " +
                    TimeToString(samples[i].start_time, TIME_DATE);
      //--- Use a button object so the displayed sample remains clickable
      CreateObject(name, OBJ_BUTTON, x, y, w, h, text);
      //--- Remove the background and border to imitate a text list
      ObjectSetInteger(m_chart_id, name, OBJPROP_BGCOLOR, clrNONE);
      ObjectSetInteger(m_chart_id, name, OBJPROP_BORDER_COLOR, clrNONE);
      ObjectSetInteger(m_chart_id, name, OBJPROP_COLOR, clrBlack);
      ObjectSetInteger(m_chart_id, name, OBJPROP_FONTSIZE, 9);
      //--- Keep the button flat while preserving its click state
      ObjectSetInteger(m_chart_id, name, OBJPROP_BORDER_TYPE, BORDER_FLAT);
      ObjectSetInteger(m_chart_id, name, OBJPROP_STATE, false);
      y += h + 2;
      if(y > m_y + m_height - 50)
         break;
     }
   ShowStatus("Click a sample to select it for deletion.", false);
   ChartRedraw(m_chart_id);
  }

//+------------------------------------------------------------------+
//| Deletes the sample selected in the panel                         |
//+------------------------------------------------------------------+
void CRecorderPanel::OnDeleteSample()
  {
   if(m_selected_sample_id == 0)
     {
      ShowStatus("No sample selected. Click a sample in the list first.", true);
      return;
     }
   if(m_library.RemoveSample(m_selected_sample_id))
     {
      ShowStatus("Sample #" + IntegerToString(m_selected_sample_id) + " deleted.", false);
      m_selected_sample_id = 0;
      RefreshSampleList();
      //--- Refresh the visible list after deletion
      OnViewSamples();
     }
   else
     {
      ShowStatus("Failed to delete sample #" + IntegerToString(m_selected_sample_id), true);
     }
  }

//+------------------------------------------------------------------+
//| Closes the recorder panel and clears its selection               |
//+------------------------------------------------------------------+
void CRecorderPanel::OnClose()
  {
   ClearSelection();
   Destroy();
  }

//+------------------------------------------------------------------+
//| Clear selection rectangle                                        |
//+------------------------------------------------------------------+
void CRecorderPanel::ClearSelection()
  {
   ObjectDelete(m_chart_id, m_rect_name);
   ChartRedraw(m_chart_id);
  }

//+------------------------------------------------------------------+
//| Find active rectangle                                            |
//+------------------------------------------------------------------+
bool CRecorderPanel::FindActiveRectangle()
  {
   return(ObjectFind(m_chart_id, m_rect_name) >= 0);
  }

//+------------------------------------------------------------------+
//| Capture current selection                                        |
//+------------------------------------------------------------------+
bool CRecorderPanel::CaptureCurrentSelection()
  {
   string error;
   bool result = ExtractFromRectangle(Symbol(), Period(), m_rect_name, m_pendingSample, error);
   if(!result)
     {
      ShowStatus("Extraction error: " + error, true);
      return(false);
     }
   m_hasPending = true;
   ShowPendingInfo();
   return(true);
  }

//+------------------------------------------------------------------+
//| Show pending info                                                |
//+------------------------------------------------------------------+
void CRecorderPanel::ShowPendingInfo()
  {
   if(!m_hasPending)
      return;
   string info = "Selected Formation\n";
   info += "Start: " + TimeToString(m_pendingSample.start_time) + "\n";
   info += "End:   " + TimeToString(m_pendingSample.end_time) + "\n";
   info += "Bars:  " + IntegerToString(m_pendingSample.bars);
   SetObjectText(m_select_info_name, info);
  }

//+------------------------------------------------------------------+
//| Update UI buttons                                                |
//+------------------------------------------------------------------+
void CRecorderPanel::UpdateUI()
  {
   if(m_isSelectionMode)
     {
      SetObjectText(m_btn_record_name, "RECORD");
      SetObjectText(m_btn_cancel_name, "CANCEL");
     }
   else
     {
      SetObjectText(m_btn_record_name, "RECORD (inactive)");
      SetObjectText(m_btn_cancel_name, "CANCEL");
     }
  }

//+------------------------------------------------------------------+
//| Refresh sample count only (not the list)                         |
//+------------------------------------------------------------------+
void CRecorderPanel::RefreshSampleList()
  {
   if(m_library == NULL)
      return;
   SPatternSample samples[];
   m_library.GetAllSamples(samples);
   int count = ArraySize(samples);
   SetObjectText(m_count_name, "Recorded Samples: " + IntegerToString(count));
   m_selected_sample_id = 0;
   DeleteObjectsWithPrefix(m_sample_prefix);
   ChartRedraw(m_chart_id);
  }

//+------------------------------------------------------------------+
//| Show status message                                              |
//+------------------------------------------------------------------+
void CRecorderPanel::ShowStatus(string msg, bool is_error = false)
  {
   SetObjectText(m_status_name, msg);
   if(is_error)
      ObjectSetInteger(m_chart_id, m_status_name, OBJPROP_COLOR, clrRed);
   else
      ObjectSetInteger(m_chart_id, m_status_name, OBJPROP_COLOR, clrDarkGreen);
  }

#endif
//+------------------------------------------------------------------+