//+------------------------------------------------------------------+ //| ContextMenu.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #include "..\Element.mqh" #include "MenuItem.mqh" #include "SeparateLine.mqh" //+------------------------------------------------------------------+ //| Класс для создания контекстного меню | //+------------------------------------------------------------------+ class CContextMenu : public CElement { private: //--- Объекты для создания пункта меню CMenuItem m_items[]; CSeparateLine m_sep_line[]; //--- Указатель на предыдущий узел CMenuItem *m_prev_node; //--- Свойства пункта меню int m_item_y_size; //--- Свойства разделительной линии color m_sepline_dark_color; color m_sepline_light_color; //--- Массивы свойств пунктов меню: // (1) Текст, (2) ярлык доступного пункта, (3) ярлык заблокированного пункта string m_text[]; string m_path_bmp_on[]; string m_path_bmp_off[]; //--- Массив номеров индексов пунктов меню, после которых нужно установить разделительную линию int m_sep_line_index[]; //--- Сторона фиксации контекстного меню ENUM_FIX_CONTEXT_MENU m_fix_side; //--- Режим свободного контекстного меню. То есть, без привязки к предыдущему узлу. bool m_free_context_menu; //--- public: CContextMenu(void); ~CContextMenu(void); //--- Методы для создания контекстного меню bool CreateContextMenu(const int x_gap=0,const int y_gap=0); //--- private: void InitializeProperties(const int x_gap,const int y_gap); bool CreateCanvas(void); bool CreateItems(void); bool CreateSeparateLine(const int item_index,const int line_index); //--- public: //--- Возвращает указатель пункта из контекстного меню CMenuItem *GetItemPointer(const uint index); CSeparateLine *GetSeparateLinePointer(const uint index); //--- (1) Сохранение и (2) получение указателя предыдущего узла, (3) установка режима свободного контекстного меню void PrevNodePointer(CMenuItem &object); CMenuItem *PrevNodePointer(void) const { return(m_prev_node); } void FreeContextMenu(const bool flag) { m_free_context_menu=flag; } //--- (1) Количество пунктов меню, (2) высота int ItemsTotal(void) const { return(::ArraySize(m_items)); } int SeparateLinesTotal(void) const { return(::ArraySize(m_sep_line)); } void ItemYSize(const int y_size) { m_item_y_size=y_size; } //--- (1) Тёмный и (2) светлый цвет разделительной линии void SeparateLineDarkColor(const color clr) { m_sepline_dark_color=clr; } void SeparateLineLightColor(const color clr) { m_sepline_light_color=clr; } //--- Установка режима фиксации контекстного меню void FixSide(const ENUM_FIX_CONTEXT_MENU side) { m_fix_side=side; } //--- Добавляет пункт меню с указанными свойствами до создания контекстного меню void AddItem(const string text,const string path_bmp_on,const string path_bmp_off,const ENUM_TYPE_MENU_ITEM type); //--- Добавляет разделительную линию после указанного пункта до создания контекстного меню void AddSeparateLine(const int item_index); //--- Возвращает описание (отображаемый текст) string DescriptionByIndex(const uint index); //--- Возвращает тип пункта меню ENUM_TYPE_MENU_ITEM TypeMenuItemByIndex(const uint index); //--- (1) Получение и (2) установка состояния чекбокса bool CheckBoxStateByIndex(const uint index); void CheckBoxStateByIndex(const uint index,const bool state); //--- (1) Возвращает и (2) устанавливает id радио-пункта по индексу int RadioItemIdByIndex(const uint index); void RadioItemIdByIndex(const uint item_index,const int radio_id); //--- (1) Возвращает выделенный радио-пункт, (2) переключает радио-пункт int SelectedRadioItem(const int radio_id); void SelectedRadioItem(const int radio_index,const int radio_id); //--- public: //--- Обработчик событий графика virtual void OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam); //--- Управление virtual void Show(void); virtual void Hide(void); virtual void Delete(void); //--- Рисует элемент virtual void Draw(void); //--- private: //--- Проверка условий на закрытие всех контекстных меню void CheckHideContextMenus(void); //--- Проверка условий на закрытие всех контекстных меню, которые были открыты после этого void CheckHideBackContextMenus(void); //--- Обработка нажатия на пункте, к которому это контекстное меню привязано bool OnClickMenuItem(const string pressed_object,const int id,const int index); //--- Приём сообщения от пункта меню для обработки void ReceiveMessageFromMenuItem(const int id,const int index_item,const string message_item); //--- Получение (1) идентификатора и (2) индекса из сообщения радио-пункта int RadioIdFromMessage(const string message); int RadioIndexByItemIndex(const int index); }; //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CContextMenu::CContextMenu(void) : m_free_context_menu(false), m_fix_side(FIX_RIGHT), m_item_y_size(24), m_sepline_dark_color(C'160,160,160'), m_sepline_light_color(clrWhite) { //--- Сохраним имя класса элемента в базовом классе CElementBase::ClassName(CLASS_NAME); //--- Контекстное меню является выпадающим элементом CElementBase::IsDropdown(true); } //+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CContextMenu::~CContextMenu(void) { } //+------------------------------------------------------------------+ //| Обработчик событий | //+------------------------------------------------------------------+ void CContextMenu::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- Обработка пермещения курсора мыши if(id==CHARTEVENT_MOUSE_MOVE) { //--- Выйти, если это свободное контекстное меню if(m_free_context_menu) return; //--- Если контекстное меню включено и левая кнопка мыши нажата if(m_mouse.LeftButtonState()) { //--- Проверим условий на закрытие всех контекстных меню CheckHideContextMenus(); return; } //--- Проверим условия на закрытие всех контекстных меню, которые были открыты после этого CheckHideBackContextMenus(); return; } //--- Обработка события нажатия левой кнопки мыши на объекте if(id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON) { if(OnClickMenuItem(sparam,(int)lparam,(int)dparam)) return; } //--- Обработка события нажатия на пункте меню if(id==CHARTEVENT_CUSTOM+ON_CLICK_MENU_ITEM) { //--- Выйти, если это свободное контекстное меню if(m_free_context_menu) return; //--- int item_id =int(lparam); int item_index =int(dparam); string item_message =sparam; //--- Приём сообщения от пункта меню для обработки ReceiveMessageFromMenuItem(item_id,item_index,item_message); return; } } //+------------------------------------------------------------------+ //| Создаёт контекстное меню | //+------------------------------------------------------------------+ bool CContextMenu::CreateContextMenu(const int x_gap=0,const int y_gap=0) { //--- Выйти, если нет указателя на главный элемент if(!CElement::CheckMainPointer()) return(false); //--- Если это привязанное контекстное меню if(!m_free_context_menu) { //--- Выйти, если нет указателя на предыдущий узел if(::CheckPointer(m_prev_node)==POINTER_INVALID) { ::Print(__FUNCTION__," > Перед созданием контекстного меню ему нужно передать " "указатель на предыдущий узел с помощью метода CContextMenu::PrevNodePointer(CMenuItem &object)."); return(false); } } //--- Инициализация свойств InitializeProperties(x_gap,y_gap); //--- Создание контекстного меню if(!CreateCanvas()) return(false); if(!CreateItems()) return(false); //--- return(true); } //+------------------------------------------------------------------+ //| Инициализация свойств | //+------------------------------------------------------------------+ void CContextMenu::InitializeProperties(const int x_gap,const int y_gap) { //--- Расчёт высоты контекстного меню зависит от количества пунктов меню и разделительных линий int items_total =ItemsTotal(); int sep_y_size =::ArraySize(m_sep_line)*9; m_y_size =(m_item_y_size*items_total+2)+sep_y_size; //--- Если координаты не указаны if(!m_free_context_menu && (x_gap==0 || y_gap==0)) { if(m_fix_side==FIX_RIGHT) { m_x =(m_anchor_right_window_side)? m_prev_node.X()-m_prev_node.XSize()+3 : m_prev_node.X2()-3; m_y =(m_anchor_bottom_window_side)? m_prev_node.Y()+1 : m_prev_node.Y()-1; } else { m_x =(m_anchor_right_window_side)? m_prev_node.X()-1 : m_prev_node.X()+1; m_y =(m_anchor_bottom_window_side)? m_prev_node.Y()-m_prev_node.YSize()+1 : m_prev_node.Y2()-1; } } //--- Если координаты указаны else { m_x =CElement::CalculateX(x_gap); m_y =CElement::CalculateY(y_gap); } //--- Цвет фона по умолчанию m_back_color =(m_back_color!=clrNONE)? m_back_color : C'240,240,240'; m_back_color_hover =(m_back_color_hover!=clrNONE)? m_back_color_hover : C'51,153,255'; m_back_color_locked =(m_back_color_locked!=clrNONE)? m_back_color_locked : clrLightGray; m_back_color_pressed =(m_back_color_pressed!=clrNONE)? m_back_color_pressed : C'51,153,255'; m_border_color =(m_border_color!=clrNONE)? m_border_color : C'150,170,180'; //--- Отступы и цвет текстовой метки m_icon_x_gap =(m_icon_x_gap!=WRONG_VALUE)? m_icon_x_gap : 3; m_icon_y_gap =(m_icon_y_gap!=WRONG_VALUE)? m_icon_y_gap : 3; m_label_x_gap =(m_label_x_gap!=WRONG_VALUE)? m_label_x_gap : 24; m_label_y_gap =(m_label_y_gap!=WRONG_VALUE)? m_label_y_gap : 5; m_label_color =(m_label_color!=clrNONE)? m_label_color : clrBlack; m_label_color_hover =(m_label_color_hover!=clrNONE)? m_label_color_hover : clrWhite; //--- Отступы от крайней точки CElementBase::XGap(CElement::CalculateXGap(m_x)); CElementBase::YGap(CElement::CalculateYGap(m_y)); } //+------------------------------------------------------------------+ //| Создаёт объект для рисования | //+------------------------------------------------------------------+ bool CContextMenu::CreateCanvas(void) { //--- Формирование имени объекта string name=CElementBase::ElementName("context_menu"); //--- Создание объекта if(!CElement::CreateCanvas(name,m_x,m_y,m_x_size,m_y_size)) return(false); //--- return(true); } //+------------------------------------------------------------------+ //| Создаёт список пунктов меню | //+------------------------------------------------------------------+ bool CContextMenu::CreateItems(void) { //--- Для определения положения разделительных линий int s=0; //--- Координаты int x=1,y=0; //--- Количество разделительных линий int sep_lines_total=::ArraySize(m_sep_line_index); //--- int items_total=ItemsTotal(); for(int i=0; i0) ? y+m_item_y_size : 1; //--- Сохраним указатель формы m_items[i].MainPointer(this); //--- Если контекстное меню с привязкой, то добавим указатель на предыдущий узел if(!m_free_context_menu) m_items[i].GetPrevNodePointer(m_prev_node); //--- Установим свойства m_items[i].Index(i); m_items[i].TwoState(m_items[i].TypeMenuItem()==MI_HAS_CONTEXT_MENU? true : false); m_items[i].XSize(m_x_size-2); m_items[i].YSize(m_item_y_size); m_items[i].IconXGap(m_icon_x_gap); m_items[i].IconYGap(m_icon_y_gap); m_items[i].IconFile(m_path_bmp_on[i]); m_items[i].IconFileLocked(m_path_bmp_off[i]); m_items[i].IconFilePressed(m_path_bmp_on[i]); m_items[i].IconFilePressedLocked(m_path_bmp_off[i]); m_items[i].BackColor(m_back_color); m_items[i].BackColorHover(m_back_color_hover); m_items[i].BackColorPressed(m_back_color_hover); m_items[i].BorderColor(m_back_color); m_items[i].BorderColorHover(m_back_color); m_items[i].BorderColorLocked(m_back_color); m_items[i].BorderColorPressed(m_back_color); m_items[i].LabelXGap(m_label_x_gap); m_items[i].LabelYGap(m_label_y_gap); m_items[i].LabelColor(m_label_color); m_items[i].LabelColorHover(m_label_color_hover); m_items[i].LabelColorPressed(m_label_color_hover); m_items[i].IsDropdown(m_is_dropdown); m_items[i].AnchorRightWindowSide(m_anchor_right_window_side); m_items[i].AnchorBottomWindowSide(m_anchor_bottom_window_side); //--- Создание пункта меню if(!m_items[i].CreateMenuItem(m_text[i],x,y)) return(false); //--- Добавить элемент в массив CElement::AddToArray(m_items[i]); //--- Обнулить фокус CElementBase::MouseFocus(false); //--- Перейти к следующему, если все разделительные линии установлены if(s>=sep_lines_total) continue; //--- Если индексы совпали, значит после этого пункта нужно установить разделительную линию if(i==m_sep_line_index[s]) { if(!CreateSeparateLine(i,s)) return(false); //--- Корректировка координаты Y для следующего пункта y=y+9; //--- Увеличение счётчика разделительных линий s++; } } return(true); } //+------------------------------------------------------------------+ //| Создаёт разделительную линию | //+------------------------------------------------------------------+ bool CContextMenu::CreateSeparateLine(const int item_index,const int line_index) { int x=CElement::CalculateXGap(m_items[item_index].X()+5); int y=CElement::CalculateYGap(m_items[item_index].Y2()+2); //--- Сохраним указатель формы m_sep_line[line_index].MainPointer(m_main); //--- Установим свойства m_sep_line[line_index].Index(line_index); m_sep_line[line_index].IsDropdown(m_is_dropdown); m_sep_line[line_index].TypeSepLine(H_SEP_LINE); m_sep_line[line_index].DarkColor(m_sepline_dark_color); m_sep_line[line_index].LightColor(m_sepline_light_color); m_sep_line[line_index].AnchorRightWindowSide(m_anchor_right_window_side); m_sep_line[line_index].AnchorBottomWindowSide(m_anchor_bottom_window_side); //--- Создание разделительной линии if(!m_sep_line[line_index].CreateSeparateLine(x,y,m_x_size-10,2)) return(false); //--- Добавить элемент в массив CElement::AddToArray(m_sep_line[line_index]); return(true); } //+------------------------------------------------------------------+ //| Возвращает указатель пункта меню по индексу | //+------------------------------------------------------------------+ CMenuItem *CContextMenu::GetItemPointer(const uint index) { uint array_size=::ArraySize(m_items); //--- Если нет ни одного пункта в контекстном меню, сообщить об этом if(array_size<1) ::Print(__FUNCTION__," > Вызов этого метода нужно осуществлять, когда в контекстном меню есть хотя бы один пункт!"); //--- Корректировка в случае выхода из диапазона uint i=(index>=array_size)? array_size-1 : index; //--- Вернуть указатель return(::GetPointer(m_items[i])); } //+------------------------------------------------------------------+ //| Возвращает указатель разделительную линию по индексу | //+------------------------------------------------------------------+ CSeparateLine *CContextMenu::GetSeparateLinePointer(const uint index) { uint array_size=::ArraySize(m_sep_line); //--- Если нет ни одного пункта в контекстном меню, сообщить об этом if(array_size<1) return(NULL); //--- Корректировка в случае выхода из диапазона uint i=(index>=array_size)? array_size-1 : index; //--- Вернуть указатель return(::GetPointer(m_sep_line[i])); } //+------------------------------------------------------------------+ //| Обмен указателями пункта меню и контекстного меню | //+------------------------------------------------------------------+ void CContextMenu::PrevNodePointer(CMenuItem &object) { //--- Сохранить указатель на пункт меню, к которому привязано это контекстное меню m_prev_node=::GetPointer(object); //--- Сохранить указатель на это контекстное меню m_prev_node.GetContextMenuPointer(this); } //+------------------------------------------------------------------+ //| Добавляет пункт меню | //+------------------------------------------------------------------+ void CContextMenu::AddItem(const string text,const string path_bmp_on,const string path_bmp_off,const ENUM_TYPE_MENU_ITEM type) { //--- Увеличим размер массивов на один элемент int array_size=::ArraySize(m_items); ::ArrayResize(m_items,array_size+1); ::ArrayResize(m_text,array_size+1); ::ArrayResize(m_path_bmp_on,array_size+1); ::ArrayResize(m_path_bmp_off,array_size+1); //--- Сохраним значения переданных параметров m_text[array_size]=text; m_path_bmp_on[array_size] =path_bmp_on; m_path_bmp_off[array_size] =path_bmp_off; //--- Установка типа пункта меню m_items[array_size].TypeMenuItem(type); } //+------------------------------------------------------------------+ //| Добавляет разделительную линию | //+------------------------------------------------------------------+ void CContextMenu::AddSeparateLine(const int item_index) { //--- Увеличим размер массивов на один элемент int array_size=::ArraySize(m_sep_line); ::ArrayResize(m_sep_line,array_size+1); ::ArrayResize(m_sep_line_index,array_size+1); //--- Сохраним номер индекса m_sep_line_index[array_size]=item_index; } //+------------------------------------------------------------------+ //| Возвращает название пункта по индексу | //+------------------------------------------------------------------+ string CContextMenu::DescriptionByIndex(const uint index) { uint array_size=::ArraySize(m_items); //--- Если нет ни одного пункта в контекстном меню, сообщить об этом if(array_size<1) ::Print(__FUNCTION__," > Вызов этого метода нужно осуществлять, когда в контекстном меню есть хотя бы один пункт!"); //--- Корректировка в случае выхода из диапазона uint i=(index>=array_size)? array_size-1 : index; //--- Вернуть описание пункта return(m_items[i].LabelText()); } //+------------------------------------------------------------------+ //| Возвращает тип пункта по индексу | //+------------------------------------------------------------------+ ENUM_TYPE_MENU_ITEM CContextMenu::TypeMenuItemByIndex(const uint index) { uint array_size=::ArraySize(m_items); //--- Если нет ни одного пункта в контекстном меню, сообщить об этом if(array_size<1) ::Print(__FUNCTION__," > Вызов этого метода нужно осуществлять, когда в контекстном меню есть хотя бы один пункт!"); //--- Корректировка в случае выхода из диапазона uint i=(index>=array_size)? array_size-1 : index; //--- Вернуть тип пункта return(m_items[i].TypeMenuItem()); } //+------------------------------------------------------------------+ //| Возвращает состояние чекбокса по индексу | //+------------------------------------------------------------------+ bool CContextMenu::CheckBoxStateByIndex(const uint index) { uint array_size=::ArraySize(m_items); //--- Если нет ни одного пункта в контекстном меню, сообщить об этом if(array_size<1) ::Print(__FUNCTION__," > Вызов этого метода нужно осуществлять, когда в контекстном меню есть хотя бы один пункт!"); //--- Корректировка в случае выхода из диапазона uint i=(index>=array_size)? array_size-1 : index; //--- Вернуть состояние пункта return(m_items[i].CheckBoxState()); } //+------------------------------------------------------------------+ //| Устанавливает состояние чекбокса по индексу | //+------------------------------------------------------------------+ void CContextMenu::CheckBoxStateByIndex(const uint index,const bool state) { //--- Проверка на выход из диапазона uint array_size=::ArraySize(m_items); //--- Если нет ни одного пункта в контекстном меню, сообщить об этом if(array_size<1) return; //--- Корректировка в случае выхода из диапазона uint i=(index>=array_size)? array_size-1 : index; //--- Установить состояние m_items[i].CheckBoxState(state); } //+------------------------------------------------------------------+ //| Возвращает id радио-пункта по индексу | //+------------------------------------------------------------------+ int CContextMenu::RadioItemIdByIndex(const uint index) { uint array_size=::ArraySize(m_items); //--- Если нет ни одного пункта в контекстном меню, сообщить об этом if(array_size<1) ::Print(__FUNCTION__," > Вызов этого метода нужно осуществлять, когда в контекстном меню есть хотя бы один пункт!"); //--- Корректировка в случае выхода из диапазона uint i=(index>=array_size)? array_size-1 : index; //--- Вернуть идентификатор return(m_items[i].RadioButtonID()); } //+------------------------------------------------------------------+ //| Устанавливает id для радио-пункта по индексу | //+------------------------------------------------------------------+ void CContextMenu::RadioItemIdByIndex(const uint index,const int id) { //--- Проверка на выход из диапазона uint array_size=::ArraySize(m_items); //--- Если нет ни одного пункта в контекстном меню, сообщить об этом if(array_size<1) return; //--- Корректировка в случае выхода из диапазона uint i=(index>=array_size)? array_size-1 : index; //--- Установить идентификатор m_items[i].RadioButtonID(id); } //+------------------------------------------------------------------+ //| Возвращает индекс радио-пункта по id | //+------------------------------------------------------------------+ int CContextMenu::SelectedRadioItem(const int radio_id) { //--- Счётчик радио-пунктов int count_radio_id=0; //--- Пройдёмся в цикле по списку пунктов контекстного меню int items_total=ItemsTotal(); for(int i=0; i-1) { //--- Получим id радио-пункта из переданного сообщения int radio_id=RadioIdFromMessage(message_item); //--- Получим индекс радио-пункта по общему индексу int radio_index=RadioIndexByItemIndex(index_item); //--- Переключить радио-пункт SelectedRadioItem(radio_index,radio_id); } //--- Скрытие контекстного меню Hide(); //--- Отправим сообщение об этом ::EventChartCustom(m_chart_id,ON_CLICK_CONTEXTMENU_ITEM,id,index_item,DescriptionByIndex(index_item)); //--- Послать сигнал на скрытие всех контекстных меню ::EventChartCustom(m_chart_id,ON_HIDE_CONTEXTMENUS,0,0,""); //--- Сообщение на восстановление доступных элементов ::EventChartCustom(m_chart_id,ON_SET_AVAILABLE,CElementBase::Id(),1,""); //--- Отправим сообщение об изменении в графическом интерфейсе ::EventChartCustom(m_chart_id,ON_CHANGE_GUI,CElementBase::Id(),0.0,""); } //+------------------------------------------------------------------+ //| Извлекает идентификатор из сообщения для радио-пункта | //+------------------------------------------------------------------+ int CContextMenu::RadioIdFromMessage(const string message) { ushort u_sep=0; string result[]; int array_size=0; //--- Получим код разделителя u_sep=::StringGetCharacter("_",0); //--- Разобьём строку ::StringSplit(message,u_sep,result); array_size=::ArraySize(result); //--- Если структура сообщения отличается от ожидаемой if(array_size!=3) { ::Print(__FUNCTION__," > Неправильная структура в сообщении для радио-пункта! message: ",message); return(WRONG_VALUE); } //--- Предотвращение выхода за пределы массива if(array_size<3) { ::Print(PREVENTING_OUT_OF_RANGE); return(WRONG_VALUE); } //--- Вернуть id радио-пункта return((int)result[2]); } //+------------------------------------------------------------------+ //| Возвращает индекс радио-пункта по общему индексу | //+------------------------------------------------------------------+ int CContextMenu::RadioIndexByItemIndex(const int index) { int radio_index=0; //--- Получаем ID радио-пункта по общему индексу int radio_id=RadioItemIdByIndex(index); //--- Счётчик пунктов из нужной группы int count_radio_id=0; //--- Пройдёмся в цикле по списку int items_total=ItemsTotal(); for(int i=0; i