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 // dropdown box
18 // implemented via context menu
19 
20 #include "C4Include.h"
21 #include "gui/C4Gui.h"
22 
23 #include "graphics/C4Draw.h"
24 #include "graphics/C4FacetEx.h"
25 #include "graphics/C4GraphicsResource.h"
26 #include "gui/C4MouseControl.h"
27 
28 namespace C4GUI
29 {
30 
31 // ----------------------------------------------------
32 // ComboBox_FillCB
33 
AddEntry(const char * szText,int32_t id)34 	void ComboBox_FillCB::AddEntry(const char *szText, int32_t id)
35 	{
36 		if (!szText) szText = "";
37 		typedef C4GUI::CBMenuHandlerEx<ComboBox, ComboBox::ComboMenuCBStruct> Handler;
38 		Handler *pHandler = new Handler(pCombo, &ComboBox::OnCtxComboSelect);
39 		pHandler->SetExtra(ComboBox::ComboMenuCBStruct(szText, id));
40 		pDrop->AddItem(szText, FormatString(LoadResStr("IDS_MSG_SELECT"), szText).getData(), Ico_Empty, pHandler);
41 	}
42 
FindEntry(const char * szText)43 	bool ComboBox_FillCB::FindEntry(const char *szText)
44 	{
45 		// check for entry with same name
46 		ContextMenu::Entry *pEntry; int32_t idx=0;
47 		while ((pEntry = pDrop->GetIndexedEntry(idx++))) if (SEqual(pEntry->GetText(), szText)) return true;
48 		return false;
49 	}
50 
SelectEntry(int32_t iEntry)51 	void ComboBox_FillCB::SelectEntry(int32_t iEntry)
52 	{
53 		pDrop->SelectItem(iEntry);
54 	}
55 
ClearEntries()56 	void ComboBox_FillCB::ClearEntries()
57 	{
58 		pDrop->Clear();
59 	}
60 
61 
62 // ----------------------------------------------------
63 // ComboBox
64 
ComboBox(const C4Rect & rtBounds)65 	ComboBox::ComboBox(const C4Rect &rtBounds) :
66 			Control(rtBounds), iOpenMenu(0), pFillCallback(nullptr), fReadOnly(false), fSimple(false), fMouseOver(false),
67 			pUseFont(nullptr), dwFontClr(C4GUI_ComboFontClr), dwBGClr(C4GUI_StandardBGColor), dwBorderClr(0), pFctSideArrow(nullptr)
68 	{
69 		*Text=0;
70 		// key callbacks - lots of possibilities to get the dropdown
71 		C4CustomKey::CodeList cbKeys;
72 		cbKeys.emplace_back(K_DOWN);
73 		cbKeys.emplace_back(K_SPACE);
74 		cbKeys.emplace_back(K_DOWN, KEYS_Alt);
75 		cbKeys.emplace_back(K_SPACE, KEYS_Alt);
76 		if (Config.Controls.GamepadGuiControl)
77 		{
78 			ControllerKeys::Ok(cbKeys);
79 			ControllerKeys::Down(cbKeys);
80 		}
81 		pKeyOpenCombo = new C4KeyBinding(cbKeys, "GUIComboOpen", KEYSCOPE_Gui,
82 		                                 new ControlKeyCB<ComboBox>(*this, &ComboBox::KeyDropDown), C4CustomKey::PRIO_Ctrl);
83 		cbKeys.clear();
84 		cbKeys.emplace_back(K_ESCAPE);
85 		if (Config.Controls.GamepadGuiControl)
86 		{
87 			ControllerKeys::Cancel(cbKeys);
88 		}
89 		pKeyCloseCombo = new C4KeyBinding(cbKeys, "GUIComboClose", KEYSCOPE_Gui,
90 		                                  new ControlKeyCB<ComboBox>(*this, &ComboBox::KeyAbortDropDown), C4CustomKey::PRIO_Ctrl);
91 	}
92 
~ComboBox()93 	ComboBox::~ComboBox()
94 	{
95 		delete pKeyCloseCombo;
96 		delete pKeyOpenCombo;
97 		if (pFillCallback) delete pFillCallback;
98 	}
99 
SetComboCB(ComboBox_FillCB * pNewFillCallback)100 	void ComboBox::SetComboCB(ComboBox_FillCB *pNewFillCallback)
101 	{
102 		if (pFillCallback) delete pFillCallback;
103 		pFillCallback = pNewFillCallback;
104 	}
105 
DoDropdown()106 	bool ComboBox::DoDropdown()
107 	{
108 		// not if readonly
109 		if (fReadOnly) return false;
110 		// get dropdown pos
111 		int32_t iX = 0;
112 		int32_t iY = rcBounds.Hgt;
113 		// do dropdown
114 		Screen *pScreen = GetScreen();
115 		if (!pScreen) return false;
116 		// item list as context menu
117 		if (!pFillCallback) return false;
118 		ContextMenu *pNewMenu = new C4GUI::ContextMenu();
119 		// init with minimum size
120 		pNewMenu->GetBounds().Wdt = std::max(rcBounds.Wdt, pNewMenu->GetBounds().Wdt);
121 		// fill with items
122 		pFillCallback->FillDropDown(this, pNewMenu);
123 		// open it on screen
124 		pScreen->DoContext(pNewMenu, this, iX, iY);
125 		// store menu
126 		iOpenMenu = pNewMenu->GetMenuIndex();
127 		// done, success
128 		return true;
129 	}
130 
AbortDropdown(bool fByUser)131 	bool ComboBox::AbortDropdown(bool fByUser)
132 	{
133 		// recheck open menu
134 		Screen *pScr = GetScreen();
135 		if (!pScr || (iOpenMenu != pScr->GetLastContextMenuIndex())) iOpenMenu = 0;
136 		if (!iOpenMenu) return false;
137 		// abort it
138 		pScr->AbortContext(fByUser);
139 		return true;
140 	}
141 
DrawElement(C4TargetFacet & cgo)142 	void ComboBox::DrawElement(C4TargetFacet &cgo)
143 	{
144 		CStdFont *pUseFont = this->pUseFont ? this->pUseFont : &(::GraphicsResource.TextFont);
145 		// recheck open menu
146 		Screen *pScr = GetScreen();
147 		if (!pScr || (iOpenMenu != pScr->GetContextMenuIndex())) iOpenMenu = 0;
148 		// calc drawing bounds
149 		int32_t x0 = cgo.TargetX + rcBounds.x, y0 = cgo.TargetY + rcBounds.y;
150 		int32_t iRightTextEnd = x0 + rcBounds.Wdt - ::GraphicsResource.fctContext.Wdt - 1;
151 		if (!fReadOnly && !fSimple)
152 		{
153 			// draw background
154 			pDraw->DrawBoxDw(cgo.Surface, x0,y0,x0+rcBounds.Wdt-1,y0+rcBounds.Hgt-1,dwBGClr);
155 			// draw frame
156 			if (dwBorderClr)
157 			{
158 				int32_t x1=cgo.TargetX+rcBounds.x,y1=cgo.TargetY+rcBounds.y,x2=x1+rcBounds.Wdt,y2=y1+rcBounds.Hgt;
159 				pDraw->DrawFrameDw(cgo.Surface, x1, y1, x2, y2-1, dwBorderClr);
160 				pDraw->DrawFrameDw(cgo.Surface, x1+1, y1+1, x2-1, y2-2, dwBorderClr);
161 			}
162 			else
163 				// default frame color
164 				Draw3DFrame(cgo);
165 			// draw button; down (phase 1) if combo is down
166 			(pFctSideArrow ? pFctSideArrow : &(::GraphicsResource.fctContext))->Draw(cgo.Surface, iRightTextEnd, y0 + (rcBounds.Hgt-::GraphicsResource.fctContext.Hgt)/2, iOpenMenu ? 1 : 0);
167 		}
168 		else if (!fReadOnly)
169 		{
170 			// draw button in simple mode: Left of text
171 			(pFctSideArrow ? pFctSideArrow : &(::GraphicsResource.fctContext))->Draw(cgo.Surface, x0, y0 + (rcBounds.Hgt-::GraphicsResource.fctContext.Hgt)/2, iOpenMenu ? 1 : 0);
172 		}
173 		// draw text
174 		if (*Text)
175 		{
176 			pDraw->StorePrimaryClipper();
177 			pDraw->SubPrimaryClipper(x0,y0,iRightTextEnd-1,y0+rcBounds.Hgt-1);
178 			pDraw->TextOut(Text, *pUseFont, 1.0f, cgo.Surface, x0 + ::GraphicsResource.fctContext.Wdt + 2, y0 + (rcBounds.Hgt-pUseFont->GetLineHeight())/2, dwFontClr, ALeft);
179 			pDraw->RestorePrimaryClipper();
180 		}
181 		// draw selection highlight
182 		if ((HasDrawFocus() || iOpenMenu || fMouseOver) && !fReadOnly)
183 		{
184 			pDraw->SetBlitMode(C4GFXBLIT_ADDITIVE);
185 			::GraphicsResource.fctButtonHighlightRound.DrawX(cgo.Surface, x0, y0, rcBounds.Wdt, rcBounds.Hgt);
186 			pDraw->ResetBlitMode();
187 		}
188 	}
189 
MouseInput(CMouse & rMouse,int32_t iButton,int32_t iX,int32_t iY,DWORD dwKeyParam)190 	void ComboBox::MouseInput(CMouse &rMouse, int32_t iButton, int32_t iX, int32_t iY, DWORD dwKeyParam)
191 	{
192 		// left-click activates menu
193 		if (!fReadOnly) if (iButton == C4MC_Button_LeftDown)
194 			{
195 				// recheck open menu
196 				Screen *pScr = GetScreen();
197 				if (!pScr || (iOpenMenu != pScr->GetLastContextMenuIndex())) iOpenMenu = 0;
198 				if (iOpenMenu)
199 					// left-click with combo down: abort has been done by screen; ignore
200 					return;
201 				else
202 					// otherwise, open it
203 					if (DoDropdown()) return;
204 			}
205 		// inherited
206 		Control::MouseInput(rMouse, iButton, iX, iY, dwKeyParam);
207 	}
208 
MouseEnter(CMouse & rMouse)209 	void ComboBox::MouseEnter(CMouse &rMouse)
210 	{
211 		fMouseOver = true;
212 		Control::MouseEnter(rMouse);
213 	}
214 
MouseLeave(CMouse & rMouse)215 	void ComboBox::MouseLeave(CMouse &rMouse)
216 	{
217 		fMouseOver = false;
218 		Control::MouseLeave(rMouse);
219 	}
220 
GetDefaultHeight()221 	int32_t ComboBox::GetDefaultHeight()
222 	{
223 		return ::GraphicsResource.TextFont.GetLineHeight() + 4;
224 	}
225 
SetText(const char * szToText)226 	void ComboBox::SetText(const char *szToText)
227 	{
228 		// set text without accelerator keys
229 		if (szToText)
230 		{
231 			StdStrBuf sTxt(szToText);
232 			sTxt.Replace("&", "");
233 			SCopy(sTxt.getData(), Text, C4MaxTitle);
234 		}
235 		else
236 			*Text=0;
237 	}
238 
OnCtxComboSelect(C4GUI::Element * pListItem,const ComboMenuCBStruct & rNewSel)239 	void ComboBox::OnCtxComboSelect(C4GUI::Element *pListItem, const ComboMenuCBStruct &rNewSel)
240 	{
241 		// ignore in readonly
242 		if (fReadOnly) return;
243 		// do callback
244 		if (!pFillCallback || !pFillCallback->OnComboSelChange(this, rNewSel.id))
245 			// callback didn't process: default behaviour
246 			SetText(rNewSel.sText.getData());
247 		// don't do anything else, because this might be deleted
248 	}
249 
250 } // namespace C4GUI
251