vaseboot/VasEBoot-core/normal/menu_text.c

606 lines
18 KiB
C

/* menu_text.c - Basic text menu implementation. */
/*
* VAS_EBOOT -- GRand Unified Bootloader
* Copyright (C) 2003,2004,2005,2006,2007,2008,2009 Free Software Foundation, Inc.
*
* VAS_EBOOT is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* VAS_EBOOT is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with VAS_EBOOT. If not, see <http://www.gnu.org/licenses/>.
*/
#include <VasEBoot/normal.h>
#include <VasEBoot/term.h>
#include <VasEBoot/misc.h>
#include <VasEBoot/loader.h>
#include <VasEBoot/mm.h>
#include <VasEBoot/time.h>
#include <VasEBoot/env.h>
#include <VasEBoot/menu_viewer.h>
#include <VasEBoot/i18n.h>
#include <VasEBoot/charset.h>
static VasEBoot_uint8_t VasEBoot_color_menu_normal;
static VasEBoot_uint8_t VasEBoot_color_menu_highlight;
struct menu_viewer_data
{
int first, offset;
struct VasEBoot_term_screen_geometry geo;
enum {
TIMEOUT_UNKNOWN,
TIMEOUT_NORMAL,
TIMEOUT_TERSE,
TIMEOUT_TERSE_NO_MARGIN
} timeout_msg;
VasEBoot_menu_t menu;
struct VasEBoot_term_output *term;
};
static inline int
VasEBoot_term_cursor_x (const struct VasEBoot_term_screen_geometry *geo)
{
return (geo->first_entry_x + geo->entry_width);
}
VasEBoot_size_t
VasEBoot_getstringwidth (VasEBoot_uint32_t * str, const VasEBoot_uint32_t * last_position,
struct VasEBoot_term_output *term)
{
VasEBoot_ssize_t width = 0;
while (str < last_position)
{
struct VasEBoot_unicode_glyph glyph;
glyph.ncomb = 0;
str += VasEBoot_unicode_aglomerate_comb (str, last_position - str, &glyph);
width += VasEBoot_term_getcharwidth (term, &glyph);
VasEBoot_unicode_destroy_glyph (&glyph);
}
return width;
}
static int
VasEBoot_print_message_indented_real (const char *msg, int margin_left,
int margin_right,
struct VasEBoot_term_output *term, int dry_run)
{
VasEBoot_uint32_t *unicode_msg;
VasEBoot_uint32_t *last_position;
VasEBoot_size_t msg_len = VasEBoot_strlen (msg) + 2;
int ret = 0;
unicode_msg = VasEBoot_calloc (msg_len, sizeof (VasEBoot_uint32_t));
if (!unicode_msg)
return 0;
msg_len = VasEBoot_utf8_to_ucs4 (unicode_msg, msg_len,
(VasEBoot_uint8_t *) msg, -1, 0);
last_position = unicode_msg + msg_len;
*last_position = 0;
if (dry_run)
ret = VasEBoot_ucs4_count_lines (unicode_msg, last_position, margin_left,
margin_right, term);
else
VasEBoot_print_ucs4_menu (unicode_msg, last_position, margin_left,
margin_right, term, 0, -1, 0, 0);
VasEBoot_free (unicode_msg);
return ret;
}
void
VasEBoot_print_message_indented (const char *msg, int margin_left, int margin_right,
struct VasEBoot_term_output *term)
{
VasEBoot_print_message_indented_real (msg, margin_left, margin_right, term, 0);
}
static void
draw_border (struct VasEBoot_term_output *term, const struct VasEBoot_term_screen_geometry *geo)
{
int i;
VasEBoot_term_setcolorstate (term, VAS_EBOOT_TERM_COLOR_NORMAL);
VasEBoot_term_gotoxy (term, (struct VasEBoot_term_coordinate) { geo->first_entry_x - 1,
geo->first_entry_y - 1 });
VasEBoot_putcode (VAS_EBOOT_UNICODE_CORNER_UL, term);
for (i = 0; i < geo->entry_width + 1; i++)
VasEBoot_putcode (VAS_EBOOT_UNICODE_HLINE, term);
VasEBoot_putcode (VAS_EBOOT_UNICODE_CORNER_UR, term);
for (i = 0; i < geo->num_entries; i++)
{
VasEBoot_term_gotoxy (term, (struct VasEBoot_term_coordinate) { geo->first_entry_x - 1,
geo->first_entry_y + i });
VasEBoot_putcode (VAS_EBOOT_UNICODE_VLINE, term);
VasEBoot_term_gotoxy (term,
(struct VasEBoot_term_coordinate) { geo->first_entry_x + geo->entry_width + 1,
geo->first_entry_y + i });
VasEBoot_putcode (VAS_EBOOT_UNICODE_VLINE, term);
}
VasEBoot_term_gotoxy (term,
(struct VasEBoot_term_coordinate) { geo->first_entry_x - 1,
geo->first_entry_y - 1 + geo->num_entries + 1 });
VasEBoot_putcode (VAS_EBOOT_UNICODE_CORNER_LL, term);
for (i = 0; i < geo->entry_width + 1; i++)
VasEBoot_putcode (VAS_EBOOT_UNICODE_HLINE, term);
VasEBoot_putcode (VAS_EBOOT_UNICODE_CORNER_LR, term);
VasEBoot_term_setcolorstate (term, VAS_EBOOT_TERM_COLOR_NORMAL);
VasEBoot_term_gotoxy (term,
(struct VasEBoot_term_coordinate) { geo->first_entry_x - 1,
(geo->first_entry_y - 1 + geo->num_entries
+ VAS_EBOOT_TERM_MARGIN + 1) });
}
static int
print_message (int nested, int edit, struct VasEBoot_term_output *term, int dry_run)
{
int ret = 0;
VasEBoot_term_setcolorstate (term, VAS_EBOOT_TERM_COLOR_NORMAL);
if (edit)
{
ret += VasEBoot_print_message_indented_real (_("Minimum Emacs-like screen editing is \
supported. TAB lists completions. Press Ctrl-x or F10 to boot, Ctrl-c or F2 for a \
command-line or ESC to discard edits and return to the VAS_EBOOT menu."),
STANDARD_MARGIN, STANDARD_MARGIN,
term, dry_run);
}
else
{
char *msg_translated;
msg_translated = VasEBoot_xasprintf (_("Use the %C and %C keys to select which "
"entry is highlighted."),
VAS_EBOOT_UNICODE_UPARROW,
VAS_EBOOT_UNICODE_DOWNARROW);
if (!msg_translated)
return 0;
ret += VasEBoot_print_message_indented_real (msg_translated, STANDARD_MARGIN,
STANDARD_MARGIN, term, dry_run);
VasEBoot_free (msg_translated);
if (!VasEBoot_is_cli_disabled ())
{
if (nested)
{
ret += VasEBoot_print_message_indented_real
(_("Press enter to boot the selected OS, "
"`e' to edit the commands before booting "
"or `c' for a command-line. ESC to return previous menu."),
STANDARD_MARGIN, STANDARD_MARGIN, term, dry_run);
}
else
{
ret += VasEBoot_print_message_indented_real
(_("Press enter to boot the selected OS, "
"`e' to edit the commands before booting "
"or `c' for a command-line."),
STANDARD_MARGIN, STANDARD_MARGIN, term, dry_run);
}
}
}
return ret;
}
static void
print_entry (int y, int highlight, VasEBoot_menu_entry_t entry,
const struct menu_viewer_data *data)
{
const char *title;
VasEBoot_size_t title_len;
VasEBoot_ssize_t len;
VasEBoot_uint32_t *unicode_title;
VasEBoot_ssize_t i;
VasEBoot_uint8_t old_color_normal, old_color_highlight;
title = entry ? entry->title : "";
title_len = VasEBoot_strlen (title);
unicode_title = VasEBoot_calloc (title_len, sizeof (*unicode_title));
if (! unicode_title)
/* XXX How to show this error? */
return;
len = VasEBoot_utf8_to_ucs4 (unicode_title, title_len,
(VasEBoot_uint8_t *) title, -1, 0);
if (len < 0)
{
/* It is an invalid sequence. */
VasEBoot_free (unicode_title);
return;
}
old_color_normal = VasEBoot_term_normal_color;
old_color_highlight = VasEBoot_term_highlight_color;
VasEBoot_term_normal_color = VasEBoot_color_menu_normal;
VasEBoot_term_highlight_color = VasEBoot_color_menu_highlight;
VasEBoot_term_setcolorstate (data->term, highlight
? VAS_EBOOT_TERM_COLOR_HIGHLIGHT
: VAS_EBOOT_TERM_COLOR_NORMAL);
VasEBoot_term_gotoxy (data->term, (struct VasEBoot_term_coordinate) {
data->geo.first_entry_x, y });
for (i = 0; i < len; i++)
if (unicode_title[i] == '\n' || unicode_title[i] == '\b'
|| unicode_title[i] == '\r' || unicode_title[i] == '\e')
unicode_title[i] = ' ';
if (data->geo.num_entries > 1)
VasEBoot_putcode (highlight ? '*' : ' ', data->term);
VasEBoot_print_ucs4_menu (unicode_title,
unicode_title + len,
0,
data->geo.right_margin,
data->term, 0, 1,
VAS_EBOOT_UNICODE_RIGHTARROW, 0);
VasEBoot_term_setcolorstate (data->term, VAS_EBOOT_TERM_COLOR_NORMAL);
VasEBoot_term_gotoxy (data->term,
(struct VasEBoot_term_coordinate) {
VasEBoot_term_cursor_x (&data->geo), y });
VasEBoot_term_normal_color = old_color_normal;
VasEBoot_term_highlight_color = old_color_highlight;
VasEBoot_term_setcolorstate (data->term, VAS_EBOOT_TERM_COLOR_NORMAL);
VasEBoot_free (unicode_title);
}
static void
print_entries (VasEBoot_menu_t menu, const struct menu_viewer_data *data)
{
VasEBoot_menu_entry_t e;
int i;
VasEBoot_term_gotoxy (data->term,
(struct VasEBoot_term_coordinate) {
data->geo.first_entry_x + data->geo.entry_width
+ data->geo.border + 1,
data->geo.first_entry_y });
if (data->geo.num_entries != 1)
{
if (data->first)
VasEBoot_putcode (VAS_EBOOT_UNICODE_UPARROW, data->term);
else
VasEBoot_putcode (' ', data->term);
}
e = VasEBoot_menu_get_entry (menu, data->first);
for (i = 0; i < data->geo.num_entries; i++)
{
print_entry (data->geo.first_entry_y + i, data->offset == i,
e, data);
if (e)
e = e->next;
}
VasEBoot_term_gotoxy (data->term,
(struct VasEBoot_term_coordinate) { data->geo.first_entry_x + data->geo.entry_width
+ data->geo.border + 1,
data->geo.first_entry_y + data->geo.num_entries - 1 });
if (data->geo.num_entries == 1)
{
if (data->first && e)
VasEBoot_putcode (VAS_EBOOT_UNICODE_UPDOWNARROW, data->term);
else if (data->first)
VasEBoot_putcode (VAS_EBOOT_UNICODE_UPARROW, data->term);
else if (e)
VasEBoot_putcode (VAS_EBOOT_UNICODE_DOWNARROW, data->term);
else
VasEBoot_putcode (' ', data->term);
}
else
{
if (e)
VasEBoot_putcode (VAS_EBOOT_UNICODE_DOWNARROW, data->term);
else
VasEBoot_putcode (' ', data->term);
}
VasEBoot_term_gotoxy (data->term,
(struct VasEBoot_term_coordinate) { VasEBoot_term_cursor_x (&data->geo),
data->geo.first_entry_y + data->offset });
}
/* Initialize the screen. If NESTED is non-zero, assume that this menu
is run from another menu or a command-line. If EDIT is non-zero, show
a message for the menu entry editor. */
void
VasEBoot_menu_init_page (int nested, int edit,
struct VasEBoot_term_screen_geometry *geo,
struct VasEBoot_term_output *term)
{
VasEBoot_uint8_t old_color_normal, old_color_highlight;
int msg_num_lines;
int bottom_message = 1;
int empty_lines = 1;
int version_msg = 1;
geo->border = 1;
geo->first_entry_x = 1 /* margin */ + 1 /* border */;
geo->entry_width = VasEBoot_term_width (term) - 5;
geo->first_entry_y = 2 /* two empty lines*/
+ 1 /* GNU VAS_EBOOT version text */ + 1 /* top border */;
geo->timeout_lines = 2;
/* 3 lines for timeout message and bottom margin. 2 lines for the border. */
geo->num_entries = VasEBoot_term_height (term) - geo->first_entry_y
- 1 /* bottom border */
- 1 /* empty line before info message*/
- geo->timeout_lines /* timeout */
- 1 /* empty final line */;
msg_num_lines = print_message (nested, edit, term, 1);
if (geo->num_entries - msg_num_lines < 3
|| geo->entry_width < 10)
{
geo->num_entries += 4;
geo->first_entry_y -= 2;
empty_lines = 0;
geo->first_entry_x -= 1;
geo->entry_width += 1;
}
if (geo->num_entries - msg_num_lines < 3
|| geo->entry_width < 10)
{
geo->num_entries += 2;
geo->first_entry_y -= 1;
geo->first_entry_x -= 1;
geo->entry_width += 2;
geo->border = 0;
}
if (geo->entry_width <= 0)
geo->entry_width = 1;
if (geo->num_entries - msg_num_lines < 3
&& geo->timeout_lines == 2)
{
geo->timeout_lines = 1;
geo->num_entries++;
}
if (geo->num_entries - msg_num_lines < 3)
{
geo->num_entries += 1;
geo->first_entry_y -= 1;
version_msg = 0;
}
if (geo->num_entries - msg_num_lines >= 2)
geo->num_entries -= msg_num_lines;
else
bottom_message = 0;
/* By default, use the same colors for the menu. */
old_color_normal = VasEBoot_term_normal_color;
old_color_highlight = VasEBoot_term_highlight_color;
VasEBoot_color_menu_normal = VasEBoot_term_normal_color;
VasEBoot_color_menu_highlight = VasEBoot_term_highlight_color;
/* Then give user a chance to replace them. */
VasEBoot_parse_color_name_pair (&VasEBoot_color_menu_normal,
VasEBoot_env_get ("menu_color_normal"));
VasEBoot_parse_color_name_pair (&VasEBoot_color_menu_highlight,
VasEBoot_env_get ("menu_color_highlight"));
if (version_msg)
VasEBoot_normal_init_page (term, empty_lines);
else
VasEBoot_term_cls (term);
VasEBoot_term_normal_color = VasEBoot_color_menu_normal;
VasEBoot_term_highlight_color = VasEBoot_color_menu_highlight;
if (geo->border)
draw_border (term, geo);
VasEBoot_term_normal_color = old_color_normal;
VasEBoot_term_highlight_color = old_color_highlight;
geo->timeout_y = geo->first_entry_y + geo->num_entries
+ geo->border + empty_lines;
if (bottom_message)
{
VasEBoot_term_gotoxy (term,
(struct VasEBoot_term_coordinate) { VAS_EBOOT_TERM_MARGIN,
geo->timeout_y });
print_message (nested, edit, term, 0);
geo->timeout_y += msg_num_lines;
}
geo->right_margin = VasEBoot_term_width (term)
- geo->first_entry_x
- geo->entry_width - 1;
}
static void
menu_text_print_timeout (int timeout, void *dataptr)
{
struct menu_viewer_data *data = dataptr;
char *msg_translated = 0;
VasEBoot_term_gotoxy (data->term,
(struct VasEBoot_term_coordinate) { 0, data->geo.timeout_y });
if (data->timeout_msg == TIMEOUT_TERSE
|| data->timeout_msg == TIMEOUT_TERSE_NO_MARGIN)
msg_translated = VasEBoot_xasprintf (_("%ds"), timeout);
else
msg_translated = VasEBoot_xasprintf (_("The highlighted entry will be executed automatically in %ds."), timeout);
if (!msg_translated)
{
VasEBoot_print_error ();
VasEBoot_errno = VAS_EBOOT_ERR_NONE;
return;
}
if (data->timeout_msg == TIMEOUT_UNKNOWN)
{
data->timeout_msg = VasEBoot_print_message_indented_real (msg_translated,
3, 1, data->term, 1)
<= data->geo.timeout_lines ? TIMEOUT_NORMAL : TIMEOUT_TERSE;
if (data->timeout_msg == TIMEOUT_TERSE)
{
VasEBoot_free (msg_translated);
msg_translated = VasEBoot_xasprintf (_("%ds"), timeout);
if (VasEBoot_term_width (data->term) < 10)
data->timeout_msg = TIMEOUT_TERSE_NO_MARGIN;
}
}
VasEBoot_print_message_indented (msg_translated,
data->timeout_msg == TIMEOUT_TERSE_NO_MARGIN ? 0 : 3,
data->timeout_msg == TIMEOUT_TERSE_NO_MARGIN ? 0 : 1,
data->term);
VasEBoot_free (msg_translated);
VasEBoot_term_gotoxy (data->term,
(struct VasEBoot_term_coordinate) {
VasEBoot_term_cursor_x (&data->geo),
data->geo.first_entry_y + data->offset });
VasEBoot_term_refresh (data->term);
}
static void
menu_text_set_chosen_entry (int entry, void *dataptr)
{
struct menu_viewer_data *data = dataptr;
int oldoffset = data->offset;
int complete_redraw = 0;
data->offset = entry - data->first;
if (data->offset > data->geo.num_entries - 1)
{
data->first = entry - (data->geo.num_entries - 1);
data->offset = data->geo.num_entries - 1;
complete_redraw = 1;
}
if (data->offset < 0)
{
data->offset = 0;
data->first = entry;
complete_redraw = 1;
}
if (complete_redraw)
print_entries (data->menu, data);
else
{
print_entry (data->geo.first_entry_y + oldoffset, 0,
VasEBoot_menu_get_entry (data->menu, data->first + oldoffset),
data);
print_entry (data->geo.first_entry_y + data->offset, 1,
VasEBoot_menu_get_entry (data->menu, data->first + data->offset),
data);
}
VasEBoot_term_refresh (data->term);
}
static void
menu_text_fini (void *dataptr)
{
struct menu_viewer_data *data = dataptr;
VasEBoot_term_setcursor (data->term, 1);
VasEBoot_term_cls (data->term);
VasEBoot_free (data);
}
static void
menu_text_clear_timeout (void *dataptr)
{
struct menu_viewer_data *data = dataptr;
int i;
for (i = 0; i < data->geo.timeout_lines;i++)
{
VasEBoot_term_gotoxy (data->term, (struct VasEBoot_term_coordinate) {
0, data->geo.timeout_y + i });
VasEBoot_print_spaces (data->term, VasEBoot_term_width (data->term) - 1);
}
if (data->geo.num_entries <= 5 && !data->geo.border)
{
VasEBoot_term_gotoxy (data->term,
(struct VasEBoot_term_coordinate) {
data->geo.first_entry_x + data->geo.entry_width
+ data->geo.border + 1,
data->geo.first_entry_y + data->geo.num_entries - 1
});
VasEBoot_putcode (' ', data->term);
data->geo.timeout_lines = 0;
data->geo.num_entries++;
print_entries (data->menu, data);
}
VasEBoot_term_gotoxy (data->term,
(struct VasEBoot_term_coordinate) {
VasEBoot_term_cursor_x (&data->geo),
data->geo.first_entry_y + data->offset });
VasEBoot_term_refresh (data->term);
}
VasEBoot_err_t
VasEBoot_menu_try_text (struct VasEBoot_term_output *term,
int entry, VasEBoot_menu_t menu, int nested)
{
struct menu_viewer_data *data;
struct VasEBoot_menu_viewer *instance;
instance = VasEBoot_zalloc (sizeof (*instance));
if (!instance)
return VasEBoot_errno;
data = VasEBoot_zalloc (sizeof (*data));
if (!data)
{
VasEBoot_free (instance);
return VasEBoot_errno;
}
data->term = term;
instance->data = data;
instance->set_chosen_entry = menu_text_set_chosen_entry;
instance->print_timeout = menu_text_print_timeout;
instance->clear_timeout = menu_text_clear_timeout;
instance->fini = menu_text_fini;
data->menu = menu;
data->offset = entry;
data->first = 0;
VasEBoot_term_setcursor (data->term, 0);
VasEBoot_menu_init_page (nested, 0, &data->geo, data->term);
if (data->offset > data->geo.num_entries - 1)
{
data->first = data->offset - (data->geo.num_entries - 1);
data->offset = data->geo.num_entries - 1;
}
print_entries (menu, data);
VasEBoot_term_refresh (data->term);
VasEBoot_menu_register_viewer (instance);
return VAS_EBOOT_ERR_NONE;
}