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