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;
669 for(; (master = (PopupMenu)window.master); window = master)
671 if(!eClass_IsDerived(master._class, _class) || master.isMenuBar)
675 if(eClass_IsDerived(master._class, _class) && master.isMenuBar)
677 master.pressed = false;
680 master.keyboardFocus = false;
681 master.selected = null;
685 result = window.Destroy(0);
686 // This looks like a hack...
691 bool MenuGoToPrevItem()
693 ItemPtr selected, current = this.selected;
694 for(selected = (current && current.prev) ? current.prev : menu.items.last;
696 (selected.item.isDivider || selected.item.placement || ITEM_DISABLED(selected.item)) &&
698 selected = selected.prev ? selected.prev : menu.items.last)
700 if(!current) current = selected; // Endless loop with no previously selected popups
702 this.selected = selected;
703 return selected && selected != current;
706 bool MenuGoToNextItem()
708 ItemPtr selected, current = this.selected;
709 for(selected = (current && current.next) ? current.next : menu.items.first;
711 (selected.item.isDivider || selected.item.placement || ITEM_DISABLED(selected.item)) &&
713 selected = selected.next ? selected.next : menu.items.first)
715 if(!current) current = selected; // Endless loop with no previously selected popups
717 this.selected = selected;
718 return selected && selected != current;
721 void MenuPopupChild(int x, int y, Menu childMenu)
726 if(childMenu.itemCount)
728 PopupMenu child { master = this, menu = childMenu };
732 Window parent = this.parent;
733 Window desktop = guiApp.desktop;
735 x += parent.absPosition.x + parent.clientStart.x - desktop.position.x;
736 y += parent.absPosition.y + parent.clientStart.y - desktop.position.y;
738 x += parent.absPosition.x + parent.clientStart.x;
739 y += parent.absPosition.y + parent.clientStart.y;
741 child.parent = desktop;
745 child.stayOnTop = true;
746 child.parent = parent;
747 child.interim = false;
749 child.position = Point { x, y };
751 // child.displayDriver = "GDI";
757 bool MenuPopupSelected()
761 int selectedX = guiApp.textMode ? 0 : 2;
763 if(selected && selected.item) // Why was this null from OnKeyHit?
765 ItemPtr selected = this.selected, ptr;
766 bool helpBreak = false;
767 Window parent = this.parent;
768 Window activeClient = parent.activeClient;
769 bool systemButtons = activeClient && activeClient.state == maximized;
771 keyboardFocus = true;
776 firstSlave.Destroy(0);
778 for(ptr = menu.items.first; ptr; ptr = ptr.next)
780 MenuItem item = ptr.item;
782 if(item.placement) continue; //&& !ptr.inserted) continue;
786 Menu childMenu = item.subMenu;
790 if(selected.item.subMenu)
791 MenuPopupChild(selectedX, 0, childMenu);
793 keyboardFocus = true;
798 return false; // true
805 int breakX = clientSize.w + 2 - (systemButtons ? 48 : 0);
806 for(nextPtr = ptr.next; nextPtr; nextPtr = nextPtr.next)
808 MenuItem nextItem = nextPtr.item;
809 if(!nextItem.isDivider && ITEM_TEXT(nextItem))
812 FontExtent(display, fontObject, ITEM_TEXT(nextItem), strlen(ITEM_TEXT(nextItem)), &len, null);
816 if(selectedX < breakX) selectedX = breakX;
820 else if(ITEM_TEXT(item))
822 FontExtent(display, fontObject, ITEM_TEXT(item), strlen(ITEM_TEXT(item)), &len, null);
823 selectedX += len + 16;
831 if(selected && selected.item.subMenu)
833 Menu childMenu = selected.item.subMenu;
838 for(ptr = menu.items.first; ptr; ptr = ptr.next)
840 MenuItem item = ptr.item;
842 if(item.placement) continue; //&& !ptr.inserted) continue;
849 y += (item.isDivider) ? DIVIDER_HEIGHT : rh;
853 PopupMenu slave = (PopupMenu)firstSlave;
854 if(!slave || slave.menu != childMenu)
856 if(firstSlave) firstSlave.Destroy(0);
857 MenuPopupChild(position.x + size.w, position.y + selectedY, childMenu);
867 bool MenuItemSelection(Menu parentMenu, ItemPtr selectionPtr, Key key)
869 MenuItem selection = selectionPtr.item;
870 if(!ITEM_DISABLED(selection))
872 Window master = this;
875 master = master.master;
877 if(selectionPtr.master)
878 master = selectionPtr.master;
879 while(eClass_IsDerived(master._class, _class) && master.master)
880 master = master.master;
882 if(selection.checkable)
883 selection.checked = !selection.checked;
884 else if(selection.radio)
886 if(selection.checked) return false;
887 selection.checked = !selection.checked;
891 MenuDestroyMasters(true);
893 /*return */selection.NotifySelect(master, selection, key.modifiers);
899 bool CheckAccelerators(Menu menu, Key key)
903 for(ptr = menu.items.first; ptr; ptr = ptr.next)
905 MenuItem item = ptr.item;
908 if(!CheckAccelerators(item.subMenu, key))
911 else if(!item.isDivider)
913 if(item.accelerator == key)
915 if(MenuItemSelection(menu, ptr, key))
923 ItemPtr FindSelected(int mx, int my, int * selectedX, int * selectedY)
925 Menu menu = this.menu;
926 // Mouse moved inside menu
927 ItemPtr selected = null;
932 if(isMenuBar && menu)
937 bool helpBreak = false;
938 Window parent = this.parent;
939 Window activeClient = parent.activeClient;
940 bool systemButtons = activeClient && activeClient.state == maximized;
942 for(ptr = menu.items.first; ptr; ptr = ptr.next)
944 MenuItem item = ptr.item;
945 if(item.placement) continue; //&& !ptr.inserted) continue;
952 int breakX = clientSize.w - (systemButtons ? 48 : 0);
953 for(nextPtr = ptr.next; nextPtr; nextPtr = nextPtr.next)
955 MenuItem nextItem = nextPtr.item;
956 if(!nextItem.isDivider && ITEM_TEXT(nextItem))
959 FontExtent(display, fontObject, ITEM_TEXT(nextItem), strlen(ITEM_TEXT(nextItem)), &len, null);
963 if(x < breakX) x = breakX;
969 char * text = ITEM_TEXT(item);
970 FontExtent(display, fontObject, text, text ? strlen(text) : 0, &len, null);
971 if((mx >= x - 16 && mx < x + len + 16))
973 if(!ITEM_DISABLED(item))
979 *selectedY = -position.y;
990 for(ptr = menu.items.first; ptr; ptr = ptr.next)
992 MenuItem item = ptr.item;
993 if(item.placement) continue; //&& !ptr.inserted) continue;
994 if(mx >= 2 && (my >= y && my < y + rh) && !item.isDivider)
996 if(!ITEM_DISABLED(item))
999 *selectedX = position.x + size.w;
1005 y += (item.isDivider) ? DIVIDER_HEIGHT : rh;
1012 void OnRedraw(Surface surface)
1014 bool hasMargin = menu ? menu.hasMargin : false;
1018 bool helpBreak = false;
1019 Window parent = this.parent;
1020 Window activeClient = parent.activeClient;
1021 bool systemButtons = activeClient && activeClient.state == maximized;
1022 int bitmapOffset = 0;
1023 bool hasBitmap = false;
1024 bool isRadio = false;
1026 surface.TextFont(fontObject);
1027 surface.TextExtent(" ", 1, null, &height);
1028 surface.SetBackground(SELECTION_COLOR);
1035 for(ptr = menu.items.first; ptr; ptr = ptr.next)
1037 if(ptr.item.bitmaps[0])
1044 for(ptr = menu.items.first; ptr; ptr = ptr.next)
1057 bitmapOffset = hasMargin ? 27 : (hasBitmap ? 18 : 12);
1058 if(hasBitmap && isRadio)
1061 else if(guiApp.textMode)
1065 // Shiny gradient for menu bar
1069 { popupMenuColor, 1 }
1071 surface.Gradient(keys, sizeof(keys)/sizeof(ColorKey), 1, vertical, 0,0, clientSize.w-1, 7);
1077 for(ptr = menu.items.first; ptr; ptr = ptr.next)
1079 MenuItem item = ptr.item;
1080 if(item.placement) continue; //&& !ptr.inserted) continue;
1081 if(!isMenuBar && selected == ptr)
1083 surface.SetForeground(SELECTION_TEXT);
1086 surface.TextOpacity(true);
1087 surface.Area(0,y,clientSize.w-1,y+rh-1);
1090 surface.Area(/*(hasMargin ? bitmapOffset : 0) +*/ 2,y,clientSize.w-3,y+rh);
1094 surface.SetForeground(foreground);
1096 surface.TextOpacity(false);
1106 int breakX = clientSize.w - (systemButtons ? 48 : 0);
1107 for(nextPtr = ptr.next; nextPtr; nextPtr = nextPtr.next)
1109 MenuItem nextItem = nextPtr.item;
1111 if(!nextItem.isDivider && ITEM_TEXT(nextItem))
1113 surface.TextExtent(ITEM_TEXT(nextItem), strlen(ITEM_TEXT(nextItem)), &len, null);
1117 if(x < breakX) x = breakX;
1125 surface.SetForeground(Color { 85, 85, 85 });
1126 surface.DrawingChar(196);
1127 surface.HLine(x + 2, x + rw - 5, y + (rh) / 2);
1128 surface.DrawingChar(' ');
1132 int start = x + hasMargin ? bitmapOffset : 2;
1133 int end = x + rw - (hasMargin ? 13 : 5);
1134 surface.foreground = Color { 85, 85, 85 };
1135 surface.HLine(start, end, y + (DIVIDER_HEIGHT) / 2);
1136 surface.foreground = white;
1137 surface.HLine(start, end, y + (DIVIDER_HEIGHT) / 2 + 1);
1143 if(selected == ptr && guiApp.textMode)
1145 surface.SetBackground(SELECTION_COLOR);
1146 surface.SetForeground(SELECTION_TEXT /*background*/);
1147 surface.TextOpacity(true);
1151 Interface::WriteKeyedTextDisabled(surface, x + bitmapOffset,
1152 y + (rh - height)/2, ITEM_TEXT(item), ITEM_HOTKEY(item), ITEM_DISABLED(item));
1154 surface.WriteText(x, y, "\373", 1);
1158 int textY = y + (rh - height)/2;
1159 BitmapResource bitmap = item.disabled ? item.bitmaps[2] : ((selected == ptr) ? item.bitmaps[1] : item.bitmaps[0]);
1160 if(!isMenuBar && bitmap)
1162 Bitmap icon = bitmap.bitmap;
1164 surface.Blit(icon, x + (isRadio ? 18 : 0) + (hasMargin ? 5 : 3), y + (rh - icon.height)/2, 0,0, icon.width, icon.height);
1168 surface.TextFont(boldFont.font);
1170 surface.TextFont(fontObject);
1172 if(ITEM_DISABLED(item) && selected == ptr)
1174 surface.SetForeground(formColor);
1175 Interface::WriteKeyedText(surface, x + bitmapOffset + 5,
1176 textY, ITEM_TEXT(item), ITEM_HOTKEY(item));
1179 Interface::WriteKeyedTextDisabled(surface, x + bitmapOffset + 5,
1180 textY, ITEM_TEXT(item), ITEM_HOTKEY(item), ITEM_DISABLED(item));
1182 // A nice vertical separation line
1183 if(hasMargin && !isMenuBar)
1185 surface.foreground = Color { 85, 85, 85 };
1186 surface.VLine(clientArea.top, clientArea.bottom, x + bitmapOffset - 2);
1187 surface.foreground = white;
1188 surface.VLine(clientArea.top, clientArea.bottom, x + bitmapOffset - 1);
1190 surface.foreground = foreground;
1193 surface.DrawLine(x+5, y+9, x+8,y+12);
1194 surface.DrawLine(x+5, y+10, x+8,y+13);
1195 surface.DrawLine(x+8, y+12, x+12,y+4);
1196 surface.DrawLine(x+8, y+13, x+12,y+5);
1203 // Draw little arrow
1206 surface.SetForeground(foreground);
1210 surface.SetForeground((selected == ptr) ? (background) : (foreground));
1211 surface.WriteText(clientSize.w-8, y+(rh - 8)/2, "\020", 1);
1215 Bitmap arrow = (selected == ptr) ? whiteSubArrow.bitmap : subArrow.bitmap;
1216 if(ITEM_DISABLED(ptr.item)) arrow = disabledSubArrow.bitmap;
1219 surface.Blit(arrow, clientSize.w-14, y+(rh - 8)/2, 0,0, arrow.width, arrow.height);
1221 surface.VLine(y+(rh - 8)/2, y+(rh - 8)/2+7, clientSize.w-10);
1222 surface.SetForeground(Color { 85, 85, 85 });
1223 surface.DrawLine(clientSize.w-10, y+(rh - 8)/2, clientSize.w-4, y+rh / 2);
1224 surface.SetForeground(formColor);
1225 surface.DrawLine(clientSize.w-10, y+(rh - 8)/2+7, clientSize.w-4, y+rh / 2);
1232 y += (item.isDivider) ? DIVIDER_HEIGHT : rh;
1234 else if(ITEM_TEXT(item) && !item.isDivider)
1237 surface.TextExtent(ITEM_TEXT(item), strlen(ITEM_TEXT(item)), &len, null);
1238 if(selected == ptr && !guiApp.textMode &&
1239 (item.subMenu || selected))
1241 surface.Bevel(pressed, x, y, len+10, height + 6);
1250 bool OnKeyDown(Key key, unichar ch)
1255 // Non-Interim menus: the slaves don't have focus... pass it down from menu bar
1258 if(!active && firstSlave)
1260 if(!firstSlave.OnKeyDown(key, ch))
1272 MenuDestroyMasters(true);
1285 for(ptr = menu.items.first; ptr; ptr = ptr.next)
1287 MenuItem item = ptr.item;
1288 if((item.placement && !ptr.inserted) || ITEM_DISABLED(item)) continue;
1289 if(((keyboardFocus || !isMenuBar) && ITEM_HOTKEY(item) == key) || (ITEM_HOTKEY(item) | Key { alt = true }) == key)
1291 if(!item.isDivider && !item.subMenu)
1293 if(MenuItemSelection(menu, ptr, key))
1296 else if(item.subMenu)
1299 MenuPopupSelected();
1305 if(ch >= 32 && ch != 128 && !isMenuBar)
1310 // Interim menus: the slaves are the ones with the focus
1311 if(result && INTERIM_MENU)
1313 Window master = this.master;
1314 if(eClass_IsDerived(master._class, _class))
1316 if(!master.OnKeyDown(key, ch))
1324 bool OnKeyUp(Key key, unichar ch)
1330 if(isMenuBar && altDown)
1335 keyboardFocus = true;
1341 if(firstSlave) firstSlave.Destroy(0);
1343 keyboardFocus = false;
1356 bool OnKeyHit(Key key, unichar ch)
1360 if(key == leftAlt || key == rightAlt) return true;
1362 if(key && isMenuBar)
1367 result = CheckAccelerators(menu, key);
1368 if(result && !key.alt && key != escape)
1373 if(result && visible)
1375 // Non-Interim menus: the slaves don't have focus... pass it down from menu bar
1378 if(!active && firstSlave)
1380 if(!firstSlave.OnKeyHit(key, ch))
1392 selected = menu.items.last;
1393 if(MenuGoToNextItem())
1401 selected = menu.items.first;
1402 if(MenuGoToPrevItem())
1410 if(MenuGoToPrevItem())
1411 MenuPopupSelected();
1416 PopupMenu master = (PopupMenu)this.master;
1417 if(master && !master.isMenuBar)
1429 if(MenuGoToNextItem() && MenuPopupSelected())
1434 if(selected && !ITEM_DISABLED(selected.item))
1436 if(!MenuPopupSelected())
1440 selected = menu.items.first;
1447 else if(!((PopupMenu)master).isMenuBar)
1449 if(MenuGoToNextItem())
1459 return MenuPopupSelected();
1463 if(firstSlave) firstSlave.Destroy(0);
1468 case down: case Key::altDown:
1471 return MenuPopupSelected();
1475 if(MenuGoToNextItem())
1480 case up: case altUp:
1483 if(MenuGoToPrevItem())
1488 case enter: case altEnter:
1489 case keyPadEnter: case altKeyPadEnter:
1492 if(!selected.item.isDivider && !selected.item.subMenu)
1494 if(MenuItemSelection(menu, selected, key))
1502 result = selected ? false : true;
1505 keyboardFocus = false;
1512 PopupMenu master = (PopupMenu)this.master;
1513 if(eClass_IsDerived(master._class, _class) && master.isMenuBar)
1515 ItemPtr selected = master.selected;
1517 master.pressed = true;
1518 master.selected = selected;
1519 master.keyboardFocus = true;
1532 if(ch >= 32 && !isMenuBar)
1535 for(ptr = menu.items.first; ptr; ptr = ptr.next)
1537 MenuItem item = ptr.item;
1538 if((item.placement && !ptr.inserted) || ITEM_DISABLED(item)) continue;
1539 if(ITEM_HOTKEY(item) == key || (ITEM_HOTKEY(item) | Key { alt = true }) == key)
1541 if(!item.isDivider && !item.subMenu)
1543 if(MenuItemSelection(menu, ptr, key))
1546 else if(item.subMenu)
1549 MenuPopupSelected();
1556 if(result && isMenuBar && pressed)
1559 // Interim menus: the slaves are the ones with the focus
1560 if(result && INTERIM_MENU)
1562 Window master = this.master;
1563 if(eClass_IsDerived(master._class, _class))
1565 if(!master.OnKeyHit(key, ch))
1573 bool OnLoadGraphics()
1575 Font font = fontObject;
1576 int maxW = 0, maxH = 0;
1581 background = isMenuBar ? menuBarColor : (menu ? menu.color : popupMenuColor);
1582 FontExtent = Display::FontExtent;
1587 // Default width & height for merging menus into menu bars
1590 FontExtent(display, font, "W",1, &maxW, &maxH);
1591 if(!guiApp.textMode)
1595 for(ptr = menu.items.first; ptr; ptr = ptr.next)
1597 MenuItem item = ptr.item;
1598 if(item.placement) continue; //&& !ptr.inserted) continue;
1605 FontExtent(display, font,ITEM_TEXT(item),strlen(ITEM_TEXT(item)),&width, &height);
1606 if(strstr(ITEM_TEXT(item), "\t"))
1609 if(item.bitmap && item.radio)
1611 if(item.subMenu) width += 20;
1612 if(!guiApp.textMode)
1617 if(!guiApp.textMode)
1618 height = DIVIDER_HEIGHT;
1623 if(width > maxW) maxW = width;
1624 if(height > maxH) maxH = height;
1626 totalHeight += height;
1628 if(item.bitmaps[0]) AddResource(item.bitmaps[0]);
1629 if(item.bitmaps[1]) AddResource(item.bitmaps[1]);
1630 if(item.bitmaps[2]) AddResource(item.bitmaps[2]);
1632 maxW += menu.hasMargin ? 32 : 24;
1635 FontExtent(display, font,menu.text,strlen(menu.text),&width, &height);
1636 if(width > maxW) maxW = width;
1637 if(height > maxH) maxH = height;
1643 if(rw < maxW) rw = maxW;
1644 if(rh < maxH) rh = maxH;
1649 bool OnResizing(int * w, int * h)
1651 Window master = this.master;
1652 Window masterMenuBar = master.menuBar;
1653 if(this != masterMenuBar)
1657 *h = Max(*h, rh + 2);
1659 *h = Max(*h, totalHeight + 2);
1661 if(this != masterMenuBar)
1671 bool OnMoving(int *x, int *y, int w, int h)
1675 Window parent = this.parent;
1678 if(*y + h > parent.clientSize.h)
1680 PopupMenu master = (PopupMenu)this.master;
1682 if(eClass_IsDerived(master._class, _class))
1684 if(master.isMenuBar)
1685 *y -= master.size.h;
1687 *y += h - clientSize.h + rh;
1691 *x = Min(*x, ((parent == guiApp.desktop && guiApp.virtualScreen.w) ? guiApp.virtualScreen.w : parent.clientSize.w) - w);
1695 *x = Min(*x, parent.size.w - w);
1696 *y = Min(*y, parent.size.h - h);
1700 *x = Min(*x, parent.clientSize.w - w);
1701 *y = Min(*y, parent.clientSize.h - h);
1703 if(parent == guiApp.desktop)
1706 *x = Max(*x, guiApp.virtualScreenPos.x);
1707 *y = Max(*y, guiApp.virtualScreenPos.y);
1719 bool OnMouseMove(int mx, int my, Modifiers mods)
1721 int selectedX, selectedY;
1724 if(mods.isSideEffect) return true;
1726 selected = FindSelected(mx, my, &selectedX, &selectedY);
1728 if((!mods.isSideEffect || !this.selected) && (/*selected && */
1729 selected != this.selected && (!selected || !ITEM_DISABLED(selected.item)) && (selected || !keyboardFocus)))
1731 if(!isMenuBar || pressed)
1733 bool pressed = this.pressed;
1735 if(firstSlave) firstSlave.Destroy(0);
1737 this.selected = selected;
1741 Menu childMenu = selected.item.subMenu;
1743 this.pressed = pressed;
1745 if(this.selected.item.subMenu)
1746 MenuPopupChild(selectedX, position.y + selectedY, childMenu);
1749 keyboardFocus = true;
1753 this.selected = selected;
1759 bool OnLeftButtonDown(int x, int y, Modifiers mods)
1763 // Had to boost this to 0.1 for Windows Basic / XP theme / Remote Desktop
1764 // Aero & Classic were fast enough for 0.01
1765 if(GetTime() - unpressedTime < 0.1)
1769 int selectedX, selectedY;
1770 if(!mods.isActivate && !pressed)
1773 keyboardFocus = true;
1775 OnMouseMove(x,y, mods);
1779 if(firstSlave) firstSlave.Destroy(0);
1780 selected = FindSelected(x, y, &selectedX, &selectedY);
1784 //keyboardFocus = false;
1793 keyboardFocus = false;
1794 if(firstSlave) firstSlave.Destroy(0);
1800 keyboardFocus = true;
1802 OnMouseMove(x, y, mods);
1806 else //if(!INTERIM_MENU) // Why was this commented out?
1808 if(!(x >= 0 && y >= 0 && x < clientSize.w && y < clientSize.h))
1810 MenuDestroyMasters(false);
1817 bool OnRightButtonDown(int x, int y, Modifiers mods)
1819 if(x >= 0 && y >= 0 && x < clientSize.w && y < clientSize.h)
1823 Window master = this.master;
1824 Window activeClient = master.activeClient;
1825 if(activeClient.state == maximized)
1826 activeClient.ShowSysMenu(absPosition.x + x, absPosition.y + y);
1832 bool OnLeftButtonUp(int mx, int my, Modifiers mods)
1834 if(mx >= 2 /*0*/ && my >= 0 && mx < clientSize.w && my < clientSize.h)
1836 Menu menu = this.menu;
1840 if(!isMenuBar && menu)
1843 for(ptr = menu.items.first; ptr; ptr = ptr.next)
1845 MenuItem item = ptr.item;
1846 if(item.placement) continue; //&& !ptr.inserted) continue;
1847 if(my >= y && my < y + rh && !item.isDivider)
1855 y += (item.isDivider) ? DIVIDER_HEIGHT : rh;
1859 if(!isMenuBar || pressed)
1863 if(!selected.item.isDivider && !selected.item.subMenu)
1868 keyboardFocus = false;
1871 if(MenuItemSelection(menu, selected, Key { modifiers = mods }))
1878 keyboardFocus = false;
1879 if(firstSlave) firstSlave.Destroy(0);
1886 OnRightButtonUp = OnLeftButtonUp;
1888 bool OnLeftDoubleClick(int x, int y, Modifiers mods)
1892 int selectedX, selectedY;
1893 ItemPtr selected = FindSelected(x, y, &selectedX, &selectedY);
1896 Window master = this.master;
1897 Window activeClient = master.activeClient;
1898 // TOFIX: Fix need for a cast here...
1899 while(activeClient && !((BorderBits)activeClient.borderStyle).fixed)
1900 activeClient = activeClient.activeClient;
1901 if(activeClient && activeClient.state == maximized)
1902 activeClient.SetState(normal, false, mods);
1908 bool OnMouseLeave(Modifiers mods)
1910 if(!pressed && !firstSlave)
1919 bool OnActivate(bool active, Window swap, bool * goOnWithActivation, bool direct)
1927 PopupMenu master = (PopupMenu)this.master;
1928 if(eClass_IsDerived(master._class, _class) && master.isMenuBar) // && swap != master && swap && swap.master != master)
1931 master.unpressedTime = GetTime();
1932 master.pressed = false;
1933 master.selected = null;
1934 master.keyboardFocus = false;
1935 // master.Update(null);
1937 // TOFIX: Redraw bug here without this...
1938 master.master.Update(null);
1946 bool destroy = true;
1950 for(master = swap.master; master; master = master.master)
1959 for(master = this.master; master; master = master.master)
1962 if(eClass_IsDerived(master._class, _class))
1969 if(MenuDestroyMasters(false))
1976 // With new activation code this is not required anymore (double effect if there)
1982 unpressedTime = GetTime();
1985 keyboardFocus = false;
1986 if(firstSlave) firstSlave.Destroy(0);
2006 if(firstSlave) firstSlave.Destroy(0);
2008 Move(position.x, position.y, size.w, size.h);
2012 if(interim || isMenuBar)
2019 get { return menu; }
2021 property bool isMenuBar { set { isMenuBar = value; } };
2022 property bool focus { get { return keyboardFocus; } };