1 #include "MenuImp.h"
2 
3 #ifdef PLATFORM_WIN32
4 #include <mmsystem.h>
5 #endif
6 
7 namespace Upp {
8 
9 #define LLOG(x)    // DLOG(x)
10 #define LTIMING(x) // RTIMING(x)
11 
MenuItemBase()12 MenuItemBase::MenuItemBase()
13 {
14 	accel = 0;
15 	state = 0;
16 	isenabled = true;
17 	type = 0;
18 	font = StdFont();
19 	leftgap = DPI(16) + Zx(6);
20 	textgap = Zy(6);
21 	accesskey = 0;
22 	NoWantFocus();
23 	style = &MenuBar::StyleDefault();
24 	Transparent();
25 	maxiconsize = Size(INT_MAX, INT_MAX);
26 }
27 
GetMenuBar() const28 MenuBar *MenuItemBase::GetMenuBar() const
29 {
30 	Ctrl *q = GetParent();
31 	while(q) {
32 		MenuBar *bar = dynamic_cast<MenuBar *>(q);
33 		if(bar)
34 			return bar;
35 		q = q->GetParent();
36 	}
37 	return NULL;
38 }
39 
InOpaqueBar() const40 bool MenuItemBase::InOpaqueBar() const
41 {
42 	const MenuBar *bar = GetMenuBar();
43 	return !(bar && bar->IsTransparent());
44 }
45 
Text(const char * s)46 Bar::Item& MenuItemBase::Text(const char *s)
47 {
48 	accesskey = ExtractAccessKey(s, text);
49 	Refresh();
50 	return *this;
51 }
52 
Key(dword key)53 Bar::Item& MenuItemBase::Key(dword key)
54 {
55 	if(key) {
56 		accel = key;
57 		Refresh();
58 	}
59 	return *this;
60 }
61 
Image(const UPP::Image & img)62 Bar::Item& MenuItemBase::Image(const UPP::Image& img)
63 {
64 	return *this;
65 }
66 
Check(bool check)67 Bar::Item& MenuItemBase::Check(bool check)
68 {
69 	type = CHECK0 + check;
70 	return *this;
71 }
72 
Radio(bool check)73 Bar::Item& MenuItemBase::Radio(bool check)
74 {
75 	type = RADIO0 + check;
76 	return *this;
77 }
78 
Bold(bool bold)79 Bar::Item& MenuItemBase::Bold(bool bold)
80 {
81 	font.Bold(bold);
82 	return *this;
83 }
84 
Tip(const char * s)85 Bar::Item& MenuItemBase::Tip(const char *s)
86 {
87 	return *this;
88 }
89 
Help(const char * s)90 Bar::Item& MenuItemBase::Help(const char *s)
91 {
92 	HelpLine(s);
93 	return *this;
94 }
95 
Description(const char * s)96 Bar::Item& MenuItemBase::Description(const char *s)
97 {
98 	Ctrl::Description(s);
99 	return *this;
100 }
101 
Topic(const char * s)102 Bar::Item& MenuItemBase::Topic(const char *s)
103 {
104 	HelpTopic(s);
105 	return *this;
106 }
107 
Enable(bool en)108 Bar::Item& MenuItemBase::Enable(bool en)
109 {
110 	isenabled = en;
111 	Refresh();
112 	return *this;
113 }
114 
GetDesc() const115 String MenuItemBase::GetDesc() const
116 {
117 	return text;
118 }
119 
GetAccessKeys() const120 dword  MenuItemBase::GetAccessKeys() const
121 {
122 	return AccessKeyBit(accesskey);
123 }
124 
AssignAccessKeys(dword used)125 void   MenuItemBase::AssignAccessKeys(dword used)
126 {
127 	if(!accesskey) {
128 		accesskey = ChooseAccessKey(text, used);
129 		used |= AccessKeyBit(accesskey);
130 	}
131 	Ctrl::AssignAccessKeys(used);
132 }
133 
DrawMnemonicText(Draw & w,int x,int y,const String & s,Font font,Color color,int mnemonic,bool menumark)134 void DrawMnemonicText(Draw& w, int x, int y, const String& s, Font font, Color color,
135                       int mnemonic, bool menumark)
136 {
137 	int apos = HIWORD(mnemonic);
138 	int q;
139 	if(apos && apos < s.GetLength())
140 		q = apos - 1;
141 	else {
142 		q = s.Find(ToUpper(mnemonic));
143 		if(q < 0)
144 			q = s.Find(ToLower(mnemonic));
145 	}
146 	w.DrawText(x, y, s, font, color);
147 	if(q < 0) return;
148 	FontInfo f = font.Info();
149 	w.DrawRect(x + GetTextSize(~s, font, q).cx, y + f.GetAscent() + 1, f[s[q]], 1,
150 	           menumark ? SColorMenuMark() : SColorMark());
151 }
152 
DrawMenuText(Draw & w,int x,int y,const String & s,Font f,bool enabled,bool hl,int mnemonic,Color color,Color hlcolor,bool menumark)153 void DrawMenuText(Draw& w, int x, int y, const String& s, Font f, bool enabled,
154                   bool hl, int mnemonic, Color color, Color hlcolor, bool menumark)
155 {
156 	if(enabled)
157 		DrawMnemonicText(w, x, y, s, f, hl ? hlcolor : color, mnemonic, menumark);
158 	else {
159 		if(GUI_GlobalStyle() >= GUISTYLE_XP)
160 			DrawMnemonicText(w, x, y, s, f, SColorDisabled, 0, menumark);
161 		else {
162 			DrawMnemonicText(w, x + 1, y + 1, s, f, SColorPaper, 0, menumark);
163 			DrawMnemonicText(w, x, y, s, f, SColorDisabled, 0, menumark);
164 		}
165 	}
166 }
167 
DrawMenuText(Draw & w,int x,int y,const String & s,Font f,bool enabled,bool hl,Color color,Color hlcolor)168 void MenuItemBase::DrawMenuText(Draw& w, int x, int y, const String& s, Font f, bool enabled,
169                                 bool hl, Color color, Color hlcolor)
170 {
171 	UPP::DrawMenuText(w, x, y, s, f, enabled, hl, VisibleAccessKeys() ? accesskey : 0,
172 	                  color, hlcolor, InOpaqueBar());
173 }
174 
PaintTopItem(Draw & w,int state)175 void MenuItemBase::PaintTopItem(Draw& w, int state) {
176 	Size sz = GetSize();
177 	String text = GetText();
178 	Size isz = GetTextSize(text, StdFont());
179 	Point tp = (sz - isz) / 2;
180 	if(GUI_GlobalStyle() >= GUISTYLE_XP) {
181 		bool opaque = InOpaqueBar();
182 		bool opaque2 = opaque || state;
183 		Color bg = SColorFace();
184 		if(opaque2)
185 			ChPaint(w, 0, 0, sz.cx, sz.cy, style->topitem[state]);
186 		else
187 		if(opaque)
188 			w.DrawRect(0, 0, sz.cx, sz.cy, bg);
189 		Color txt = opaque ? style->topitemtext[0] : GetLabelTextColor(this);
190 		Color hltxt = opaque2 ? style->topitemtext[state] : GetLabelTextColor(this);
191 		if(!opaque && state != 2 && style->opaquetest) { // Fix issues when text color is not compatible with transparent background (e.g. Ubuntu Ambience)]
192 			Color c = state == 1 && !IsNull(style->topitem[0]) ? SColorHighlight() : bg;
193 			int g = Grayscale(c);
194 			bool dark = IsDark(c);
195 			if(abs(g - Grayscale(txt)) < 70)
196 				txt = dark ? White() : Black();
197 			if(abs(g - Grayscale(hltxt)) < 70)
198 				hltxt = dark ? White() : Black();
199 		}
200 		DrawMenuText(w, tp.x, tp.y, text, GetFont(), IsItemEnabled(), state,
201 		             txt, hltxt);
202 	}
203 	else {
204 		w.DrawRect(sz, SColorFace);
205 		static const ColorF b0[] = { (ColorF)1, SColorLight, SColorLight, SColorShadow, SColorShadow, };
206 		static const ColorF b1[] = { (ColorF)1, SColorShadow, SColorShadow, SColorLight, SColorLight, };
207 		DrawMenuText(w, tp.x, tp.y, text, GetFont(), IsItemEnabled(), false,
208 		             SColorMenuText, SColorHighlightText);
209 		if(state)
210 			DrawBorder(w, 0, 0, sz.cx, sz.cy, state == 2 ? b1 : b0);
211 	}
212 }
213 
214 // -------------------------------------
215 
Image(const UPP::Image & img)216 Bar::Item& MenuItem::Image(const UPP::Image& img)
217 {
218 	licon = img;
219 	Refresh();
220 	return *this;
221 }
222 
RightImage(const UPP::Image & img)223 MenuItem& MenuItem::RightImage(const UPP::Image& img)
224 {
225 	ricon = img;
226 	Refresh();
227 	return *this;
228 }
229 
SendHelpLine()230 void MenuItem::SendHelpLine()
231 {
232 	BarCtrl::SendHelpLine(this);
233 }
234 
ClearHelpLine()235 void MenuItem::ClearHelpLine()
236 {
237 	BarCtrl::ClearHelpLine(this);
238 }
239 
MouseEnter(Point,dword)240 void MenuItem::MouseEnter(Point, dword)
241 {
242 	SetFocus();
243 	Refresh();
244 	SendHelpLine();
245 }
246 
MouseLeave()247 void MenuItem::MouseLeave()
248 {
249     if(HasFocus() && GetParent())
250         GetParent()->SetFocus();
251     ClearHelpLine();
252 }
253 
GotFocus()254 void MenuItem::GotFocus()
255 {
256 	Refresh();
257 	SendHelpLine();
258 }
259 
LostFocus()260 void MenuItem::LostFocus()
261 {
262 	Refresh();
263 	ClearHelpLine();
264 }
265 
GetVisualState()266 int MenuItem::GetVisualState()
267 {
268 	return HasFocus() ?
269 	       (HasMouse() && GetMouseLeft() || GetMouseRight()) ? PUSH : HIGHLIGHT : NORMAL;
270 }
271 
SyncState()272 void MenuItem::SyncState()
273 {
274 	int s = GetVisualState();
275 	if(s != state) {
276 		state = s;
277 		Refresh();
278 	}
279 }
280 
Paint(Draw & w)281 void MenuItem::Paint(Draw& w)
282 {
283 	int q = text.Find('\t');
284 	String txt, keydesc;
285 	if(accel)
286 		keydesc = GetKeyDesc(accel);
287 	if(q >= 0) {
288 		keydesc = text.Mid(q + 1);
289 		txt = text.Mid(0, q);
290 	}
291 	else
292 		txt = text;
293 	state = GetVisualState();
294 	bool hl = state != NORMAL;
295 	Size sz = GetSize();
296 
297 	if(hl) {
298 		if(GUI_GlobalStyle() >= GUISTYLE_XP)
299 			ChPaint(w, 0, 0, sz.cx, sz.cy, style->item);
300 		else
301 			w.DrawRect(sz, SColorHighlight);
302 	}
303 	UPP::Image li = licon;
304 	if(li.IsEmpty()) {
305 		switch(type) {
306 		case CHECK0: li = CtrlImg::MenuCheck0(); break;
307 		case CHECK1: li = CtrlImg::MenuCheck1(); break;
308 		case RADIO0: li = CtrlImg::MenuRadio0(); break;
309 		case RADIO1: li = CtrlImg::MenuRadio1(); break;
310 		}
311 	}
312 	Size isz = li.GetSize();
313 	int iy = (sz.cy - isz.cy) / 2;
314 	bool chk = false;
315 	int x = max(Zx(3), (leftgap + textgap - isz.cx) / 2);
316 	if(!licon.IsEmpty() && type) {
317 		chk = type == CHECK1 || type == RADIO1;
318 		Rect rr = RectC(x - Zx(2), iy - Zy(2), isz.cx + Zx(4), isz.cy + Zy(4));
319 		if(GUI_GlobalStyle() >= GUISTYLE_XP) {
320 			if(chk && !hl) {
321 				if(IsNull(style->icheck))
322 					DrawXPButton(w, rr, BUTTON_EDGE|BUTTON_CHECKED);
323 				else
324 					ChPaint(w, rr, style->icheck);
325 			}
326 		}
327 		else {
328 			w.DrawRect(x - Zx(1), iy - Zy(1), isz.cx + Zx(2), isz.cy + Zy(2),
329 			           chk ? Blend(SColorFace, SColorLight) : SColorFace);
330 			DrawBorder(w, rr, chk ? ThinInsetBorder : ThinOutsetBorder);
331 		}
332 	}
333 	if(isenabled)
334 		DrawHighlightImage(w, x, iy, li, hl || chk, true);
335 	else
336 		w.DrawImage(x, iy, DisabledImage(li));
337 	x = max(isz.cx + Zx(3), leftgap) + textgap;
338 	isz = GetTextSize(text, StdFont());
339 	DrawMenuText(w, x, (sz.cy - isz.cy) / 2, txt, font, isenabled, hl, style->menutext,
340 	             style->itemtext);
341 	isz = ricon.GetSize();
342 	if(isenabled)
343 		w.DrawImage(sz.cx - isz.cx, (sz.cy - isz.cy) / 2, ricon, hl ? style->itemtext : style->menutext);
344 	else
345 		w.DrawImage(sz.cx - isz.cx, (sz.cy - isz.cy) / 2, DisabledImage(ricon));
346 	x = sz.cx - max(isz.cx, Zx(16)) - Zx(1);
347 	if(!IsEmpty(keydesc)) {
348 		isz = GetTextSize(keydesc, StdFont());
349 		UPP::DrawMenuText(w, x - isz.cx - Zx(2), (sz.cy - isz.cy) / 2, keydesc, font, isenabled, hl,
350 		                  0, SColorMenuMark(), style->itemtext, false);
351 	}
352 }
353 
GetMinSize() const354 Size MenuItem::GetMinSize() const
355 {
356 	Size sz1 = GetTextSize(text, font);
357 	Size sz2(0, 0);
358 	if(accel) {
359 		sz2 = GetTextSize(GetKeyDesc(accel), font);
360 		sz2.cx += Zx(12);
361 	}
362 	Size lsz = min(maxiconsize, licon.GetSize());
363 	Size rsz = ricon.GetSize();
364 	return AddFrameSize(Size(max(lsz.cx, leftgap) + sz1.cx + max(sz2.cx, (rsz.cx ? Zx(16) : 0))
365 	                         + max(rsz.cx, Zx(16)) + textgap + Zx(10),
366 	                         max(max(lsz.cy, rsz.cy) + Zy(4), sz1.cy + Zy(6))));
367 }
368 
LeftUp(Point,dword)369 void MenuItem::LeftUp(Point, dword)
370 {
371 	if(!isenabled) return;
372 #ifdef PLATFORM_WIN32
373 #ifdef PLATFORM_WINCE
374 	PlaySound(L"MenuCommand", NULL, SND_ASYNC|SND_NODEFAULT); //TODO?
375 #else
376 	PlaySound("MenuCommand", NULL, SND_ASYNC|SND_NODEFAULT);
377 #endif
378 #endif
379 	LLOG("Menu Item pre Action");
380 	Ptr<MenuItem> p = this; // action can destroy the menu and this instance too
381 	Action();
382 	if(p) {
383 		MenuBar *bar = GetMenuBar();
384 		if(bar)
385 			bar->action_taken = true;
386 	}
387 	LLOG("Menu Item post Action");
388 }
389 
RightUp(Point p,dword w)390 void MenuItem::RightUp(Point p, dword w)
391 {
392 	LeftUp(p, w);
393 }
394 
HotKey(dword key)395 bool MenuItem::HotKey(dword key)
396 {
397 	if(isenabled && (key == accel || CompareAccessKey(accesskey, key)
398 	|| key < 256 && IsAlNum((char)key) && CompareAccessKey(accesskey, ToUpper((char)key) + K_DELTA + K_ALT))) {
399 		LLOG("MenuItem::HotKey(" << key << ") -> SetFocus");
400 		SetFocus();
401 		Sync();
402 		Sleep(50);
403 		Action();
404 		return true;
405 	}
406 	return false;
407 }
408 
Key(dword key,int count)409 bool MenuItem::Key(dword key, int count)
410 {
411 	if(key == K_ENTER && isenabled) {
412 		Action();
413 		return true;
414 	}
415 	return false;
416 }
417 
418 // ----------------------------------------------------
419 
Pull(Ctrl * item,Point p,Size sz)420 void SubMenuBase::Pull(Ctrl *item, Point p, Size sz)
421 {
422 	menu.KillDelayedClose();
423 	if(!item->IsOpen() || menu.IsOpen()) return;
424 	menu.Clear();
425 #ifdef PLATFORM_COCOA
426 	menu.UppMenu();
427 #endif
428 	if(parentmenu)
429 		menu.SetStyle(*parentmenu->style);
430 	proc(menu);
431 	if(parentmenu) {
432 		parentmenu->SetActiveSubmenu(&menu, item);
433 		menu.SetParentMenu(parentmenu);
434 	}
435 	menu.PopUp(parentmenu, p, sz);
436 	if(parentmenu) {
437 		parentmenu->doeffect = false;
438 		parentmenu->WhenSubMenuOpen();
439 	}
440 	menu.KillDelayedClose();
441 }
442 
443 // ----------------------------------------------------
444 
SubMenuItem()445 SubMenuItem::SubMenuItem()
446 {
447 	RightImage(CtrlImg::right_arrow());
448 }
449 
GotFocus()450 void SubMenuItem::GotFocus()
451 {
452 	Refresh();
453 }
454 
Pull()455 void SubMenuItem::Pull()
456 {
457 	Rect r = GetScreenRect();
458 	Point p = r.TopRight();
459 	p.x -= 3;
460 	p.y -= 2;
461 	SetFocus(); // avoid returning focus to window widgets by closing submenu
462 	SubMenuBase::Pull(this, p, Size(-r.Width(), 0));
463 	if(parentmenu)
464 		parentmenu->SyncState();
465 }
466 
MouseEnter(Point,dword)467 void SubMenuItem::MouseEnter(Point, dword)
468 {
469 	SetFocus();
470 	Refresh();
471 	if(!menu.IsOpen() && isenabled)
472 		SetTimeCallback(400, THISBACK(Pull), TIMEID_PULL);
473 }
474 
MouseLeave()475 void SubMenuItem::MouseLeave()
476 {
477 	if(HasFocus() && GetParent())
478 		GetParent()->SetFocus();
479 	KillTimeCallback(TIMEID_PULL);
480 }
481 
GetVisualState()482 int SubMenuItem::GetVisualState()
483 {
484 	if(menu.IsOpen() && !GetParent()->HasFocusDeep())
485 		return PUSH;
486 	return MenuItem::GetVisualState();
487 }
488 
Key(dword key,int count)489 bool SubMenuItem::Key(dword key, int count)
490 {
491 	if(key == K_RIGHT || key == K_ENTER) {
492 		Pull();
493 		return true;
494 	}
495 	return MenuItem::Key(key, count);
496 }
497 
HotKey(dword key)498 bool SubMenuItem::HotKey(dword key)
499 {
500 	if(isenabled && (key == accel || CompareAccessKey(accesskey, key))) {
501 		Pull();
502 		return true;
503 	}
504 	return false;
505 }
506 
507 
508 // ----------------------------------------------------
509 
GetMinSize() const510 Size TopSubMenuItem::GetMinSize() const
511 {
512 	Size sz = Zsz(10, 5);
513 	sz.cx = (sz.cx + 1) & 0xfffffffe; // We need even number, otherwise it looks asymmetric
514 	return AddFrameSize(GetTextSize(text, font) + sz);
515 }
516 
GetState()517 int  TopSubMenuItem::GetState()
518 {
519 	if(parentmenu && parentmenu->GetActiveSubmenu() == &menu) return PUSH;
520 	if(HasMouse() && GetParent() && !GetParent()->HasFocusDeep() &&
521 	                 (!parentmenu || !parentmenu->GetActiveSubmenu() || parentmenu->GetActiveSubmenu() == &menu)
522 	   || HasFocus())
523 		return HIGHLIGHT;
524 	return NORMAL;
525 }
526 
Paint(Draw & w)527 void TopSubMenuItem::Paint(Draw& w)
528 {
529 	PaintTopItem(w, state = GetState());
530 }
531 
Pull()532 void TopSubMenuItem::Pull()
533 {
534 	Rect r = GetScreenRect();
535 	if(parentmenu && parentmenu->IsChild() && !parentmenu->submenu)
536 		parentmenu->SetupRestoreFocus();
537 	Point p = r.BottomLeft();
538 	if(GUI_GlobalStyle() >= GUISTYLE_XP)
539 		p += style->pullshift;
540 	SetFocus(); // avoid returning focus to window widgets by closing submenu
541 	SubMenuBase::Pull(this, p, Size(r.Width(), -r.Height()));
542 	if(parentmenu)
543 		parentmenu->SyncState();
544 }
545 
MouseEnter(Point p,dword)546 void TopSubMenuItem::MouseEnter(Point p, dword)
547 {
548 	LLOG("TopSubMenuItem::MouseEnter");
549 	Refresh();
550 	if(isenabled && parentmenu->GetActiveSubmenu())
551 		Pull();
552 }
553 
Key(dword key,int)554 bool TopSubMenuItem::Key(dword key, int) {
555 	if(isenabled && (key == K_ENTER || key == K_DOWN)) {
556 		Pull();
557 		return true;
558 	}
559 	return false;
560 }
561 
GotFocus()562 void TopSubMenuItem::GotFocus()
563 {
564 	LLOG("TopSubMenuItem::GotFocus");
565 	Refresh();
566 }
567 
LostFocus()568 void TopSubMenuItem::LostFocus()
569 {
570 	LLOG("TopSubMenuItem::LostFocus");
571 	Refresh();
572 }
573 
HotKey(dword key)574 bool TopSubMenuItem::HotKey(dword key)
575 {
576 	if(BarCtrl::Scan(proc, key))
577 		return true;
578 	if(isenabled && (key == accel || CompareAccessKey(accesskey, key))) {
579 		Pull();
580 		return true;
581 	}
582 	return false;
583 }
584 
MouseLeave()585 void TopSubMenuItem::MouseLeave()
586 {
587 	Refresh();
588 }
589 
LeftDown(Point,dword)590 void TopSubMenuItem::LeftDown(Point, dword)
591 {
592 	if(isenabled && !menu.IsOpen()) {
593 		Pull();
594 		Refresh();
595 	}
596 }
597 
SyncState()598 void TopSubMenuItem::SyncState()
599 {
600 	int q = GetState();
601 	if(q != state) {
602 		state = q;
603 		Refresh();
604 	}
605 }
606 
607 // ----------------------------------------------------
608 
GetState()609 int  TopMenuItem::GetState()
610 {
611 	if(!IsEnabled()) return NORMAL;
612 	if(HasMouse() && GetMouseLeft() || GetMouseRight()) return PUSH;
613 	if(HasFocus() || HasMouse()) return HIGHLIGHT;
614 	return NORMAL;
615 }
616 
Paint(Draw & w)617 void TopMenuItem::Paint(Draw& w)
618 {
619 	PaintTopItem(w, state = GetState());
620 }
621 
MouseEnter(Point,dword)622 void TopMenuItem::MouseEnter(Point, dword)
623 {
624 	Refresh();
625 }
626 
MouseLeave()627 void TopMenuItem::MouseLeave()
628 {
629 	Refresh();
630 }
631 
LeftUp(Point,dword)632 void TopMenuItem::LeftUp(Point, dword)
633 {
634 	if(!isenabled) return;
635 	Action();
636 	Refresh();
637 }
638 
LeftDown(Point,dword)639 void TopMenuItem::LeftDown(Point, dword)
640 {
641 	Refresh();
642 }
643 
GotFocus()644 void TopMenuItem::GotFocus()
645 {
646 	Refresh();
647 }
648 
LostFocus()649 void TopMenuItem::LostFocus()
650 {
651 	Refresh();
652 }
653 
Key(dword key,int count)654 bool TopMenuItem::Key(dword key, int count)
655 {
656 	if(isenabled && key == K_ENTER) {
657 		Action();
658 		return true;
659 	}
660 	return false;
661 }
662 
GetMinSize() const663 Size TopMenuItem::GetMinSize() const
664 {
665 	return AddFrameSize(GetTextSize(text, StdFont()) + Zsz(10, 5));
666 }
667 
GetStdHeight(Font font)668 int TopMenuItem::GetStdHeight(Font font)
669 {
670 	return font.Info().GetHeight() + Zy(7);
671 }
672 
SyncState()673 void TopMenuItem::SyncState()
674 {
675 	if(state != GetState()) {
676 		state = GetState();
677 		Refresh();
678 	}
679 }
680 
681 }
682