1/*
2 * Copyright 2004-2010, Axel Dörfler, axeld@pinc-software.de.
3 * Copyright 2011, Rene Gollent, rene@gollent.com.
4 * Distributed under the terms of the MIT License.
5 */
6
7
8#include <boot/platform.h>
9#include <boot/menu.h>
10#include <boot/platform/generic/text_console.h>
11#include <boot/platform/generic/text_menu.h>
12
13#include <string.h>
14
15
16// position
17static const int32 kFirstLine = 8;
18static const int32 kOffsetX = 10;
19static const int32 kHelpLines = 3;
20
21// colors
22static const console_color kBackgroundColor = BLACK;
23static const console_color kTextColor = WHITE;
24static const console_color kCopyrightColor = CYAN;
25static const console_color kTitleColor = WHITE;
26static const console_color kTitleBackgroundColor = RED;
27static const console_color kHelpTextColor = WHITE;
28
29static const console_color kItemColor = GRAY;
30static const console_color kSelectedItemColor = WHITE;
31static const console_color kItemBackgroundColor = kBackgroundColor;
32static const console_color kSelectedItemBackgroundColor = GRAY;
33static const console_color kDisabledColor = DARK_GRAY;
34
35static const console_color kSliderColor = CYAN;
36static const console_color kSliderBackgroundColor = DARK_GRAY;
37static const console_color kArrowColor = GRAY;
38
39static int32 sMenuOffset = 0;
40
41
42static void run_menu(Menu* menu);
43
44
45static int32
46menu_height()
47{
48	return console_height() - kFirstLine - 1 - kHelpLines;
49}
50
51
52static void
53print_spacing(int32 count)
54{
55	for (int32 i = 0; i < count; i++)
56		putchar(' ');
57}
58
59
60static void
61print_centered(int32 line, const char *text, bool resetPosition = true)
62{
63	console_set_cursor(console_width() / 2 - strlen(text) / 2, line);
64	printf("%s", text);
65
66	if (resetPosition) {
67		console_set_cursor(0, 0);
68			// this avoids unwanted line feeds
69	}
70}
71
72
73static void
74print_item_at(int32 line, MenuItem *item, bool clearHelp = true)
75{
76	bool selected = item->IsSelected();
77
78	line -= sMenuOffset;
79	if (line < 0 || line >= menu_height())
80		return;
81
82	console_color background = selected
83		? kSelectedItemBackgroundColor : kItemBackgroundColor;
84	console_color foreground = selected
85		? kSelectedItemColor : kItemColor;
86
87	if (!item->IsEnabled())
88		foreground = kDisabledColor;
89
90	console_set_cursor(kOffsetX, line + kFirstLine);
91	console_set_color(foreground, background);
92
93	size_t length = strlen(item->Label()) + 1;
94
95	if (item->Type() == MENU_ITEM_MARKABLE) {
96		console_set_color(DARK_GRAY, background);
97		printf(" [");
98		console_set_color(foreground, background);
99		printf("%c", item->IsMarked() ? 'x' : ' ');
100		console_set_color(DARK_GRAY, background);
101		printf("] ");
102		console_set_color(foreground, background);
103
104		length += 4;
105	} else
106		printf(" ");
107
108	printf(item->Label());
109
110	if (item->Submenu() && item->Submenu()->Type() == CHOICE_MENU) {
111		// show the current choice (if any)
112		const char *text = " (Current: ";
113		printf(text);
114		length += strlen(text);
115
116		Menu *subMenu = item->Submenu();
117		if (subMenu->ChoiceText() != NULL)
118			text = subMenu->ChoiceText();
119		else
120			text = "None";
121		length += strlen(text);
122
123		console_set_color(selected ? DARK_GRAY : WHITE, background);
124
125		printf(text);
126
127		console_set_color(foreground, background);
128		putchar(')');
129		length++;
130	}
131
132	print_spacing(console_width() - length - 2*kOffsetX);
133
134	if (!selected)
135		return;
136
137	console_set_cursor(0, console_height() - kHelpLines);
138	console_set_color(kHelpTextColor, kBackgroundColor);
139
140	if (clearHelp) {
141		// clear help text area
142		for (int32 i = 0; i < console_width() - 1; i++)
143			putchar(' ');
144		putchar('\n');
145		for (int32 i = 0; i < console_width() - 1; i++)
146			putchar(' ');
147
148		console_set_cursor(0, console_height() - kHelpLines);
149	}
150
151	if (item->HelpText() != NULL) {
152		// show help text at the bottom of the screen,
153		// center it, and wrap it correctly
154
155		const char *text = item->HelpText();
156		int32 width = console_width() - 2 * kOffsetX;
157		int32 length = strlen(text);
158
159		if (length > width * 2)
160			width += 2 * kOffsetX - 1;
161
162		char buffer[width + 1];
163		buffer[width] = '\0';
164			// make sure the buffer is always terminated
165
166		int32 row = 0;
167
168		for (int32 i = 0; i < length && row < 2; i++) {
169			while (text[i] == ' ')
170				i++;
171
172			// copy as much bytes as possible
173			int32 bytes = width;
174			if (bytes > length - i)
175				bytes = length - i;
176
177			memcpy(buffer, text + i, bytes);
178			buffer[bytes] = '\0';
179
180			char *pos = strchr(buffer, '\n');
181			if (pos != NULL)
182				bytes = pos - buffer;
183			else if (bytes < length - i) {
184				// search for possible line breaks
185				pos = strrchr(buffer, ' ');
186				if (pos != NULL)
187					bytes = pos - buffer;
188				else {
189					// no wrapping possible
190				}
191			}
192
193			i += bytes;
194			buffer[bytes] = '\0';
195			print_centered(console_height() - kHelpLines + row, buffer);
196			row++;
197		}
198	}
199}
200
201
202static void
203draw_menu(Menu *menu)
204{
205	console_set_color(kTextColor, kBackgroundColor);
206	console_clear_screen();
207
208	print_centered(1, "Welcome to the");
209	print_centered(2, "Haiku Boot Loader");
210
211	console_set_color(kCopyrightColor, kBackgroundColor);
212	print_centered(4, "Copyright 2004-2012 Haiku Inc.");
213
214	if (menu->Title()) {
215		console_set_cursor(kOffsetX, kFirstLine - 2);
216		console_set_color(kTitleColor, kTitleBackgroundColor);
217
218		printf(" %s", menu->Title());
219		print_spacing(console_width() - 1
220			- strlen(menu->Title()) - 2 * kOffsetX);
221	}
222
223	MenuItemIterator iterator = menu->ItemIterator();
224	MenuItem *item;
225	int32 i = 0;
226
227	while ((item = iterator.Next()) != NULL) {
228		if (item->Type() == MENU_ITEM_SEPARATOR) {
229			putchar('\n');
230			i++;
231			continue;
232		}
233
234		print_item_at(i++, item, false);
235	}
236
237	int32 height = menu_height();
238	if (menu->CountItems() >= height) {
239		int32 x = console_width() - kOffsetX;
240		console_set_cursor(x, kFirstLine);
241		console_set_color(kArrowColor, kBackgroundColor);
242		putchar(30/*24*/);
243		height--;
244
245		int32 start = sMenuOffset * height / menu->CountItems();
246		int32 end = (sMenuOffset + height) * height / menu->CountItems();
247
248		for (i = 1; i < height; i++) {
249			console_set_cursor(x, kFirstLine + i);
250			if (i >= start && i <= end)
251				console_set_color(WHITE, kSliderColor);
252			else
253				console_set_color(WHITE, kSliderBackgroundColor);
254
255			putchar(' ');
256		}
257
258		console_set_cursor(x, kFirstLine + i);
259		console_set_color(kArrowColor, kBackgroundColor);
260		putchar(31/*25*/);
261	}
262}
263
264
265static int32
266first_selectable_item(Menu *menu)
267{
268	int32 index = -1;
269	MenuItem *item;
270
271	while ((item = menu->ItemAt(++index)) != NULL) {
272		if (item->IsEnabled() && item->Type() != MENU_ITEM_SEPARATOR)
273			break;
274	}
275
276	return index;
277}
278
279
280static int32
281last_selectable_item(Menu *menu)
282{
283	int32 index = menu->CountItems();
284	MenuItem *item;
285
286	while ((item = menu->ItemAt(--index)) != NULL) {
287		if (item->IsEnabled() && item->Type() != MENU_ITEM_SEPARATOR)
288			break;
289	}
290
291	return index;
292}
293
294
295static bool
296make_item_visible(Menu *menu, int32 selected)
297{
298	if (sMenuOffset > selected
299		|| sMenuOffset + menu_height() <= selected) {
300		if (sMenuOffset > selected)
301			sMenuOffset = selected;
302		else
303			sMenuOffset = selected + 1 - menu_height();
304
305		draw_menu(menu);
306		return true;
307	}
308
309	return false;
310}
311
312
313static int32
314select_previous_valid_item(Menu *menu, int32 selected)
315{
316	MenuItem *item;
317	while ((item = menu->ItemAt(selected)) != NULL) {
318		if (item->IsEnabled() && item->Type() != MENU_ITEM_SEPARATOR)
319			break;
320
321		selected--;
322	}
323
324	if (selected < 0)
325		return first_selectable_item(menu);
326
327	return selected;
328}
329
330
331static int32
332select_next_valid_item(Menu *menu, int32 selected)
333{
334	MenuItem *item;
335	while ((item = menu->ItemAt(selected)) != NULL) {
336		if (item->IsEnabled() && item->Type() != MENU_ITEM_SEPARATOR)
337			break;
338
339		selected++;
340	}
341
342	if (selected >= menu->CountItems())
343		return last_selectable_item(menu);
344
345	return selected;
346}
347
348
349static bool
350invoke_item(Menu* menu, MenuItem* item, int32& selected, char key)
351{
352	// leave the menu
353	if (item->Submenu() != NULL && key == TEXT_CONSOLE_KEY_RETURN) {
354		int32 offset = sMenuOffset;
355		menu->Hide();
356
357		run_menu(item->Submenu());
358		if (item->Target() != NULL)
359			(*item->Target())(menu, item);
360
361		// restore current menu
362		sMenuOffset = offset;
363		menu->FindSelected(&selected);
364		menu->Show();
365		draw_menu(menu);
366	} else if (item->Type() == MENU_ITEM_MARKABLE) {
367		// toggle state
368		item->SetMarked(!item->IsMarked());
369		print_item_at(selected, item);
370
371		if (item->Target() != NULL)
372			(*item->Target())(menu, item);
373	} else if (key == TEXT_CONSOLE_KEY_RETURN) {
374		// the space key does not exit the menu, only return does
375		if (menu->Type() == CHOICE_MENU
376			&& item->Type() != MENU_ITEM_NO_CHOICE
377			&& item->Type() != MENU_ITEM_TITLE)
378			item->SetMarked(true);
379
380		if (item->Target() != NULL)
381			(*item->Target())(menu, item);
382		return true;
383	}
384
385	return false;
386}
387
388
389static void
390run_menu(Menu* menu)
391{
392	sMenuOffset = 0;
393	menu->Show();
394
395	draw_menu(menu);
396
397	// Get selected entry, or select the last one, if there is none
398	int32 selected;
399	MenuItem *item = menu->FindSelected(&selected);
400	if (item == NULL) {
401		selected = 0;
402		item = menu->ItemAt(selected);
403		if (item != NULL)
404			item->Select(true);
405	}
406
407	make_item_visible(menu, selected);
408
409	while (true) {
410		int key = console_wait_for_key();
411
412		item = menu->ItemAt(selected);
413
414		if (TEXT_CONSOLE_IS_CURSOR_KEY(key)) {
415			int32 oldSelected = selected;
416
417			switch (key) {
418				case TEXT_CONSOLE_KEY_UP:
419					selected = select_previous_valid_item(menu, selected - 1);
420					break;
421				case TEXT_CONSOLE_KEY_DOWN:
422					selected = select_next_valid_item(menu, selected + 1);
423					break;
424				case TEXT_CONSOLE_KEY_PAGE_UP:
425				case TEXT_CONSOLE_KEY_LEFT:
426					selected = select_previous_valid_item(menu,
427						selected - menu_height() + 1);
428					break;
429				case TEXT_CONSOLE_KEY_PAGE_DOWN:
430				case TEXT_CONSOLE_KEY_RIGHT:
431					selected = select_next_valid_item(menu,
432						selected + menu_height() - 1);
433					break;
434				case TEXT_CONSOLE_KEY_HOME:
435					selected = first_selectable_item(menu);
436					break;
437				case TEXT_CONSOLE_KEY_END:
438					selected = last_selectable_item(menu);
439					break;
440			}
441
442			// check if selected has changed
443			if (selected != oldSelected) {
444				MenuItem *item = menu->ItemAt(selected);
445				if (item != NULL)
446					item->Select(true);
447
448				make_item_visible(menu, selected);
449				// make sure that the new selected entry is visible
450				if (sMenuOffset > selected
451					|| sMenuOffset + menu_height() <= selected) {
452					if (sMenuOffset > selected)
453						sMenuOffset = selected;
454					else
455						sMenuOffset = selected + 1 - menu_height();
456
457					draw_menu(menu);
458				}
459			}
460		} else if (key == TEXT_CONSOLE_KEY_RETURN
461			|| key == TEXT_CONSOLE_KEY_SPACE) {
462			if (invoke_item(menu, item, selected, key))
463				break;
464		} else if (key == TEXT_CONSOLE_KEY_ESCAPE
465			&& menu->Type() != MAIN_MENU) {
466			// escape key was hit
467			break;
468		} else {
469			// Shortcut processing
470			shortcut_hook function = menu->FindShortcut(key);
471			if (function != NULL)
472				function(key);
473			else {
474				item = menu->FindItemByShortcut(key);
475				if (item != NULL && invoke_item(menu, item, selected,
476						TEXT_CONSOLE_KEY_RETURN)) {
477					break;
478				}
479			}
480		}
481	}
482
483	menu->Hide();
484}
485
486
487//	#pragma mark -
488
489
490void
491platform_generic_update_text_menu_item(Menu *menu, MenuItem *item)
492{
493	if (menu->IsHidden())
494		return;
495
496	int32 index = menu->IndexOf(item);
497	if (index == -1)
498		return;
499
500	print_item_at(index, item);
501}
502
503
504void
505platform_generic_run_text_menu(Menu *menu)
506{
507//	platform_switch_to_text_mode();
508
509	run_menu(menu);
510
511//	platform_switch_to_logo();
512}
513
514
515size_t
516platform_generic_get_user_input_text(Menu* menu, MenuItem* item, char* buffer,
517	size_t bufferSize)
518{
519	size_t pos = 0;
520
521	memset(buffer, 0, bufferSize);
522
523	int32 promptLength = strlen(item->Label()) + 2;
524	int32 line = menu->IndexOf(item) - sMenuOffset;
525	if (line < 0 || line >= menu_height())
526		return 0;
527
528	line += kFirstLine;
529	console_set_cursor(kOffsetX, line);
530	int32 x = kOffsetX + 1;
531	console_set_cursor(0, line);
532	console_set_color(kSelectedItemColor, kSelectedItemBackgroundColor);
533	print_spacing(console_width());
534	console_set_color(kTextColor, kBackgroundColor);
535	console_set_cursor(0, line);
536	print_spacing(x);
537	printf(item->Label());
538	printf(": ");
539	x += promptLength;
540	console_set_color(kSelectedItemColor, kSelectedItemBackgroundColor);
541	console_show_cursor();
542	console_set_cursor(x, line);
543
544	int32 scrollOffset = 0;
545	bool doScroll = false;
546	int key = 0;
547	size_t dataLength = 0;
548	while (true) {
549		key = console_wait_for_key();
550		if (key == TEXT_CONSOLE_KEY_RETURN || key == TEXT_CONSOLE_KEY_ESCAPE)
551			break;
552		else if (key >= TEXT_CONSOLE_CURSOR_KEYS_START
553			&& key < TEXT_CONSOLE_CURSOR_KEYS_END)
554		{
555			switch (key)	{
556				case TEXT_CONSOLE_KEY_LEFT:
557					if (pos > 0)
558						pos--;
559					else if (scrollOffset > 0) {
560						scrollOffset--;
561						doScroll = true;
562					}
563					break;
564				case TEXT_CONSOLE_KEY_RIGHT:
565					if (pos < dataLength) {
566						if (x + (int32)pos == console_width() - 1) {
567							scrollOffset++;
568							doScroll = true;
569						} else
570							pos++;
571					}
572					break;
573				default:
574					break;
575			}
576		} else if (key == TEXT_CONSOLE_KEY_BACKSPACE) {
577			if (pos != 0 || scrollOffset > 0) {
578				if (pos > 0)
579					pos--;
580				else if (scrollOffset > 0)
581					scrollOffset--;
582				dataLength--;
583				int32 offset = pos + scrollOffset;
584				memmove(buffer + offset, buffer + offset + 1, dataLength - offset);
585				console_set_cursor(x + pos, line);
586				putchar(' ');
587				// if this was a mid-line backspace, the line will need to be redrawn
588				if (pos + scrollOffset < dataLength)
589					doScroll = true;
590			}
591			// only accept printable ascii characters
592		} else if (key > 32 || key == TEXT_CONSOLE_KEY_SPACE) {
593			if (pos < (bufferSize - 1)) {
594				buffer[pos + scrollOffset] = key;
595				if (x + (int32)pos < console_width() - 1) {
596					putchar(key);
597					pos++;
598				} else {
599					scrollOffset++;
600					doScroll = true;
601				}
602
603				dataLength++;
604			}
605		}
606
607		if (doScroll) {
608			console_set_cursor(x, line);
609			for (int32 i = x; i < console_width() - 1; i++)
610				putchar(buffer[scrollOffset + i - x]);
611			doScroll = false;
612		}
613		console_set_cursor(x + pos, line);
614	}
615
616	console_hide_cursor();
617	draw_menu(menu);
618
619	return key == TEXT_CONSOLE_KEY_RETURN ? pos : 0;
620}
621