rockbox/apps/menu.c
Thomas Martitz 4c48b59be8 User definable UI viewport, to be able to restrict the UI into a viewport for all bitmap displays.
Flyspray: FS#8799

This will allow for pretty themes, for example those with nice glass effects on their backdrops (some might argue they're wasting screen space but it's upto them),
as well as allowing for future background WPS updates in the main UI.

Plugins are not converted yet, they simply use the full screen. Ideally, any plugin that does *not* want the UI viewport, should take care of that itself (i.e. plugins should normally use the UI viewport).

git-svn-id: svn://svn.rockbox.org/rockbox/trunk@22365 a1c6a512-1295-4272-9138-f99709370657
2009-08-16 22:20:11 +00:00

687 lines
23 KiB
C

/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2002 Robert E. Hak
*
* This program 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 2
* of the License, or (at your option) any later version.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/
/*
2005 Kevin Ferrare :
- Multi screen support
- Rewrote/removed a lot of code now useless with the new gui API
*/
#include <stdbool.h>
#include <stdlib.h>
#include "config.h"
#include "system.h"
#include "lcd.h"
#include "font.h"
#include "menu.h"
#include "button.h"
#include "kernel.h"
#include "debug.h"
#include "usb.h"
#include "panic.h"
#include "settings.h"
#include "settings_list.h"
#include "option_select.h"
#include "screens.h"
#include "talk.h"
#include "lang.h"
#include "misc.h"
#include "action.h"
#include "menus/exported_menus.h"
#include "string.h"
#include "root_menu.h"
#include "audio.h"
#include "viewport.h"
#include "quickscreen.h"
#ifdef HAVE_LCD_BITMAP
#include "icons.h"
#endif
/* gui api */
#include "list.h"
#include "buttonbar.h"
#define MAX_MENUS 8
/* used to allow for dynamic menus */
#define MAX_MENU_SUBITEMS 64
static struct menu_item_ex *current_submenus_menu;
static int current_subitems[MAX_MENU_SUBITEMS];
static int current_subitems_count = 0;
static int talk_menu_item(int selected_item, void *data);
static void get_menu_callback(const struct menu_item_ex *m,
menu_callback_type *menu_callback)
{
if (m->flags&(MENU_HAS_DESC|MENU_DYNAMIC_DESC))
*menu_callback= m->callback_and_desc->menu_callback;
else
*menu_callback = m->menu_callback;
}
static int get_menu_selection(int selected_item, const struct menu_item_ex *menu)
{
int type = (menu->flags&MENU_TYPE_MASK);
if ((type == MT_MENU || type == MT_RETURN_ID)
&& (selected_item<current_subitems_count))
return current_subitems[selected_item];
return selected_item;
}
static int find_menu_selection(int selected)
{
int i;
for (i=0; i< current_subitems_count; i++)
if (current_subitems[i] == selected)
return i;
return 0;
}
static char * get_menu_item_name(int selected_item,
void * data,
char *buffer,
size_t buffer_len)
{
const struct menu_item_ex *menu = (const struct menu_item_ex *)data;
int type = (menu->flags&MENU_TYPE_MASK);
selected_item = get_menu_selection(selected_item, menu);
(void)buffer_len;
/* only MT_MENU or MT_RETURN_ID is allowed in here */
if (type == MT_RETURN_ID)
{
if (menu->flags&MENU_DYNAMIC_DESC)
return menu->menu_get_name_and_icon->list_get_name(selected_item,
menu->menu_get_name_and_icon->list_get_name_data, buffer);
return (char*)menu->strings[selected_item];
}
menu = menu->submenus[selected_item];
if ((menu->flags&MENU_DYNAMIC_DESC) && (type != MT_SETTING_W_TEXT))
return menu->menu_get_name_and_icon->list_get_name(selected_item,
menu->menu_get_name_and_icon->list_get_name_data, buffer);
type = (menu->flags&MENU_TYPE_MASK);
if ((type == MT_SETTING) || (type == MT_SETTING_W_TEXT))
{
const struct settings_list *v
= find_setting(menu->variable, NULL);
if (v)
return str(v->lang_id);
else return "Not Done yet!";
}
return P2STR(menu->callback_and_desc->desc);
}
#ifdef HAVE_LCD_BITMAP
static enum themable_icons menu_get_icon(int selected_item, void * data)
{
const struct menu_item_ex *menu = (const struct menu_item_ex *)data;
int menu_icon = Icon_NOICON;
selected_item = get_menu_selection(selected_item, menu);
if ((menu->flags&MENU_TYPE_MASK) == MT_RETURN_ID)
{
return Icon_Menu_functioncall;
}
menu = menu->submenus[selected_item];
if (menu->flags&MENU_HAS_DESC)
menu_icon = menu->callback_and_desc->icon_id;
else if (menu->flags&MENU_DYNAMIC_DESC)
menu_icon = menu->menu_get_name_and_icon->icon_id;
if (menu_icon == Icon_NOICON)
{
switch (menu->flags&MENU_TYPE_MASK)
{
case MT_SETTING:
case MT_SETTING_W_TEXT:
menu_icon = Icon_Menu_setting;
break;
case MT_MENU:
menu_icon = Icon_Submenu;
break;
case MT_FUNCTION_CALL:
case MT_RETURN_VALUE:
menu_icon = Icon_Menu_functioncall;
break;
}
}
return menu_icon;
}
#endif
static void init_menu_lists(const struct menu_item_ex *menu,
struct gui_synclist *lists, int selected, bool callback,
struct viewport parent[NB_SCREENS])
{
int i, count = MENU_GET_COUNT(menu->flags);
int type = (menu->flags&MENU_TYPE_MASK);
menu_callback_type menu_callback = NULL;
int icon;
current_subitems_count = 0;
if (type == MT_RETURN_ID)
get_menu_callback(menu, &menu_callback);
for (i=0; i<count; i++)
{
if (type != MT_RETURN_ID)
get_menu_callback(menu->submenus[i],&menu_callback);
if (menu_callback)
{
if (menu_callback(ACTION_REQUEST_MENUITEM,
type==MT_RETURN_ID ? (void*)(intptr_t)i: menu->submenus[i])
!= ACTION_EXIT_MENUITEM)
{
current_subitems[current_subitems_count] = i;
current_subitems_count++;
}
}
else
{
current_subitems[current_subitems_count] = i;
current_subitems_count++;
}
}
current_submenus_menu = (struct menu_item_ex *)menu;
gui_synclist_init(lists,get_menu_item_name,(void*)menu,false,1, parent);
#ifdef HAVE_LCD_BITMAP
if (menu->callback_and_desc->icon_id == Icon_NOICON)
icon = Icon_Submenu_Entered;
else
icon = menu->callback_and_desc->icon_id;
gui_synclist_set_title(lists, P2STR(menu->callback_and_desc->desc), icon);
gui_synclist_set_icon_callback(lists, menu_get_icon);
#else
(void)icon;
gui_synclist_set_icon_callback(lists, NULL);
#endif
if(global_settings.talk_menu)
gui_synclist_set_voice_callback(lists, talk_menu_item);
gui_synclist_set_nb_items(lists,current_subitems_count);
gui_synclist_limit_scroll(lists,true);
gui_synclist_select_item(lists, find_menu_selection(selected));
get_menu_callback(menu,&menu_callback);
if (callback && menu_callback)
menu_callback(ACTION_ENTER_MENUITEM,menu);
gui_synclist_draw(lists);
gui_synclist_speak_item(lists);
}
static int talk_menu_item(int selected_item, void *data)
{
const struct menu_item_ex *menu = (const struct menu_item_ex *)data;
int id = -1;
int type;
unsigned char *str;
int sel = get_menu_selection(selected_item, menu);
if ((menu->flags&MENU_TYPE_MASK) == MT_MENU)
{
type = menu->submenus[sel]->flags&MENU_TYPE_MASK;
if ((type == MT_SETTING) || (type == MT_SETTING_W_TEXT))
talk_setting(menu->submenus[sel]->variable);
else
{
if (menu->submenus[sel]->flags&(MENU_DYNAMIC_DESC))
{
int (*list_speak_item)(int selected_item, void * data)
= menu->submenus[sel]->menu_get_name_and_icon->
list_speak_item;
if(list_speak_item)
list_speak_item(sel, menu->submenus[sel]->
menu_get_name_and_icon->
list_get_name_data);
else
{
char buffer[80];
str = menu->submenus[sel]->menu_get_name_and_icon->
list_get_name(sel, menu->submenus[sel]->
menu_get_name_and_icon->
list_get_name_data, buffer);
id = P2ID(str);
}
}
else
id = P2ID(menu->submenus[sel]->callback_and_desc->desc);
if (id != -1)
talk_id(id,false);
}
}
else if(((menu->flags&MENU_TYPE_MASK) == MT_RETURN_ID))
{
if ((menu->flags&MENU_DYNAMIC_DESC) == 0)
{
unsigned char *s = (unsigned char *)menu->strings[sel];
/* string list, try to talk it if ID2P was used */
id = P2ID(s);
if (id != -1)
talk_id(id,false);
}
}
return 0;
}
void do_setting_from_menu(const struct menu_item_ex *temp,
struct viewport parent[NB_SCREENS])
{
int setting_id, oldval;
const struct settings_list *setting = find_setting(
temp->variable,
&setting_id);
char *title;
char padded_title[MAX_PATH];
int var_type = setting->flags&F_T_MASK;
if (var_type == F_T_INT || var_type == F_T_UINT)
{
oldval = *(int*)setting->setting;
}
else if (var_type == F_T_BOOL)
{
oldval = *(bool*)setting->setting;
}
else
oldval = 0;
if ((temp->flags&MENU_TYPE_MASK) == MT_SETTING_W_TEXT)
title = temp->callback_and_desc->desc;
else
title = ID2P(setting->lang_id);
/* Pad the title string by repeating it. This is needed
so the scroll settings title can actually be used to
test the setting */
if (setting->flags&F_PADTITLE)
{
int i = 0, len;
if (setting->lang_id == -1)
title = (char*)setting->cfg_vals;
else
title = P2STR((unsigned char*)title);
len = strlen(title);
while (i < MAX_PATH-1)
{
int padlen = MIN(len, MAX_PATH-1-i);
memcpy(&padded_title[i], title, padlen);
i += padlen;
if (i<MAX_PATH-1)
padded_title[i++] = ' ';
}
padded_title[i] = '\0';
title = padded_title;
}
option_screen((struct settings_list *)setting, parent,
setting->flags&F_TEMPVAR, title);
}
/* display a menu */
int do_menu(const struct menu_item_ex *start_menu, int *start_selected,
struct viewport parent[NB_SCREENS], bool hide_bars)
{
int selected = start_selected? *start_selected : 0;
int action;
struct gui_synclist lists;
const struct menu_item_ex *temp, *menu;
int ret = 0, i;
bool redraw_lists;
int oldbars = viewportmanager_get_statusbar();
viewportmanager_set_statusbar(hide_bars ? VP_SB_HIDE_ALL : oldbars);
const struct menu_item_ex *menu_stack[MAX_MENUS];
int menu_stack_selected_item[MAX_MENUS];
int stack_top = 0;
bool in_stringlist, done = false;
struct viewport *vps = NULL;
#ifdef HAVE_BUTTONBAR
struct gui_buttonbar buttonbar;
gui_buttonbar_init(&buttonbar);
gui_buttonbar_set_display(&buttonbar, &(screens[SCREEN_MAIN]) );
gui_buttonbar_set(&buttonbar, "<<<", "", "");
#endif
menu_callback_type menu_callback = NULL;
if (start_menu == NULL)
menu = &main_menu_;
else menu = start_menu;
/* if hide_bars is true, assume parent has been fixed before passed into
* this function, e.g. with viewport_set_defaults(parent, screen) */
init_menu_lists(menu, &lists, selected, true, parent);
vps = *(lists.parent);
in_stringlist = ((menu->flags&MENU_TYPE_MASK) == MT_RETURN_ID);
/* load the callback, and only reload it if menu changes */
get_menu_callback(menu, &menu_callback);
#ifdef HAVE_BUTTONBAR
if (!hide_bars)
{
gui_buttonbar_set(&buttonbar, "<<<", "", "");
gui_buttonbar_draw(&buttonbar);
}
#endif
while (!done)
{
redraw_lists = false;
if (!hide_bars)
{
#ifdef HAVE_BUTTONBAR
gui_buttonbar_draw(&buttonbar);
#endif
}
action = get_action(CONTEXT_MAINMENU,
list_do_action_timeout(&lists, HZ));
/* HZ so the status bar redraws corectly */
if (menu_callback)
{
int old_action = action;
action = menu_callback(action, menu);
if (action == ACTION_EXIT_AFTER_THIS_MENUITEM)
{
action = old_action;
ret = MENU_SELECTED_EXIT; /* will exit after returning
from selection */
}
else if (action == ACTION_REDRAW)
{
action = old_action;
redraw_lists = true;
}
}
if (gui_synclist_do_button(&lists, &action, LIST_WRAP_UNLESS_HELD))
continue;
if (action == ACTION_NONE)
continue;
#ifdef HAVE_QUICKSCREEN
else if (action == ACTION_STD_QUICKSCREEN)
{
quick_screen_quick(action);
redraw_lists = true;
}
#endif
#ifdef HAVE_RECORDING
else if (action == ACTION_STD_REC)
{
ret = GO_TO_RECSCREEN;
done = true;
}
#endif
else if (action == ACTION_TREE_WPS)
{
ret = GO_TO_PREVIOUS_MUSIC;
done = true;
}
else if (action == ACTION_TREE_STOP)
{
redraw_lists = list_stop_handler();
}
else if (action == ACTION_STD_CONTEXT)
{
if (menu == &root_menu_)
{
ret = GO_TO_ROOTITEM_CONTEXT;
done = true;
}
else if (!in_stringlist)
{
int type;
selected = get_menu_selection(gui_synclist_get_sel_pos(&lists),menu);
temp = menu->submenus[selected];
type = (temp->flags&MENU_TYPE_MASK);
if ((type == MT_SETTING_W_TEXT || type == MT_SETTING))
{
#ifdef HAVE_QUICKSCREEN
MENUITEM_STRINGLIST(quickscreen_able_option,
ID2P(LANG_ONPLAY_MENU_TITLE), NULL,
ID2P(LANG_RESET_SETTING),
ID2P(LANG_LEFT_QS_ITEM),
ID2P(LANG_BOTTOM_QS_ITEM),
ID2P(LANG_RIGHT_QS_ITEM));
#endif
MENUITEM_STRINGLIST(notquickscreen_able_option,
ID2P(LANG_ONPLAY_MENU_TITLE), NULL,
ID2P(LANG_RESET_SETTING));
const struct menu_item_ex *menu;
int menu_selection = 0;
const struct settings_list *setting =
find_setting(temp->variable, NULL);
#ifdef HAVE_QUICKSCREEN
if (is_setting_quickscreenable(setting))
menu = &quickscreen_able_option;
else
#endif
menu = &notquickscreen_able_option;
switch (do_menu(menu, &menu_selection, NULL, false))
{
case GO_TO_PREVIOUS:
break;
case 0: /* reset setting */
reset_setting(setting, setting->setting);
break;
#ifdef HAVE_QUICKSCREEN
break;
case 1: /* set as left QS item */
set_as_qs_item(setting, QUICKSCREEN_LEFT);
break;
case 2: /* set as bottom QS item */
set_as_qs_item(setting, QUICKSCREEN_BOTTOM);
break;
case 3: /* set as right QS item */
set_as_qs_item(setting, QUICKSCREEN_RIGHT);
break;
#endif
} /* swicth(do_menu()) */
redraw_lists = true;
}
} /* else if (!in_stringlist) */
}
else if (action == ACTION_STD_MENU)
{
if (menu != &root_menu_)
ret = GO_TO_ROOT;
else
ret = GO_TO_PREVIOUS;
done = true;
}
else if (action == ACTION_STD_CANCEL)
{
bool exiting_menu = false;
in_stringlist = false;
/* might be leaving list, so stop scrolling */
FOR_NB_SCREENS(i)
{
screens[i].stop_scroll();
}
if (menu_callback)
menu_callback(ACTION_EXIT_MENUITEM, menu);
if (menu->flags&MENU_EXITAFTERTHISMENU)
done = true;
else if ((menu->flags&MENU_TYPE_MASK) == MT_MENU)
exiting_menu = true;
if (stack_top > 0)
{
stack_top--;
menu = menu_stack[stack_top];
if (!exiting_menu && (menu->flags&MENU_EXITAFTERTHISMENU))
done = true;
else
init_menu_lists(menu, &lists,
menu_stack_selected_item[stack_top], false, vps);
/* new menu, so reload the callback */
get_menu_callback(menu, &menu_callback);
}
else if (menu != &root_menu_)
{
ret = GO_TO_PREVIOUS;
done = true;
}
}
else if (action == ACTION_STD_OK)
{
int type;
/* entering an item that may not be a list, so stop scrolling */
FOR_NB_SCREENS(i)
{
screens[i].stop_scroll();
}
#ifdef HAVE_BUTTONBAR
if (!hide_bars)
{
gui_buttonbar_unset(&buttonbar);
gui_buttonbar_draw(&buttonbar);
}
#endif
selected = get_menu_selection(gui_synclist_get_sel_pos(&lists), menu);
temp = menu->submenus[selected];
redraw_lists = true;
if (in_stringlist)
type = (menu->flags&MENU_TYPE_MASK);
else
{
type = (temp->flags&MENU_TYPE_MASK);
get_menu_callback(temp, &menu_callback);
if (menu_callback)
{
action = menu_callback(ACTION_ENTER_MENUITEM,temp);
if (action == ACTION_EXIT_MENUITEM)
break;
}
}
switch (type)
{
case MT_MENU:
if (stack_top < MAX_MENUS)
{
menu_stack[stack_top] = menu;
menu_stack_selected_item[stack_top] = selected;
stack_top++;
init_menu_lists(temp, &lists, 0, true, vps);
redraw_lists = false; /* above does the redraw */
menu = temp;
}
break;
case MT_FUNCTION_CALL:
{
int return_value;
if (temp->flags&MENU_FUNC_USEPARAM)
return_value = temp->function->function_w_param(
temp->function->param);
else
return_value = temp->function->function();
if (!(menu->flags&MENU_EXITAFTERTHISMENU) ||
(temp->flags&MENU_EXITAFTERTHISMENU))
{
init_menu_lists(menu, &lists, selected, true, vps);
}
if (temp->flags&MENU_FUNC_CHECK_RETVAL)
{
if (return_value != 0)
{
done = true;
ret = return_value;
}
}
break;
}
case MT_SETTING:
case MT_SETTING_W_TEXT:
{
do_setting_from_menu(temp, vps);
break;
}
case MT_RETURN_ID:
if (in_stringlist)
{
done = true;
ret = selected;
}
else if (stack_top < MAX_MENUS)
{
menu_stack[stack_top] = menu;
menu_stack_selected_item[stack_top] = selected;
stack_top++;
menu = temp;
init_menu_lists(menu,&lists,0,false, vps);
redraw_lists = false; /* above does the redraw */
in_stringlist = true;
}
break;
case MT_RETURN_VALUE:
ret = temp->value;
done = true;
break;
}
if (type != MT_MENU)
{
if (menu_callback)
menu_callback(ACTION_EXIT_MENUITEM,temp);
}
if (current_submenus_menu != menu)
init_menu_lists(menu,&lists,selected,true,vps);
/* callback was changed, so reload the menu's callback */
get_menu_callback(menu, &menu_callback);
if ((menu->flags&MENU_EXITAFTERTHISMENU) &&
!(temp->flags&MENU_EXITAFTERTHISMENU))
{
done = true;
break;
}
#ifdef HAVE_BUTTONBAR
if (!hide_bars)
{
gui_buttonbar_set(&buttonbar, "<<<", "", "");
gui_buttonbar_draw(&buttonbar);
}
#endif
}
else if(default_event_handler(action) == SYS_USB_CONNECTED)
{
ret = MENU_ATTACHED_USB;
done = true;
}
if (redraw_lists && !done)
{
if (menu_callback)
menu_callback(ACTION_REDRAW, menu);
gui_synclist_draw(&lists);
gui_synclist_speak_item(&lists);
}
}
if (start_selected)
{
/* make sure the start_selected variable is set to
the selected item from the menu do_menu() was called from */
if (stack_top > 0)
{
menu = menu_stack[0];
init_menu_lists(menu,&lists,menu_stack_selected_item[0],true, vps);
}
*start_selected = get_menu_selection(
gui_synclist_get_sel_pos(&lists), menu);
}
viewportmanager_set_statusbar(oldbars);
return ret;
}