1 /*
2  * This source file is part of libRocket, the HTML/CSS Interface Middleware
3  *
4  * For the latest information, see http://www.librocket.com
5  *
6  * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd
7  *
8  * Permission is hereby granted, free of charge, to any person obtaining a copy
9  * of this software and associated documentation files (the "Software"), to deal
10  * in the Software without restriction, including without limitation the rights
11  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12  * copies of the Software, and to permit persons to whom the Software is
13  * furnished to do so, subject to the following conditions:
14  *
15  * The above copyright notice and this permission notice shall be included in
16  * all copies or substantial portions of the Software.
17  *
18  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24  * THE SOFTWARE.
25  *
26  */
27 
28 #include "WidgetDropDown.h"
29 #include "../../Include/Rocket/Core/Math.h"
30 #include "../../Include/Rocket/Core/Factory.h"
31 #include "../../Include/Rocket/Core/ElementUtilities.h"
32 #include "../../Include/Rocket/Core/Event.h"
33 #include "../../Include/Rocket/Core/Input.h"
34 #include "../../Include/Rocket/Core/Property.h"
35 #include "../../Include/Rocket/Core/StyleSheetKeywords.h"
36 #include "../../Include/Rocket/Controls/ElementFormControl.h"
37 
38 namespace Rocket {
39 namespace Controls {
40 
WidgetDropDown(ElementFormControl * element)41 WidgetDropDown::WidgetDropDown(ElementFormControl* element)
42 {
43 	parent_element = element;
44 
45 	box_layout_dirty = false;
46 	value_layout_dirty = false;
47 
48 	selected_option = -1;
49 
50 	// Create the button and selection elements.
51 	button_element = Core::Factory::InstanceElement(parent_element, "*", "selectarrow", Rocket::Core::XMLAttributes());
52 	value_element = Core::Factory::InstanceElement(element, "*", "selectvalue", Rocket::Core::XMLAttributes());
53 	selection_element = Core::Factory::InstanceElement(parent_element, "*", "selectbox", Rocket::Core::XMLAttributes());
54 
55 	value_element->SetProperty("overflow", "hidden");
56 
57 	selection_element->SetProperty("visibility", "hidden");
58 	selection_element->SetProperty("z-index", Core::Property(1.0f, Core::Property::NUMBER));
59 	selection_element->SetProperty("clip", "none");
60 
61 	parent_element->AddEventListener("click", this, true);
62 	parent_element->AddEventListener("blur", this);
63 	parent_element->AddEventListener("focus", this);
64 	parent_element->AddEventListener("keydown", this, true);
65 
66 	// Add the elements to our parent element.
67 	parent_element->AppendChild(button_element, false);
68 	parent_element->AppendChild(selection_element, false);
69 	parent_element->AppendChild(value_element, false);
70 }
71 
~WidgetDropDown()72 WidgetDropDown::~WidgetDropDown()
73 {
74 	ClearOptions();
75 
76 	parent_element->RemoveEventListener("click", this, true);
77 	parent_element->RemoveEventListener("blur", this);
78 	parent_element->RemoveEventListener("focus", this);
79 	parent_element->RemoveEventListener("keydown", this, true);
80 
81 	button_element->RemoveReference();
82 	selection_element->RemoveReference();
83 	value_element->RemoveReference();
84 }
85 
86 // Updates the selection box layout if necessary.
OnRender()87 void WidgetDropDown::OnRender()
88 {
89 	if (box_layout_dirty)
90 	{
91 		Core::Box box;
92 		Core::ElementUtilities::BuildBox(box, parent_element->GetBox().GetSize(), selection_element);
93 
94 		// Layout the selection box.
95 		Core::ElementUtilities::FormatElement(selection_element, parent_element->GetBox().GetSize(Core::Box::BORDER));
96 		selection_element->SetOffset(Rocket::Core::Vector2f(box.GetEdge(Core::Box::MARGIN, Core::Box::LEFT), parent_element->GetBox().GetSize(Core::Box::BORDER).y + box.GetEdge(Core::Box::MARGIN, Core::Box::TOP)), parent_element);
97 
98 		box_layout_dirty = false;
99 	}
100 
101 	if (value_layout_dirty)
102 	{
103 		Core::ElementUtilities::FormatElement(value_element, parent_element->GetBox().GetSize(Core::Box::BORDER));
104 		value_element->SetOffset(parent_element->GetBox().GetPosition(Core::Box::CONTENT), parent_element);
105 
106 		value_layout_dirty = false;
107 	}
108 }
109 
OnLayout()110 void WidgetDropDown::OnLayout()
111 {
112 	if(parent_element->IsDisabled())
113 	{
114 		// Propagate disabled state to selectvalue and selectarrow
115 		value_element->SetPseudoClass("disabled", true);
116 		button_element->SetPseudoClass("disabled", true);
117 	}
118 
119 	// Layout the button and selection boxes.
120 	Core::Box parent_box = parent_element->GetBox();
121 
122 	Core::ElementUtilities::PositionElement(button_element, Rocket::Core::Vector2f(0, 0), Core::ElementUtilities::TOP_RIGHT);
123 	Core::ElementUtilities::PositionElement(selection_element, Rocket::Core::Vector2f(0, 0), Core::ElementUtilities::TOP_LEFT);
124 
125 	// Calculate the value element position and size.
126 	Rocket::Core::Vector2f size;
127 	size.x = parent_element->GetBox().GetSize(Core::Box::CONTENT).x - button_element->GetBox().GetSize(Core::Box::MARGIN).x;
128 	size.y = parent_element->GetBox().GetSize(Core::Box::CONTENT).y;
129 
130 	value_element->SetOffset(parent_element->GetBox().GetPosition(Core::Box::CONTENT), parent_element);
131 	value_element->SetBox(Core::Box(size));
132 
133 	box_layout_dirty = true;
134 	value_layout_dirty = true;
135 }
136 
137 // Sets the value of the widget.
SetValue(const Rocket::Core::String & _value)138 void WidgetDropDown::SetValue(const Rocket::Core::String& _value)
139 {
140 	for (size_t i = 0; i < options.size(); ++i)
141 	{
142 		if (options[i].GetValue() == _value)
143 		{
144 			SetSelection((int) i);
145 			return;
146 		}
147 	}
148 
149 	value = _value;
150 	value_element->SetInnerRML(value);
151 	value_layout_dirty = true;
152 
153 	selected_option = -1;
154 }
155 
156 // Returns the current value of the widget.
GetValue() const157 const Rocket::Core::String& WidgetDropDown::GetValue() const
158 {
159 	return value;
160 }
161 
162 // Sets the index of the selection. If the new index lies outside of the bounds, it will be clamped.
SetSelection(int selection,bool force)163 void WidgetDropDown::SetSelection(int selection, bool force)
164 {
165 	Rocket::Core::String new_value;
166 
167 	if (selection < 0 ||
168 		selection >= (int) options.size())
169 	{
170 		selection = -1;
171 	}
172 	else
173 	{
174 		new_value = options[selection].GetValue();
175 	}
176 
177 	if (force ||
178 		selection != selected_option ||
179 		value != new_value)
180 	{
181 		selected_option = selection;
182 		value = new_value;
183 
184 		Rocket::Core::String value_rml;
185 		if (selected_option >= 0)
186 			options[selected_option].GetElement()->GetInnerRML(value_rml);
187 
188 		value_element->SetInnerRML(value_rml);
189 		value_layout_dirty = true;
190 
191 		Rocket::Core::Dictionary parameters;
192 		parameters.Set("value", value);
193 		parent_element->DispatchEvent("change", parameters);
194 	}
195 }
196 
197 // Returns the index of the currently selected item.
GetSelection() const198 int WidgetDropDown::GetSelection() const
199 {
200 	return selected_option;
201 }
202 
203 // Adds a new option to the select control.
AddOption(const Rocket::Core::String & rml,const Rocket::Core::String & value,int before,bool select,bool selectable)204 int WidgetDropDown::AddOption(const Rocket::Core::String& rml, const Rocket::Core::String& value, int before, bool select, bool selectable)
205 {
206 	// Instance a new element for the option.
207 	Core::Element* element = Core::Factory::InstanceElement(selection_element, "*", "option", Rocket::Core::XMLAttributes());
208 
209 	// Force to block display and inject the RML. Register a click handler so we can be notified of selection.
210 	element->SetProperty("display", "block");
211 	element->SetProperty("clip", "auto");
212 	element->SetInnerRML(rml);
213 	element->AddEventListener("click", this);
214 
215 	int option_index;
216 	if (before < 0 ||
217 		before >= (int) options.size())
218 	{
219 		selection_element->AppendChild(element);
220 		options.push_back(SelectOption(element, value, selectable));
221 		option_index = (int) options.size() - 1;
222 	}
223 	else
224 	{
225 		selection_element->InsertBefore(element, selection_element->GetChild(before));
226 		options.insert(options.begin() + before, SelectOption(element, value, selectable));
227 		option_index = before;
228 	}
229 
230 	element->RemoveReference();
231 
232 	// Select the option if appropriate.
233 	if (select)
234 		SetSelection(option_index);
235 
236 	box_layout_dirty = true;
237 	return option_index;
238 }
239 
240 // Removes an option from the select control.
RemoveOption(int index)241 void WidgetDropDown::RemoveOption(int index)
242 {
243 	if (index < 0 ||
244 		index >= (int) options.size())
245 		return;
246 
247 	// Remove the listener and delete the option element.
248 	options[index].GetElement()->RemoveEventListener("click", this);
249 	selection_element->RemoveChild(options[index].GetElement());
250 	options.erase(options.begin() + index);
251 
252 	box_layout_dirty = true;
253 }
254 
255 // Removes all options from the list.
ClearOptions()256 void WidgetDropDown::ClearOptions()
257 {
258 	while (!options.empty())
259 		RemoveOption((int) options.size() - 1);
260 }
261 
262 // Returns on of the widget's options.
GetOption(int index)263 SelectOption* WidgetDropDown::GetOption(int index)
264 {
265 	if (index < 0 ||
266 		index >= GetNumOptions())
267 		return NULL;
268 
269 	return &options[index];
270 }
271 
272 // Returns the number of options in the widget.
GetNumOptions() const273 int WidgetDropDown::GetNumOptions() const
274 {
275 	return (int) options.size();
276 }
277 
ProcessEvent(Core::Event & event)278 void WidgetDropDown::ProcessEvent(Core::Event& event)
279 {
280 	if (parent_element->IsDisabled())
281 		return;
282 
283 	// Process the button onclick
284 	if (event == "click")
285 	{
286 
287 		if (event.GetCurrentElement()->GetParentNode() == selection_element)
288 		{
289 			// Find the element in the options and fire the selection event
290 			for (size_t i = 0; i < options.size(); i++)
291 			{
292 				if (options[i].GetElement() == event.GetCurrentElement())
293 				{
294 					if (options[i].IsSelectable())
295 					{
296 						SetSelection((int)i);
297 						event.StopPropagation();
298 
299 						ShowSelectBox(false);
300 						parent_element->Focus();
301 					}
302 				}
303 			}
304 		}
305 		else
306 		{
307 			// We have to check that this event isn't targeted to an element
308 			// inside the selection box as we'll get all events coming from our
309 			// root level select element as well as the ones coming from options (which
310 			// get caught in the above if)
311 			Core::Element* element = event.GetTargetElement();
312 			while (element && element != parent_element)
313 			{
314 				if (element == selection_element)
315 					return;
316 				element = element->GetParentNode();
317 			}
318 
319 			if (selection_element->GetProperty< int >("visibility") == Core::VISIBILITY_HIDDEN)
320 				ShowSelectBox(true);
321 			else
322 				ShowSelectBox(false);
323 		}
324 	}
325 	else if (event == "blur" && event.GetTargetElement() == parent_element)
326 	{
327 		ShowSelectBox(false);
328 	}
329 	else if (event == "keydown")
330 	{
331 		Core::Input::KeyIdentifier key_identifier = (Core::Input::KeyIdentifier) event.GetParameter< int >("key_identifier", 0);
332 
333 		switch (key_identifier)
334 		{
335 			case Core::Input::KI_UP:
336 				SetSelection((selected_option - 1 + (int)options.size()) % (int)options.size());
337 				break;
338 			case Core::Input::KI_DOWN:
339 				SetSelection((selected_option + 1) % (int)options.size());
340 				break;
341 			default:
342 				break;
343 		}
344 	}
345 
346 	if (event.GetTargetElement() == parent_element)
347 	{
348 		if (event == "focus")
349 		{
350 			value_element->SetPseudoClass("focus", true);
351 			button_element->SetPseudoClass("focus", true);
352 		}
353 		else if (event == "blur")
354 		{
355 			value_element->SetPseudoClass("focus", false);
356 			button_element->SetPseudoClass("focus", false);
357 		}
358 	}
359 
360 }
361 
362 // Shows or hides the selection box.
ShowSelectBox(bool show)363 void WidgetDropDown::ShowSelectBox(bool show)
364 {
365 	if (show)
366 	{
367 		selection_element->SetProperty("visibility", "visible");
368 		value_element->SetPseudoClass("checked", true);
369 		button_element->SetPseudoClass("checked", true);
370 	}
371 	else
372 	{
373 		selection_element->SetProperty("visibility", "hidden");
374 		value_element->SetPseudoClass("checked", false);
375 		button_element->SetPseudoClass("checked", false);
376 	}
377 }
378 
379 }
380 }
381