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