1 /*
2  * OpenClonk, http://www.openclonk.org
3  *
4  * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/
5  * Copyright (c) 2009-2016, The OpenClonk Team and contributors
6  *
7  * Distributed under the terms of the ISC license; see accompanying file
8  * "COPYING" for details.
9  *
10  * "Clonk" is a registered trademark of Matthes Bender, used with permission.
11  * See accompanying file "TRADEMARK" for details.
12  *
13  * To redistribute this file separately, substitute the full license texts
14  * for the above references.
15  */
16 // generic user interface
17 // context menu
18 
19 #include "C4Include.h"
20 #include "gui/C4Gui.h"
21 
22 #include "graphics/C4Draw.h"
23 #include "graphics/C4FacetEx.h"
24 #include "graphics/C4GraphicsResource.h"
25 #include "gui/C4MouseControl.h"
26 
27 namespace C4GUI
28 {
29 
30 	int32_t ContextMenu::iGlobalMenuIndex = 0;
31 
32 
33 // ----------------------------------------------------
34 // ContextMenu::Entry
35 
DrawElement(C4TargetFacet & cgo)36 	void ContextMenu::Entry::DrawElement(C4TargetFacet &cgo)
37 	{
38 		// icon
39 		if (icoIcon > Ico_None)
40 		{
41 			// get icon counts
42 			int32_t iXMax, iYMax;
43 			::GraphicsResource.fctIcons.GetPhaseNum(iXMax, iYMax);
44 			if (!iXMax)
45 				iXMax = 6;
46 			// load icon
47 			const C4Facet &rfctIcon = ::GraphicsResource.fctIcons.GetPhase(icoIcon % iXMax, icoIcon / iXMax);
48 			rfctIcon.DrawX(cgo.Surface, rcBounds.x + cgo.TargetX, rcBounds.y + cgo.TargetY, rcBounds.Hgt, rcBounds.Hgt);
49 		}
50 		// print out label
51 		if (!!sText)
52 			pDraw->TextOut(sText.getData(), ::GraphicsResource.TextFont, 1.0f, cgo.Surface, cgo.TargetX+rcBounds.x+GetIconIndent(), rcBounds.y + cgo.TargetY, C4GUI_ContextFontClr, ALeft);
53 		// submenu arrow
54 		if (pSubmenuHandler)
55 		{
56 			C4Facet &rSubFct = ::GraphicsResource.fctSubmenu;
57 			rSubFct.Draw(cgo.Surface, cgo.TargetX+rcBounds.x+rcBounds.Wdt - rSubFct.Wdt, cgo.TargetY+rcBounds.y+(rcBounds.Hgt - rSubFct.Hgt)/2);
58 		}
59 	}
60 
Entry(const char * szText,Icons icoIcon,MenuHandler * pMenuHandler,ContextHandler * pSubmenuHandler)61 	ContextMenu::Entry::Entry(const char *szText, Icons icoIcon, MenuHandler *pMenuHandler, ContextHandler *pSubmenuHandler)
62 			: Element(), cHotkey(0), icoIcon(icoIcon), pMenuHandler(pMenuHandler), pSubmenuHandler(pSubmenuHandler)
63 	{
64 		// set text with hotkey
65 		if (szText)
66 		{
67 			sText.Copy(szText);
68 			ExpandHotkeyMarkup(sText, cHotkey);
69 			// adjust size
70 			::GraphicsResource.TextFont.GetTextExtent(sText.getData(), rcBounds.Wdt, rcBounds.Hgt, true);
71 		}
72 		else
73 		{
74 			rcBounds.Wdt = 40;
75 			rcBounds.Hgt = ::GraphicsResource.TextFont.GetLineHeight();
76 		}
77 		// regard icon
78 		rcBounds.Wdt += GetIconIndent();
79 		// submenu arrow
80 		if (pSubmenuHandler) rcBounds.Wdt += ::GraphicsResource.fctSubmenu.Wdt+2;
81 	}
82 
83 // ----------------------------------------------------
84 // ContextMenu
85 
ContextMenu()86 	ContextMenu::ContextMenu() : Window()
87 	{
88 		iMenuIndex = ++iGlobalMenuIndex;
89 		// set min size
90 		rcBounds.Wdt=40; rcBounds.Hgt=7;
91 		// key bindings
92 		C4CustomKey::CodeList Keys;
93 		Keys.emplace_back(K_UP);
94 		if (Config.Controls.GamepadGuiControl)
95 		{
96 			ControllerKeys::Up(Keys);
97 		}
98 		pKeySelUp = new C4KeyBinding(Keys, "GUIContextSelUp", KEYSCOPE_Gui,
99 		                             new C4KeyCB<ContextMenu>(*this, &ContextMenu::KeySelUp), C4CustomKey::PRIO_Context);
100 
101 		Keys.clear();
102 		Keys.emplace_back(K_DOWN);
103 		if (Config.Controls.GamepadGuiControl)
104 		{
105 			ControllerKeys::Down(Keys);
106 		}
107 		pKeySelDown = new C4KeyBinding(Keys, "GUIContextSelDown", KEYSCOPE_Gui,
108 		                               new C4KeyCB<ContextMenu>(*this, &ContextMenu::KeySelDown), C4CustomKey::PRIO_Context);
109 
110 		Keys.clear();
111 		Keys.emplace_back(K_RIGHT);
112 		if (Config.Controls.GamepadGuiControl)
113 		{
114 			ControllerKeys::Right(Keys);
115 		}
116 		pKeySubmenu = new C4KeyBinding(Keys, "GUIContextSubmenu", KEYSCOPE_Gui,
117 		                               new C4KeyCB<ContextMenu>(*this, &ContextMenu::KeySubmenu), C4CustomKey::PRIO_Context);
118 
119 		Keys.clear();
120 		Keys.emplace_back(K_LEFT);
121 		if (Config.Controls.GamepadGuiControl)
122 		{
123 			ControllerKeys::Left(Keys);
124 		}
125 		pKeyBack = new C4KeyBinding(Keys, "GUIContextBack", KEYSCOPE_Gui,
126 		                            new C4KeyCB<ContextMenu>(*this, &ContextMenu::KeyBack), C4CustomKey::PRIO_Context);
127 
128 		Keys.clear();
129 		Keys.emplace_back(K_ESCAPE);
130 		if (Config.Controls.GamepadGuiControl)
131 		{
132 			ControllerKeys::Cancel(Keys);
133 		}
134 		pKeyAbort = new C4KeyBinding(Keys, "GUIContextAbort", KEYSCOPE_Gui,
135 		                             new C4KeyCB<ContextMenu>(*this, &ContextMenu::KeyAbort), C4CustomKey::PRIO_Context);
136 
137 		Keys.clear();
138 		Keys.emplace_back(K_RETURN);
139 		if (Config.Controls.GamepadGuiControl)
140 		{
141 			ControllerKeys::Ok(Keys);
142 		}
143 		pKeyConfirm = new C4KeyBinding(Keys, "GUIContextConfirm", KEYSCOPE_Gui,
144 		                               new C4KeyCB<ContextMenu>(*this, &ContextMenu::KeyConfirm), C4CustomKey::PRIO_Context);
145 
146 		pKeyHotkey = new C4KeyBinding(C4KeyCodeEx(KEY_Any), "GUIContextHotkey", KEYSCOPE_Gui,
147 		                              new C4KeyCBPassKey<ContextMenu>(*this, &ContextMenu::KeyHotkey), C4CustomKey::PRIO_Context);
148 	}
149 
~ContextMenu()150 	ContextMenu::~ContextMenu()
151 	{
152 		// del any submenu
153 		if (pSubmenu) { delete pSubmenu; pSubmenu=nullptr; }
154 		// forward RemoveElement to screen
155 		Screen *pScreen = GetScreen();
156 		if (pScreen) pScreen->RemoveElement(this);
157 		// clear key bindings
158 		delete pKeySelUp;
159 		delete pKeySelDown;
160 		delete pKeySubmenu;
161 		delete pKeyBack;
162 		delete pKeyAbort;
163 		delete pKeyConfirm;
164 		delete pKeyHotkey;
165 		// clear children to get appropriate callbacks
166 		Clear();
167 	}
168 
Abort(bool fByUser)169 	void ContextMenu::Abort(bool fByUser)
170 	{
171 		// effect
172 		if (fByUser) GUISound("UI::Close");
173 		// simply del menu: dtor will remove itself
174 		delete this;
175 	}
176 
DrawElement(C4TargetFacet & cgo)177 	void ContextMenu::DrawElement(C4TargetFacet &cgo)
178 	{
179 		// draw context menu bg
180 		pDraw->DrawBoxDw(cgo.Surface, rcBounds.x+cgo.TargetX, rcBounds.y+cgo.TargetY,
181 		                   rcBounds.x+rcBounds.Wdt+cgo.TargetX-1, rcBounds.y+rcBounds.Hgt+cgo.TargetY-1,
182 		                   C4GUI_ContextBGColor);
183 		// context bg: mark selected item
184 		if (pSelectedItem)
185 		{
186 			// get marked item bounds
187 			C4Rect rcSelArea = pSelectedItem->GetBounds();
188 			// do indent
189 			rcSelArea.x += GetClientRect().x;
190 			rcSelArea.y += GetClientRect().y;
191 			// draw
192 			pDraw->DrawBoxDw(cgo.Surface, rcSelArea.x+cgo.TargetX, rcSelArea.y+cgo.TargetY,
193 			                   rcSelArea.x+rcSelArea.Wdt+cgo.TargetX-1, rcSelArea.y+rcSelArea.Hgt+cgo.TargetY-1,
194 			                   C4GUI_ContextSelColor);
195 		}
196 		// draw frame
197 		Draw3DFrame(cgo);
198 	}
199 
MouseInput(CMouse & rMouse,int32_t iButton,int32_t iX,int32_t iY,DWORD dwKeyParam)200 	void ContextMenu::MouseInput(CMouse &rMouse, int32_t iButton, int32_t iX, int32_t iY, DWORD dwKeyParam)
201 	{
202 		// inherited
203 		Window::MouseInput(rMouse, iButton, iX, iY, dwKeyParam);
204 		// mouse is in client area?
205 		if (GetClientRect().Contains(iX+rcBounds.x, iY+rcBounds.y))
206 		{
207 			// reset selection
208 			Element *pPrevSelectedItem = pSelectedItem;
209 			pSelectedItem = nullptr;
210 			// get client component the mouse is over
211 			iX -= GetMarginLeft(); iY -= GetMarginTop();
212 			for (Element *pCurr = GetFirst(); pCurr; pCurr = pCurr->GetNext())
213 				if (pCurr->GetBounds().Contains(iX, iY))
214 					pSelectedItem = pCurr;
215 			// selection change sound
216 			if (pSelectedItem != pPrevSelectedItem)
217 			{
218 				SelectionChanged(true);
219 				// selection by mouse: Check whether submenu can be opened
220 				CheckOpenSubmenu();
221 			}
222 			// check mouse click
223 			if (iButton == C4MC_Button_LeftDown)
224 				{ DoOK(); return; }
225 		}
226 	}
227 
MouseLeaveEntry(CMouse & rMouse,Entry * pOldEntry)228 	void ContextMenu::MouseLeaveEntry(CMouse &rMouse, Entry *pOldEntry)
229 	{
230 		// no submenu open? then deselect any selected item
231 		if (pOldEntry==pSelectedItem && !pSubmenu)
232 		{
233 			pSelectedItem = nullptr;
234 			SelectionChanged(true);
235 		}
236 	}
237 
KeySelUp()238 	bool ContextMenu::KeySelUp()
239 	{
240 		// not if focus is in submenu
241 		if (pSubmenu) return false;
242 		Element *pPrevSelectedItem = pSelectedItem;
243 		// select prev
244 		if (pSelectedItem) pSelectedItem = pSelectedItem->GetPrev();
245 		// nothing selected or beginning reached: cycle
246 		if (!pSelectedItem) pSelectedItem = GetLastContained();
247 		// selection might have changed
248 		if (pSelectedItem != pPrevSelectedItem) SelectionChanged(true);
249 		return true;
250 	}
251 
KeySelDown()252 	bool ContextMenu::KeySelDown()
253 	{
254 		// not if focus is in submenu
255 		if (pSubmenu) return false;
256 		Element *pPrevSelectedItem = pSelectedItem;
257 		// select next
258 		if (pSelectedItem) pSelectedItem = pSelectedItem->GetNext();
259 		// nothing selected or end reached: cycle
260 		if (!pSelectedItem) pSelectedItem = GetFirstContained();
261 		// selection might have changed
262 		if (pSelectedItem != pPrevSelectedItem) SelectionChanged(true);
263 		return true;
264 	}
265 
KeySubmenu()266 	bool ContextMenu::KeySubmenu()
267 	{
268 		// not if focus is in submenu
269 		if (pSubmenu) return false;
270 		CheckOpenSubmenu();
271 		return true;
272 	}
273 
KeyBack()274 	bool ContextMenu::KeyBack()
275 	{
276 		// not if focus is in submenu
277 		if (pSubmenu) return false;
278 		// close submenu on keyboard input
279 		if (IsSubmenu()) { Abort(true); return true; }
280 		return false;
281 	}
282 
KeyAbort()283 	bool ContextMenu::KeyAbort()
284 	{
285 		// not if focus is in submenu
286 		if (pSubmenu) return false;
287 		Abort(true);
288 		return true;
289 	}
290 
KeyConfirm()291 	bool ContextMenu::KeyConfirm()
292 	{
293 		// not if focus is in submenu
294 		if (pSubmenu) return false;
295 		CheckOpenSubmenu();
296 		DoOK();
297 		return true;
298 	}
299 
KeyHotkey(const C4KeyCodeEx & key)300 	bool ContextMenu::KeyHotkey(const C4KeyCodeEx &key)
301 	{
302 		// not if focus is in submenu
303 		if (pSubmenu) return false;
304 		Element *pPrevSelectedItem = pSelectedItem;
305 		StdStrBuf sKey = C4KeyCodeEx::KeyCode2String(key.Key, true, true);
306 		// do hotkey procs for standard alphanumerics only
307 		if (sKey.getLength() != 1) return false;
308 		WORD wKey = WORD(*sKey.getData());
309 		if (Inside<C4KeyCode, C4KeyCode, C4KeyCode>(wKey, 'A', 'Z') || Inside<C4KeyCode, C4KeyCode, C4KeyCode>(wKey, '0', '9'))
310 		{
311 			// process hotkeys
312 			uint32_t ch = wKey;
313 			for (Element *pCurr = GetFirst(); pCurr; pCurr = pCurr->GetNext())
314 				if (pCurr->OnHotkey(ch))
315 				{
316 					pSelectedItem = pCurr;
317 					if (pSelectedItem != pPrevSelectedItem) SelectionChanged(true);
318 					CheckOpenSubmenu();
319 					DoOK();
320 					return true;
321 				}
322 			return false;
323 		}
324 		// unrecognized hotkey
325 		return false;
326 	}
327 
UpdateElementPositions()328 	void ContextMenu::UpdateElementPositions()
329 	{
330 		// first item at zero offset
331 		Element *pCurr = GetFirst();
332 		if (!pCurr) return;
333 		pCurr->GetBounds().y = 0;
334 		int32_t iMinWdt = std::max<int32_t>(20, pCurr->GetBounds().Wdt);;
335 		int32_t iOverallHgt = pCurr->GetBounds().Hgt;
336 		// others stacked under it
337 		while ((pCurr = pCurr->GetNext()))
338 		{
339 			iMinWdt = std::max(iMinWdt, pCurr->GetBounds().Wdt);
340 			int32_t iYSpace = pCurr->GetListItemTopSpacing();
341 			int32_t iNewY = iOverallHgt + iYSpace;
342 			iOverallHgt += pCurr->GetBounds().Hgt + iYSpace;
343 			if (iNewY != pCurr->GetBounds().y)
344 			{
345 				pCurr->GetBounds().y = iNewY;
346 				pCurr->UpdateOwnPos();
347 			}
348 		}
349 		// don't make smaller
350 		iMinWdt = std::max(iMinWdt, rcBounds.Wdt - GetMarginLeft() - GetMarginRight());
351 		// all entries same size
352 		for (pCurr = GetFirst(); pCurr; pCurr = pCurr->GetNext())
353 			if (pCurr->GetBounds().Wdt != iMinWdt)
354 			{
355 				pCurr->GetBounds().Wdt = iMinWdt;
356 				pCurr->UpdateOwnPos();
357 			}
358 		// update own size
359 		rcBounds.Wdt = iMinWdt + GetMarginLeft() + GetMarginRight();
360 		rcBounds.Hgt = std::max<int32_t>(iOverallHgt, 8) + GetMarginTop() + GetMarginBottom();
361 		UpdateSize();
362 	}
363 
RemoveElement(Element * pChild)364 	void ContextMenu::RemoveElement(Element *pChild)
365 	{
366 		// inherited
367 		Window::RemoveElement(pChild);
368 		// target lost?
369 		if (pChild == pTarget) { Abort(false); return; }
370 		// submenu?
371 		if (pChild == pSubmenu) pSubmenu = nullptr;
372 		// clear selection var
373 		if (pChild == pSelectedItem)
374 		{
375 			pSelectedItem = nullptr;
376 			SelectionChanged(false);
377 		}
378 		// forward to any submenu
379 		if (pSubmenu) pSubmenu->RemoveElement(pChild);
380 		// forward to mouse
381 		if (GetScreen())
382 			GetScreen()->Mouse.RemoveElement(pChild);
383 		// update positions
384 		UpdateElementPositions();
385 	}
386 
AddElement(Element * pChild)387 	bool ContextMenu::AddElement(Element *pChild)
388 	{
389 		// add it
390 		Window::AddElement(pChild);
391 		// update own size and positions
392 		UpdateElementPositions();
393 		// success
394 		return true;
395 	}
396 
InsertElement(Element * pChild,Element * pInsertBefore)397 	bool ContextMenu::InsertElement(Element *pChild, Element *pInsertBefore)
398 	{
399 		// insert it
400 		Window::InsertElement(pChild, pInsertBefore);
401 		// update own size and positions
402 		UpdateElementPositions();
403 		// success
404 		return true;
405 	}
406 
ElementSizeChanged(Element * pOfElement)407 	void ContextMenu::ElementSizeChanged(Element *pOfElement)
408 	{
409 		// inherited
410 		Window::ElementSizeChanged(pOfElement);
411 		// update positions of all list items
412 		UpdateElementPositions();
413 	}
414 
ElementPosChanged(Element * pOfElement)415 	void ContextMenu::ElementPosChanged(Element *pOfElement)
416 	{
417 		// inherited
418 		Window::ElementSizeChanged(pOfElement);
419 		// update positions of all list items
420 		UpdateElementPositions();
421 	}
422 
SelectionChanged(bool fByUser)423 	void ContextMenu::SelectionChanged(bool fByUser)
424 	{
425 		// any selection?
426 		if (pSelectedItem)
427 		{
428 			// effect
429 			if (fByUser) GUISound("UI::Select");
430 		}
431 		// close any submenu from prev selection
432 		if (pSubmenu) pSubmenu->Abort(true);
433 	}
434 
GetScreen()435 	Screen *ContextMenu::GetScreen()
436 	{
437 		// context menus don't have a parent; get screen by static var
438 		return Screen::GetScreenS();
439 	}
440 
CtxMouseInput(CMouse & rMouse,int32_t iButton,int32_t iScreenX,int32_t iScreenY,DWORD dwKeyParam)441 	bool ContextMenu::CtxMouseInput(CMouse &rMouse, int32_t iButton, int32_t iScreenX, int32_t iScreenY, DWORD dwKeyParam)
442 	{
443 		// check submenu
444 		if (pSubmenu)
445 			if (pSubmenu->CtxMouseInput(rMouse, iButton, iScreenX, iScreenY, dwKeyParam)) return true;
446 		// check bounds
447 		if (!rcBounds.Contains(iScreenX, iScreenY)) return false;
448 		// inside menu: do input in local coordinates
449 		MouseInput(rMouse, iButton, iScreenX - rcBounds.x, iScreenY - rcBounds.y, dwKeyParam);
450 		return true;
451 	}
452 
CharIn(const char * c)453 	bool ContextMenu::CharIn(const char * c)
454 	{
455 		// forward to submenu
456 		if (pSubmenu) return pSubmenu->CharIn(c);
457 		return false;
458 	}
459 
Draw(C4TargetFacet & cgo)460 	void ContextMenu::Draw(C4TargetFacet &cgo)
461 	{
462 		// In editor mode, the surface is not assigned
463 		// The menu is drawn directly by the dialogue, so just exit here.
464 		if (!cgo.Surface) return;
465 		// draw self
466 		Window::Draw(cgo);
467 		// draw submenus on top
468 		if (pSubmenu) pSubmenu->Draw(cgo);
469 	}
470 
Open(Element * pTarget,int32_t iScreenX,int32_t iScreenY)471 	void ContextMenu::Open(Element *pTarget, int32_t iScreenX, int32_t iScreenY)
472 	{
473 		// set pos
474 		rcBounds.x = iScreenX; rcBounds.y = iScreenY;
475 		UpdatePos();
476 		// set target
477 		this->pTarget = pTarget;
478 		// effect :)
479 		GUISound("UI::Open");
480 		// done
481 	}
482 
CheckOpenSubmenu()483 	void ContextMenu::CheckOpenSubmenu()
484 	{
485 		// safety
486 		if (!GetScreen()) return;
487 		// anything selected?
488 		if (!pSelectedItem) return;
489 		// get as entry
490 		Entry *pSelEntry = (Entry *) pSelectedItem;
491 		// has submenu handler?
492 		ContextHandler *pSubmenuHandler = pSelEntry->pSubmenuHandler;
493 		if (!pSubmenuHandler) return;
494 		// create submenu then
495 		if (pSubmenu) pSubmenu->Abort(false);
496 		pSubmenu = pSubmenuHandler->OnSubcontext(pTarget);
497 		// get open pos
498 		int32_t iX = GetClientRect().x + pSelEntry->GetBounds().x + pSelEntry->GetBounds().Wdt;
499 		int32_t iY = GetClientRect().y + pSelEntry->GetBounds().y + pSelEntry->GetBounds().Hgt/2;
500 		int32_t iScreenWdt = GetScreen()->GetBounds().Wdt, iScreenHgt = GetScreen()->GetBounds().Hgt;
501 		if (iY + pSubmenu->GetBounds().Hgt >= iScreenHgt)
502 		{
503 			// bottom too narrow: open to top, if height is sufficient
504 			// otherwise, open to top from bottom screen pos
505 			if (iY < pSubmenu->GetBounds().Hgt) iY = iScreenHgt;
506 			iY -= pSubmenu->GetBounds().Hgt;
507 		}
508 		if (iX + pSubmenu->GetBounds().Wdt >= iScreenWdt)
509 		{
510 			// right too narrow: try opening left of this menu
511 			// otherwise, open to left from right screen border
512 			if (GetClientRect().x < pSubmenu->GetBounds().Wdt)
513 				iX = iScreenWdt;
514 			else
515 				iX = GetClientRect().x;
516 			iX -= pSubmenu->GetBounds().Wdt;
517 		}
518 		// open it
519 		pSubmenu->Open(pTarget, iX, iY);
520 	}
521 
IsSubmenu()522 	bool ContextMenu::IsSubmenu()
523 	{
524 		return GetScreen() && GetScreen()->pContext!=this;
525 	}
526 
DoOK()527 	void ContextMenu::DoOK()
528 	{
529 		// safety
530 		if (!GetScreen()) return;
531 		// anything selected?
532 		if (!pSelectedItem) return;
533 		// get as entry
534 		Entry *pSelEntry = (Entry *) pSelectedItem;
535 		// get CB; take over pointer
536 		MenuHandler *pCallback = pSelEntry->GetAndZeroCallback();
537 		Element *pTarget = this->pTarget;
538 		if (!pCallback) return;
539 		// close all menus (deletes this class!) w/o sound
540 		GetScreen()->AbortContext(false);
541 		// sound
542 		GUISound("UI::Click");
543 		// do CB
544 		pCallback->OnOK(pTarget);
545 		// free CB class
546 		delete pCallback;
547 	}
548 
SelectItem(int32_t iIndex)549 	void ContextMenu::SelectItem(int32_t iIndex)
550 	{
551 		// get item to be selected (may be nullptr on purpose!)
552 		Element *pNewSelElement = GetElementByIndex(iIndex);
553 		if (pNewSelElement != pSelectedItem) return;
554 		// set new
555 		pSelectedItem = pNewSelElement;
556 		SelectionChanged(false);
557 	}
558 
559 
560 // ----------------------------------------------------
561 // ContextButton
562 
ContextButton(C4Rect & rtBounds)563 	ContextButton::ContextButton(C4Rect &rtBounds) : Control(rtBounds), iOpenMenu(0), fMouseOver(false)
564 	{
565 		RegisterContextKey();
566 	}
567 
ContextButton(Element * pForEl,bool fAdd,int32_t iHIndent,int32_t iVIndent)568 	ContextButton::ContextButton(Element *pForEl, bool fAdd, int32_t iHIndent, int32_t iVIndent)
569 			: Control(C4Rect(0,0,0,0)), iOpenMenu(0), fMouseOver(false)
570 	{
571 		SetBounds(pForEl->GetToprightCornerRect(16, 16, iHIndent, iVIndent));
572 		// copy context handler
573 		SetContextHandler(pForEl->GetContextHandler());
574 		// add if desired
575 		Container *pCont;
576 		if (fAdd) if ((pCont = pForEl->GetContainer()))
577 				pCont->AddElement(this);
578 		RegisterContextKey();
579 	}
580 
~ContextButton()581 	ContextButton::~ContextButton()
582 	{
583 		delete pKeyContext;
584 	}
585 
RegisterContextKey()586 	void ContextButton::RegisterContextKey()
587 	{
588 		// reg keys for pressing the context button
589 		C4CustomKey::CodeList ContextKeys;
590 		ContextKeys.emplace_back(K_RIGHT);
591 		ContextKeys.emplace_back(K_DOWN);
592 		ContextKeys.emplace_back(K_SPACE);
593 		ContextKeys.emplace_back(K_RIGHT, KEYS_Alt);
594 		ContextKeys.emplace_back(K_DOWN, KEYS_Alt);
595 		ContextKeys.emplace_back(K_SPACE, KEYS_Alt);
596 		pKeyContext = new C4KeyBinding(ContextKeys, "GUIContextButtonPress", KEYSCOPE_Gui,
597 		                               new ControlKeyCB<ContextButton>(*this, &ContextButton::KeyContext), C4CustomKey::PRIO_Ctrl);
598 	}
599 
DoContext(int32_t iX,int32_t iY)600 	bool ContextButton::DoContext(int32_t iX, int32_t iY)
601 	{
602 		// get context pos
603 		if (iX<0)
604 		{
605 			iX = rcBounds.Wdt/2;
606 			iY = rcBounds.Hgt/2;
607 		}
608 		// do context
609 		ContextHandler *pCtx = GetContextHandler();
610 		if (!pCtx) return false;
611 		if (!pCtx->OnContext(this, iX, iY)) return false;
612 		// store menu
613 		Screen *pScr = GetScreen();
614 		if (!pScr) return false;
615 		iOpenMenu = pScr->GetContextMenuIndex();
616 		// return whether all was successful
617 		return !!iOpenMenu;
618 	}
619 
DrawElement(C4TargetFacet & cgo)620 	void ContextButton::DrawElement(C4TargetFacet &cgo)
621 	{
622 		// recheck open menu
623 		Screen *pScr = GetScreen();
624 		if (!pScr || (iOpenMenu != pScr->GetContextMenuIndex())) iOpenMenu = 0;
625 		// calc drawing bounds
626 		int32_t x0 = cgo.TargetX + rcBounds.x, y0 = cgo.TargetY + rcBounds.y;
627 		// draw button; down (phase 1) if a menu is open
628 		::GraphicsResource.fctContext.Draw(cgo.Surface, x0, y0, iOpenMenu ? 1 : 0);
629 		// draw selection highlight
630 		if (HasDrawFocus() || (fMouseOver && IsInActiveDlg(false)) || iOpenMenu)
631 		{
632 			pDraw->SetBlitMode(C4GFXBLIT_ADDITIVE);
633 			::GraphicsResource.fctButtonHighlight.DrawX(cgo.Surface, x0, y0, rcBounds.Wdt, rcBounds.Hgt);
634 			pDraw->ResetBlitMode();
635 		}
636 	}
637 
MouseInput(CMouse & rMouse,int32_t iButton,int32_t iX,int32_t iY,DWORD dwKeyParam)638 	void ContextButton::MouseInput(CMouse &rMouse, int32_t iButton, int32_t iX, int32_t iY, DWORD dwKeyParam)
639 	{
640 		// left-click activates menu
641 		if ((iButton == C4MC_Button_LeftDown) || (iButton == C4MC_Button_RightDown))
642 			if (DoContext()) return;
643 		// inherited
644 		Control::MouseInput(rMouse, iButton, iX, iY, dwKeyParam);
645 	}
646 
MouseEnter(CMouse & rMouse)647 	void ContextButton::MouseEnter(CMouse &rMouse)
648 	{
649 		Control::MouseEnter(rMouse);
650 		// remember mouse state for button highlight
651 		fMouseOver = true;
652 	}
653 
MouseLeave(CMouse & rMouse)654 	void ContextButton::MouseLeave(CMouse &rMouse)
655 	{
656 		Control::MouseLeave(rMouse);
657 		// mouse left
658 		fMouseOver = false;
659 	}
660 
661 
662 
663 } // end of namespace
664 
665