1
2
3
4
5
6 using System;
7 using System.IO;
8
9 namespace System.Screen
10 {
11 public class MenuControlCreateParams
12 {
13 public nothrow MenuControlCreateParams() :
14 controlCreateParams(),
15 highlightColor(ConsoleColor.defaultColor),
16 disabledColor(ConsoleColor.defaultColor),
17 focusedItemForeColor(ConsoleColor.defaultColor),
18 focusedItemBackColor(ConsoleColor.defaultColor),
19 gap(-1)
20 {
21 }
22 public nothrow MenuControlCreateParams& Defaults()
23 {
24 return *this;
25 }
26 public nothrow MenuControlCreateParams& HighlightColor(ConsoleColor highlightColor_)
27 {
28 highlightColor = highlightColor_;
29 return *this;
30 }
31 public nothrow MenuControlCreateParams& DisabledColor(ConsoleColor disabledColor_)
32 {
33 disabledColor = disabledColor_;
34 return *this;
35 }
36 public nothrow MenuControlCreateParams& FocusedItemForeColor(ConsoleColor focusedItemForeColor_)
37 {
38 focusedItemForeColor = focusedItemForeColor_;
39 return *this;
40 }
41 public nothrow MenuControlCreateParams& FocusedItemBackColor(ConsoleColor focusedItemBackColor_)
42 {
43 focusedItemBackColor = focusedItemBackColor_;
44 return *this;
45 }
46 public ControlCreateParams controlCreateParams;
47 public ConsoleColor highlightColor;
48 public ConsoleColor disabledColor;
49 public ConsoleColor focusedItemForeColor;
50 public ConsoleColor focusedItemBackColor;
51 public int gap;
52 }
53
54 public enum MenuControlFlags
55 {
56 none = 0, changed = 1 << 0, menuOpen = 1 << 1
57 }
58
59 public class MenuControl : Control
60 {
61 public nothrow MenuControl(MenuControlCreateParams& createParams) : base(createParams.controlCreateParams), flags(MenuControlFlags.none)
62 {
63 if (createParams.highlightColor == ConsoleColor.defaultColor)
64 {
65 highlightColor = ConsoleColor.red;
66 }
67 else
68 {
69 highlightColor = createParams.highlightColor;
70 }
71 if (createParams.disabledColor == ConsoleColor.defaultColor)
72 {
73 disabledColor = ConsoleColor.darkGray;
74 }
75 else
76 {
77 disabledColor = createParams.disabledColor;
78 }
79 if (createParams.focusedItemForeColor == ConsoleColor.defaultColor)
80 {
81 focusedItemForeColor = cast<ConsoleColor>(defaultFocusedControlForeColor);
82 }
83 else
84 {
85 focusedItemForeColor = createParams.focusedItemForeColor;
86 }
87 if (createParams.focusedItemBackColor == ConsoleColor.defaultColor)
88 {
89 focusedItemBackColor = cast<ConsoleColor>(defaultFocusedControlBackColor);
90 }
91 else
92 {
93 focusedItemBackColor = createParams.focusedItemBackColor;
94 }
95 if (createParams.gap == -1)
96 {
97 gap = 8;
98 }
99 else
100 {
101 gap = createParams.gap;
102 }
103 }
104 public nothrow void SetChanged()
105 {
106 flags = cast<MenuControlFlags>(flags | MenuControlFlags.changed);
107 }
108 public nothrow bool IsChanged() const
109 {
110 return (flags & MenuControlFlags.changed) != MenuControlFlags.none;
111 }
112 public nothrow void ResetChanged()
113 {
114 flags = cast<MenuControlFlags>(flags & ~MenuControlFlags.changed);
115 }
116 public nothrow void SetOpen()
117 {
118 if (!IsOpen())
119 {
120 flags = cast<MenuControlFlags>(flags | MenuControlFlags.menuOpen);
121 Invalidate();
122 }
123 }
124 public virtual nothrow void ResetOpen()
125 {
126 if (IsOpen())
127 {
128 focusedMenuItem = null;
129 flags = cast<MenuControlFlags>(flags & ~MenuControlFlags.menuOpen);
130 Application.Instance().MainWindow()->Invalidate();
131 }
132 }
133 public nothrow bool IsOpen() const
134 {
135 return (flags & MenuControlFlags.menuOpen) != MenuControlFlags.none;
136 }
137 public nothrow ConsoleColor HighlightColor() const
138 {
139 return highlightColor;
140 }
141 public nothrow ConsoleColor DisabledColor() const
142 {
143 return disabledColor;
144 }
145 public nothrow ConsoleColor FocusedItemForeColor() const
146 {
147 return focusedItemForeColor;
148 }
149 public nothrow ConsoleColor FocusedItemBackColor() const
150 {
151 return focusedItemBackColor;
152 }
153 public nothrow int Gap() const
154 {
155 return gap;
156 }
157 public nothrow MenuItem* FocusedMenuItem() const
158 {
159 return focusedMenuItem;
160 }
161 public nothrow void SetFocusedMenuItem(MenuItem* focusedMenuItem_)
162 {
163 focusedMenuItem = focusedMenuItem_;
164 }
165 public override void OnGotFocus()
166 {
167 focusedMenuItem = null;
168 }
169 public override void OnLostFocus()
170 {
171 focusedMenuItem = null;
172 }
173 private MenuControlFlags flags;
174 private ConsoleColor highlightColor;
175 private ConsoleColor disabledColor;
176 private ConsoleColor focusedItemForeColor;
177 private ConsoleColor focusedItemBackColor;
178 private int gap;
179 private MenuItem* focusedMenuItem;
180 }
181
182 public class MenuBar : MenuControl
183 {
184 public nothrow MenuBar(MenuControlCreateParams& createParams) : base(createParams), menuItems(this)
185 {
186 InvalidateGuard guard(this, InvalidateKind.dontInvalidate);
187 if (Location().IsDefault())
188 {
189 SetLocation(Point(0, 0));
190 }
191 if (GetSize().IsDefault())
192 {
193 SetSize(Size(TerminalWindowWidth(), 1));
194 }
195 if (ForeColor() == ConsoleColor.defaultColor)
196 {
197 SetForeColor(ConsoleColor.black);
198 }
199 if (BackColor() == ConsoleColor.defaultColor)
200 {
201 SetBackColor(ConsoleColor.gray);
202 }
203 SetChanged();
204 }
205 public void AddMenuItem(MenuItem* menuItem)
206 {
207 menuItems.AddChild(menuItem);
208 menuItem->SetAccessKey();
209 }
210 public override nothrow void ResetOpen()
211 {
212 base->ResetOpen();
213 Component* child = menuItems.FirstChild();
214 while (child != null)
215 {
216 if (child is MenuItem*)
217 {
218 MenuItem* menuItem = cast<MenuItem*>(child);
219 menuItem->SetState(MenuItemState.closed);
220 }
221 child = child->NextSibling();
222 }
223 }
224 public override void OnKeyPressed(KeyEventArgs& args)
225 {
226 if (args.Handled()) return;
227 if (IsOpen() && args.Key() == keyEscape)
228 {
229 ResetOpen();
230 SetFocusedMenuItem(null);
231 args.SetHandled();
232 return;
233 }
234 MenuItem* focusedMenuItem = FocusedMenuItem();
235 if (focusedMenuItem != null)
236 {
237 focusedMenuItem->OnKeyPressed(args);
238 if (args.Handled())
239 {
240 return;
241 }
242 }
243 uchar key = args.Key();
244 if (key >= 'a' && key <= 'z')
245 {
246 key = ToUpper(key);
247 }
248 Component* child = menuItems.FirstChild();
249 while (child != null)
250 {
251 if (child is MenuItem*)
252 {
253 MenuItem* menuItem = cast<MenuItem*>(child);
254 if (menuItem->IsEnabled())
255 {
256 if (menuItem->AccessKey() == key)
257 {
258 args.SetHandled();
259 ResetOpen();
260 SetOpen();
261 menuItem->SetState(MenuItemState.open);
262 SetFocusedMenuItem(menuItem->FirstChildItem());
263 Invalidate();
264 return;
265 }
266 else
267 {
268 if (menuItem->DispatchKey(key))
269 {
270 args.SetHandled();
271 return;
272 }
273 }
274 }
275 }
276 child = child->NextSibling();
277 }
278 }
279 public override void OnWriteScreen(WriteScreenEventArgs& args)
280 {
281 base->OnWriteScreen(args);
282 Rect rect = args.GetRect();
283 if (rect.IsDefault())
284 {
285 rect = GetRect();
286 }
287 Clear(rect, ForeColor(), BackColor());
288 if (IsChanged())
289 {
290 Measure();
291 }
292 WriteMenuItems();
293 MenuItem* focusedMenuItem = FocusedMenuItem();
294 if (focusedMenuItem != null)
295 {
296 Point location = focusedMenuItem->Location();
297 SetCursorPos(location.x + 1, location.y);
298 }
299 }
300 private void Measure()
301 {
302 Point loc = Location();
303 loc.x = loc.x + 1;
304 Component* child = menuItems.FirstChild();
305 while (child != null)
306 {
307 if (child is MenuItem*)
308 {
309 MenuItem* menuItem = cast<MenuItem*>(child);
310 menuItem->Measure(loc);
311 }
312 child = child->NextSibling();
313 }
314 }
315 private void WriteMenuItems()
316 {
317 Component* child = menuItems.FirstChild();
318 while (child != null)
319 {
320 if (child is MenuItem*)
321 {
322 MenuItem* menuItem = cast<MenuItem*>(child);
323 menuItem->WriteScreen();
324 }
325 child = child->NextSibling();
326 }
327 }
328 private Container menuItems;
329 }
330
331 public enum MenuItemState
332 {
333 closed = 0, open = 1
334 }
335
336 public enum MenuItemFlags
337 {
338 none = 0, disabled = 1 << 0
339 }
340
341 public class MenuItem : Component
342 {
343 public nothrow MenuItem(const string& text_, uchar shortcut_) : state(MenuItemState.closed), flags(MenuItemFlags.none), text(ToUtf32(text_)), items(this), accessKey(), shortcut(shortcut_)
344 {
345 }
346 public nothrow MenuItem(const string& text_) : this(text_, uchar())
347 {
348 }
349 public nothrow void SetText(const string& text_)
350 {
351 text = ToUtf32(text_);
352 SetAccessKey();
353 }
354 public nothrow void SetDisabled()
355 {
356 flags = cast<MenuItemFlags>(flags | MenuItemFlags.disabled);
357 }
358 public nothrow void SetEnabled()
359 {
360 flags = cast<MenuItemFlags>(flags & ~MenuItemFlags.disabled);
361 }
362 public nothrow bool IsDisabled() const
363 {
364 return (flags & MenuItemFlags.disabled) != MenuItemFlags.none;
365 }
366 public nothrow bool IsEnabled() const
367 {
368 return (flags & MenuItemFlags.disabled) == MenuItemFlags.none;
369 }
370 public nothrow void SetState(MenuItemState state_)
371 {
372 if (state != state_)
373 {
374 state = state_;
375 }
376 }
377 public nothrow bool IsTopLevel() const
378 {
379 return ParentMenuItem() == null;
380 }
381 public nothrow bool IsFocused() const
382 {
383 MenuControl* menuControl = GetMenuControl();
384 if (menuControl != null)
385 {
386 return menuControl->FocusedMenuItem() == this;
387 }
388 else
389 {
390 return false;
391 }
392 }
393 public nothrow MenuItem* FirstChildItem() const
394 {
395 Component* child = items.FirstChild();
396 while (child != null)
397 {
398 if (child is MenuItem*)
399 {
400 return cast<MenuItem*>(child);
401 }
402 child = child->NextSibling();
403 }
404 return null;
405 }
406 public nothrow MenuItem* LastChildItem() const
407 {
408 Component* child = items.LastChild();
409 while (child != null)
410 {
411 if (child is MenuItem*)
412 {
413 return cast<MenuItem*>(child);
414 }
415 child = child->PrevSibling();
416 }
417 return null;
418 }
419 public nothrow MenuItem* NextMenuItem() const
420 {
421 Component* nextSibling = NextSibling();
422 while (nextSibling != null)
423 {
424 if (nextSibling is MenuItem*)
425 {
426 return cast<MenuItem*>(nextSibling);
427 }
428 nextSibling = nextSibling->NextSibling();
429 }
430 return null;
431 }
432 public nothrow MenuItem* PrevMenuItem() const
433 {
434 Component* prevSibling = PrevSibling();
435 while (prevSibling != null)
436 {
437 if (prevSibling is MenuItem*)
438 {
439 return cast<MenuItem*>(prevSibling);
440 }
441 prevSibling = prevSibling->PrevSibling();
442 }
443 return null;
444 }
445 public nothrow bool HasChildItems() const
446 {
447 return !items.IsEmpty();
448 }
449 public void AddMenuItem(MenuItem* menuItem)
450 {
451 items.AddChild(menuItem);
452 menuItem->SetAccessKey();
453 }
454 public nothrow MenuItem* ParentMenuItem()
455 {
456 Container* container = GetContainer();
457 if (container != null)
458 {
459 Component* parent = container->Parent();
460 if (parent != null)
461 {
462 if (parent is MenuItem*)
463 {
464 return cast<MenuItem*>(parent);
465 }
466 }
467 }
468 return null;
469 }
470 public MenuControl* GetMenuControl()
471 {
472 Container* container = GetContainer();
473 if (container != null)
474 {
475 Component* parent = container->Parent();
476 if (parent is MenuControl*)
477 {
478 return cast<MenuControl*>(parent);
479 }
480 else if (parent is MenuItem*)
481 {
482 MenuItem* parentMenuItem = cast<MenuItem*>(parent);
483 return parentMenuItem->GetMenuControl();
484 }
485 }
486 else
487 {
488 throw Exception("menu control not found");
489 }
490 }
491 public nothrow const Rect& MenuBoxRect() const
492 {
493 return menuBoxRect;
494 }
495 public nothrow const Point& Location() const
496 {
497 return location;
498 }
499 public void OnKeyPressed(KeyEventArgs& args)
500 {
501 MenuControl* menuControl = GetMenuControl();
502 if (IsFocused())
503 {
504 switch (args.Key())
505 {
506 case keyNewline:
507 {
508 args.SetHandled();
509 Select();
510 menuControl->SetFocusedMenuItem(null);
511 MenuItem* parentItem = ParentMenuItem();
512 if (parentItem != null)
513 {
514 Application.Instance().MainWindow()->Invalidate(parentItem->MenuBoxRect());
515 }
516 break;
517 }
518 case keyUp:
519 {
520 MenuItem* prevMenuItem = PrevMenuItem();
521 if (prevMenuItem != null)
522 {
523 menuControl->SetFocusedMenuItem(prevMenuItem);
524 args.SetHandled();
525 menuControl->Invalidate();
526 }
527 break;
528 }
529 case keyDown:
530 {
531 MenuItem* nextMenuItem = NextMenuItem();
532 if (nextMenuItem != null)
533 {
534 menuControl->SetFocusedMenuItem(nextMenuItem);
535 args.SetHandled();
536 menuControl->Invalidate();
537 }
538 break;
539 }
540 }
541 }
542 }
543 public bool DispatchKey(uchar key)
544 {
545 Component* child = items.FirstChild();
546 while (child != null)
547 {
548 if (child is MenuItem*)
549 {
550 MenuItem* menuItem = cast<MenuItem*>(child);
551 if (menuItem->IsEnabled())
552 {
553 if (state == MenuItemState.open)
554 {
555 if (menuItem->AccessKey() == ToUpper(key))
556 {
557 if (menuItem->HasChildItems())
558 {
559 menuItem->SetState(MenuItemState.open);
560 MenuControl* menuControl = GetMenuControl();
561 menuControl->Invalidate();
562 return true;
563 }
564 else
565 {
566 menuItem->Select();
567 Application.Instance().MainWindow()->Invalidate(menuBoxRect);
568 return true;
569 }
570 }
571 else if (menuItem->Shortcut() == key)
572 {
573 menuItem->Select();
574 Application.Instance().MainWindow()->Invalidate(menuBoxRect);
575 return true;
576 }
577 }
578 else if (menuItem->Shortcut() == key)
579 {
580 menuItem->Select();
581 Application.Instance().MainWindow()->Invalidate(menuBoxRect);
582 return true;
583 }
584 else
585 {
586 bool handled = menuItem->DispatchKey(key);
587 if (handled)
588 {
589 return true;
590 }
591 }
592 }
593 }
594 child = child->NextSibling();
595 }
596 return false;
597 }
598 public nothrow Event<SelectEventHandler>& SelectEvent()
599 {
600 return selectEvent;
601 }
602 public void Select()
603 {
604 MenuControl* menuControl = GetMenuControl();
605 menuControl->ResetOpen();
606 MenuItem* parent = ParentMenuItem();
607 if (parent != null)
608 {
609 parent->SetState(MenuItemState.closed);
610 }
611 selectEvent.Fire();
612 }
613 public nothrow void Measure(Point& loc)
614 {
615 if (IsTopLevel())
616 {
617 MeasureTopLevel(loc);
618 }
619 else
620 {
621 MeasureChild(loc);
622 }
623 }
624 public nothrow int TextLength() const
625 {
626 int ampPos = cast<int>(text.Find('&'));
627 if (ampPos != -1)
628 {
629 int n = cast<int>(text.Length() - 1);
630 return n;
631 }
632 else
633 {
634 return cast<int>(text.Length());
635 }
636 }
637 public nothrow ustring ShortcutText() const
638 {
639 if (shortcut != '\0')
640 {
641 ustring shortcutText = ToUtf32(KeyName(shortcut));
642 return shortcutText;
643 }
644 else
645 {
646 return ustring();
647 }
648 }
649 public nothrow uchar AccessKey() const
650 {
651 return accessKey;
652 }
653 public nothrow void SetAccessKey()
654 {
655 int ampPos = cast<int>(text.Find('&'));
656 if (ampPos != -1 && ampPos < text.Length() - 1)
657 {
658 if (IsTopLevel())
659 {
660 accessKey = cast<uchar>(cast<int>(keyAltA) + (cast<int>(ToUpper(text[ampPos + 1])) - cast<int>('A')));
661 }
662 else
663 {
664 accessKey = ToUpper(text[ampPos + 1]);
665 }
666 }
667 else
668 {
669 accessKey = '\0';
670 }
671 }
672 public nothrow uchar Shortcut() const
673 {
674 return shortcut;
675 }
676 public void WriteScreen()
677 {
678 if (IsTopLevel())
679 {
680 WriteTopLevel();
681 }
682 else
683 {
684 WriteChild();
685 }
686 }
687 public nothrow Rect ItemRect() const
688 {
689 MenuItem* parent = ParentMenuItem();
690 Rect parentMenuBox = parent->MenuBoxRect();
691 Size sz(parentMenuBox.size.w - 2, 1);
692 return Rect(location, sz);
693 }
694 private void MeasureTopLevel(Point& loc)
695 {
696 location = loc;
697 menuBoxRect.location.x = location.x - 1;
698 menuBoxRect.location.y = location.y + 1;
699 MeasureSize();
700 loc.x = loc.x + TextLength() + 2;
701 MeasureChildren();
702 }
703 private void MeasureChildren()
704 {
705 Point loc(location.x, location.y + 2);
706 Component* child = items.FirstChild();
707 while (child != null)
708 {
709 if (child is MenuItem*)
710 {
711 MenuItem* menuItem = cast<MenuItem*>(child);
712 menuItem->MeasureChild(loc);
713 }
714 child = child->NextSibling();
715 }
716 }
717 private void MeasureSize()
718 {
719 MenuControl* menuControl = GetMenuControl();
720 int numChildren = 0;
721 int maxTextLength = 0;
722 int maxShortcutLength = 0;
723 Component* child = items.FirstChild();
724 while (child != null)
725 {
726 if (child is MenuItem*)
727 {
728 MenuItem* menuItem = cast<MenuItem*>(child);
729 int textLength = menuItem->TextLength();
730 if (textLength > maxTextLength)
731 {
732 maxTextLength = textLength;
733 }
734 int shortcutTextLength = cast<int>(menuItem->ShortcutText().Length());
735 if (shortcutTextLength > maxShortcutLength)
736 {
737 maxShortcutLength = shortcutTextLength;
738 }
739 ++numChildren;
740 }
741 child = child->NextSibling();
742 }
743 menuBoxRect.size = Size(maxTextLength + menuControl->Gap() + maxShortcutLength + 2, numChildren + 2);
744 }
745 private void MeasureChild(Point& loc)
746 {
747 location = loc;
748 loc.y = loc.y + 1;
749 }
750 private void WriteTopLevel()
751 {
752 WriteText();
753 if (state == MenuItemState.open)
754 {
755 WriteMenuBox();
756 }
757 }
758 private void WriteChild()
759 {
760 if (IsFocused())
761 {
762 WriteFocused();
763 }
764 else
765 {
766 WriteText();
767 MenuItem* parent = ParentMenuItem();
768 Rect parentMenuBox = parent->MenuBoxRect();
769 ustring shortcutText = ShortcutText();
770 int x = cast<int>(parentMenuBox.Right() - 1 - shortcutText.Length());
771 int y = location.y;
772 SetCursorPos(x, y);
773 Terminal.Out() << shortcutText;
774 if (state == MenuItemState.open)
775 {
776 WriteMenuBox();
777 }
778 }
779 }
780 private void WriteFocused()
781 {
782 MenuControl* menuControl = GetMenuControl();
783 Clear(ItemRect(), menuControl->FocusedItemForeColor(), menuControl->FocusedItemBackColor());
784 Terminal.Out() << SetColors(menuControl->FocusedItemForeColor(), menuControl->FocusedItemBackColor());
785 WriteText();
786 MenuItem* parent = ParentMenuItem();
787 Rect parentMenuBox = parent->MenuBoxRect();
788 ustring shortcutText = ShortcutText();
789 int x = cast<int>(parentMenuBox.Right() - 1 - shortcutText.Length());
790 int y = location.y;
791 SetCursorPos(x, y);
792 ConsoleColor textColor = menuControl->ForeColor();
793 if (IsDisabled())
794 {
795 textColor = menuControl->DisabledColor();
796 }
797 Terminal.Out() << SetColors(menuControl->FocusedItemForeColor(), menuControl->FocusedItemBackColor());
798 Terminal.Out() << shortcutText;
799 }
800 private void WriteText()
801 {
802 MenuControl* menuControl = GetMenuControl();
803 ConsoleColor textColor = menuControl->ForeColor();
804 if (IsDisabled())
805 {
806 textColor = menuControl->DisabledColor();
807 }
808 SetCursorPos(location.x + 1, location.y);
809 int ampPos = cast<int>(text.Find('&'));
810 bool isFocused = IsFocused();
811 if (ampPos != -1)
812 {
813 ustring prefix = text.Substring(0, ampPos);
814 if (!isFocused)
815 {
816 Terminal.Out() << SetColors(textColor, menuControl->BackColor());
817 }
818 else
819 {
820 Terminal.Out() << SetColors(menuControl->FocusedItemForeColor(), menuControl->FocusedItemBackColor());
821 }
822 Terminal.Out() << prefix;
823 ustring highlightStr = text.Substring(ampPos + 1, 1);
824 if (!isFocused)
825 {
826 Terminal.Out() << SetColors(menuControl->HighlightColor(), menuControl->BackColor());
827 }
828 Terminal.Out() << highlightStr;
829 if (!isFocused)
830 {
831 Terminal.Out() << SetColors(textColor, menuControl->BackColor());
832 }
833 ustring suffix = text.Substring(ampPos + 2);
834 Terminal.Out() << suffix;
835 }
836 else
837 {
838 Terminal.Out() << text;
839 }
840 }
841 private void WriteMenuBox()
842 {
843 MenuControl* menuControl = GetMenuControl();
844 WriteBox(menuBoxRect, menuControl->ForeColor(), menuControl->BackColor());
845 Component* child = items.FirstChild();
846 while (child != null)
847 {
848 if (child is MenuItem*)
849 {
850 MenuItem* menuItem = cast<MenuItem*>(child);
851 menuItem->WriteScreen();
852 }
853 child = child->NextSibling();
854 }
855 }
856 private MenuItemState state;
857 private MenuItemFlags flags;
858 private Point location;
859 private Rect menuBoxRect;
860 private ustring text;
861 private Container items;
862 private uchar accessKey;
863 private uchar shortcut;
864 private Event<SelectEventHandler> selectEvent;
865 }
866
867 public abstract class SelectAction
868 {
869 public SelectAction(MenuItem* menuItem)
870 {
871 menuItem->SelectEvent().AddHandler(Select);
872 }
873 public virtual default ~SelectAction();
874 public abstract void Execute();
875 private void Select()
876 {
877 Execute();
878 }
879 }
880 }