xref: /haiku/src/system/boot/platform/bios_ia32/menu.cpp (revision 93aeb8c3bc3f13cb1f282e3e749258a23790d947)
1 /*
2  * Copyright 2004-2005, Axel Dörfler, axeld@pinc-software.de. All rights reserved.
3  * Distributed under the terms of the MIT License.
4  */
5 
6 
7 #include "bios.h"
8 #include "console.h"
9 #include "keyboard.h"
10 #include "smp.h"
11 #include "video.h"
12 
13 #include <boot/platform.h>
14 #include <boot/menu.h>
15 #include <safemode.h>
16 
17 #include <string.h>
18 
19 
20 // position
21 static const int32 kFirstLine = 8;
22 static const int32 kOffsetX = 10;
23 static const int32 kHelpLines = 3;
24 
25 // colors
26 static const console_color kBackgroundColor = BLACK;
27 static const console_color kTextColor = WHITE;
28 static const console_color kCopyrightColor = CYAN;
29 static const console_color kTitleColor = WHITE;
30 static const console_color kTitleBackgroundColor = RED;
31 static const console_color kHelpTextColor = WHITE;
32 
33 static const console_color kItemColor = GRAY;
34 static const console_color kSelectedItemColor = WHITE;
35 static const console_color kItemBackgroundColor = kBackgroundColor;
36 static const console_color kSelectedItemBackgroundColor = GRAY;
37 static const console_color kDisabledColor = DARK_GRAY;
38 
39 static const console_color kSliderColor = CYAN;
40 static const console_color kSliderBackgroundColor = DARK_GRAY;
41 static const console_color kArrowColor = GRAY;
42 
43 static int32 sMenuOffset = 0;
44 
45 
46 static int32
47 menu_height()
48 {
49 	return console_height() - kFirstLine - 1 - kHelpLines;
50 }
51 
52 
53 static void
54 print_spacing(int32 count)
55 {
56 	for (int32 i = 0; i < count; i++)
57 		putchar(' ');
58 }
59 
60 
61 static void
62 print_centered(int32 line, const char *text)
63 {
64 	console_set_cursor(console_width() / 2 - strlen(text) / 2, line);
65 	printf("%s", text);
66 
67 	console_set_cursor(0, 0);
68 		// this avoids unwanted line feeds
69 }
70 
71 
72 static void
73 print_item_at(int32 line, MenuItem *item, bool clearHelp = true)
74 {
75 	bool selected = item->IsSelected();
76 
77 	line -= sMenuOffset;
78 	if (line < 0 || line >= menu_height())
79 		return;
80 
81 	console_color background = selected ? kSelectedItemBackgroundColor : kItemBackgroundColor;
82 	console_color foreground = selected ? kSelectedItemColor : kItemColor;
83 
84 	if (!item->IsEnabled())
85 		foreground = kDisabledColor;
86 
87 	console_set_cursor(kOffsetX, line + kFirstLine);
88 	console_set_color(foreground, background);
89 
90 	size_t length = strlen(item->Label()) + 1;
91 
92 	if (item->Type() == MENU_ITEM_MARKABLE) {
93 		console_set_color(DARK_GRAY, background);
94 		printf(" [");
95 		console_set_color(foreground, background);
96 		printf("%c", item->IsMarked() ? 'x' : ' ');
97 		console_set_color(DARK_GRAY, background);
98 		printf("] ");
99 		console_set_color(foreground, background);
100 
101 		length += 4;
102 	} else
103 		printf(" ");
104 
105 	printf(item->Label());
106 
107 	if (item->Submenu() && item->Submenu()->Type() == CHOICE_MENU) {
108 		// show the current choice (if any)
109 		const char *text = " (Current: ";
110 		printf(text);
111 		length += strlen(text);
112 
113 		Menu *subMenu = item->Submenu();
114 		if (subMenu->ChoiceText() != NULL)
115 			text = subMenu->ChoiceText();
116 		else
117 			text = "None";
118 		length += strlen(text);
119 
120 		console_set_color(selected ? DARK_GRAY : WHITE, background);
121 
122 		printf(text);
123 
124 		console_set_color(foreground, background);
125 		putchar(')');
126 		length++;
127 	}
128 
129 	print_spacing(console_width() - length - 2*kOffsetX);
130 
131 	if (!selected)
132 		return;
133 
134 	console_set_cursor(0, console_height() - kHelpLines);
135 	console_set_color(kHelpTextColor, kBackgroundColor);
136 
137 	if (clearHelp) {
138 		// clear help text area
139 		for (int32 i = 0; i < console_width() * 2 - 1; i++)
140 			putchar(' ');
141 
142 		console_set_cursor(0, console_height() - kHelpLines);
143 	}
144 
145 	if (item->HelpText() != NULL) {
146 		// show help text at the bottom of the screen,
147 		// center it, and wrap it correctly
148 
149 		const char *text = item->HelpText();
150 		int32 width = console_width() - 2 * kOffsetX;
151 		int32 length = strlen(text);
152 
153 		if (length > width * 2)
154 			width += 2 * kOffsetX - 1;
155 
156 		char buffer[width + 1];
157 		buffer[width] = '\0';
158 			// make sure the buffer is always terminated
159 
160 		int32 row = 0;
161 
162 		for (int32 i = 0; i < length && row < 2; i++) {
163 			while (text[i] == ' ')
164 				i++;
165 
166 			// copy as much bytes as possible
167 			int32 bytes = width;
168 			if (bytes > length - i)
169 				bytes = length - i;
170 
171 			memcpy(buffer, text + i, bytes);
172 			buffer[bytes] = '\0';
173 
174 			char *pos = strchr(buffer, '\n');
175 			if (pos != NULL)
176 				i = pos - buffer;
177 			else if (bytes < length - i) {
178 				// search for possible line breaks
179 				pos = strrchr(buffer, ' ');
180 				if (pos != NULL)
181 					i = pos - buffer;
182 				else {
183 					// no wrapping possible
184 					i += bytes;
185 				}
186 			} else
187 				i += bytes;
188 
189 			buffer[i] = '\0';
190 			print_centered(console_height() - kHelpLines + row, buffer);
191 			row++;
192 		}
193 	}
194 }
195 
196 
197 static void
198 draw_menu(Menu *menu)
199 {
200 	console_set_color(kTextColor, kBackgroundColor);
201 	console_clear_screen();
202 
203 	print_centered(1, "Welcome To The");
204 	print_centered(2, "Haiku Boot Loader");
205 
206 	console_set_color(kCopyrightColor, kBackgroundColor);
207 	print_centered(4, "Copyright 2004-2005 Haiku Inc.");
208 
209 	if (menu->Title()) {
210 		console_set_cursor(kOffsetX, kFirstLine - 2);
211 		console_set_color(kTitleColor, kTitleBackgroundColor);
212 
213 		printf(" %s", menu->Title());
214 		print_spacing(console_width() - 1 - strlen(menu->Title()) - 2*kOffsetX);
215 	}
216 
217 	MenuItemIterator iterator = menu->ItemIterator();
218 	MenuItem *item;
219 	int32 i = 0;
220 
221 	while ((item = iterator.Next()) != NULL) {
222 		if (item->Type() == MENU_ITEM_SEPARATOR) {
223 			putchar('\n');
224 			i++;
225 			continue;
226 		}
227 
228 		print_item_at(i++, item, false);
229 	}
230 
231 	int32 height = menu_height();
232 	if (menu->CountItems() >= height) {
233 		int32 x = console_width() - kOffsetX;
234 		console_set_cursor(x, kFirstLine);
235 		console_set_color(kArrowColor, kBackgroundColor);
236 		putchar(30/*24*/);
237 		height--;
238 
239 		int32 start = sMenuOffset * height / menu->CountItems();
240 		int32 end = (sMenuOffset + height) * height / menu->CountItems();
241 
242 		for (i = 1; i < height; i++) {
243 			console_set_cursor(x, kFirstLine + i);
244 			if (i >= start && i <= end)
245 				console_set_color(WHITE, kSliderColor);
246 			else
247 				console_set_color(WHITE, kSliderBackgroundColor);
248 
249 			putchar(' ');
250 		}
251 
252 		console_set_cursor(x, kFirstLine + i);
253 		console_set_color(kArrowColor, kBackgroundColor);
254 		putchar(31/*25*/);
255 	}
256 }
257 
258 
259 static int32
260 first_selectable_item(Menu *menu)
261 {
262 	int32 index = -1;
263 	MenuItem *item;
264 
265 	while ((item = menu->ItemAt(++index)) != NULL) {
266 		if (item->IsEnabled() && item->Type() != MENU_ITEM_SEPARATOR)
267 			break;
268 	}
269 
270 	return index;
271 }
272 
273 
274 static int32
275 last_selectable_item(Menu *menu)
276 {
277 	int32 index = menu->CountItems();
278 	MenuItem *item;
279 
280 	while ((item = menu->ItemAt(--index)) != NULL) {
281 		if (item->IsEnabled() && item->Type() != MENU_ITEM_SEPARATOR)
282 			break;
283 	}
284 
285 	return index;
286 }
287 
288 
289 static bool
290 make_item_visible(Menu *menu, int32 selected)
291 {
292 	if (sMenuOffset > selected
293 		|| sMenuOffset + menu_height() <= selected) {
294 		if (sMenuOffset > selected)
295 			sMenuOffset = selected;
296 		else
297 			sMenuOffset = selected + 1 - menu_height();
298 
299 		draw_menu(menu);
300 		return true;
301 	}
302 
303 	return false;
304 }
305 
306 
307 static int32
308 select_previous_valid_item(Menu *menu, int32 selected)
309 {
310 	MenuItem *item;
311 	while ((item = menu->ItemAt(selected)) != NULL) {
312 		if (item->IsEnabled() && item->Type() != MENU_ITEM_SEPARATOR)
313 			break;
314 
315 		selected--;
316 	}
317 
318 	if (selected < 0)
319 		return first_selectable_item(menu);
320 
321 	return selected;
322 }
323 
324 
325 static int32
326 select_next_valid_item(Menu *menu, int32 selected)
327 {
328 	MenuItem *item;
329 	while ((item = menu->ItemAt(selected)) != NULL) {
330 		if (item->IsEnabled() && item->Type() != MENU_ITEM_SEPARATOR)
331 			break;
332 
333 		selected++;
334 	}
335 
336 	if (selected >= menu->CountItems())
337 		return last_selectable_item(menu);
338 
339 	return selected;
340 }
341 
342 
343 static void
344 run_menu(Menu *menu)
345 {
346 	sMenuOffset = 0;
347 	menu->Show();
348 
349 	draw_menu(menu);
350 
351 	// Get selected entry, or select the last one, if there is none
352 	int32 selected;
353 	MenuItem *item = menu->FindSelected(&selected);
354 	if (item == NULL) {
355 		selected = menu->CountItems() - 1;
356 		item = menu->ItemAt(selected);
357 		if (item != NULL)
358 			item->Select(true);
359 	}
360 
361 	make_item_visible(menu, selected);
362 
363 	while (true) {
364 		union key key = wait_for_key();
365 
366 		item = menu->ItemAt(selected);
367 
368 		if (key.code.ascii == 0) {
369 			int32 oldSelected = selected;
370 
371 			switch (key.code.bios) {
372 				case BIOS_KEY_UP:
373 					selected = select_previous_valid_item(menu, selected - 1);
374 					break;
375 				case BIOS_KEY_DOWN:
376 					selected = select_next_valid_item(menu, selected + 1);
377 					break;
378 				case BIOS_KEY_PAGE_UP:
379 					selected = select_previous_valid_item(menu, selected - menu_height() + 1);
380 					break;
381 				case BIOS_KEY_PAGE_DOWN:
382 					selected = select_next_valid_item(menu, selected + menu_height() - 1);
383 					break;
384 				case BIOS_KEY_HOME:
385 					selected = first_selectable_item(menu);
386 					break;
387 				case BIOS_KEY_END:
388 					selected = last_selectable_item(menu);
389 					break;
390 			}
391 
392 			// check if selected has changed
393 			if (selected != oldSelected) {
394 				MenuItem *item = menu->ItemAt(selected);
395 				if (item != NULL)
396 					item->Select(true);
397 
398 				make_item_visible(menu, selected);
399 				// make sure that the new selected entry is visible
400 				if (sMenuOffset > selected
401 					|| sMenuOffset + menu_height() <= selected) {
402 					if (sMenuOffset > selected)
403 						sMenuOffset = selected;
404 					else
405 						sMenuOffset = selected + 1 - menu_height();
406 
407 					draw_menu(menu);
408 				}
409 			}
410 		} else if (key.code.ascii == 0xd || key.code.ascii == ' ') {
411 			// leave the menu
412 			if (item->Submenu() != NULL) {
413 				int32 offset = sMenuOffset;
414 				menu->Hide();
415 
416 				run_menu(item->Submenu());
417 				if (item->Target() != NULL)
418 					(*item->Target())(menu, item);
419 
420 				// restore current menu
421 				sMenuOffset = offset;
422 				menu->FindSelected(&selected);
423 				menu->Show();
424 				draw_menu(menu);
425 			} else if (item->Type() == MENU_ITEM_MARKABLE) {
426 				// toggle state
427 				item->SetMarked(!item->IsMarked());
428 				print_item_at(selected, item);
429 
430 				if (item->Target() != NULL)
431 					(*item->Target())(menu, item);
432 			} else if (key.code.ascii == 0xd) {
433 				// the space key does not exit the menu
434 
435 				if (menu->Type() == CHOICE_MENU
436 					&& item->Type() != MENU_ITEM_NO_CHOICE
437 					&& item->Type() != MENU_ITEM_TITLE)
438 					item->SetMarked(true);
439 
440 				if (item->Target() != NULL)
441 					(*item->Target())(menu, item);
442 
443 				break;
444 			}
445 		} else if (key.code.ascii == 0x1b && menu->Type() != MAIN_MENU)
446 			// escape key was hit
447 			break;
448 	}
449 
450 	menu->Hide();
451 }
452 
453 
454 //	#pragma mark -
455 
456 
457 void
458 platform_add_menus(Menu *menu)
459 {
460 	MenuItem *item;
461 
462 	switch (menu->Type()) {
463 		case MAIN_MENU:
464 			menu->AddItem(item = new MenuItem("Select fail-safe video mode", video_mode_menu()));
465 			item->SetTarget(video_mode_hook);
466 			break;
467 		case SAFE_MODE_MENU:
468 			smp_add_safemode_menus(menu);
469 
470 			menu->AddItem(item = new MenuItem("Don't call the BIOS"));
471 			item->SetType(MENU_ITEM_MARKABLE);
472 			break;
473 		default:
474 			break;
475 	}
476 }
477 
478 
479 void
480 platform_update_menu_item(Menu *menu, MenuItem *item)
481 {
482 	if (menu->IsHidden())
483 		return;
484 
485 	int32 index = menu->IndexOf(item);
486 	if (index == -1)
487 		return;
488 
489 	print_item_at(index, item);
490 }
491 
492 
493 void
494 platform_run_menu(Menu *menu)
495 {
496 //	platform_switch_to_text_mode();
497 
498 	run_menu(menu);
499 
500 //	platform_switch_to_logo();
501 }
502 
503