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