xref: /haiku/src/system/boot/platform/generic/text_menu.cpp (revision 16d5c24e533eb14b7b8a99ee9f3ec9ba66335b1e)
1 /*
2  * Copyright 2004-2009, 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() - 1; i++)
137 			putchar(' ');
138 		putchar('\n');
139 		for (int32 i = 0; i < console_width() - 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 				bytes = pos - buffer;
177 			else if (bytes < length - i) {
178 				// search for possible line breaks
179 				pos = strrchr(buffer, ' ');
180 				if (pos != NULL)
181 					bytes = pos - buffer;
182 				else {
183 					// no wrapping possible
184 				}
185 			}
186 
187 			i += bytes;
188 			buffer[bytes] = '\0';
189 			print_centered(console_height() - kHelpLines + row, buffer);
190 			row++;
191 		}
192 	}
193 }
194 
195 
196 static void
197 draw_menu(Menu *menu)
198 {
199 	console_set_color(kTextColor, kBackgroundColor);
200 	console_clear_screen();
201 
202 	print_centered(1, "Welcome To The");
203 	print_centered(2, "Haiku Boot Loader");
204 
205 	console_set_color(kCopyrightColor, kBackgroundColor);
206 	print_centered(4, "Copyright 2004-2009 Haiku Inc.");
207 
208 	if (menu->Title()) {
209 		console_set_cursor(kOffsetX, kFirstLine - 2);
210 		console_set_color(kTitleColor, kTitleBackgroundColor);
211 
212 		printf(" %s", menu->Title());
213 		print_spacing(console_width() - 1
214 			- 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 = 0;
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 		int key = console_wait_for_key();
365 
366 		item = menu->ItemAt(selected);
367 
368 		if (TEXT_CONSOLE_IS_CURSOR_KEY(key)) {
369 			int32 oldSelected = selected;
370 
371 			switch (key) {
372 				case TEXT_CONSOLE_KEY_UP:
373 					selected = select_previous_valid_item(menu, selected - 1);
374 					break;
375 				case TEXT_CONSOLE_KEY_DOWN:
376 					selected = select_next_valid_item(menu, selected + 1);
377 					break;
378 				case TEXT_CONSOLE_KEY_PAGE_UP:
379 				case TEXT_CONSOLE_KEY_LEFT:
380 					selected = select_previous_valid_item(menu,
381 						selected - menu_height() + 1);
382 					break;
383 				case TEXT_CONSOLE_KEY_PAGE_DOWN:
384 				case TEXT_CONSOLE_KEY_RIGHT:
385 					selected = select_next_valid_item(menu,
386 						selected + menu_height() - 1);
387 					break;
388 				case TEXT_CONSOLE_KEY_HOME:
389 					selected = first_selectable_item(menu);
390 					break;
391 				case TEXT_CONSOLE_KEY_END:
392 					selected = last_selectable_item(menu);
393 					break;
394 			}
395 
396 			// check if selected has changed
397 			if (selected != oldSelected) {
398 				MenuItem *item = menu->ItemAt(selected);
399 				if (item != NULL)
400 					item->Select(true);
401 
402 				make_item_visible(menu, selected);
403 				// make sure that the new selected entry is visible
404 				if (sMenuOffset > selected
405 					|| sMenuOffset + menu_height() <= selected) {
406 					if (sMenuOffset > selected)
407 						sMenuOffset = selected;
408 					else
409 						sMenuOffset = selected + 1 - menu_height();
410 
411 					draw_menu(menu);
412 				}
413 			}
414 		} else if (key == TEXT_CONSOLE_KEY_RETURN
415 			|| key == TEXT_CONSOLE_KEY_SPACE) {
416 			// leave the menu
417 			if (item->Submenu() != NULL && key == TEXT_CONSOLE_KEY_RETURN) {
418 				int32 offset = sMenuOffset;
419 				menu->Hide();
420 
421 				run_menu(item->Submenu());
422 				if (item->Target() != NULL)
423 					(*item->Target())(menu, item);
424 
425 				// restore current menu
426 				sMenuOffset = offset;
427 				menu->FindSelected(&selected);
428 				menu->Show();
429 				draw_menu(menu);
430 			} else if (item->Type() == MENU_ITEM_MARKABLE) {
431 				// toggle state
432 				item->SetMarked(!item->IsMarked());
433 				print_item_at(selected, item);
434 
435 				if (item->Target() != NULL)
436 					(*item->Target())(menu, item);
437 			} else if (key == TEXT_CONSOLE_KEY_RETURN) {
438 				// the space key does not exit the menu
439 
440 				if (menu->Type() == CHOICE_MENU
441 					&& item->Type() != MENU_ITEM_NO_CHOICE
442 					&& item->Type() != MENU_ITEM_TITLE)
443 					item->SetMarked(true);
444 
445 				if (item->Target() != NULL)
446 					(*item->Target())(menu, item);
447 
448 				break;
449 			}
450 		} else if (key == TEXT_CONSOLE_KEY_ESCAPE && menu->Type() != MAIN_MENU)
451 			// escape key was hit
452 			break;
453 	}
454 
455 	menu->Hide();
456 }
457 
458 
459 //	#pragma mark -
460 
461 
462 void
463 platform_generic_update_text_menu_item(Menu *menu, MenuItem *item)
464 {
465 	if (menu->IsHidden())
466 		return;
467 
468 	int32 index = menu->IndexOf(item);
469 	if (index == -1)
470 		return;
471 
472 	print_item_at(index, item);
473 }
474 
475 
476 void
477 platform_generic_run_text_menu(Menu *menu)
478 {
479 //	platform_switch_to_text_mode();
480 
481 	run_menu(menu);
482 
483 //	platform_switch_to_logo();
484 }
485 
486