1 ///////////////////////////////////////////////////////////////////////////////
2 //            Copyright (C) 2004-2010 by The Allacrost Project
3 //                         All Rights Reserved
4 //
5 // This code is licensed under the GNU GPL version 2. It is free software
6 // and you may modify it and/or redistribute it under the terms of this license.
7 // See http://www.gnu.org/copyleft/gpl.html for details.
8 ///////////////////////////////////////////////////////////////////////////////
9 
10 #include <sstream>
11 
12 #include "option.h"
13 #include "video.h"
14 
15 using namespace std;
16 
17 using namespace hoa_utils;
18 using namespace hoa_video;
19 using namespace hoa_video::private_video;
20 using namespace hoa_input;
21 using namespace hoa_gui::private_gui;
22 
23 namespace hoa_gui {
24 
25 ////////////////////////////////////////////////////////////////////////////////
26 // Option class methods
27 ////////////////////////////////////////////////////////////////////////////////
28 
Option()29 Option::Option() :
30 	disabled(false),
31 	image(NULL)
32 {}
33 
34 
35 
~Option()36 Option::~Option() {
37 	Clear();
38 }
39 
40 
41 
Option(const Option & copy)42 Option::Option(const Option& copy) :
43 	disabled(copy.disabled),
44 	elements(copy.elements),
45 	text(copy.text)
46 {
47 	if (copy.image == NULL) {
48 		image = NULL;
49 	}
50 	else {
51 		image = new StillImage(*(copy.image));
52 	}
53 }
54 
55 
56 
operator =(const Option & copy)57 Option& Option::operator=(const Option& copy) {
58 	// Handle the case were a dumbass assigns an object to itself
59 	if (this == &copy) {
60 		return *this;
61 	}
62 
63 	disabled = copy.disabled;
64 	elements = copy.elements;
65 	text = copy.text;
66 	if (copy.image == NULL) {
67 		image = NULL;
68 	}
69 	else {
70 		image = new StillImage(*(copy.image));
71 	}
72 
73 	return *this;
74 }
75 
76 
77 
Clear()78 void Option::Clear() {
79 	disabled = false;
80 	elements.clear();
81 	text.clear();
82 	if (image != NULL) {
83 		delete image;
84 		image = NULL;
85 	}
86 }
87 
88 ////////////////////////////////////////////////////////////////////////////////
89 // OptionBox class methods
90 ////////////////////////////////////////////////////////////////////////////////
91 
OptionBox()92 OptionBox::OptionBox() :
93 	GUIControl(),
94 	_initialized(false),
95 	_number_rows(1),
96 	_number_columns(1),
97 	_number_cell_rows(1),
98 	_number_cell_columns(1),
99 	_cell_width(0.0f),
100 	_cell_height(0.0f),
101 	_selection_mode(VIDEO_SELECT_SINGLE),
102 	_horizontal_wrap_mode(VIDEO_WRAP_MODE_NONE),
103 	_vertical_wrap_mode(VIDEO_WRAP_MODE_NONE),
104 	_enable_switching(false),
105 	_draw_left_column(0),
106 	_draw_top_row(0),
107 	_cursor_xoffset(0.0f),
108 	_cursor_yoffset(0.0f),
109 	_scroll_offset(0.0f),
110 	_option_xalign(VIDEO_X_LEFT),
111 	_option_yalign(VIDEO_Y_CENTER),
112 	_scissoring(false),
113 	_scissoring_owner(false),
114 	_draw_horizontal_arrows(false),
115 	_draw_vertical_arrows(false),
116 	_grey_up_arrow(false),
117 	_grey_down_arrow(false),
118 	_grey_left_arrow(false),
119 	_grey_right_arrow(false),
120 	_event(0),
121 	_selection(0),
122 	_first_selection(-1),
123 	_cursor_state(VIDEO_CURSOR_STATE_VISIBLE),
124 	_blink(false),
125 	_blink_time(0),
126 	_scrolling(false),
127 	_scroll_time(0),
128 	_scroll_direction(0),
129 	_horizontal_arrows_position(H_POSITION_BOTTOM),
130 	_vertical_arrows_position(V_POSITION_RIGHT)
131 {
132 	// TEMP
133 	_width = 1.0f;
134 	_height = 1.0f;
135 }
136 
Update(uint32 frame_time)137 void OptionBox::Update(uint32 frame_time) {
138 	_event = 0; // Clear all events
139 
140 	_blink = ((_blink_time / VIDEO_CURSOR_BLINK_RATE) % 2) == 1;
141 	_blink_time += frame_time;
142 
143 	if (_scrolling) {
144 		_scroll_time += frame_time;
145 
146     	if (_scroll_time > VIDEO_OPTION_SCROLL_TIME) {
147 			_scroll_time = 0;
148 			_scrolling = false;
149 			_scroll_offset = 0.0f;
150 		}
151 		else {
152 			// [phuedx] Calculate the _scroll_offset independant of the coordinate system
153 			_scroll_offset = (_scroll_time / static_cast<float>(VIDEO_OPTION_SCROLL_TIME)) * _cell_height;
154 			if (_scroll_direction == -1 ) { // Up
155 				_scroll_offset = _cell_height - _scroll_offset;
156 			}
157 		}
158 	}
159 }
160 
161 
162 
Draw()163 void OptionBox::Draw() {
164 	// Do nothing if the option box is not properly initialized
165 	if (!IsInitialized(_initialization_errors)) {
166 		cout << "ERROR: Could not draw OptionBox" << endl;
167 		return;
168 	}
169 
170 	VideoManager->PushState();
171 	VideoManager->SetDrawFlags(_xalign, _yalign, VIDEO_BLEND, 0);
172 	VideoManager->DisableScissoring();
173 
174 	// TODO: This call is also made at the end of this function. It is made here because for some
175 	// strange reason, only the option box outline is drawn and not the outline for the individual
176 	// cells. I'm not sure what part of the code between here and the end of this function could
177 	// affect that. This bug needs to be resolved and then this call to _DEBUG_DrawOutline() should
178 	// be removed, leaving only the call at the bottom of the function
179 	if (GUIManager->DEBUG_DrawOutlines() == true) {
180 		//_DEBUG_DrawOutline();
181 	}
182 	float left, right, bottom, top;
183 
184 	// ---------- (1) Determine the edge dimensions of the option box
185 	left = 0.0f;
186 	right = _number_cell_columns * _cell_width;
187 	bottom = 0.0f;
188 	top = _number_cell_rows * _cell_height;
189 	CalculateAlignedRect(left, right, bottom, top);
190 
191 	CoordSys &cs = VideoManager->_current_context.coordinate_system;
192 
193 	// ---------- (2) Determine the option cells to be drawn and any offsets needed for scrolling
194 	VideoManager->SetDrawFlags(_option_xalign, _option_yalign, VIDEO_X_NOFLIP, VIDEO_Y_NOFLIP, VIDEO_BLEND, 0);
195 
196 	float xoff = _cell_width * cs.GetHorizontalDirection();
197 	float yoff = -_cell_height * cs.GetVerticalDirection();
198 	bool finished = false;
199 
200 	// [phuedx] Align the scroll offset with the current coordinate system
201 	_scroll_offset *= cs.GetVerticalDirection();
202 
203 	OptionCellBounds bounds;
204 	bounds.y_top = top + _scroll_offset;
205 	bounds.y_center = bounds.y_top - 0.5f * _cell_height * cs.GetVerticalDirection();
206 	bounds.y_bottom = (bounds.y_center * 2.0f) - bounds.y_top;
207 
208 
209   // ---------- (3) Iterate through all the visible option cells and draw them and the draw cursor
210 	for (uint32 row = _draw_top_row; row < _draw_top_row + _number_cell_rows && finished == false; row++) {
211 
212 		bounds.x_left = left;
213 		bounds.x_center = bounds.x_left + (0.5f * _cell_width * cs.GetHorizontalDirection());
214 		bounds.x_right = (bounds.x_center * 2.0f) - bounds.x_left;
215 
216 		// Draw the columns of options
217 		for (uint32 col = _draw_left_column; col < _draw_left_column + _number_cell_columns; ++col) {
218 			uint32 index = row * _number_cell_columns + col;
219 
220 			// If there are more visible cells than there are options available we leave those cells empty
221 			if (index >= GetNumberOptions()) {
222 				finished = true;
223 				break;
224 			}
225 
226 			float left_edge = 999999.0f; // The x offset to where the visible option contents begin
227 			_DrawOption(_options.at(index), bounds, _scroll_offset, left_edge);
228 
229 			// Draw the cursor if the previously drawn option was or is selected
230 			if ((static_cast<int32>(index) == _selection || static_cast<int32>(index) == _first_selection) &&
231 				_cursor_state != VIDEO_CURSOR_STATE_HIDDEN && (_cursor_state != VIDEO_CURSOR_STATE_BLINKING || _blink == true))
232 			{
233 				// If this option was the first selection, draw it darkened so that it has a different appearance
234 				bool darken = (static_cast<int32>(index) == _first_selection) ? true : false;
235 				_DrawCursor(bounds, _scroll_offset, left_edge, darken);
236 			}
237 
238 			bounds.x_left += xoff;
239 			bounds.x_center += xoff;
240 			bounds.x_right += xoff;
241 		} // for (int32 col = 0; col < _number_columns; ++col)
242 
243 		bounds.y_top += yoff;
244 		bounds.y_center += yoff;
245 		bounds.y_bottom += yoff;
246 	} // for (int32 row = row_min; row < row_max; row++)
247 
248 	// ---------- (4) Draw scroll arrows where appropriate
249 	_DetermineScrollArrows();
250 	std::vector<StillImage>* arrows = GUIManager->GetScrollArrows();
251 
252 
253 	float w, h;
254 	this->GetDimensions(w, h);
255 
256 	if (_draw_vertical_arrows) {
257 		VideoManager->SetDrawFlags(VIDEO_X_RIGHT, VIDEO_Y_BOTTOM, VIDEO_BLEND, 0);
258 		VideoManager->Move(right, top);
259 		if (_grey_up_arrow)
260 			arrows->at(4).Draw();
261 		else
262 			arrows->at(0).Draw();
263 
264 		VideoManager->SetDrawFlags(VIDEO_X_RIGHT, VIDEO_Y_TOP, VIDEO_BLEND, 0);
265 		VideoManager->Move(right, bottom);
266 		if (_grey_down_arrow)
267 			arrows->at(5).Draw();
268 		else
269 			arrows->at(1).Draw();
270 	}
271 
272 	if (_draw_horizontal_arrows) {
273 		VideoManager->SetDrawFlags(VIDEO_X_RIGHT, VIDEO_Y_BOTTOM, VIDEO_BLEND, 0);
274 		VideoManager->Move(left, bottom);
275 		if (_grey_left_arrow)
276 			arrows->at(7).Draw();
277 		else
278 			arrows->at(3).Draw();
279 
280 		VideoManager->SetDrawFlags(VIDEO_X_LEFT, VIDEO_Y_BOTTOM, VIDEO_BLEND, 0);
281 		VideoManager->Move(right, bottom);
282 		if (_grey_right_arrow)
283 			arrows->at(6).Draw();
284 		else
285 			arrows->at(2).Draw();
286 	}
287 
288 	VideoManager->SetDrawFlags(_xalign, _yalign, VIDEO_BLEND, 0);
289 
290 	if (GUIManager->DEBUG_DrawOutlines() == true)
291 		GUIControl::_DEBUG_DrawOutline();
292 
293 	VideoManager->PopState();
294 } // void OptionBox::Draw()
295 
296 
297 
SetDimensions(float width,float height,uint8 num_cols,uint8 num_rows,uint8 cell_cols,uint8 cell_rows)298 void OptionBox::SetDimensions(float width, float height, uint8 num_cols, uint8 num_rows, uint8 cell_cols, uint8 cell_rows) {
299 	if (num_rows == 0 || num_cols == 0) {
300 		IF_PRINT_WARNING(VIDEO_DEBUG) << "num_rows/num_cols argument was zero" << endl;
301 		return;
302 	}
303 
304 	if (cell_rows == 0 || cell_cols == 0) {
305 		IF_PRINT_WARNING(VIDEO_DEBUG) << "cell_rows/cell_cols argument was zero" << endl;
306 		return;
307 	}
308 
309 	if (num_rows < cell_rows || num_cols < cell_cols) {
310 		IF_PRINT_WARNING(VIDEO_DEBUG) << "num_rows/num_cols was less than cell_rows/cell_cols" << endl;
311 		return;
312 	}
313 
314 	_width = width;
315 	_height = height;
316 	_number_columns = num_cols;
317 	_number_rows = num_rows;
318 	_number_cell_columns = cell_cols;
319 	_number_cell_rows = cell_rows;
320 	_cell_width = _width / cell_cols;
321 	_cell_height = _height / cell_rows;
322 }
323 
324 
325 
SetOptions(const vector<ustring> & option_text)326 void OptionBox::SetOptions(const vector<ustring>& option_text) {
327 	ClearOptions();
328 	for (vector<ustring>::const_iterator i = option_text.begin(); i != option_text.end(); i++) {
329 		const ustring& str = *i;
330 		Option option;
331 
332 		if (_ConstructOption(str, option) == false) {
333 			ClearOptions();
334 			IF_PRINT_WARNING(VIDEO_DEBUG) << "an option contained an invalid formatted string: " << MakeStandardString(*i) << endl;
335 			return;
336 		}
337 		_options.push_back(option);
338 	}
339 }
340 
341 
342 
ClearOptions()343 void OptionBox::ClearOptions() {
344 	_options.clear();
345 }
346 
347 
348 
AddOption()349 void OptionBox::AddOption() {
350 	Option option;
351 	if (_ConstructOption(ustring(), option) == false) {
352 		IF_PRINT_WARNING(VIDEO_DEBUG) << "failed to construct option using an empty string"  << endl;
353 		return;
354 	}
355 
356 	_options.push_back(option);
357 }
358 
359 
360 
AddOption(const hoa_utils::ustring & text)361 void OptionBox::AddOption(const hoa_utils::ustring& text) {
362 	Option option;
363 	if (_ConstructOption(text, option) == false) {
364 		IF_PRINT_WARNING(VIDEO_DEBUG) << "argument contained an invalid formatted string: " << MakeStandardString(text) << endl;
365 		return;
366 	}
367 
368 	_options.push_back(option);
369 }
370 
371 
372 
AddOptionElementText(uint32 option_index,const ustring & text)373 void OptionBox::AddOptionElementText(uint32 option_index, const ustring& text) {
374 	if (option_index >= GetNumberOptions()) {
375 		IF_PRINT_WARNING(VIDEO_DEBUG) << "out-of-range option_index argument: " << option_index << endl;
376 		return;
377 	}
378 
379 	Option& this_option = _options[option_index];
380 	OptionElement new_element;
381 
382 	new_element.type = VIDEO_OPTION_ELEMENT_TEXT;
383 	new_element.value = static_cast<int32>(this_option.text.size());
384 	this_option.text.push_back(text);
385 	this_option.elements.push_back(new_element);
386 }
387 
388 
389 
AddOptionElementImage(uint32 option_index,string & image_filename)390 void OptionBox::AddOptionElementImage(uint32 option_index, string& image_filename) {
391 	if (option_index >= GetNumberOptions()) {
392 		IF_PRINT_WARNING(VIDEO_DEBUG) << "out-of-range option_index argument: " << option_index << endl;
393 		return;
394 	}
395 
396 	Option& this_option = _options[option_index];
397 	OptionElement new_element;
398 
399 	new_element.type = VIDEO_OPTION_ELEMENT_IMAGE;
400 	new_element.value = 0;
401 
402 	this_option.image = new StillImage();
403 	if (this_option.image->Load(image_filename) == false) {
404 		IF_PRINT_WARNING(VIDEO_DEBUG) << "failed to add image element because image file load failed" << image_filename << endl;
405 		delete this_option.image;
406 		this_option.image = NULL;
407 		return;
408 	}
409 
410 	this_option.elements.push_back(new_element);
411 }
412 
413 
414 
AddOptionElementImage(uint32 option_index,const StillImage * image)415 void OptionBox::AddOptionElementImage(uint32 option_index, const StillImage* image) {
416 	if (option_index >= GetNumberOptions()) {
417 		IF_PRINT_WARNING(VIDEO_DEBUG) << "out-of-range option_index argument: " << option_index << endl;
418 		return;
419 	}
420 	if (image == NULL) {
421 		IF_PRINT_WARNING(VIDEO_DEBUG) << "image argument was NULL" << endl;
422 		return;
423 	}
424 
425 	Option& this_option = _options[option_index];
426 	OptionElement new_element;
427 
428 	new_element.type = VIDEO_OPTION_ELEMENT_IMAGE;
429 	new_element.value = 0;
430 
431 	this_option.image = new StillImage(*image);
432 	this_option.elements.push_back(new_element);
433 }
434 
435 
436 
AddOptionElementAlignment(uint32 option_index,OptionElementType position_type)437 void OptionBox::AddOptionElementAlignment(uint32 option_index, OptionElementType position_type) {
438 	if (option_index >= GetNumberOptions()) {
439 		IF_PRINT_WARNING(VIDEO_DEBUG) << "out-of-range option_index argument: " << option_index << endl;
440 		return;
441 	}
442 	if ((position_type != VIDEO_OPTION_ELEMENT_LEFT_ALIGN) &&
443 		(position_type != VIDEO_OPTION_ELEMENT_CENTER_ALIGN) &&
444 		(position_type != VIDEO_OPTION_ELEMENT_RIGHT_ALIGN))
445 	{
446 		IF_PRINT_WARNING(VIDEO_DEBUG) << "invalid position_type argument" << position_type <<  endl;
447 	}
448 
449 	Option& this_option = _options[option_index];
450 	OptionElement new_element;
451 
452 	new_element.type = position_type;
453 	new_element.value = 0;
454 	this_option.elements.push_back(new_element);
455 }
456 
457 
458 
SetOptionText(uint32 index,const hoa_utils::ustring & text)459 bool OptionBox::SetOptionText(uint32 index, const hoa_utils::ustring &text) {
460 	if (index >= GetNumberOptions()) {
461 		IF_PRINT_WARNING(VIDEO_DEBUG) << "argument was invalid (out of bounds): " << index << endl;
462 		return false;
463 	}
464 
465 	_ConstructOption(text, _options[index]);
466 	return true;
467 }
468 
469 
470 
SetSelection(uint32 index)471 void OptionBox::SetSelection(uint32 index) {
472 	if (index >= GetNumberOptions()) {
473 		IF_PRINT_WARNING(VIDEO_DEBUG) << "argument was invalid (out of bounds): " << index << endl;
474 		return;
475 	}
476 
477 	_selection = index;
478 	int32 select_row = _selection / _number_columns;
479 
480 	// If the new selection isn't currently being displayed, instantly scroll to it
481 	if (select_row < _scroll_offset || select_row > (_scroll_offset + _number_rows - 1)) {
482 		_scroll_offset = select_row - _number_rows + 1;
483 
484 		int32 total_num_rows = (GetNumberOptions() + _number_columns - 1) / _number_columns;
485 
486 		if (_scroll_offset + _number_rows >= total_num_rows) {
487 			_scroll_offset = total_num_rows - _number_rows;
488 		}
489 	}
490 }
491 
492 
493 
EnableOption(uint32 index,bool enable)494 void OptionBox::EnableOption(uint32 index, bool enable) {
495 	if (index >= GetNumberOptions()) {
496 		IF_PRINT_WARNING(VIDEO_DEBUG) << "argument index was invalid: " << index << endl;
497 		return;
498 	}
499 
500 	_options[index].disabled = !enable;
501 }
502 
503 
504 
IsOptionEnabled(uint32 index)505 bool OptionBox::IsOptionEnabled(uint32 index) {
506 	if (index >= GetNumberOptions()) {
507 		IF_PRINT_WARNING(VIDEO_DEBUG) << "argument index was invalid: " << index << endl;
508 		return false;
509 	}
510 
511 	return (!_options[index].disabled);
512 }
513 
514 
515 
IsEnabled(uint32 index) const516 bool OptionBox::IsEnabled(uint32 index) const {
517 	if (index >= GetNumberOptions()) {
518 		IF_PRINT_WARNING(VIDEO_DEBUG) << "argument index was invalid: " << index << endl;
519 		return false;
520 	}
521 
522 	return !_options[index].disabled;
523 }
524 
525 
526 
GetEmbeddedImage(uint32 index) const527 StillImage* OptionBox::GetEmbeddedImage(uint32 index) const {
528 	if (index >= GetNumberOptions()) {
529 		IF_PRINT_WARNING(VIDEO_DEBUG) << "argument index was invalid: " << index << endl;
530 		return nullptr;
531 	}
532 
533 	return _options[index].image;
534 }
535 
536 
537 
IsInitialized(string & error_messages)538 bool OptionBox::IsInitialized(string& error_messages) {
539 	ostringstream s;
540 	error_messages.clear();
541 
542 	if (_width <= 0.0f)
543 		s << "* Invalid width (" << _width << ")" << endl;
544 
545 	if (_height <= 0.0f)
546 		s << "* Invalid height (" << _height << ")" << endl;
547 
548 	if (_number_rows <= 0)
549 		s << "* Invalid number of rows (" << _number_rows << ")" << endl;
550 
551 	if (_number_columns <= 0)
552 		s << "* Invalid number of columns (" << _number_columns << ")" << endl;
553 
554 	if (_cell_width <= 0.0f && _number_columns > 1)
555 		s << "* Invalid horizontal spacing (" << _cell_width << ")" << endl;
556 
557 	if (_cell_height <= 0.0f && _number_rows > 1)
558 		s << "* Invalid vertical spacing (" << _cell_height << ")" << endl;
559 
560 	if (_option_xalign < VIDEO_X_LEFT || _option_xalign > VIDEO_X_RIGHT)
561 		s << "* Invalid x align (" << _option_xalign << ")" << endl;
562 
563 	if (_option_yalign < VIDEO_Y_TOP || _option_yalign > VIDEO_Y_BOTTOM)
564 		s << "* Invalid y align (" << _option_yalign << ")" << endl;
565 
566 	if (_text_style.font.empty())
567 		s << "* Invalid font (none has been set)" << endl;
568 
569 	if (_selection_mode <= VIDEO_SELECT_INVALID || _selection_mode >= VIDEO_SELECT_TOTAL)
570 		s << "* Invalid selection mode (" << _selection_mode << ")" << endl;
571 
572 	error_messages = s.str();
573 
574 	if (error_messages.empty())
575 		_initialized = true;
576 	else
577 		_initialized = false;
578 
579 	return _initialized;
580 }
581 
582 // -----------------------------------------------------------------------------
583 // Input Handling Methods
584 // -----------------------------------------------------------------------------
585 
InputConfirm()586 void OptionBox::InputConfirm() {
587 	// Abort if an invalid option is selected
588 	if (_selection < 0 || _selection >= static_cast<int32>(GetNumberOptions())) {
589 		IF_PRINT_WARNING(VIDEO_DEBUG) << "an invalid (out of bounds) option was selected: " << _selection << endl;
590 		return;
591 	}
592 
593 	// Ignore input while scrolling, or if an event has already been logged
594 	if (_scrolling || _event || _options[_selection].disabled)
595 		return;
596 
597 	// Case #1: switch the position of two different options
598 	if (_enable_switching && _first_selection >= 0 && _selection != _first_selection) {
599 		Option temp = _options[_selection];
600 		_options[_selection] = _options[_first_selection];
601 		_options[_first_selection] = temp;
602 		_first_selection = -1; // Done so that we know we're not in switching mode any more
603 		_event = VIDEO_OPTION_SWITCH;
604 	}
605 
606 	// Case #2: partial confirm (confirming the first element in a double confirm)
607 	else if (_selection_mode == VIDEO_SELECT_DOUBLE && _first_selection == -1) {
608 		_first_selection = _selection;
609 	}
610 
611 	// Case #3: standard confirm
612 	else {
613 		if (_options[_selection].disabled) {
614 			// TODO: do something to tell player they confirmed on a disabled option?
615 			return;
616 		}
617 		_event = VIDEO_OPTION_CONFIRM;
618 		// Get out of switch mode
619 		_first_selection = -1;
620 	}
621 }
622 
623 
624 
InputCancel()625 void OptionBox::InputCancel() {
626 	// Ignore input while scrolling, or if an event has already been logged
627 	if (_scrolling || _event)
628 		return;
629 
630 	// If we're in switching mode unselect the first selection
631 	if (_first_selection >= 0)
632 		_first_selection = -1;
633 	else
634 		_event = VIDEO_OPTION_CANCEL;
635 }
636 
637 
638 
InputUp()639 void OptionBox::InputUp() {
640 	// Ignore input while scrolling, or if an event has already been logged
641 	if (_scrolling || _event)
642 		return;
643 
644 	if (_ChangeSelection(-1, false) == false)
645 		_event = VIDEO_OPTION_BOUNDS_UP;
646 }
647 
648 
649 
InputDown()650 void OptionBox::InputDown() {
651 	// Ignore input while scrolling, or if an event has already been logged
652 	if (_scrolling || _event)
653 		return;
654 
655 	if(_ChangeSelection(1, false) == false)
656 		_event = VIDEO_OPTION_BOUNDS_DOWN;
657 }
658 
659 
660 
InputLeft()661 void OptionBox::InputLeft() {
662 	// Ignore input while scrolling, or if an event has already been logged
663 	if (_scrolling || _event)
664 		return;
665 
666 	if (_ChangeSelection(-1, true) == false)
667 		_event = VIDEO_OPTION_BOUNDS_LEFT;
668 }
669 
670 
671 
InputRight()672 void OptionBox::InputRight() {
673 	// Ignore input while scrolling, or if an event has already been logged
674 	if (_scrolling || _event)
675 		return;
676 
677 	if (_ChangeSelection(1, true) == false)
678 		_event = VIDEO_OPTION_BOUNDS_RIGHT;
679 }
680 
681 // -----------------------------------------------------------------------------
682 // Member Access Functions
683 // -----------------------------------------------------------------------------
684 
SetTextStyle(const TextStyle & style)685 void OptionBox::SetTextStyle(const TextStyle& style) {
686 	if (TextManager->GetFontProperties(style.font) == NULL) {
687 		IF_PRINT_WARNING(VIDEO_DEBUG) << "text style references an invalid font name: " << style.font << endl;
688 		return;
689 	}
690 
691 	_text_style = style;
692 	_initialized = IsInitialized(_initialization_errors);
693 }
694 
695 
696 
SetCursorState(CursorState state)697 void OptionBox::SetCursorState(CursorState state) {
698 	if (state <= VIDEO_CURSOR_STATE_INVALID || state >= VIDEO_CURSOR_STATE_TOTAL) {
699 		IF_PRINT_WARNING(VIDEO_DEBUG) << "invalid function argument : " << state << endl;
700 		return;
701 	}
702 
703 	_cursor_state = state;
704 }
705 
706 
707 
SetHorizontalArrowsPosition(HORIZONTAL_ARROWS_POSITION position)708 void OptionBox::SetHorizontalArrowsPosition(HORIZONTAL_ARROWS_POSITION position) {
709 	_horizontal_arrows_position = position;
710 }
711 
712 
713 
SetVerticalArrowsPosition(VERTICAL_ARROWS_POSITION position)714 void OptionBox::SetVerticalArrowsPosition(VERTICAL_ARROWS_POSITION position) {
715 	_vertical_arrows_position = position;
716 }
717 
718 // -----------------------------------------------------------------------------
719 // Private Methods
720 // -----------------------------------------------------------------------------
721 
_ConstructOption(const ustring & format_string,Option & op)722 bool OptionBox::_ConstructOption(const ustring& format_string, Option& op) {
723 	op.Clear();
724 
725 	// This is a valid case. It simply means we add an option with no tags, text, or other data.
726 	if (format_string.empty()) {
727 		return true;
728 	}
729 
730 	// Copy the format_string into a temporary string that we can manipulate
731 	ustring tmp = format_string;
732 
733 	while (tmp.empty() == false) {
734 		OptionElement new_element;
735 
736 		if (tmp[0] == OPEN_TAG) { // Process a new tag
737 			size_t length = tmp.length();
738 
739 			if (length < 3) {
740 				// All formatting tags are at least 3 characters long because you need the opening (<)
741 				// and close (>) plus stuff in the middle. So anything less than 3 characters is a problem.
742 
743 				IF_PRINT_WARNING(VIDEO_DEBUG) << "failed because a tag opening was detected with an inadequate "
744 					<< "number of remaining characters to construct a full tag: " << MakeStandardString(format_string) << endl;
745 				return false;
746 			}
747 
748 			size_t end_position = tmp.find(END_TAG);
749 
750 			if (end_position == ustring::npos) { // Did not find the end of the tag
751 				IF_PRINT_WARNING(VIDEO_DEBUG) << "failed because a matching end tag could not be found for an open tag: "
752 					<< MakeStandardString(format_string) << endl;
753 				return false;
754 			}
755 
756 			if (tmp[2] == END_TAG) { // First check if the tag is a 1-character alignment tag
757 				if (tmp[1] == CENTER_TAG1 || tmp[1] == CENTER_TAG2) {
758 					new_element.type = VIDEO_OPTION_ELEMENT_CENTER_ALIGN;
759 				}
760 				else if (tmp[1] == RIGHT_TAG1 || tmp[1] == RIGHT_TAG2) {
761 					new_element.type = VIDEO_OPTION_ELEMENT_RIGHT_ALIGN;
762 				}
763 				else if (tmp[1] == LEFT_TAG1 || tmp[1] == LEFT_TAG2) {
764 					new_element.type = VIDEO_OPTION_ELEMENT_LEFT_ALIGN;
765 				}
766 			}
767 			else { // The tag contains more than 1-character
768 				// Extract the tag string
769 				string tag_text = MakeStandardString(tmp.substr(1, end_position - 1));
770 
771 				if (IsStringNumeric(tag_text)) { // Then this must be a positioning tag
772 					new_element.type  = VIDEO_OPTION_ELEMENT_POSITION;
773 					new_element.value = atoi(tag_text.c_str());
774 				}
775 				else { // Then this must be an image tag
776 					if (op.image != NULL) {
777 						IF_PRINT_WARNING(VIDEO_DEBUG) << "failed because two image tags were embedded within a single option"
778 							<< MakeStandardString(format_string) << endl;
779 						return false;
780 					}
781 					op.image = new StillImage();
782 					if (op.image->Load(tag_text) == false) {
783 						IF_PRINT_WARNING(VIDEO_DEBUG) << "failed because of an invalid image tag: "
784 							<< MakeStandardString(format_string) << endl;
785 						return false;
786 					}
787 					new_element.type  = VIDEO_OPTION_ELEMENT_IMAGE;
788 					new_element.value = 0;
789 				}
790 			}
791 
792 			// Finished processing the tag so update the tmp string
793 			if (end_position == length - 1) { // End of string
794 				tmp.clear();
795 			}
796 			else {
797 				tmp = tmp.substr(end_position + 1, length - end_position - 1);
798 			}
799 		} // if (tmp[0] == OPEN_TAG)
800 
801 		else { // If this isn't a tag, then it is raw text that should be added to the option
802 			new_element.type = VIDEO_OPTION_ELEMENT_TEXT;
803 			new_element.value = static_cast<int32>(op.text.size());
804 
805 			// find the distance until the next tag
806 			size_t tag_begin = tmp.find(OPEN_TAG);
807 
808 			if (tag_begin == ustring::npos) { // There are no more tags remaining, so extract the entire string
809 				op.text.push_back(tmp);
810 				tmp.clear();
811 			}
812 			else { // Another tag remains to be processed, so extract the text substring
813 				op.text.push_back(tmp.substr(0, tag_begin));
814 				tmp = tmp.substr(tag_begin, tmp.length() - tag_begin);
815 			}
816 		}
817 
818 		op.elements.push_back(new_element);
819 	} // while (tmp.empty() == false)
820 
821 	return true;
822 } // bool _ConstructOption(const ustring& format_string, Option& option)
823 
824 
825 
_ChangeSelection(int32 offset,bool horizontal)826 bool OptionBox::_ChangeSelection(int32 offset, bool horizontal) {
827 	// Do nothing if the movement is horizontal and there is only one column with no horizontal wrap shifting
828 	if (horizontal == true && _number_cell_columns == 1 && _horizontal_wrap_mode != VIDEO_WRAP_MODE_SHIFTED)
829 		return false;
830 
831 	// Do nothing if the movement is vertical and there is only one row with no vertical wrap shifting
832 	if (horizontal == false && _number_cell_rows == 1 && _vertical_wrap_mode != VIDEO_WRAP_MODE_SHIFTED)
833 		return false;
834 
835 	// Get the row, column coordinates for the current selection
836 	int32 row = _selection / _number_columns;
837 	int32 col = _selection % _number_columns;
838 	bool bounds_exceeded = false;
839 
840 	// Determine if the movement selection will exceed a column or row bondary
841 	int new_row = (row + offset) * _number_columns;
842 	if ((horizontal == true && ((col + offset < 0) || (col + offset >= _number_columns) ||
843 			(col + offset >= static_cast<int32>(GetNumberOptions())))) ||
844 		(horizontal == false && ((new_row < 0) || (new_row >= _number_rows) ||
845 			(new_row >= static_cast<int32>(GetNumberOptions())))))
846 	{
847 		bounds_exceeded = true;
848 	}
849 
850 	// Case #1: movement selection is within bounds
851 	if (bounds_exceeded == false) {
852 		if (horizontal)
853 			_selection += offset;
854 		else
855 			_selection += (offset * _number_columns);
856 	}
857 
858 	// Case #2: movement exceeds bounds, no wrapping enabled
859 	else if ((horizontal == true && _horizontal_wrap_mode == VIDEO_WRAP_MODE_NONE) ||
860 		(horizontal == false && _vertical_wrap_mode == VIDEO_WRAP_MODE_NONE))
861 	{
862 		return false;
863 	}
864 
865 	// Case #3: horizontal movement with wrapping enabled
866 	else if (horizontal == true) {
867 		if (col + offset <= 0) { // The left boundary was exceeded
868 			if (_horizontal_wrap_mode == VIDEO_WRAP_MODE_STRAIGHT) {
869 				offset = _number_columns - 1;
870 			}
871 			// Make sure vertical wrapping is allowed if horizontal wrap mode is shifting
872 			else if (_horizontal_wrap_mode == VIDEO_WRAP_MODE_SHIFTED && _vertical_wrap_mode != VIDEO_WRAP_MODE_NONE) {
873 				offset += GetNumberOptions();
874 			}
875 			else {
876 				return false;
877 			}
878 		}
879 		else { // The right boundary was exceeded
880 			if (_horizontal_wrap_mode == VIDEO_WRAP_MODE_STRAIGHT)
881 				offset -= _number_columns;
882 			// Make sure vertical wrapping is allowed if horizontal wrap mode is shifting
883 			else if (_horizontal_wrap_mode == VIDEO_WRAP_MODE_SHIFTED && _vertical_wrap_mode != VIDEO_WRAP_MODE_NONE) {
884 				offset = 0;
885 				_selection++;
886 			}
887 			else
888 				return false;
889 		}
890 		_selection = (_selection + offset) % GetNumberOptions();
891 	}
892 
893 	// Case #4: vertical movement with wrapping enabled
894 	else {
895 		if (row + offset <= 0) { // The top boundary was exceeded
896 			if (_vertical_wrap_mode == VIDEO_WRAP_MODE_STRAIGHT)
897 				offset += GetNumberOptions();
898 			// Make sure horizontal wrapping is allowed if vertical wrap mode is shifting
899 			else if (_vertical_wrap_mode == VIDEO_WRAP_MODE_SHIFTED && _horizontal_wrap_mode != VIDEO_WRAP_MODE_NONE)
900 				offset += (_number_columns - 1);
901 			else
902 				return false;
903 		}
904 		else  { // The bottom boundary was exceeded
905 			if (_vertical_wrap_mode == VIDEO_WRAP_MODE_STRAIGHT) {
906 				if (row + offset >= _number_rows)
907 					offset -= GetNumberOptions();
908 			}
909 			// Make sure horizontal wrapping is allowed if vertical wrap mode is shifting
910 			else if (_vertical_wrap_mode == VIDEO_WRAP_MODE_SHIFTED && _horizontal_wrap_mode != VIDEO_WRAP_MODE_NONE)
911 				offset -= (_number_columns - 1);
912 			else
913 				return false;
914 		}
915 		_selection = (_selection + (offset * _number_columns)) % GetNumberOptions();
916 	}
917 
918 	// Determine if the new selection is not displayed in any cells. If so, scroll it into view.
919 	int32 selection_row = _selection / _number_columns;
920 	int32 selection_col = _selection % _number_columns;
921 
922 	if ((static_cast<uint32>(selection_row) < _draw_top_row)) {
923 		_scrolling = true;
924 		_scroll_time = 0;
925 		_draw_top_row = selection_row;
926 
927 		if (selection_row < _scroll_offset)
928 			_scroll_direction = -1 * (_scroll_offset - row); // scroll up
929 		else
930 			_scroll_direction = 1 * (row - _number_rows - _scroll_offset + 1); // scroll down
931 
932 		_scroll_offset += _scroll_direction;
933 	}
934 
935 	else if ((static_cast<uint32>(selection_row) >= (_draw_top_row + _number_cell_rows)) ) {
936 		_scrolling = true;
937 		_scroll_time = 0;
938 		_draw_top_row = selection_row - _number_cell_rows + 1;
939 
940 		if (selection_row < _scroll_offset)
941 			_scroll_direction = -1 * (_scroll_offset - row); // scroll up
942 		else
943 			_scroll_direction = 1 * (row - _number_rows - _scroll_offset + 1); // scroll down
944 
945 		_scroll_offset += _scroll_direction;
946 	}
947 
948 	else if ((static_cast<uint32>(selection_col) < _draw_left_column)) {
949 		_scrolling = true;
950 		_scroll_time = 0;
951 		_draw_left_column = selection_col;
952 
953 		if (selection_row < _scroll_offset)
954 			_scroll_direction = -1 * (_scroll_offset - row); // scroll up
955 		else
956 			_scroll_direction = 1 * (row - _number_rows - _scroll_offset + 1); // scroll down
957 
958 		_scroll_offset += _scroll_direction;
959 	}
960 
961 	else if ((static_cast<uint32>(selection_col) >= (_draw_left_column + _number_cell_columns))) {
962 		_scrolling = true;
963 		_scroll_time = 0;
964 		_draw_left_column = selection_col - _number_cell_columns + 1;
965 
966 		if (selection_row < _scroll_offset)
967 			_scroll_direction = -1 * (_scroll_offset - row); // scroll up
968 		else
969 			_scroll_direction = 1 * (row - _number_rows - _scroll_offset + 1); // scroll down
970 
971 		_scroll_offset += _scroll_direction;
972 	}
973 
974 	// If the new selection isn't currently being displayed, scroll it into view
975 // 	row = _selection / _number_columns;
976 // 	if (row < _scroll_offset || row >= _scroll_offset + _number_rows) {
977 // 		_scrolling = true;
978 // 		_scroll_time = 0;
979 //
980 // 		if (row < _scroll_offset)
981 // 			_scroll_direction = -1 * (_scroll_offset - row); // scroll up
982 // 		else
983 // 			_scroll_direction = 1 * (row - _number_rows - _scroll_offset + 1); // scroll down
984 //
985 // 		_scroll_offset += _scroll_direction;
986 // 	}
987 
988 	_event = VIDEO_OPTION_SELECTION_CHANGE;
989 	return true;
990 } // bool OptionBox::_ChangeSelection(int32 offset, bool horizontal)
991 
992 
993 
_SetupAlignment(int32 xalign,int32 yalign,const OptionCellBounds & bounds,float & x,float & y)994 void OptionBox::_SetupAlignment(int32 xalign, int32 yalign, const OptionCellBounds& bounds, float& x, float& y) {
995 	VideoManager->SetDrawFlags(xalign, yalign, 0);
996 
997 	switch (xalign) {
998 		case VIDEO_X_LEFT:
999 			x = bounds.x_left;
1000 			break;
1001 		case VIDEO_X_CENTER:
1002 			x = bounds.x_center;
1003 			break;
1004 		default:
1005 			x = bounds.x_right;
1006 			break;
1007 	}
1008 
1009 	switch (yalign) {
1010 		case VIDEO_Y_TOP:
1011 			y = bounds.y_top;
1012 			break;
1013 		case VIDEO_Y_CENTER:
1014 			y = bounds.y_center;
1015 			break;
1016 		default:
1017 			y = bounds.y_bottom;
1018 			break;
1019 	}
1020 
1021 	VideoManager->Move(x, y);
1022 } // void OptionBox::_SetupAlignment(int32 xalign, int32 yalign, const OptionCellBounds& bounds, float& x, float& y)
1023 
1024 
1025 
_DetermineScrollArrows()1026 void OptionBox::_DetermineScrollArrows() {
1027 	_grey_up_arrow = false;
1028 	_grey_down_arrow = false;
1029 	_grey_left_arrow = false;
1030 	_grey_right_arrow = false;
1031 
1032 	_draw_horizontal_arrows = (_number_cell_columns < _number_columns) && (static_cast<int32>(GetNumberOptions()) > _number_cell_columns);
1033 	_draw_vertical_arrows = (_number_cell_rows < _number_rows) && (static_cast<int32>(GetNumberOptions()) > _number_columns * _number_cell_rows);
1034 
1035 	if (_horizontal_wrap_mode == VIDEO_WRAP_MODE_NONE) {
1036 		if (_draw_left_column == 0)
1037 			_grey_left_arrow = true;
1038 		if (static_cast<int32>(_draw_left_column + _number_cell_columns) >= _number_columns)
1039 			_grey_right_arrow = true;
1040 		if (_selection >= static_cast<int32>(_options.size() - 1))
1041 			_grey_right_arrow = true;
1042 	}
1043 
1044 	if (_vertical_wrap_mode == VIDEO_WRAP_MODE_NONE) {
1045 		if (_draw_top_row == 0)
1046 			_grey_up_arrow = true;
1047 		if (static_cast<int32>(_draw_top_row + _number_cell_rows) > _number_rows)
1048 			_grey_down_arrow = true;
1049 		if (_selection + _number_cell_columns >= static_cast<int32>(_options.size()))
1050 			_grey_down_arrow = true;
1051 	}
1052 }
1053 
1054 
1055 
_DrawOption(const Option & op,const OptionCellBounds & bounds,float scroll_offset,float & left_edge)1056 void OptionBox::_DrawOption(const Option& op, const OptionCellBounds &bounds, float scroll_offset, float& left_edge) {
1057 	// TODO: this function doesn't make use of the scroll_offset parameter currently, but I'm pretty sure it is
1058 	// needed somewhere to get scrolling full working. Once the scrolling feature has been enabled and verified
1059 	// for correctness if this paramater is still unused, remove it.
1060 
1061 	float x, y;
1062 	int32 xalign = _option_xalign;
1063 	int32 yalign = _option_yalign;
1064 	CoordSys &cs = VideoManager->_current_context.coordinate_system;
1065 
1066 	_SetupAlignment(xalign, yalign, bounds, x, y);
1067 
1068 	// Iterate through all option elements in the current option
1069 	for (int32 element = 0; element < static_cast<int32>(op.elements.size()); element++) {
1070 		switch (op.elements[element].type) {
1071 			case VIDEO_OPTION_ELEMENT_LEFT_ALIGN:
1072 			{
1073 				xalign = VIDEO_X_LEFT;
1074 				_SetupAlignment(xalign, _option_yalign, bounds, x, y);
1075 				break;
1076 			}
1077 			case VIDEO_OPTION_ELEMENT_CENTER_ALIGN:
1078 			{
1079 				xalign = VIDEO_X_CENTER;
1080 				_SetupAlignment(xalign, _option_yalign, bounds, x, y);
1081 				break;
1082 			}
1083 			case VIDEO_OPTION_ELEMENT_RIGHT_ALIGN:
1084 			{
1085 				xalign = VIDEO_X_RIGHT;
1086 				_SetupAlignment(xalign, _option_yalign, bounds, x, y);
1087 				break;
1088 			}
1089 			case VIDEO_OPTION_ELEMENT_IMAGE:
1090 			{
1091 				if (op.disabled)
1092 					op.image->Draw(Color::gray);
1093 				else
1094 					op.image->Draw(Color::white);
1095 
1096 				float width = op.image->GetWidth();
1097 				float edge = x - bounds.x_left; // edge value for VIDEO_X_LEFT
1098 				if (xalign == VIDEO_X_CENTER)
1099 					edge -= width * 0.5f * cs.GetHorizontalDirection();
1100 				else if (xalign == VIDEO_X_RIGHT)
1101 					edge -= width * cs.GetHorizontalDirection();
1102 				if (edge < left_edge)
1103 					left_edge = edge;
1104 				break;
1105 			}
1106 			case VIDEO_OPTION_ELEMENT_POSITION:
1107 			{
1108 				x = bounds.x_left + op.elements[element].value * cs.GetHorizontalDirection();
1109 				VideoManager->Move(x, y);
1110 				break;
1111 			}
1112 			case VIDEO_OPTION_ELEMENT_TEXT:
1113 			{
1114 				int32 text_index = op.elements[element].value;
1115 
1116 				if (text_index >= 0 && text_index < static_cast<int32>(op.text.size())) {
1117 					const ustring& text = op.text[text_index];
1118 					float width = static_cast<float>(VideoManager->Text()->CalculateTextWidth(_text_style.font, text));
1119 					float edge = x - bounds.x_left; // edge value for VIDEO_X_LEFT
1120 
1121 					if (xalign == VIDEO_X_CENTER)
1122 						edge -= width * 0.5f * cs.GetHorizontalDirection();
1123 					else if (xalign == VIDEO_X_RIGHT)
1124 						edge -= width * cs.GetHorizontalDirection();
1125 
1126 					if (edge < left_edge)
1127 						left_edge = edge;
1128 					if (op.disabled) {
1129 						Color saved = _text_style.color;
1130 						_text_style.color = Color::gray;
1131 						TextManager->Draw(text, _text_style);
1132 						_text_style.color = saved;
1133 					}
1134 					else {
1135 						TextManager->Draw(text, _text_style);
1136 					}
1137 				}
1138 
1139 				break;
1140 			}
1141 			case VIDEO_OPTION_ELEMENT_INVALID:
1142 			case VIDEO_OPTION_ELEMENT_TOTAL:
1143 			default:
1144 			{
1145 				IF_PRINT_WARNING(VIDEO_DEBUG) << "invalid option element type was present" << endl;
1146 				break;
1147 			}
1148 		} // switch (op.elements[element].type)
1149 	} // for (int32 element = 0; element < static_cast<int32>(op.elements.size()); element++)
1150 } // void OptionBox::_DrawOption(const Option& op, const OptionCellBounds &bounds, float scroll_offset, float& left_edge)
1151 
1152 
1153 
_DrawCursor(const OptionCellBounds & bounds,float scroll_offset,float left_edge,bool darken)1154 void OptionBox::_DrawCursor(const OptionCellBounds &bounds, float scroll_offset, float left_edge, bool darken) {
1155 	// [phuedx] In this case the scroll offset is not used, however it should be.
1156 	// The Draw() function (and all helper functions) should be able able to
1157 	// render without knowledge of the private member variable _scroll_offset.
1158 
1159 	float x, y;
1160 
1161 	// Should never scissor the cursor
1162 	VideoManager->DisableScissoring();
1163 
1164 	float cursor_offset = 0.0f;
1165 
1166 	// [phuedx] The scroll_offset has already been calculated and projected on to the current coordinate system
1167 	if (_scrolling) {
1168 		cursor_offset = -scroll_offset;
1169 	}
1170 
1171 	_SetupAlignment(VIDEO_X_LEFT, _option_yalign, bounds, x, y);
1172 	VideoManager->SetDrawFlags(VIDEO_BLEND, 0);
1173 	VideoManager->MoveRelative(left_edge + _cursor_xoffset, _cursor_yoffset + cursor_offset);
1174 
1175 	StillImage *default_cursor = VideoManager->GetDefaultCursor();
1176 
1177 	if (default_cursor == NULL)
1178 		IF_PRINT_WARNING(VIDEO_DEBUG) << "invalid (NULL) cursor image" << endl;
1179 
1180 	if (darken == false)
1181 		default_cursor->Draw();
1182 	else
1183 		default_cursor->Draw(Color(1.0f, 1.0f, 1.0f, 0.5f));
1184 } // void OptionBox::_DrawCursor(const OptionCellBounds &bounds, float scroll_offset, float left_edge, bool darken)
1185 
1186 
1187 
_DEBUG_DrawOutline()1188 void OptionBox::_DEBUG_DrawOutline() {
1189 	float left = 0.0f;
1190 	float right = _width;
1191 	float bottom = 0.0f;
1192 	float top = _height;
1193 
1194 	// Draw the outline of the option box area
1195 	VideoManager->Move(0.0f, 0.0f);
1196 	CalculateAlignedRect(left, right, bottom, top);
1197 	VideoManager->DrawRectangleOutline(left, right, bottom, top, 3, alpha_black);
1198 	VideoManager->DrawRectangleOutline(left, right, bottom, top, 1, alpha_white);
1199 
1200 	// Draw outline for inner cell rows
1201 	float cell_row = top;
1202 	for (int32 i = 1; i < _number_cell_rows; i++) {
1203 		cell_row += _cell_height;
1204 		VideoManager->DrawLine(left, cell_row, right, cell_row, 3, alpha_black);
1205 		VideoManager->DrawLine(left, cell_row, right, cell_row, 1, alpha_white);
1206 	}
1207 
1208 	// Draw outline for inner cell columns
1209 	float cell_col = left;
1210 	for (int32 i = 1; i < _number_cell_columns; i++) {
1211 		cell_col += _cell_width;
1212 		VideoManager->DrawLine(cell_col, bottom, cell_col, top, 3, alpha_black);
1213 		VideoManager->DrawLine(cell_col, bottom, cell_col, top, 1, alpha_white);
1214 	}
1215 }
1216 
1217 } // namespace hoa_gui
1218