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 uint 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;
187 property bool copyText
193 if(text && !copyText)
194 text = CopyString(ITEM_TEXT(this));
204 property bool bold { set { bold = value; } get { return bold; } };
206 virtual bool Window::NotifySelect(MenuItem selection, Modifiers mods);
216 BitmapResource bitmaps[3];
217 bool checkable, radio;
223 bool manualAccelText;
229 // delete ITEM_TEXT(this);
238 public class MenuDivider : MenuItem
245 // property Menu parent { set {} };
248 public class MenuPlacement : MenuItem
257 property Menu parent { set {} };
258 property char * text { set {} };
259 property Key hotKey { set {} };
267 int OnCompare(Menu menu)
269 return (this != null) != (menu != null);
272 void AddItem(MenuItem item)
278 for(ptr = items.first; ptr; ptr = ptr.next)
280 MenuItem check = ptr.item;
283 if(!strcmpTillTab(ITEM_TEXT(check), ITEM_TEXT(item)))
294 ptr.inserted = InsertedFlags { placed = true };
295 ptr.oldItem = ptr.item;
310 void RemoveItem(MenuItem item)
312 if(item.menu == this)
315 for(ptr = items.first; ptr; ptr = ptr.next)
318 if(ptr.inserted.placed)
320 ptr.item = ptr.oldItem;
326 if(!ptr.inserted.placed)
336 void AddSubMenu(Menu subMenu)
340 MenuItem menuItem { };
341 ItemPtr ptr { item = menuItem };
348 menuItem.menu = this;
352 menuItem.subMenu = subMenu;
358 void AddDynamic(MenuItem addedItem, Window master, bool persistent)
362 ItemPtr ptr = null, oldItemPtr;
364 for(oldItemPtr = items.first; oldItemPtr; oldItemPtr = oldItemPtr.next)
366 if((oldItemPtr.item.subMenu || oldItemPtr.item.placement) && !strcmpTillTab(ITEM_TEXT(oldItemPtr.item), ITEM_TEXT(addedItem)))
368 MenuItem oldItem = oldItemPtr.item;
369 if(!oldItem.placement)
371 oldItem.subMenu.Merge(addedItem.subMenu, true, master);
373 // If sub menu already has a master...
376 oldItemPtr.inserted = InsertedFlags { cleanItem = true };
377 if(!oldItemPtr.oldItem)
378 oldItemPtr.oldItem = oldItem;
379 oldItemPtr.item = addedItem;
391 ptr.inserted = InsertedFlags { deleteLink = true, deleteItem = true };
394 ptr.inserted = InsertedFlags { cleanItem = true, deleteItem = true };
396 ptr.item = addedItem;
400 addedItem.menu = this;
404 MenuItem FindItem(bool (* Window::notifySelect)(MenuItem selection, Modifiers mods), uint id)
408 for(ptr = items.first; ptr; ptr = ptr.next)
410 MenuItem item = ptr.item;
413 MenuItem subItem = item.subMenu.FindItem(notifySelect, id);
414 if(subItem) return subItem;
416 else if(!item.isDivider && !item.placement)
418 if(item.id == id && item.NotifySelect == notifySelect)
428 while((ptr = items.first))
431 if(ptr.inserted.cleanItem || ptr.inserted.placed)
433 ptr.item = ptr.oldItem;
441 void Merge(Menu menuBeingMerged, bool menuBar, Window window)
443 bool separated = false;
444 ItemPtr beingMergedItemPtr;
446 for(beingMergedItemPtr = menuBeingMerged.items.first; beingMergedItemPtr; beingMergedItemPtr = beingMergedItemPtr.next)
448 MenuItem beingMergedItem = beingMergedItemPtr.item;
449 ItemPtr mergeIntoItemPtr = null;
451 if(!beingMergedItem) continue;
452 if(beingMergedItem.subMenu)
454 for(mergeIntoItemPtr = items.first; mergeIntoItemPtr; mergeIntoItemPtr = mergeIntoItemPtr.next)
456 if((mergeIntoItemPtr.item.subMenu || mergeIntoItemPtr.item.placement) && !strcmpTillTab(ITEM_TEXT(mergeIntoItemPtr.item), ITEM_TEXT(beingMergedItem)))
458 MenuItem mergeIntoItem = mergeIntoItemPtr.item;
459 if(!beingMergedItem.placement || beingMergedItemPtr.inserted)
461 if(!mergeIntoItem.placement && !mergeIntoItemPtr.inserted.cleanItem) // Added this last check for ActiveChild overriding ActiveClient's menu
463 mergeIntoItem.subMenu.Merge(beingMergedItem.subMenu, menuBar, window);
465 // If sub menu already has a master...
468 mergeIntoItemPtr.inserted = InsertedFlags { cleanItem = true };
469 if(!mergeIntoItemPtr.oldItem)
470 mergeIntoItemPtr.oldItem = mergeIntoItem;
471 mergeIntoItemPtr.item = beingMergedItem;
473 mergeIntoItemPtr.master = window;
480 else if(!beingMergedItem.isDivider)
482 for(mergeIntoItemPtr = items.first; mergeIntoItemPtr; mergeIntoItemPtr = mergeIntoItemPtr.next)
484 MenuItem mergeIntoItem = mergeIntoItemPtr.item;
485 if(/*!mergeIntoItem.subMenu && /*mergeIntoItem.placement && !mergeIntoItemPtr.inserted && */!strcmpTillTab(ITEM_TEXT(mergeIntoItem), ITEM_TEXT(beingMergedItem)))
487 //if(!beingMergedItem.placement || beingMergedItemPtr.inserted)
489 mergeIntoItemPtr.inserted = InsertedFlags { cleanItem = true };
490 if(!mergeIntoItemPtr.oldItem)
491 mergeIntoItemPtr.oldItem = mergeIntoItem;
492 mergeIntoItemPtr.item = beingMergedItem;
493 mergeIntoItemPtr.master = beingMergedItemPtr.master ? beingMergedItemPtr.master : window;
500 if(!mergeIntoItemPtr)
502 if(beingMergedItem.placement && !beingMergedItemPtr.inserted)
504 // Simply add the placement at the end
505 mergeIntoItemPtr = ItemPtr { };
506 mergeIntoItemPtr.inserted = InsertedFlags { deleteLink = true, cleanItem = true };
507 mergeIntoItemPtr.item = beingMergedItem;
508 mergeIntoItemPtr.master = beingMergedItemPtr.master ? beingMergedItemPtr.master : window;
509 items.Add(mergeIntoItemPtr);
514 ItemPtr previous = items.last;
517 // If it is a menu bar, add the item before the first divider
518 for(previous = items.first; previous; previous = previous.next)
519 if(previous.item.isDivider && !previous.inserted) // Added previous.inserted check
521 previous = previous.prev;
527 if(previous && !previous.item.isDivider && !separated)
531 item = MenuDivider { },
532 inserted = InsertedFlags { deleteLink = true, deleteItem = true }
534 items.Insert(previous, ptr);
541 if(!beingMergedItem.isDivider || (previous.item && !previous.item.isDivider))
543 mergeIntoItemPtr = ItemPtr { };
544 items.Insert(previous, mergeIntoItemPtr);
545 mergeIntoItemPtr.inserted = InsertedFlags { deleteLink = true, cleanItem = true };
546 mergeIntoItemPtr.item = beingMergedItem;
547 mergeIntoItemPtr.master = beingMergedItemPtr.master ? beingMergedItemPtr.master : window;
555 void Clean(Window window)
558 for(ptr = items.first; ptr; ptr = next)
560 MenuItem item = ptr.item;
563 if(ptr.inserted.cleanItem)
565 ptr.item = ptr.oldItem;
568 else if(item.subMenu)
569 item.subMenu.Clean(window);
571 if(ptr.inserted.deleteItem)
574 if(ptr.inserted.deleteLink || ptr.inserted.cleanItem)
576 if(ptr.inserted.deleteLink)
580 ptr.inserted.deleteLink = false;
581 ptr.inserted.cleanItem = false;
582 ptr.inserted.deleteItem = false;
588 Menu FindMenu(char * name)
592 for(ptr = items.first; ptr; ptr = ptr.next)
594 MenuItem item = ptr.item;
596 if(item.subMenu && item.subMenu.text && !strcmpTillTab(item.subMenu.text, name))
602 property Menu parent { set { if(value) value.AddSubMenu(this); } };
603 property char * text { set { text = value; /* CopyString(value);*/ } };
604 property Key hotKey { set { hotKey = value; } };
605 property bool hasMargin { set { hasMargin = value; } };
620 color = popupMenuColor;
630 public class PopupMenu : Window
632 class_property(icon) = "<:ecere>controls/menu.png";
644 void (* FontExtent)(Display display, Font font, char * text, int len, int * width, int * height);
646 FontResource boldFont { faceName = font.faceName, font.size, bold = true, window = this };
647 BitmapResource subArrow { fileName = "<:ecere>elements/arrowRight.png", window = this };
648 BitmapResource whiteSubArrow { fileName = "<:ecere>elements/arrowRight.png", monochrome = true, window = this };
649 BitmapResource disabledSubArrow { fileName = "<:ecere>elements/arrowRight.png", grayed = true, window = this };
656 if(menu) delete menu;
660 bool MenuDestroyMasters(bool unselect)
663 PopupMenu window = this, master;
666 for(; (master = (PopupMenu)window.master); window = master)
668 if(!eClass_IsDerived(master._class, _class) || master.isMenuBar)
672 if(eClass_IsDerived(master._class, _class) && master.isMenuBar)
674 master.pressed = false;
677 master.keyboardFocus = false;
678 master.selected = null;
682 result = window.Destroy(0);
683 // This looks like a hack...
688 bool MenuGoToPrevItem()
690 ItemPtr selected, current = this.selected;
691 for(selected = (current && current.prev) ? current.prev : menu.items.last;
693 (selected.item.isDivider || selected.item.placement || ITEM_DISABLED(selected.item)) &&
695 selected = selected.prev ? selected.prev : menu.items.last)
697 if(!current) current = selected; // Endless loop with no previously selected popups
699 this.selected = selected;
700 return selected && selected != current;
703 bool MenuGoToNextItem()
705 ItemPtr selected, current = this.selected;
706 for(selected = (current && current.next) ? current.next : menu.items.first;
708 (selected.item.isDivider || selected.item.placement || ITEM_DISABLED(selected.item)) &&
710 selected = selected.next ? selected.next : menu.items.first)
712 if(!current) current = selected; // Endless loop with no previously selected popups
714 this.selected = selected;
715 return selected && selected != current;
718 void MenuPopupChild(int x, int y, Menu childMenu)
723 if(childMenu.itemCount)
725 PopupMenu child { master = this, menu = childMenu };
729 Window parent = this.parent;
730 Window desktop = guiApp.desktop;
732 x += parent.absPosition.x + parent.clientStart.x - desktop.position.x;
733 y += parent.absPosition.y + parent.clientStart.y - desktop.position.y;
735 x += parent.absPosition.x + parent.clientStart.x;
736 y += parent.absPosition.y + parent.clientStart.y;
738 child.parent = desktop;
742 child.stayOnTop = true;
743 child.parent = parent;
744 child.interim = false;
746 child.position = Point { x, y };
748 // child.displayDriver = "GDI";
754 bool MenuPopupSelected()
758 int selectedX = guiApp.textMode ? 0 : 2;
760 if(selected && selected.item) // Why was this null from OnKeyHit?
762 ItemPtr selected = this.selected, ptr;
763 bool helpBreak = false;
764 Window parent = this.parent;
765 Window activeClient = parent.activeClient;
766 bool systemButtons = activeClient && activeClient.state == maximized;
768 keyboardFocus = true;
773 firstSlave.Destroy(0);
775 for(ptr = menu.items.first; ptr; ptr = ptr.next)
777 MenuItem item = ptr.item;
779 if(item.placement) continue; //&& !ptr.inserted) continue;
783 Menu childMenu = item.subMenu;
787 if(selected.item.subMenu)
788 MenuPopupChild(selectedX, 0, childMenu);
790 keyboardFocus = true;
795 return false; // true
802 int breakX = clientSize.w + 2 - (systemButtons ? 48 : 0);
803 for(nextPtr = ptr.next; nextPtr; nextPtr = nextPtr.next)
805 MenuItem nextItem = nextPtr.item;
806 if(!nextItem.isDivider && ITEM_TEXT(nextItem))
809 FontExtent(display, fontObject, ITEM_TEXT(nextItem), strlen(ITEM_TEXT(nextItem)), &len, null);
813 if(selectedX < breakX) selectedX = breakX;
817 else if(ITEM_TEXT(item))
819 FontExtent(display, fontObject, ITEM_TEXT(item), strlen(ITEM_TEXT(item)), &len, null);
820 selectedX += len + 16;
828 if(selected && selected.item.subMenu)
830 Menu childMenu = selected.item.subMenu;
835 for(ptr = menu.items.first; ptr; ptr = ptr.next)
837 MenuItem item = ptr.item;
839 if(item.placement) continue; //&& !ptr.inserted) continue;
846 y += (item.isDivider) ? DIVIDER_HEIGHT : rh;
850 PopupMenu slave = (PopupMenu)firstSlave;
851 if(!slave || slave.menu != childMenu)
853 if(firstSlave) firstSlave.Destroy(0);
854 MenuPopupChild(position.x + size.w, position.y + selectedY, childMenu);
864 bool MenuItemSelection(Menu parentMenu, ItemPtr selectionPtr, Key key)
866 MenuItem selection = selectionPtr.item;
867 if(!ITEM_DISABLED(selection))
869 Window master = this;
872 master = master.master;
874 if(selectionPtr.master)
875 master = selectionPtr.master;
876 while(eClass_IsDerived(master._class, _class) && master.master)
877 master = master.master;
879 if(selection.checkable)
880 selection.checked = !selection.checked;
881 else if(selection.radio)
883 if(selection.checked) return false;
884 selection.checked = !selection.checked;
888 MenuDestroyMasters(true);
890 /*return */selection.NotifySelect(master, selection, key.modifiers);
896 bool CheckAccelerators(Menu menu, Key key)
900 for(ptr = menu.items.first; ptr; ptr = ptr.next)
902 MenuItem item = ptr.item;
905 if(!CheckAccelerators(item.subMenu, key))
908 else if(!item.isDivider)
910 if(item.accelerator == key)
912 if(MenuItemSelection(menu, ptr, key))
920 ItemPtr FindSelected(int mx, int my, int * selectedX, int * selectedY)
922 Menu menu = this.menu;
923 // Mouse moved inside menu
924 ItemPtr selected = null;
929 if(isMenuBar && menu)
934 bool helpBreak = false;
935 Window parent = this.parent;
936 Window activeClient = parent.activeClient;
937 bool systemButtons = activeClient && activeClient.state == maximized;
939 for(ptr = menu.items.first; ptr; ptr = ptr.next)
941 MenuItem item = ptr.item;
942 if(item.placement) continue; //&& !ptr.inserted) continue;
949 int breakX = clientSize.w - (systemButtons ? 48 : 0);
950 for(nextPtr = ptr.next; nextPtr; nextPtr = nextPtr.next)
952 MenuItem nextItem = nextPtr.item;
953 if(!nextItem.isDivider && ITEM_TEXT(nextItem))
956 FontExtent(display, fontObject, ITEM_TEXT(nextItem), strlen(ITEM_TEXT(nextItem)), &len, null);
960 if(x < breakX) x = breakX;
966 char * text = ITEM_TEXT(item);
967 FontExtent(display, fontObject, text, text ? strlen(text) : 0, &len, null);
968 if((mx >= x - 16 && mx < x + len + 16))
970 if(!ITEM_DISABLED(item))
976 *selectedY = -position.y;
987 for(ptr = menu.items.first; ptr; ptr = ptr.next)
989 MenuItem item = ptr.item;
990 if(item.placement) continue; //&& !ptr.inserted) continue;
991 if(mx >= 2 && (my >= y && my < y + rh) && !item.isDivider)
993 if(!ITEM_DISABLED(item))
996 *selectedX = position.x + size.w;
1002 y += (item.isDivider) ? DIVIDER_HEIGHT : rh;
1009 void OnRedraw(Surface surface)
1011 bool hasMargin = menu ? menu.hasMargin : false;
1015 bool helpBreak = false;
1016 Window parent = this.parent;
1017 Window activeClient = parent.activeClient;
1018 bool systemButtons = activeClient && activeClient.state == maximized;
1019 int bitmapOffset = 0;
1020 bool hasBitmap = false;
1022 surface.TextFont(fontObject);
1023 surface.TextExtent(" ", 1, null, &height);
1024 surface.SetBackground(SELECTION_COLOR);
1031 for(ptr = menu.items.first; ptr; ptr = ptr.next)
1033 if(ptr.item.bitmaps[0])
1044 bitmapOffset = hasMargin ? 27 : (hasBitmap ? 18 : 12);
1046 else if(guiApp.textMode)
1050 // Shiny gradient for menu bar
1054 { popupMenuColor, 1 }
1056 surface.Gradient(keys, sizeof(keys)/sizeof(ColorKey), 1, vertical, 0,0, clientSize.w-1, 7);
1062 for(ptr = menu.items.first; ptr; ptr = ptr.next)
1064 MenuItem item = ptr.item;
1065 if(item.placement) continue; //&& !ptr.inserted) continue;
1066 if(!isMenuBar && selected == ptr)
1068 surface.SetForeground(SELECTION_TEXT);
1071 surface.TextOpacity(true);
1072 surface.Area(0,y,clientSize.w-1,y+rh-1);
1075 surface.Area(/*(hasMargin ? bitmapOffset : 0) +*/ 2,y,clientSize.w-3,y+rh);
1079 surface.SetForeground(foreground);
1081 surface.TextOpacity(false);
1091 int breakX = clientSize.w - (systemButtons ? 48 : 0);
1092 for(nextPtr = ptr.next; nextPtr; nextPtr = nextPtr.next)
1094 MenuItem nextItem = nextPtr.item;
1096 if(!nextItem.isDivider && ITEM_TEXT(nextItem))
1098 surface.TextExtent(ITEM_TEXT(nextItem), strlen(ITEM_TEXT(nextItem)), &len, null);
1102 if(x < breakX) x = breakX;
1110 surface.SetForeground(Color { 85, 85, 85 });
1111 surface.DrawingChar(196);
1112 surface.HLine(x + 2, x + rw - 5, y + (rh) / 2);
1113 surface.DrawingChar(' ');
1117 int start = x + hasMargin ? bitmapOffset : 2;
1118 int end = x + rw - (hasMargin ? 13 : 5);
1119 surface.foreground = Color { 85, 85, 85 };
1120 surface.HLine(start, end, y + (DIVIDER_HEIGHT) / 2);
1121 surface.foreground = white;
1122 surface.HLine(start, end, y + (DIVIDER_HEIGHT) / 2 + 1);
1128 if(selected == ptr && guiApp.textMode)
1130 surface.SetBackground(SELECTION_COLOR);
1131 surface.SetForeground(SELECTION_TEXT /*background*/);
1132 surface.TextOpacity(true);
1136 Interface::WriteKeyedTextDisabled(surface, x + bitmapOffset,
1137 y + (rh - height)/2, ITEM_TEXT(item), ITEM_HOTKEY(item), ITEM_DISABLED(item));
1139 surface.WriteText(x, y, "\373", 1);
1143 int textY = y + (rh - height)/2;
1144 BitmapResource bitmap = item.disabled ? item.bitmaps[2] : ((selected == ptr) ? item.bitmaps[1] : item.bitmaps[0]);
1145 if(!isMenuBar && bitmap)
1147 Bitmap icon = bitmap.bitmap;
1149 surface.Blit(icon, x + hasMargin ? 5 : 3, y + (rh - icon.height)/2, 0,0, icon.width, icon.height);
1153 surface.TextFont(boldFont.font);
1155 surface.TextFont(fontObject);
1157 if(ITEM_DISABLED(item) && selected == ptr)
1159 surface.SetForeground(formColor);
1160 Interface::WriteKeyedText(surface, x + bitmapOffset + 5,
1161 textY, ITEM_TEXT(item), ITEM_HOTKEY(item));
1164 Interface::WriteKeyedTextDisabled(surface, x + bitmapOffset + 5,
1165 textY, ITEM_TEXT(item), ITEM_HOTKEY(item), ITEM_DISABLED(item));
1167 // A nice vertical separation line
1168 if(hasMargin && !isMenuBar)
1170 surface.foreground = Color { 85, 85, 85 };
1171 surface.VLine(clientArea.top, clientArea.bottom, x + bitmapOffset - 2);
1172 surface.foreground = white;
1173 surface.VLine(clientArea.top, clientArea.bottom, x + bitmapOffset - 1);
1175 surface.foreground = foreground;
1178 surface.DrawLine(x+5, y+9, x+8,y+12);
1179 surface.DrawLine(x+5, y+10, x+8,y+13);
1180 surface.DrawLine(x+8, y+12, x+12,y+4);
1181 surface.DrawLine(x+8, y+13, x+12,y+5);
1188 // Draw little arrow
1191 surface.SetForeground(foreground);
1195 surface.SetForeground((selected == ptr) ? (background) : (foreground));
1196 surface.WriteText(clientSize.w-8, y+(rh - 8)/2, "\020", 1);
1200 Bitmap arrow = (selected == ptr) ? whiteSubArrow.bitmap : subArrow.bitmap;
1201 if(ITEM_DISABLED(ptr.item)) arrow = disabledSubArrow.bitmap;
1204 surface.Blit(arrow, clientSize.w-14, y+(rh - 8)/2, 0,0, arrow.width, arrow.height);
1206 surface.VLine(y+(rh - 8)/2, y+(rh - 8)/2+7, clientSize.w-10);
1207 surface.SetForeground(Color { 85, 85, 85 });
1208 surface.DrawLine(clientSize.w-10, y+(rh - 8)/2, clientSize.w-4, y+rh / 2);
1209 surface.SetForeground(formColor);
1210 surface.DrawLine(clientSize.w-10, y+(rh - 8)/2+7, clientSize.w-4, y+rh / 2);
1217 y += (item.isDivider) ? DIVIDER_HEIGHT : rh;
1219 else if(ITEM_TEXT(item) && !item.isDivider)
1222 surface.TextExtent(ITEM_TEXT(item), strlen(ITEM_TEXT(item)), &len, null);
1223 if(selected == ptr && !guiApp.textMode &&
1224 (item.subMenu || selected))
1226 surface.Bevel(pressed, x, y, len+10, height + 6);
1235 bool OnKeyDown(Key key, unichar ch)
1240 // Non-Interim menus: the slaves don't have focus... pass it down from menu bar
1243 if(!active && firstSlave)
1245 if(!firstSlave.OnKeyDown(key, ch))
1257 MenuDestroyMasters(true);
1270 for(ptr = menu.items.first; ptr; ptr = ptr.next)
1272 MenuItem item = ptr.item;
1273 if((item.placement && !ptr.inserted) || ITEM_DISABLED(item)) continue;
1274 if(((keyboardFocus || !isMenuBar) && ITEM_HOTKEY(item) == key) || (ITEM_HOTKEY(item) | Key { alt = true }) == key)
1276 if(!item.isDivider && !item.subMenu)
1278 if(MenuItemSelection(menu, ptr, key))
1281 else if(item.subMenu)
1284 MenuPopupSelected();
1290 if(ch >= 32 && ch != 128 && !isMenuBar)
1295 // Interim menus: the slaves are the ones with the focus
1296 if(result && INTERIM_MENU)
1298 Window master = this.master;
1299 if(eClass_IsDerived(master._class, _class))
1301 if(!master.OnKeyDown(key, ch))
1309 bool OnKeyUp(Key key, unichar ch)
1315 if(isMenuBar && altDown)
1320 keyboardFocus = true;
1326 if(firstSlave) firstSlave.Destroy(0);
1328 keyboardFocus = false;
1341 bool OnKeyHit(Key key, unichar ch)
1345 if(key == leftAlt || key == rightAlt) return true;
1347 if(key && isMenuBar)
1352 result = CheckAccelerators(menu, key);
1353 if(result && !key.alt && key != escape)
1358 if(result && visible)
1360 // Non-Interim menus: the slaves don't have focus... pass it down from menu bar
1363 if(!active && firstSlave)
1365 if(!firstSlave.OnKeyHit(key, ch))
1377 selected = menu.items.last;
1378 if(MenuGoToNextItem())
1386 selected = menu.items.first;
1387 if(MenuGoToPrevItem())
1395 if(MenuGoToPrevItem())
1396 MenuPopupSelected();
1401 PopupMenu master = (PopupMenu)this.master;
1402 if(master && !master.isMenuBar)
1414 if(MenuGoToNextItem() && MenuPopupSelected())
1419 if(selected && !ITEM_DISABLED(selected.item))
1421 if(!MenuPopupSelected())
1425 selected = menu.items.first;
1432 else if(!((PopupMenu)master).isMenuBar)
1434 if(MenuGoToNextItem())
1444 return MenuPopupSelected();
1448 if(firstSlave) firstSlave.Destroy(0);
1453 case down: case Key::altDown:
1456 return MenuPopupSelected();
1460 if(MenuGoToNextItem())
1465 case up: case altUp:
1468 if(MenuGoToPrevItem())
1473 case enter: case altEnter:
1474 case keyPadEnter: case altKeyPadEnter:
1477 if(!selected.item.isDivider && !selected.item.subMenu)
1479 if(MenuItemSelection(menu, selected, key))
1487 result = selected ? false : true;
1490 keyboardFocus = false;
1497 PopupMenu master = (PopupMenu)this.master;
1498 if(eClass_IsDerived(master._class, _class) && master.isMenuBar)
1500 ItemPtr selected = master.selected;
1502 master.pressed = true;
1503 master.selected = selected;
1504 master.keyboardFocus = true;
1517 if(ch >= 32 && !isMenuBar)
1520 for(ptr = menu.items.first; ptr; ptr = ptr.next)
1522 MenuItem item = ptr.item;
1523 if((item.placement && !ptr.inserted) || ITEM_DISABLED(item)) continue;
1524 if(ITEM_HOTKEY(item) == key || (ITEM_HOTKEY(item) | Key { alt = true }) == key)
1526 if(!item.isDivider && !item.subMenu)
1528 if(MenuItemSelection(menu, ptr, key))
1531 else if(item.subMenu)
1534 MenuPopupSelected();
1541 if(result && isMenuBar && pressed)
1544 // Interim menus: the slaves are the ones with the focus
1545 if(result && INTERIM_MENU)
1547 Window master = this.master;
1548 if(eClass_IsDerived(master._class, _class))
1550 if(!master.OnKeyHit(key, ch))
1558 bool OnLoadGraphics()
1560 Font font = fontObject;
1561 int maxW = 0, maxH = 0;
1566 background = isMenuBar ? menuBarColor : (menu ? menu.color : popupMenuColor);
1567 FontExtent = Display::FontExtent;
1572 // Default width & height for merging menus into menu bars
1575 FontExtent(display, font, "W",1, &maxW, &maxH);
1576 if(!guiApp.textMode)
1580 for(ptr = menu.items.first; ptr; ptr = ptr.next)
1582 MenuItem item = ptr.item;
1583 if(item.placement) continue; //&& !ptr.inserted) continue;
1590 FontExtent(display, font,ITEM_TEXT(item),strlen(ITEM_TEXT(item)),&width, &height);
1591 if(strstr(ITEM_TEXT(item), "\t"))
1594 if(item.subMenu) width += 20;
1595 if(!guiApp.textMode)
1600 if(!guiApp.textMode)
1601 height = DIVIDER_HEIGHT;
1606 if(width > maxW) maxW = width;
1607 if(height > maxH) maxH = height;
1609 totalHeight += height;
1611 if(item.bitmaps[0]) AddResource(item.bitmaps[0]);
1612 if(item.bitmaps[1]) AddResource(item.bitmaps[1]);
1613 if(item.bitmaps[2]) AddResource(item.bitmaps[2]);
1615 maxW += menu.hasMargin ? 32 : 24;
1618 FontExtent(display, font,menu.text,strlen(menu.text),&width, &height);
1619 if(width > maxW) maxW = width;
1620 if(height > maxH) maxH = height;
1626 if(rw < maxW) rw = maxW;
1627 if(rh < maxH) rh = maxH;
1632 bool OnResizing(int * w, int * h)
1634 Window master = this.master;
1635 Window masterMenuBar = master.menuBar;
1636 if(this != masterMenuBar)
1640 *h = Max(*h, rh + 2);
1642 *h = Max(*h, totalHeight + 2);
1644 if(this != masterMenuBar)
1654 bool OnMoving(int *x, int *y, int w, int h)
1658 Window parent = this.parent;
1661 if(*y + h > parent.clientSize.h)
1663 PopupMenu master = (PopupMenu)this.master;
1665 if(eClass_IsDerived(master._class, _class))
1667 if(master.isMenuBar)
1668 *y -= master.size.h;
1670 *y += h - clientSize.h + rh;
1674 *x = Min(*x, ((parent == guiApp.desktop && guiApp.virtualScreen.w) ? guiApp.virtualScreen.w : parent.clientSize.w) - w);
1678 *x = Min(*x, parent.size.w - w);
1679 *y = Min(*y, parent.size.h - h);
1683 *x = Min(*x, parent.clientSize.w - w);
1684 *y = Min(*y, parent.clientSize.h - h);
1686 if(parent == guiApp.desktop)
1689 *x = Max(*x, guiApp.virtualScreenPos.x);
1690 *y = Max(*y, guiApp.virtualScreenPos.y);
1702 bool OnMouseMove(int mx, int my, Modifiers mods)
1704 int selectedX, selectedY;
1706 ItemPtr selected = FindSelected(mx, my, &selectedX, &selectedY);
1708 if((!mods.isSideEffect || !this.selected) && (/*selected && */
1709 selected != this.selected && (!selected || !ITEM_DISABLED(selected.item)) && (selected || !keyboardFocus)))
1711 if(!isMenuBar || pressed)
1713 bool pressed = this.pressed;
1715 if(firstSlave) firstSlave.Destroy(0);
1717 this.selected = selected;
1721 Menu childMenu = selected.item.subMenu;
1723 this.pressed = pressed;
1725 if(this.selected.item.subMenu)
1726 MenuPopupChild(selectedX, position.y + selectedY, childMenu);
1729 keyboardFocus = true;
1733 this.selected = selected;
1739 bool OnLeftButtonDown(int x, int y, Modifiers mods)
1743 Time t = GetTime(), u = unpressedTime;
1744 // Had to boost this to 0.1 for Windows Basic / XP theme / Remote Desktop
1745 // Aero & Classic were fast enough for 0.01
1746 if(GetTime() - unpressedTime < 0.1)
1750 int selectedX, selectedY;
1751 if(!mods.isActivate && !pressed)
1754 keyboardFocus = true;
1756 OnMouseMove(x,y, mods);
1760 if(firstSlave) firstSlave.Destroy(0);
1761 selected = FindSelected(x, y, &selectedX, &selectedY);
1765 //keyboardFocus = false;
1774 keyboardFocus = false;
1775 if(firstSlave) firstSlave.Destroy(0);
1781 keyboardFocus = true;
1783 OnMouseMove(x, y, mods);
1787 else //if(!INTERIM_MENU) // Why was this commented out?
1789 if(!(x >= 0 && y >= 0 && x < clientSize.w && y < clientSize.h))
1791 MenuDestroyMasters(false);
1798 bool OnRightButtonDown(int x, int y, Modifiers mods)
1800 if(x >= 0 && y >= 0 && x < clientSize.w && y < clientSize.h)
1804 Window master = this.master;
1805 Window activeClient = master.activeClient;
1806 if(activeClient.state == maximized)
1807 activeClient.ShowSysMenu(absPosition.x + x, absPosition.y + y);
1813 bool OnLeftButtonUp(int mx, int my, Modifiers mods)
1815 if(mx >= 2 /*0*/ && my >= 0 && mx < clientSize.w && my < clientSize.h)
1817 Menu menu = this.menu;
1821 if(!isMenuBar && menu)
1824 for(ptr = menu.items.first; ptr; ptr = ptr.next)
1826 MenuItem item = ptr.item;
1827 if(item.placement) continue; //&& !ptr.inserted) continue;
1828 if(my >= y && my < y + rh && !item.isDivider)
1836 y += (item.isDivider) ? DIVIDER_HEIGHT : rh;
1840 if(!isMenuBar || pressed)
1844 if(!selected.item.isDivider && !selected.item.subMenu)
1849 keyboardFocus = false;
1852 if(MenuItemSelection(menu, selected, (Key)mods))
1859 keyboardFocus = false;
1860 if(firstSlave) firstSlave.Destroy(0);
1867 OnRightButtonUp = OnLeftButtonUp;
1869 bool OnLeftDoubleClick(int x, int y, Modifiers mods)
1873 int selectedX, selectedY;
1874 ItemPtr selected = FindSelected(x, y, &selectedX, &selectedY);
1877 Window master = this.master;
1878 Window activeClient = master.activeClient;
1879 // TOFIX: Fix need for a cast here...
1880 while(activeClient && !((BorderBits)activeClient.borderStyle).fixed)
1881 activeClient = activeClient.activeClient;
1882 if(activeClient && activeClient.state == maximized)
1883 activeClient.SetState(normal, false, mods);
1889 bool OnMouseLeave(Modifiers mods)
1891 if(!pressed && !firstSlave)
1900 bool OnActivate(bool active, Window swap, bool * goOnWithActivation, bool direct)
1908 PopupMenu master = (PopupMenu)this.master;
1909 if(eClass_IsDerived(master._class, _class) && master.isMenuBar) // && swap != master && swap && swap.master != master)
1912 master.unpressedTime = GetTime();
1913 master.pressed = false;
1914 master.selected = null;
1915 master.keyboardFocus = false;
1916 // master.Update(null);
1918 // TOFIX: Redraw bug here without this...
1919 master.master.Update(null);
1927 bool destroy = true;
1931 for(master = swap.master; master; master = master.master)
1940 for(master = this.master; master; master = master.master)
1943 if(eClass_IsDerived(master._class, _class))
1950 if(MenuDestroyMasters(false))
1957 // With new activation code this is not required anymore (double effect if there)
1963 unpressedTime = GetTime();
1966 keyboardFocus = false;
1967 if(firstSlave) firstSlave.Destroy(0);
1987 if(firstSlave) firstSlave.Destroy(0);
1989 Move(position.x, position.y, size.w, size.h);
1993 if(interim || isMenuBar)
2001 property bool isMenuBar { set { isMenuBar = value; } };
2002 property bool focus { get { return keyboardFocus; } };