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