1 // Copyright (C) 2002-2012 Nikolaus Gebhardt
2 // This file is part of the "Irrlicht Engine".
3 // For conditions of distribution and use, see copyright notice in irrlicht.h
4 
5 #include "CGUIComboBox.h"
6 #ifdef _IRR_COMPILE_WITH_GUI_
7 
8 #include "IGUIEnvironment.h"
9 #include "IVideoDriver.h"
10 #include "IGUISkin.h"
11 #include "IGUIEnvironment.h"
12 #include "IGUIFont.h"
13 #include "IGUIButton.h"
14 #include "CGUIListBox.h"
15 #include "os.h"
16 
17 namespace irr
18 {
19 namespace gui
20 {
21 
22 //! constructor
CGUIComboBox(IGUIEnvironment * environment,IGUIElement * parent,s32 id,core::rect<s32> rectangle)23 CGUIComboBox::CGUIComboBox(IGUIEnvironment* environment, IGUIElement* parent,
24 	s32 id, core::rect<s32> rectangle)
25 	: IGUIComboBox(environment, parent, id, rectangle),
26 	ListButton(0), SelectedText(0), ListBox(0), LastFocus(0),
27 	Selected(-1), HAlign(EGUIA_UPPERLEFT), VAlign(EGUIA_CENTER), MaxSelectionRows(5), HasFocus(false)
28 {
29 	#ifdef _DEBUG
30 	setDebugName("CGUIComboBox");
31 	#endif
32 
33 	IGUISkin* skin = Environment->getSkin();
34 
35 	s32 width = 15;
36 	if (skin)
37 		width = skin->getSize(EGDS_WINDOW_BUTTON_WIDTH);
38 
39 	core::rect<s32> r;
40 	r.UpperLeftCorner.X = rectangle.getWidth() - width - 2;
41 	r.LowerRightCorner.X = rectangle.getWidth() - 2;
42 
43 	r.UpperLeftCorner.Y = 2;
44 	r.LowerRightCorner.Y = rectangle.getHeight() - 2;
45 
46 	ListButton = Environment->addButton(r, this, -1, L"");
47 	if (skin && skin->getSpriteBank())
48 	{
49 		ListButton->setSpriteBank(skin->getSpriteBank());
50 		ListButton->setSprite(EGBS_BUTTON_UP, skin->getIcon(EGDI_CURSOR_DOWN), skin->getColor(EGDC_WINDOW_SYMBOL));
51 		ListButton->setSprite(EGBS_BUTTON_DOWN, skin->getIcon(EGDI_CURSOR_DOWN), skin->getColor(EGDC_WINDOW_SYMBOL));
52 	}
53 	ListButton->setAlignment(EGUIA_LOWERRIGHT, EGUIA_LOWERRIGHT, EGUIA_UPPERLEFT, EGUIA_LOWERRIGHT);
54 	ListButton->setSubElement(true);
55 	ListButton->setTabStop(false);
56 
57 	r.UpperLeftCorner.X = 2;
58 	r.UpperLeftCorner.Y = 2;
59 	r.LowerRightCorner.X = RelativeRect.getWidth() - (ListButton->getAbsolutePosition().getWidth() + 2);
60 	r.LowerRightCorner.Y = RelativeRect.getHeight() - 2;
61 
62 	SelectedText = Environment->addStaticText(L"", r, false, false, this, -1, false);
63 	SelectedText->setSubElement(true);
64 	SelectedText->setAlignment(EGUIA_UPPERLEFT, EGUIA_LOWERRIGHT, EGUIA_UPPERLEFT, EGUIA_LOWERRIGHT);
65 	SelectedText->setTextAlignment(EGUIA_UPPERLEFT, EGUIA_CENTER);
66 	if (skin)
67 		SelectedText->setOverrideColor(skin->getColor(EGDC_BUTTON_TEXT));
68 	SelectedText->enableOverrideColor(true);
69 
70 	// this element can be tabbed to
71 	setTabStop(true);
72 	setTabOrder(-1);
73 }
74 
75 
setTextAlignment(EGUI_ALIGNMENT horizontal,EGUI_ALIGNMENT vertical)76 void CGUIComboBox::setTextAlignment(EGUI_ALIGNMENT horizontal, EGUI_ALIGNMENT vertical)
77 {
78 	HAlign = horizontal;
79 	VAlign = vertical;
80 	SelectedText->setTextAlignment(horizontal, vertical);
81 }
82 
83 
84 //! Set the maximal number of rows for the selection listbox
setMaxSelectionRows(u32 max)85 void CGUIComboBox::setMaxSelectionRows(u32 max)
86 {
87 	MaxSelectionRows = max;
88 
89 	// force recalculation of open listbox
90 	if (ListBox)
91 	{
92 		openCloseMenu();
93 		openCloseMenu();
94 	}
95 }
96 
97 //! Get the maximimal number of rows for the selection listbox
getMaxSelectionRows() const98 u32 CGUIComboBox::getMaxSelectionRows() const
99 {
100 	return MaxSelectionRows;
101 }
102 
103 
104 //! Returns amount of items in box
getItemCount() const105 u32 CGUIComboBox::getItemCount() const
106 {
107 	return Items.size();
108 }
109 
110 
111 //! returns string of an item. the idx may be a value from 0 to itemCount-1
getItem(u32 idx) const112 const wchar_t* CGUIComboBox::getItem(u32 idx) const
113 {
114 	if (idx >= Items.size())
115 		return 0;
116 
117 	return Items[idx].Name.c_str();
118 }
119 
120 //! returns string of an item. the idx may be a value from 0 to itemCount-1
getItemData(u32 idx) const121 u32 CGUIComboBox::getItemData(u32 idx) const
122 {
123 	if (idx >= Items.size())
124 		return 0;
125 
126 	return Items[idx].Data;
127 }
128 
129 //! Returns index based on item data
getIndexForItemData(u32 data) const130 s32 CGUIComboBox::getIndexForItemData(u32 data ) const
131 {
132 	for ( u32 i = 0; i < Items.size (); ++i )
133 	{
134 		if ( Items[i].Data == data )
135 			return i;
136 	}
137 	return -1;
138 }
139 
140 
141 //! Removes an item from the combo box.
removeItem(u32 idx)142 void CGUIComboBox::removeItem(u32 idx)
143 {
144 	if (idx >= Items.size())
145 		return;
146 
147 	if (Selected == (s32)idx)
148 		setSelected(-1);
149 
150 	Items.erase(idx);
151 }
152 
153 
154 //! Returns caption of this element.
getText() const155 const wchar_t* CGUIComboBox::getText() const
156 {
157 	return getItem(Selected);
158 }
159 
160 
161 //! adds an item and returns the index of it
addItem(const wchar_t * text,u32 data)162 u32 CGUIComboBox::addItem(const wchar_t* text, u32 data)
163 {
164 	Items.push_back( SComboData ( text, data ) );
165 
166 	if (Selected == -1)
167 		setSelected(0);
168 
169 	return Items.size() - 1;
170 }
171 
172 
173 //! deletes all items in the combo box
clear()174 void CGUIComboBox::clear()
175 {
176 	Items.clear();
177 	setSelected(-1);
178 }
179 
180 
181 //! returns id of selected item. returns -1 if no item is selected.
getSelected() const182 s32 CGUIComboBox::getSelected() const
183 {
184 	return Selected;
185 }
186 
187 
188 //! sets the selected item. Set this to -1 if no item should be selected
setSelected(s32 idx)189 void CGUIComboBox::setSelected(s32 idx)
190 {
191 	if (idx < -1 || idx >= (s32)Items.size())
192 		return;
193 
194 	Selected = idx;
195 	if (Selected == -1)
196 		SelectedText->setText(L"");
197 	else
198 		SelectedText->setText(Items[Selected].Name.c_str());
199 }
200 
201 
202 //! called if an event happened.
OnEvent(const SEvent & event)203 bool CGUIComboBox::OnEvent(const SEvent& event)
204 {
205 	if (isEnabled())
206 	{
207 		switch(event.EventType)
208 		{
209 
210 		case EET_KEY_INPUT_EVENT:
211 			if (ListBox && event.KeyInput.PressedDown && event.KeyInput.Key == KEY_ESCAPE)
212 			{
213 				// hide list box
214 				openCloseMenu();
215 				return true;
216 			}
217 			else
218 			if (event.KeyInput.Key == KEY_RETURN || event.KeyInput.Key == KEY_SPACE)
219 			{
220 				if (!event.KeyInput.PressedDown)
221 				{
222 					openCloseMenu();
223 				}
224 
225 				ListButton->setPressed(ListBox == 0);
226 
227 				return true;
228 			}
229 			else
230 			if (event.KeyInput.PressedDown)
231 			{
232 				s32 oldSelected = Selected;
233 				bool absorb = true;
234 				switch (event.KeyInput.Key)
235 				{
236 					case KEY_DOWN:
237 						setSelected(Selected+1);
238 						break;
239 					case KEY_UP:
240 						setSelected(Selected-1);
241 						break;
242 					case KEY_HOME:
243 					case KEY_PRIOR:
244 						setSelected(0);
245 						break;
246 					case KEY_END:
247 					case KEY_NEXT:
248 						setSelected((s32)Items.size()-1);
249 						break;
250 					default:
251 						absorb = false;
252 				}
253 
254 				if (Selected <0)
255 					setSelected(0);
256 
257 				if (Selected >= (s32)Items.size())
258 					setSelected((s32)Items.size() -1);
259 
260 				if (Selected != oldSelected)
261 				{
262 					sendSelectionChangedEvent();
263 					return true;
264 				}
265 
266 				if (absorb)
267 					return true;
268 			}
269 			break;
270 
271 		case EET_GUI_EVENT:
272 
273 			switch(event.GUIEvent.EventType)
274 			{
275 			case EGET_ELEMENT_FOCUS_LOST:
276 				if (ListBox &&
277 					(Environment->hasFocus(ListBox) || ListBox->isMyChild(event.GUIEvent.Caller) ) &&
278 					event.GUIEvent.Element != this &&
279 					!isMyChild(event.GUIEvent.Element) &&
280 					!ListBox->isMyChild(event.GUIEvent.Element))
281 				{
282 					openCloseMenu();
283 				}
284 				break;
285 			case EGET_BUTTON_CLICKED:
286 				if (event.GUIEvent.Caller == ListButton)
287 				{
288 					openCloseMenu();
289 					return true;
290 				}
291 				break;
292 			case EGET_LISTBOX_SELECTED_AGAIN:
293 			case EGET_LISTBOX_CHANGED:
294 				if (event.GUIEvent.Caller == ListBox)
295 				{
296 					setSelected(ListBox->getSelected());
297 					if (Selected <0 || Selected >= (s32)Items.size())
298 						setSelected(-1);
299 					openCloseMenu();
300 
301 					sendSelectionChangedEvent();
302 				}
303 				return true;
304 			default:
305 				break;
306 			}
307 			break;
308 		case EET_MOUSE_INPUT_EVENT:
309 
310 			switch(event.MouseInput.Event)
311 			{
312 			case EMIE_LMOUSE_PRESSED_DOWN:
313 				{
314 					core::position2d<s32> p(event.MouseInput.X, event.MouseInput.Y);
315 
316 					// send to list box
317 					if (ListBox && ListBox->isPointInside(p) && ListBox->OnEvent(event))
318 						return true;
319 
320 					return true;
321 				}
322 			case EMIE_LMOUSE_LEFT_UP:
323 				{
324 					core::position2d<s32> p(event.MouseInput.X, event.MouseInput.Y);
325 
326 					// send to list box
327 					if (!(ListBox &&
328 							ListBox->getAbsolutePosition().isPointInside(p) &&
329 							ListBox->OnEvent(event)))
330 					{
331 						openCloseMenu();
332 					}
333 					return true;
334 				}
335 			case EMIE_MOUSE_WHEEL:
336 				{
337 					s32 oldSelected = Selected;
338 					setSelected( Selected + ((event.MouseInput.Wheel < 0) ? 1 : -1));
339 
340 					if (Selected <0)
341 						setSelected(0);
342 
343 					if (Selected >= (s32)Items.size())
344 						setSelected((s32)Items.size() -1);
345 
346 					if (Selected != oldSelected)
347 					{
348 						sendSelectionChangedEvent();
349 						return true;
350 					}
351 				}
352 			default:
353 				break;
354 			}
355 			break;
356 		default:
357 			break;
358 		}
359 	}
360 
361 	return IGUIElement::OnEvent(event);
362 }
363 
364 
sendSelectionChangedEvent()365 void CGUIComboBox::sendSelectionChangedEvent()
366 {
367 	if (Parent)
368 	{
369 		SEvent event;
370 
371 		event.EventType = EET_GUI_EVENT;
372 		event.GUIEvent.Caller = this;
373 		event.GUIEvent.Element = 0;
374 		event.GUIEvent.EventType = EGET_COMBO_BOX_CHANGED;
375 		Parent->OnEvent(event);
376 	}
377 }
378 
379 
380 //! draws the element and its children
draw()381 void CGUIComboBox::draw()
382 {
383 	if (!IsVisible)
384 		return;
385 
386 	IGUISkin* skin = Environment->getSkin();
387 	IGUIElement *currentFocus = Environment->getFocus();
388 	if (currentFocus != LastFocus)
389 	{
390 		HasFocus = currentFocus == this || isMyChild(currentFocus);
391 		LastFocus = currentFocus;
392 	}
393 
394 	// set colors each time as skin-colors can be changed
395 	SelectedText->setBackgroundColor(skin->getColor(EGDC_HIGH_LIGHT));
396 	if(isEnabled())
397 	{
398 		SelectedText->setDrawBackground(HasFocus);
399 		SelectedText->setOverrideColor(skin->getColor(HasFocus ? EGDC_HIGH_LIGHT_TEXT : EGDC_BUTTON_TEXT));
400 	}
401 	else
402 	{
403 		SelectedText->setDrawBackground(false);
404 		SelectedText->setOverrideColor(skin->getColor(EGDC_GRAY_TEXT));
405 	}
406 	ListButton->setSprite(EGBS_BUTTON_UP, skin->getIcon(EGDI_CURSOR_DOWN), skin->getColor(isEnabled() ? EGDC_WINDOW_SYMBOL : EGDC_GRAY_WINDOW_SYMBOL));
407 	ListButton->setSprite(EGBS_BUTTON_DOWN, skin->getIcon(EGDI_CURSOR_DOWN), skin->getColor(isEnabled() ? EGDC_WINDOW_SYMBOL : EGDC_GRAY_WINDOW_SYMBOL));
408 
409 
410 	core::rect<s32> frameRect(AbsoluteRect);
411 
412 	// draw the border
413 
414 	skin->draw3DSunkenPane(this, skin->getColor(EGDC_3D_HIGH_LIGHT),
415 		true, true, frameRect, &AbsoluteClippingRect);
416 
417 	// draw children
418 	IGUIElement::draw();
419 }
420 
421 
openCloseMenu()422 void CGUIComboBox::openCloseMenu()
423 {
424 	if (ListBox)
425 	{
426 		// close list box
427 		Environment->setFocus(this);
428 		ListBox->remove();
429 		ListBox = 0;
430 	}
431 	else
432 	{
433 		if (Parent)
434 			Parent->bringToFront(this);
435 
436 		IGUISkin* skin = Environment->getSkin();
437 		u32 h = Items.size();
438 
439 		if (h > getMaxSelectionRows())
440 			h = getMaxSelectionRows();
441 		if (h == 0)
442 			h = 1;
443 
444 		IGUIFont* font = skin->getFont();
445 		if (font)
446 			h *= (font->getDimension(L"A").Height + 4);
447 
448 		// open list box
449 		core::rect<s32> r(0, AbsoluteRect.getHeight(),
450 			AbsoluteRect.getWidth(), AbsoluteRect.getHeight() + h);
451 
452 		ListBox = new CGUIListBox(Environment, this, -1, r, false, true, true);
453 		ListBox->setSubElement(true);
454 		ListBox->setNotClipped(true);
455 		ListBox->drop();
456 
457 		// ensure that list box is always completely visible
458 		if (ListBox->getAbsolutePosition().LowerRightCorner.Y > Environment->getRootGUIElement()->getAbsolutePosition().getHeight())
459 			ListBox->setRelativePosition( core::rect<s32>(0, -ListBox->getAbsolutePosition().getHeight(), AbsoluteRect.getWidth(), 0) );
460 
461 		for (s32 i=0; i<(s32)Items.size(); ++i)
462 			ListBox->addItem(Items[i].Name.c_str());
463 
464 		ListBox->setSelected(Selected);
465 
466 		// set focus
467 		Environment->setFocus(ListBox);
468 	}
469 }
470 
471 
472 //! Writes attributes of the element.
serializeAttributes(io::IAttributes * out,io::SAttributeReadWriteOptions * options=0) const473 void CGUIComboBox::serializeAttributes(io::IAttributes* out, io::SAttributeReadWriteOptions* options=0) const
474 {
475 	IGUIComboBox::serializeAttributes(out,options);
476 
477 	out->addEnum ("HTextAlign", HAlign, GUIAlignmentNames);
478 	out->addEnum ("VTextAlign", VAlign, GUIAlignmentNames);
479 	out->addInt("MaxSelectionRows", (s32)MaxSelectionRows );
480 
481 	out->addInt	("Selected",	Selected );
482 	out->addInt	("ItemCount",	Items.size());
483 	for (u32 i=0; i < Items.size(); ++i)
484 	{
485 		core::stringc s = "Item";
486 		s += i;
487 		s += "Text";
488 		out->addString(s.c_str(), Items[i].Name.c_str());
489 	}
490 }
491 
492 
493 //! Reads attributes of the element
deserializeAttributes(io::IAttributes * in,io::SAttributeReadWriteOptions * options=0)494 void CGUIComboBox::deserializeAttributes(io::IAttributes* in, io::SAttributeReadWriteOptions* options=0)
495 {
496 	IGUIComboBox::deserializeAttributes(in,options);
497 
498 	setTextAlignment( (EGUI_ALIGNMENT) in->getAttributeAsEnumeration("HTextAlign", GUIAlignmentNames),
499                       (EGUI_ALIGNMENT) in->getAttributeAsEnumeration("VTextAlign", GUIAlignmentNames));
500 	setMaxSelectionRows( (u32)(in->getAttributeAsInt("MaxSelectionRows")) );
501 
502 	// clear the list
503 	clear();
504 	// get item count
505 	u32 c = in->getAttributeAsInt("ItemCount");
506 	// add items
507 	for (u32 i=0; i < c; ++i)
508 	{
509 		core::stringc s = "Item";
510 		s += i;
511 		s += "Text";
512 		addItem(in->getAttributeAsStringW(s.c_str()).c_str(), 0);
513 	}
514 
515 	setSelected(in->getAttributeAsInt("Selected"));
516 }
517 
518 } // end namespace gui
519 } // end namespace irr
520 
521 
522 #endif // _IRR_COMPILE_WITH_GUI_
523 
524