1 namespace gui::controls;
5 private define SELECTION_COLOR = guiApp.currentSkin.selectionColor; //Color { 10, 36, 106 };
6 private define SELECTION_TEXT = guiApp.currentSkin.selectionText; //Color { 10, 36, 106 };
7 private define menuBarColor = Color { 211, 218, 237 };
8 private define popupMenuColor = Color { 229,234,245 };
10 class ItemPtr : struct
13 MenuItem item, oldItem;
14 InsertedFlags inserted;
18 #define ITEM_DISABLED(i) ((i).disabled || ((i).subMenu && !(i).subMenu.items.count))
20 class InsertedFlags { bool deleteLink:1, deleteItem:1, cleanItem:1, placed:1; };
22 #define ITEM_TEXT(item) (item.subMenu ? item.subMenu.text : item.text)
23 #define ITEM_HOTKEY(item) (item.subMenu ? item.subMenu.hotKey : item.hotKey)
25 #define DIVIDER_HEIGHT (guiApp.textMode ? textCellH : 8)
27 #define INTERIM_MENU (isMenuBar || interim)
28 //#define INTERIM_MENU interim
30 static int strcmpTillTab(char * a, char * b)
33 else if(b && !a) return -1;
37 for(c = 0; a[c] && b[c] && a[c] != '\t' && b[c] != '\t'; c++)
38 if(a[c] > b[c]) return 1;
39 else if(b[c] > a[c]) return -1;
40 if(a[c] && a[c] != '\t') return 1;
41 else if(b[c] && b[c] != '\t') return -1;
56 menu.RemoveItem(this);
69 text = CopyString(value);
74 manualAccelText = (value && strchr(value, '\t'));
76 if(accelerator && !manualAccelText)
77 property::accelerator = accelerator;
80 property Key hotKey { set { hotKey = value; } };
81 property Key accelerator
87 if(!manualAccelText && text)
89 char accelString[50] = "\t";
94 if(value.ctrl) strcat(accelString, $"Ctrl+");
95 if(value.alt) strcat(accelString, $"Alt+");
96 if(value.shift) strcat(accelString, $"Shift+");
99 strcat(accelString, "0");
100 else if(value.code >= k1 && value.code <= k9)
102 accelString[strlen(accelString)] = '1' + (char)(value.code - k1);
103 accelString[strlen(accelString)+1] = 0;
107 Key accel = value.code;
108 bool needClass = false;
110 char * result = accel.OnGetString(tempString, null, &needClass);
111 int len = strlen(accelString);
112 if(result) strcpy(accelString + len, result);
113 // accelString[len] = toupper(accelString[len]);
116 tabPos = strchr(text, '\t');
118 length = tabPos - text;
120 length = strlen(text);
122 newText = new char[length+strlen(accelString)+1];
123 memcpy(newText, text, length);
125 strcat(newText, accelString);
126 if(copyText) delete text;
133 property bool checked
138 if(menu && radio && value)
141 ItemPtr groupFirst = menu.items.first;
142 ItemPtr otherItemPtr;
143 for(otherItemPtr = menu.items.first; otherItemPtr; otherItemPtr = otherItemPtr.next)
145 MenuItem otherItem = otherItemPtr.item;
146 if(otherItem.isDivider)
147 groupFirst = otherItemPtr.next;
148 else if(!otherItem.placement)
150 if(otherItem == this)
154 for(otherItemPtr = groupFirst; otherItemPtr; otherItemPtr = otherItemPtr.next)
156 MenuItem otherItem = otherItemPtr.item;
157 if(otherItem.isDivider)
159 else if(!otherItem.placement && otherItem.radio && otherItem != this)
160 otherItem.checked = false;
163 // Should callback be called here? guess not ;)
165 get { return checked; }
167 property bool disabled { set { if(this) disabled = value; } };
168 property bool checkable { set { checkable = value; } };
169 property bool isRadio { set { radio = value; } };
171 property uint64 id { set { id = value; } get { return id; } };
172 property BitmapResource bitmap
180 bitmaps[1] = value ? (value.alphaBlend ? value : { fileName = value.fileName, monochrome = true }) : null;
181 bitmaps[2] = value ? { fileName = value.fileName, grayed = true } : null;
189 get { return bitmaps[0]; }
191 property bool copyText
197 if(text && !copyText)
198 text = CopyString(ITEM_TEXT(this));
208 property bool bold { set { bold = value; } get { return bold; } };
210 virtual bool Window::NotifySelect(MenuItem selection, Modifiers mods);
220 BitmapResource bitmaps[3];
221 bool checkable, radio;
227 bool manualAccelText;
233 // delete ITEM_TEXT(this);
242 public class MenuDivider : MenuItem
249 // property Menu parent { set {} };
252 public class MenuPlacement : MenuItem
261 property Menu parent { set {} };
262 property char * text { set {} };
263 property Key hotKey { set {} };
271 int OnCompare(Menu menu)
273 return (this != null) != (menu != null);
276 void AddItem(MenuItem item)
282 for(ptr = items.first; ptr; ptr = ptr.next)
284 MenuItem check = ptr.item;
287 if(!strcmpTillTab(ITEM_TEXT(check), ITEM_TEXT(item)))
298 ptr.inserted = InsertedFlags { placed = true };
299 ptr.oldItem = ptr.item;
314 void RemoveItem(MenuItem item)
316 if(item.menu == this)
319 for(ptr = items.first; ptr; ptr = ptr.next)
322 if(ptr.inserted.placed)
324 ptr.item = ptr.oldItem;
330 if(!ptr.inserted.placed)
340 void AddSubMenu(Menu subMenu)
344 MenuItem menuItem { };
345 ItemPtr ptr { item = menuItem };
352 menuItem.menu = this;
356 menuItem.subMenu = subMenu;
362 void AddDynamic(MenuItem addedItem, Window master, bool persistent)
366 ItemPtr ptr = null, oldItemPtr;
368 for(oldItemPtr = items.first; oldItemPtr; oldItemPtr = oldItemPtr.next)
370 if((oldItemPtr.item.subMenu || oldItemPtr.item.placement) && !strcmpTillTab(ITEM_TEXT(oldItemPtr.item), ITEM_TEXT(addedItem)))
372 MenuItem oldItem = oldItemPtr.item;
373 if(!oldItem.placement)
375 oldItem.subMenu.Merge(addedItem.subMenu, true, master);
377 // If sub menu already has a master...
380 oldItemPtr.inserted = InsertedFlags { cleanItem = true };
381 if(!oldItemPtr.oldItem)
382 oldItemPtr.oldItem = oldItem;
383 oldItemPtr.item = addedItem;
395 ptr.inserted = InsertedFlags { deleteLink = true, deleteItem = true };
398 ptr.inserted = InsertedFlags { cleanItem = true, deleteItem = true };
400 ptr.item = addedItem;
404 addedItem.menu = this;
408 MenuItem FindItem(bool (* Window::notifySelect)(MenuItem selection, Modifiers mods), uint64 id)
412 for(ptr = items.first; ptr; ptr = ptr.next)
414 MenuItem item = ptr.item;
417 MenuItem subItem = item.subMenu.FindItem(notifySelect, id);
418 if(subItem) return subItem;
420 else if(!item.isDivider && !item.placement)
422 if(item.id == id && item.NotifySelect == notifySelect)
432 while((ptr = items.first))
435 if(ptr.inserted.cleanItem || ptr.inserted.placed)
437 ptr.item = ptr.oldItem;
445 void Merge(Menu menuBeingMerged, bool menuBar, Window window)
447 bool separated = false;
448 ItemPtr beingMergedItemPtr;
450 for(beingMergedItemPtr = menuBeingMerged.items.first; beingMergedItemPtr; beingMergedItemPtr = beingMergedItemPtr.next)
452 MenuItem beingMergedItem = beingMergedItemPtr.item;
453 ItemPtr mergeIntoItemPtr = null;
455 if(!beingMergedItem) continue;
456 if(beingMergedItem.subMenu)
458 for(mergeIntoItemPtr = items.first; mergeIntoItemPtr; mergeIntoItemPtr = mergeIntoItemPtr.next)
460 if((mergeIntoItemPtr.item.subMenu || mergeIntoItemPtr.item.placement) && !strcmpTillTab(ITEM_TEXT(mergeIntoItemPtr.item), ITEM_TEXT(beingMergedItem)))
462 MenuItem mergeIntoItem = mergeIntoItemPtr.item;
463 if(!beingMergedItem.placement || beingMergedItemPtr.inserted)
465 if(!mergeIntoItem.placement && !mergeIntoItemPtr.inserted.cleanItem) // Added this last check for ActiveChild overriding ActiveClient's menu
467 mergeIntoItem.subMenu.Merge(beingMergedItem.subMenu, menuBar, window);
469 // If sub menu already has a master...
472 mergeIntoItemPtr.inserted = InsertedFlags { cleanItem = true };
473 if(!mergeIntoItemPtr.oldItem)
474 mergeIntoItemPtr.oldItem = mergeIntoItem;
475 mergeIntoItemPtr.item = beingMergedItem;
477 mergeIntoItemPtr.master = window;
484 else if(!beingMergedItem.isDivider)
486 for(mergeIntoItemPtr = items.first; mergeIntoItemPtr; mergeIntoItemPtr = mergeIntoItemPtr.next)
488 MenuItem mergeIntoItem = mergeIntoItemPtr.item;
489 if(/*!mergeIntoItem.subMenu && /-*mergeIntoItem.placement && !mergeIntoItemPtr.inserted && */!strcmpTillTab(ITEM_TEXT(mergeIntoItem), ITEM_TEXT(beingMergedItem)))
491 //if(!beingMergedItem.placement || beingMergedItemPtr.inserted)
493 mergeIntoItemPtr.inserted = InsertedFlags { cleanItem = true };
494 if(!mergeIntoItemPtr.oldItem)
495 mergeIntoItemPtr.oldItem = mergeIntoItem;
496 mergeIntoItemPtr.item = beingMergedItem;
497 mergeIntoItemPtr.master = beingMergedItemPtr.master ? beingMergedItemPtr.master : window;
504 if(!mergeIntoItemPtr)
506 if(beingMergedItem.placement && !beingMergedItemPtr.inserted)
508 // Simply add the placement at the end
509 mergeIntoItemPtr = ItemPtr { };
510 mergeIntoItemPtr.inserted = InsertedFlags { deleteLink = true, cleanItem = true };
511 mergeIntoItemPtr.item = beingMergedItem;
512 mergeIntoItemPtr.master = beingMergedItemPtr.master ? beingMergedItemPtr.master : window;
513 items.Add(mergeIntoItemPtr);
518 ItemPtr previous = items.last;
521 // If it is a menu bar, add the item before the first divider
522 for(previous = items.first; previous; previous = previous.next)
523 if(previous.item.isDivider && !previous.inserted) // Added previous.inserted check
525 previous = previous.prev;
531 if(previous && !previous.item.isDivider && !separated)
535 item = MenuDivider { },
536 inserted = InsertedFlags { deleteLink = true, deleteItem = true }
538 items.Insert(previous, ptr);
545 if(!beingMergedItem.isDivider || !previous || (previous.item && !previous.item.isDivider))
547 mergeIntoItemPtr = ItemPtr { };
548 items.Insert(previous, mergeIntoItemPtr);
549 mergeIntoItemPtr.inserted = InsertedFlags { deleteLink = true, cleanItem = true };
550 mergeIntoItemPtr.item = beingMergedItem;
551 mergeIntoItemPtr.master = beingMergedItemPtr.master ? beingMergedItemPtr.master : window;
559 void Clean(Window window)
562 for(ptr = items.first; ptr; ptr = next)
564 MenuItem item = ptr.item;
567 if(ptr.inserted.cleanItem)
569 ptr.item = ptr.oldItem;
572 else if(item.subMenu)
573 item.subMenu.Clean(window);
575 if(ptr.inserted.deleteItem)
578 if(ptr.inserted.deleteLink || ptr.inserted.cleanItem)
580 if(ptr.inserted.deleteLink)
584 ptr.inserted.deleteLink = false;
585 ptr.inserted.cleanItem = false;
586 ptr.inserted.deleteItem = false;
592 Menu FindMenu(char * name)
596 for(ptr = items.first; ptr; ptr = ptr.next)
598 MenuItem item = ptr.item;
600 if(item.subMenu && item.subMenu.text && !strcmpTillTab(item.subMenu.text, name))
606 property Menu parent { set { if(value) value.AddSubMenu(this); } };
607 property char * text { set { text = value; /* CopyString(value);*/ } };
608 property Key hotKey { set { hotKey = value; } };
609 property bool hasMargin { set { hasMargin = value; } };
624 color = popupMenuColor;
634 public class PopupMenu : Window
636 class_property(icon) = "<:ecere>controls/menu.png";
648 void (* FontExtent)(Display display, Font font, char * text, int len, int * width, int * height);
650 FontResource boldFont { faceName = font.faceName, font.size, bold = true, window = this };
651 BitmapResource subArrow { fileName = "<:ecere>elements/arrowRight.png", window = this };
652 BitmapResource whiteSubArrow { fileName = "<:ecere>elements/arrowRight.png", monochrome = true, window = this };
653 BitmapResource disabledSubArrow { fileName = "<:ecere>elements/arrowRight.png", grayed = true, window = this };
660 if(menu) delete menu;
664 bool MenuDestroyMasters(bool unselect)
667 PopupMenu window = this, master;
670 for(; (master = (PopupMenu)window.master); window = master)
672 if(!eClass_IsDerived(master._class, _class) || master.isMenuBar)
676 if(eClass_IsDerived(master._class, _class) && master.isMenuBar)
678 master.pressed = false;
681 master.keyboardFocus = false;
682 master.selected = null;
686 result = window.Destroy(0);
687 // This looks like a hack...
692 bool MenuGoToPrevItem()
694 ItemPtr selected, current = this.selected;
695 for(selected = (current && current.prev) ? current.prev : menu.items.last;
697 (selected.item.isDivider || selected.item.placement || ITEM_DISABLED(selected.item)) &&
699 selected = selected.prev ? selected.prev : menu.items.last)
701 if(!current) current = selected; // Endless loop with no previously selected popups
703 this.selected = selected;
704 return selected && selected != current;
707 bool MenuGoToNextItem()
709 ItemPtr selected, current = this.selected;
710 for(selected = (current && current.next) ? current.next : menu.items.first;
712 (selected.item.isDivider || selected.item.placement || ITEM_DISABLED(selected.item)) &&
714 selected = selected.next ? selected.next : menu.items.first)
716 if(!current) current = selected; // Endless loop with no previously selected popups
718 this.selected = selected;
719 return selected && selected != current;
722 void MenuPopupChild(int x, int y, Menu childMenu)
727 if(childMenu.itemCount)
729 PopupMenu child { master = this, menu = childMenu };
733 Window parent = this.parent;
734 Window desktop = guiApp.desktop;
736 x += parent.absPosition.x + parent.clientStart.x - desktop.position.x;
737 y += parent.absPosition.y + parent.clientStart.y - desktop.position.y;
739 x += parent.absPosition.x + parent.clientStart.x;
740 y += parent.absPosition.y + parent.clientStart.y;
742 child.parent = desktop;
746 child.stayOnTop = true;
747 child.parent = parent;
748 child.interim = false;
750 child.position = Point { x, y };
752 // child.displayDriver = "GDI";
758 bool MenuPopupSelected()
762 int selectedX = guiApp.textMode ? 0 : 2;
764 if(selected && selected.item) // Why was this null from OnKeyHit?
766 ItemPtr selected = this.selected, ptr;
767 bool helpBreak = false;
768 Window parent = this.parent;
769 Window activeClient = parent.activeClient;
770 bool systemButtons = activeClient && activeClient.state == maximized;
772 keyboardFocus = true;
777 firstSlave.Destroy(0);
779 for(ptr = menu.items.first; ptr; ptr = ptr.next)
781 MenuItem item = ptr.item;
783 if(item.placement) continue; //&& !ptr.inserted) continue;
787 Menu childMenu = item.subMenu;
791 if(selected.item.subMenu)
792 MenuPopupChild(selectedX, 0, childMenu);
794 keyboardFocus = true;
799 return false; // true
806 int breakX = clientSize.w + 2 - (systemButtons ? 48 : 0);
807 for(nextPtr = ptr.next; nextPtr; nextPtr = nextPtr.next)
809 MenuItem nextItem = nextPtr.item;
810 if(!nextItem.isDivider && ITEM_TEXT(nextItem))
813 FontExtent(display, fontObject, ITEM_TEXT(nextItem), strlen(ITEM_TEXT(nextItem)), &len, null);
817 if(selectedX < breakX) selectedX = breakX;
821 else if(ITEM_TEXT(item))
823 FontExtent(display, fontObject, ITEM_TEXT(item), strlen(ITEM_TEXT(item)), &len, null);
824 selectedX += len + 16;
832 if(selected && selected.item.subMenu)
834 Menu childMenu = selected.item.subMenu;
839 for(ptr = menu.items.first; ptr; ptr = ptr.next)
841 MenuItem item = ptr.item;
843 if(item.placement) continue; //&& !ptr.inserted) continue;
850 y += (item.isDivider) ? DIVIDER_HEIGHT : rh;
854 PopupMenu slave = (PopupMenu)firstSlave;
855 if(!slave || slave.menu != childMenu)
857 if(firstSlave) firstSlave.Destroy(0);
858 MenuPopupChild(position.x + size.w, position.y + selectedY, childMenu);
868 bool MenuItemSelection(Menu parentMenu, ItemPtr selectionPtr, Key key)
870 MenuItem selection = selectionPtr.item;
871 if(!ITEM_DISABLED(selection))
873 Window master = this;
876 master = master.master;
878 if(selectionPtr.master)
879 master = selectionPtr.master;
880 while(eClass_IsDerived(master._class, _class) && master.master)
881 master = master.master;
883 if(selection.checkable)
884 selection.checked = !selection.checked;
885 else if(selection.radio)
887 if(selection.checked) return false;
888 selection.checked = !selection.checked;
892 MenuDestroyMasters(true);
894 /*return */selection.NotifySelect(master, selection, key.modifiers);
900 bool CheckAccelerators(Menu menu, Key key)
904 for(ptr = menu.items.first; ptr; ptr = ptr.next)
906 MenuItem item = ptr.item;
909 if(!CheckAccelerators(item.subMenu, key))
912 else if(!item.isDivider)
914 if(item.accelerator == key)
916 if(MenuItemSelection(menu, ptr, key))
924 ItemPtr FindSelected(int mx, int my, int * selectedX, int * selectedY)
926 Menu menu = this.menu;
927 // Mouse moved inside menu
928 ItemPtr selected = null;
933 if(isMenuBar && menu)
938 bool helpBreak = false;
939 Window parent = this.parent;
940 Window activeClient = parent.activeClient;
941 bool systemButtons = activeClient && activeClient.state == maximized;
943 for(ptr = menu.items.first; ptr; ptr = ptr.next)
945 MenuItem item = ptr.item;
946 if(item.placement) continue; //&& !ptr.inserted) continue;
953 int breakX = clientSize.w - (systemButtons ? 48 : 0);
954 for(nextPtr = ptr.next; nextPtr; nextPtr = nextPtr.next)
956 MenuItem nextItem = nextPtr.item;
957 if(!nextItem.isDivider && ITEM_TEXT(nextItem))
960 FontExtent(display, fontObject, ITEM_TEXT(nextItem), strlen(ITEM_TEXT(nextItem)), &len, null);
964 if(x < breakX) x = breakX;
970 char * text = ITEM_TEXT(item);
971 FontExtent(display, fontObject, text, text ? strlen(text) : 0, &len, null);
972 if((mx >= x - 16 && mx < x + len + 16))
974 if(!ITEM_DISABLED(item))
980 *selectedY = -position.y;
991 for(ptr = menu.items.first; ptr; ptr = ptr.next)
993 MenuItem item = ptr.item;
994 if(item.placement) continue; //&& !ptr.inserted) continue;
995 if(mx >= 2 && (my >= y && my < y + rh) && !item.isDivider)
997 if(!ITEM_DISABLED(item))
1000 *selectedX = position.x + size.w;
1006 y += (item.isDivider) ? DIVIDER_HEIGHT : rh;
1013 void OnRedraw(Surface surface)
1015 bool hasMargin = menu ? menu.hasMargin : false;
1019 bool helpBreak = false;
1020 Window parent = this.parent;
1021 Window activeClient = parent.activeClient;
1022 bool systemButtons = activeClient && activeClient.state == maximized;
1023 int bitmapOffset = 0;
1024 bool hasBitmap = false;
1025 bool isRadio = false;
1027 surface.TextFont(fontObject);
1028 surface.TextExtent(" ", 1, null, &height);
1029 surface.SetBackground(SELECTION_COLOR);
1036 for(ptr = menu.items.first; ptr; ptr = ptr.next)
1038 if(ptr.item.bitmaps[0])
1045 for(ptr = menu.items.first; ptr; ptr = ptr.next)
1058 bitmapOffset = hasMargin ? 27 : (hasBitmap ? 18 : 12);
1059 if(hasBitmap && isRadio)
1062 else if(guiApp.textMode)
1066 // Shiny gradient for menu bar
1070 { popupMenuColor, 1 }
1072 surface.Gradient(keys, sizeof(keys)/sizeof(ColorKey), 1, vertical, 0,0, clientSize.w-1, 7);
1078 for(ptr = menu.items.first; ptr; ptr = ptr.next)
1080 MenuItem item = ptr.item;
1081 if(item.placement) continue; //&& !ptr.inserted) continue;
1082 if(!isMenuBar && selected == ptr)
1084 surface.SetForeground(SELECTION_TEXT);
1087 surface.TextOpacity(true);
1088 surface.Area(0,y,clientSize.w-1,y+rh-1);
1091 surface.Area(/*(hasMargin ? bitmapOffset : 0) +*/ 2,y,clientSize.w-3,y+rh);
1095 surface.SetForeground(foreground);
1097 surface.TextOpacity(false);
1107 int breakX = clientSize.w - (systemButtons ? 48 : 0);
1108 for(nextPtr = ptr.next; nextPtr; nextPtr = nextPtr.next)
1110 MenuItem nextItem = nextPtr.item;
1112 if(!nextItem.isDivider && ITEM_TEXT(nextItem))
1114 surface.TextExtent(ITEM_TEXT(nextItem), strlen(ITEM_TEXT(nextItem)), &len, null);
1118 if(x < breakX) x = breakX;
1126 surface.SetForeground(Color { 85, 85, 85 });
1127 surface.DrawingChar(196);
1128 surface.HLine(x + 2, x + rw - 5, y + (rh) / 2);
1129 surface.DrawingChar(' ');
1133 int start = x + hasMargin ? bitmapOffset : 2;
1134 int end = x + rw - (hasMargin ? 13 : 5);
1135 surface.foreground = Color { 85, 85, 85 };
1136 surface.HLine(start, end, y + (DIVIDER_HEIGHT) / 2);
1137 surface.foreground = white;
1138 surface.HLine(start, end, y + (DIVIDER_HEIGHT) / 2 + 1);
1144 if(selected == ptr && guiApp.textMode)
1146 surface.SetBackground(SELECTION_COLOR);
1147 surface.SetForeground(SELECTION_TEXT /*background*/);
1148 surface.TextOpacity(true);
1152 Interface::WriteKeyedTextDisabled(surface, x + bitmapOffset,
1153 y + (rh - height)/2, ITEM_TEXT(item), ITEM_HOTKEY(item), ITEM_DISABLED(item));
1155 surface.WriteText(x, y, "\373", 1);
1159 int textY = y + (rh - height)/2;
1160 BitmapResource bitmap = item.disabled ? item.bitmaps[2] : ((selected == ptr) ? item.bitmaps[1] : item.bitmaps[0]);
1161 if(!isMenuBar && bitmap)
1163 Bitmap icon = bitmap.bitmap;
1165 surface.Blit(icon, x + (isRadio ? 18 : 0) + (hasMargin ? 5 : 3), y + (rh - icon.height)/2, 0,0, icon.width, icon.height);
1169 surface.TextFont(boldFont.font);
1171 surface.TextFont(fontObject);
1173 if(ITEM_DISABLED(item) && selected == ptr)
1175 surface.SetForeground(formColor);
1176 Interface::WriteKeyedText(surface, x + bitmapOffset + 5,
1177 textY, ITEM_TEXT(item), ITEM_HOTKEY(item));
1180 Interface::WriteKeyedTextDisabled(surface, x + bitmapOffset + 5,
1181 textY, ITEM_TEXT(item), ITEM_HOTKEY(item), ITEM_DISABLED(item));
1183 // A nice vertical separation line
1184 if(hasMargin && !isMenuBar)
1186 surface.foreground = Color { 85, 85, 85 };
1187 surface.VLine(clientArea.top, clientArea.bottom, x + bitmapOffset - 2);
1188 surface.foreground = white;
1189 surface.VLine(clientArea.top, clientArea.bottom, x + bitmapOffset - 1);
1191 surface.foreground = foreground;
1194 surface.DrawLine(x+5, y+9, x+8,y+12);
1195 surface.DrawLine(x+5, y+10, x+8,y+13);
1196 surface.DrawLine(x+8, y+12, x+12,y+4);
1197 surface.DrawLine(x+8, y+13, x+12,y+5);
1204 // Draw little arrow
1207 surface.SetForeground(foreground);
1211 surface.SetForeground((selected == ptr) ? (background) : (foreground));
1212 surface.WriteText(clientSize.w-8, y+(rh - 8)/2, "\020", 1);
1216 Bitmap arrow = (selected == ptr) ? whiteSubArrow.bitmap : subArrow.bitmap;
1217 if(ITEM_DISABLED(ptr.item)) arrow = disabledSubArrow.bitmap;
1220 surface.Blit(arrow, clientSize.w-14, y+(rh - 8)/2, 0,0, arrow.width, arrow.height);
1222 surface.VLine(y+(rh - 8)/2, y+(rh - 8)/2+7, clientSize.w-10);
1223 surface.SetForeground(Color { 85, 85, 85 });
1224 surface.DrawLine(clientSize.w-10, y+(rh - 8)/2, clientSize.w-4, y+rh / 2);
1225 surface.SetForeground(formColor);
1226 surface.DrawLine(clientSize.w-10, y+(rh - 8)/2+7, clientSize.w-4, y+rh / 2);
1233 y += (item.isDivider) ? DIVIDER_HEIGHT : rh;
1235 else if(ITEM_TEXT(item) && !item.isDivider)
1238 surface.TextExtent(ITEM_TEXT(item), strlen(ITEM_TEXT(item)), &len, null);
1239 if(selected == ptr && !guiApp.textMode &&
1240 (item.subMenu || selected))
1242 surface.Bevel(pressed, x, y, len+10, height + 6);
1251 bool OnKeyDown(Key key, unichar ch)
1256 // Non-Interim menus: the slaves don't have focus... pass it down from menu bar
1259 if(!active && firstSlave)
1261 if(!firstSlave.OnKeyDown(key, ch))
1273 MenuDestroyMasters(true);
1286 for(ptr = menu.items.first; ptr; ptr = ptr.next)
1288 MenuItem item = ptr.item;
1289 if((item.placement && !ptr.inserted) || ITEM_DISABLED(item)) continue;
1290 if(((keyboardFocus || !isMenuBar) && ITEM_HOTKEY(item) == key) || (ITEM_HOTKEY(item) | Key { alt = true }) == key)
1292 if(!item.isDivider && !item.subMenu)
1294 if(MenuItemSelection(menu, ptr, key))
1297 else if(item.subMenu)
1300 MenuPopupSelected();
1306 if(ch >= 32 && ch != 128 && !isMenuBar)
1311 // Interim menus: the slaves are the ones with the focus
1312 if(result && INTERIM_MENU)
1314 Window master = this.master;
1315 if(eClass_IsDerived(master._class, _class))
1317 if(!master.OnKeyDown(key, ch))
1325 bool OnKeyUp(Key key, unichar ch)
1331 if(isMenuBar && altDown)
1336 keyboardFocus = true;
1342 if(firstSlave) firstSlave.Destroy(0);
1344 keyboardFocus = false;
1357 bool OnKeyHit(Key key, unichar ch)
1361 if(key == leftAlt || key == rightAlt) return true;
1363 if(key && isMenuBar)
1368 result = CheckAccelerators(menu, key);
1369 if(result && !key.alt && key != escape)
1374 if(result && visible)
1376 // Non-Interim menus: the slaves don't have focus... pass it down from menu bar
1379 if(!active && firstSlave)
1381 if(!firstSlave.OnKeyHit(key, ch))
1393 selected = menu.items.last;
1394 if(MenuGoToNextItem())
1402 selected = menu.items.first;
1403 if(MenuGoToPrevItem())
1411 if(MenuGoToPrevItem())
1412 MenuPopupSelected();
1417 PopupMenu master = (PopupMenu)this.master;
1418 if(master && !master.isMenuBar)
1430 if(MenuGoToNextItem() && MenuPopupSelected())
1435 if(selected && !ITEM_DISABLED(selected.item))
1437 if(!MenuPopupSelected())
1441 selected = menu.items.first;
1448 else if(!((PopupMenu)master).isMenuBar)
1450 if(MenuGoToNextItem())
1460 return MenuPopupSelected();
1464 if(firstSlave) firstSlave.Destroy(0);
1469 case down: case Key::altDown:
1472 return MenuPopupSelected();
1476 if(MenuGoToNextItem())
1481 case up: case altUp:
1484 if(MenuGoToPrevItem())
1489 case enter: case altEnter:
1490 case keyPadEnter: case altKeyPadEnter:
1493 if(!selected.item.isDivider && !selected.item.subMenu)
1495 if(MenuItemSelection(menu, selected, key))
1503 result = selected ? false : true;
1506 keyboardFocus = false;
1513 PopupMenu master = (PopupMenu)this.master;
1514 if(eClass_IsDerived(master._class, _class) && master.isMenuBar)
1516 ItemPtr selected = master.selected;
1518 master.pressed = true;
1519 master.selected = selected;
1520 master.keyboardFocus = true;
1533 if(ch >= 32 && !isMenuBar)
1536 for(ptr = menu.items.first; ptr; ptr = ptr.next)
1538 MenuItem item = ptr.item;
1539 if((item.placement && !ptr.inserted) || ITEM_DISABLED(item)) continue;
1540 if(ITEM_HOTKEY(item) == key || (ITEM_HOTKEY(item) | Key { alt = true }) == key)
1542 if(!item.isDivider && !item.subMenu)
1544 if(MenuItemSelection(menu, ptr, key))
1547 else if(item.subMenu)
1550 MenuPopupSelected();
1557 if(result && isMenuBar && pressed)
1560 // Interim menus: the slaves are the ones with the focus
1561 if(result && INTERIM_MENU)
1563 Window master = this.master;
1564 if(eClass_IsDerived(master._class, _class))
1566 if(!master.OnKeyHit(key, ch))
1574 bool OnLoadGraphics()
1576 Font font = fontObject;
1577 int maxW = 0, maxH = 0;
1582 background = isMenuBar ? menuBarColor : (menu ? menu.color : popupMenuColor);
1583 FontExtent = Display::FontExtent;
1588 // Default width & height for merging menus into menu bars
1591 FontExtent(display, font, "W",1, &maxW, &maxH);
1592 if(!guiApp.textMode)
1596 for(ptr = menu.items.first; ptr; ptr = ptr.next)
1598 MenuItem item = ptr.item;
1599 if(item.placement) continue; //&& !ptr.inserted) continue;
1606 FontExtent(display, font,ITEM_TEXT(item),strlen(ITEM_TEXT(item)),&width, &height);
1607 if(strstr(ITEM_TEXT(item), "\t"))
1610 if(item.bitmap && item.radio)
1612 if(item.subMenu) width += 20;
1613 if(!guiApp.textMode)
1618 if(!guiApp.textMode)
1619 height = DIVIDER_HEIGHT;
1624 if(width > maxW) maxW = width;
1625 if(height > maxH) maxH = height;
1627 totalHeight += height;
1629 if(item.bitmaps[0]) AddResource(item.bitmaps[0]);
1630 if(item.bitmaps[1]) AddResource(item.bitmaps[1]);
1631 if(item.bitmaps[2]) AddResource(item.bitmaps[2]);
1633 maxW += menu.hasMargin ? 32 : 24;
1636 FontExtent(display, font,menu.text,strlen(menu.text),&width, &height);
1637 if(width > maxW) maxW = width;
1638 if(height > maxH) maxH = height;
1644 if(rw < maxW) rw = maxW;
1645 if(rh < maxH) rh = maxH;
1650 bool OnResizing(int * w, int * h)
1652 Window master = this.master;
1653 Window masterMenuBar = master.menuBar;
1654 if(this != masterMenuBar)
1658 *h = Max(*h, rh + 2);
1660 *h = Max(*h, totalHeight + 2);
1662 if(this != masterMenuBar)
1672 bool OnMoving(int *x, int *y, int w, int h)
1676 Window parent = this.parent;
1679 if(*y + h > parent.clientSize.h)
1681 PopupMenu master = (PopupMenu)this.master;
1683 if(eClass_IsDerived(master._class, _class))
1685 if(master.isMenuBar)
1686 *y -= master.size.h;
1688 *y += h - clientSize.h + rh;
1692 *x = Min(*x, ((parent == guiApp.desktop && guiApp.virtualScreen.w) ? guiApp.virtualScreen.w : parent.clientSize.w) - w);
1696 *x = Min(*x, parent.size.w - w);
1697 *y = Min(*y, parent.size.h - h);
1701 *x = Min(*x, parent.clientSize.w - w);
1702 *y = Min(*y, parent.clientSize.h - h);
1704 if(parent == guiApp.desktop)
1707 *x = Max(*x, guiApp.virtualScreenPos.x);
1708 *y = Max(*y, guiApp.virtualScreenPos.y);
1720 bool OnMouseMove(int mx, int my, Modifiers mods)
1722 int selectedX, selectedY;
1725 if(mods.isSideEffect) return true;
1727 selected = FindSelected(mx, my, &selectedX, &selectedY);
1729 if((!mods.isSideEffect || !this.selected) && (/*selected && */
1730 selected != this.selected && (!selected || !ITEM_DISABLED(selected.item)) && (selected || !keyboardFocus)))
1732 if(!isMenuBar || pressed)
1734 bool pressed = this.pressed;
1736 if(firstSlave) firstSlave.Destroy(0);
1738 this.selected = selected;
1742 Menu childMenu = selected.item.subMenu;
1744 this.pressed = pressed;
1746 if(this.selected.item.subMenu)
1747 MenuPopupChild(selectedX, position.y + selectedY, childMenu);
1750 keyboardFocus = true;
1754 this.selected = selected;
1760 bool OnLeftButtonDown(int x, int y, Modifiers mods)
1764 Time t = GetTime(), u = unpressedTime;
1765 // Had to boost this to 0.1 for Windows Basic / XP theme / Remote Desktop
1766 // Aero & Classic were fast enough for 0.01
1767 if(GetTime() - unpressedTime < 0.1)
1771 int selectedX, selectedY;
1772 if(!mods.isActivate && !pressed)
1775 keyboardFocus = true;
1777 OnMouseMove(x,y, mods);
1781 if(firstSlave) firstSlave.Destroy(0);
1782 selected = FindSelected(x, y, &selectedX, &selectedY);
1786 //keyboardFocus = false;
1795 keyboardFocus = false;
1796 if(firstSlave) firstSlave.Destroy(0);
1802 keyboardFocus = true;
1804 OnMouseMove(x, y, mods);
1808 else //if(!INTERIM_MENU) // Why was this commented out?
1810 if(!(x >= 0 && y >= 0 && x < clientSize.w && y < clientSize.h))
1812 MenuDestroyMasters(false);
1819 bool OnRightButtonDown(int x, int y, Modifiers mods)
1821 if(x >= 0 && y >= 0 && x < clientSize.w && y < clientSize.h)
1825 Window master = this.master;
1826 Window activeClient = master.activeClient;
1827 if(activeClient.state == maximized)
1828 activeClient.ShowSysMenu(absPosition.x + x, absPosition.y + y);
1834 bool OnLeftButtonUp(int mx, int my, Modifiers mods)
1836 if(mx >= 2 /*0*/ && my >= 0 && mx < clientSize.w && my < clientSize.h)
1838 Menu menu = this.menu;
1842 if(!isMenuBar && menu)
1845 for(ptr = menu.items.first; ptr; ptr = ptr.next)
1847 MenuItem item = ptr.item;
1848 if(item.placement) continue; //&& !ptr.inserted) continue;
1849 if(my >= y && my < y + rh && !item.isDivider)
1857 y += (item.isDivider) ? DIVIDER_HEIGHT : rh;
1861 if(!isMenuBar || pressed)
1865 if(!selected.item.isDivider && !selected.item.subMenu)
1870 keyboardFocus = false;
1873 if(MenuItemSelection(menu, selected, Key { modifiers = mods }))
1880 keyboardFocus = false;
1881 if(firstSlave) firstSlave.Destroy(0);
1888 OnRightButtonUp = OnLeftButtonUp;
1890 bool OnLeftDoubleClick(int x, int y, Modifiers mods)
1894 int selectedX, selectedY;
1895 ItemPtr selected = FindSelected(x, y, &selectedX, &selectedY);
1898 Window master = this.master;
1899 Window activeClient = master.activeClient;
1900 // TOFIX: Fix need for a cast here...
1901 while(activeClient && !((BorderBits)activeClient.borderStyle).fixed)
1902 activeClient = activeClient.activeClient;
1903 if(activeClient && activeClient.state == maximized)
1904 activeClient.SetState(normal, false, mods);
1910 bool OnMouseLeave(Modifiers mods)
1912 if(!pressed && !firstSlave)
1921 bool OnActivate(bool active, Window swap, bool * goOnWithActivation, bool direct)
1929 PopupMenu master = (PopupMenu)this.master;
1930 if(eClass_IsDerived(master._class, _class) && master.isMenuBar) // && swap != master && swap && swap.master != master)
1933 master.unpressedTime = GetTime();
1934 master.pressed = false;
1935 master.selected = null;
1936 master.keyboardFocus = false;
1937 // master.Update(null);
1939 // TOFIX: Redraw bug here without this...
1940 master.master.Update(null);
1948 bool destroy = true;
1952 for(master = swap.master; master; master = master.master)
1961 for(master = this.master; master; master = master.master)
1964 if(eClass_IsDerived(master._class, _class))
1971 if(MenuDestroyMasters(false))
1978 // With new activation code this is not required anymore (double effect if there)
1984 unpressedTime = GetTime();
1987 keyboardFocus = false;
1988 if(firstSlave) firstSlave.Destroy(0);
2008 if(firstSlave) firstSlave.Destroy(0);
2010 Move(position.x, position.y, size.w, size.h);
2014 if(interim || isMenuBar)
2021 get { return menu; }
2023 property bool isMenuBar { set { isMenuBar = value; } };
2024 property bool focus { get { return keyboardFocus; } };