rockbox/apps/gui/option_select.c
Christian Soffke dfd9c10589 Eliminate skin updates in between activities
1) Adds way to pop activity without refreshing the skin at
the same time.

Activities are sometimes popped in immediate succession,
or one activity is popped before another one is pushed right
away. This can lead to the UI appearing glitchy, due to an
activity only appearing for a split-second, which is especially
noticeable with complex skins that change the dimensions
of the UI viewport depending on the current activity

To fix this, prevent superfluous skin updates

* when switching between:
- WPS and browser
- WPS and Playlist Catalogue
- WPS and playlist
- WPS and Settings/System/Plugins

* when accessing Track Info or when displaying
bookmarks using the context menu on the WPS

* when switching from QuickScreen to Shortcuts Menu

2) The playlist viewer activity was pushed & popped
redundantly by playlist_view.

----
NB:
Behavior has remained unchanged in all instances of the
code where pop_current_activity() has been replaced by
pop_current_activity(ACTIVITY_REFRESH_NOW).

Change-Id: I56b517b8c9dba823a9fed3a3f558d7469dcea9fd
2022-12-11 11:50:33 +01:00

621 lines
21 KiB
C

/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2005 by Kevin Ferrare
* Copyright (C) 2007 by Jonathan Gordon
*
* 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.
*
****************************************************************************/
#include <stdlib.h>
#include "string-extra.h"
#include "config.h"
#include "system.h"
#include "option_select.h"
#include "kernel.h"
#include "lang.h"
#include "talk.h"
#include "settings_list.h"
#include "sound.h"
#include "list.h"
#include "action.h"
#include "misc.h"
#include "splash.h"
#include "menu.h"
#include "quickscreen.h"
static int selection_to_val(const struct settings_list *setting, int selection);
int option_value_as_int(const struct settings_list *setting)
{
int type = (setting->flags & F_T_MASK);
int temp = 0;
if (type == F_T_BOOL)
temp = *(bool*)setting->setting?1:0;
else if (type == F_T_UINT || type == F_T_INT)
temp = *(int*)setting->setting;
return temp;
}
/* return an auto ranged time string, unit_idx specifies lowest or
base index of the passed value -- flag F_TIME_SETTING calls this */
static const char *option_get_timestring(char *buf, int buf_len,
int val, int unit_idx)
{
return format_time_auto(buf, buf_len, val, unit_idx | UNIT_TRIM_ZERO, false);
}
/* these two vars are needed so arbitrary values can be added to the
TABLE_SETTING settings if the F_ALLOW_ARBITRARY_VALS flag is set */
static int table_setting_oldval = 0, table_setting_array_position = 0;
const char *option_get_valuestring(const struct settings_list *setting,
char *buffer, int buf_len,
intptr_t temp_var)
{
const char* str = buffer;
if ((setting->flags & F_BOOL_SETTING) == F_BOOL_SETTING)
{
bool val = (bool)temp_var;
strmemccpy(buffer, str(val? setting->bool_setting->lang_yes :
setting->bool_setting->lang_no), buf_len);
}
#if 0 /* probably dont need this one */
else if ((setting->flags & F_FILENAME) == F_FILENAME)
{
struct filename_setting *info = setting->filename_setting;
snprintf(buffer, buf_len, "%s%s%s", info->prefix,
(char*)temp_var, info->suffix);
}
#endif
else if (((setting->flags & F_INT_SETTING) == F_INT_SETTING) ||
((setting->flags & F_TABLE_SETTING) == F_TABLE_SETTING))
{
const struct int_setting *int_info = setting->int_setting;
const struct table_setting *tbl_info = setting->table_setting;
int info_unit;
const char *str_unit;
const char* (*formatter)(char*, size_t, int, const char*);
if ((setting->flags & F_INT_SETTING) == F_INT_SETTING)
{
formatter = int_info->formatter;
info_unit = int_info->unit;
}
else
{
formatter = tbl_info->formatter;
info_unit = tbl_info->unit;
}
if ((setting->flags & F_TIME_SETTING) == F_TIME_SETTING)
str = option_get_timestring(buffer, buf_len, (long)temp_var, info_unit);
str_unit = unit_strings_core[info_unit];
if (formatter)
str = formatter(buffer, buf_len, (int)temp_var, str_unit);
else if ((setting->flags & F_TIME_SETTING) != F_TIME_SETTING)
snprintf(buffer, buf_len, "%d %s", (int)temp_var, str_unit?str_unit:"");
}
else if ((setting->flags & F_T_SOUND) == F_T_SOUND)
{
format_sound_value(buffer, buf_len,
setting->sound_setting->setting,
temp_var);
}
else if ((setting->flags & F_CHOICE_SETTING) == F_CHOICE_SETTING)
{
if (setting->flags & F_CHOICETALKS)
{
const struct choice_setting *info = setting->choice_setting;
if (info->talks[(int)temp_var] < LANG_LAST_INDEX_IN_ARRAY)
{
strmemccpy(buffer, str(info->talks[(int)temp_var]), buf_len);
}
else
{
cfg_int_to_string(setting, (int)temp_var, buffer, buf_len);
}
}
else
{
int value = (int)temp_var;
char *val = P2STR(setting->choice_setting->desc[value]);
strmemccpy(buffer, val, buf_len);
}
}
return str;
}
void option_talk_value(const struct settings_list *setting, int value, bool enqueue)
{
if ((setting->flags & F_BOOL_SETTING) == F_BOOL_SETTING)
{
bool val = (value==1);
talk_id(val? setting->bool_setting->lang_yes :
setting->bool_setting->lang_no, enqueue);
}
#if 0 /* probably dont need this one */
else if ((setting->flags & F_FILENAME) == F_FILENAME)
{
}
#endif
else if (((setting->flags & F_INT_SETTING) == F_INT_SETTING) ||
((setting->flags & F_TABLE_SETTING) == F_TABLE_SETTING))
{
const struct int_setting *int_info = setting->int_setting;
const struct table_setting *tbl_info = setting->table_setting;
int unit;
int32_t (*get_talk_id)(int, int);
if ((setting->flags & F_INT_SETTING) == F_INT_SETTING)
{
unit = int_info->unit;
get_talk_id = int_info->get_talk_id;
}
else
{
unit = tbl_info->unit;
get_talk_id = tbl_info->get_talk_id;
}
if (get_talk_id)
talk_id(get_talk_id(value, unit), enqueue);
else if ((setting->flags & F_TIME_SETTING) == F_TIME_SETTING)
talk_time_intervals(value, unit, enqueue);
else
talk_value(value, unit, enqueue);
}
else if ((setting->flags & F_T_SOUND) == F_T_SOUND)
{
int talkunit = UNIT_INT;
int sound_setting = setting->sound_setting->setting;
const char *unit = sound_unit(sound_setting);
int decimals = sound_numdecimals(sound_setting);
int phys = sound_val2phys(sound_setting, value);
if (!strcmp(unit, "dB"))
talkunit = UNIT_DB;
else if (!strcmp(unit, "%"))
talkunit = UNIT_PERCENT;
else if (!strcmp(unit, "Hz"))
talkunit = UNIT_HERTZ;
talk_value_decimal(phys, talkunit, decimals, enqueue);
}
else if ((setting->flags & F_CHOICE_SETTING) == F_CHOICE_SETTING)
{
if (setting->flags & F_CHOICETALKS)
{
talk_id(setting->choice_setting->talks[value], enqueue);
}
else
{
talk_id(P2ID(setting->choice_setting->desc[value]), enqueue);
}
}
}
static int option_talk(int selected_item, void * data)
{
struct settings_list *setting = (struct settings_list *)data;
int temp_var = selection_to_val(setting, selected_item);
option_talk_value(setting, temp_var, false);
return 0;
}
#if defined(HAVE_QUICKSCREEN) || defined(HAVE_RECORDING) || defined(HAVE_TOUCHSCREEN)
/* only the quickscreen and recording trigger needs this */
void option_select_next_val(const struct settings_list *setting,
bool previous, bool apply)
{
int val = 0;
int *value = setting->setting;
if ((setting->flags & F_BOOL_SETTING) == F_BOOL_SETTING)
{
*(bool*)value = !*(bool*)value;
if (apply && setting->bool_setting->option_callback)
setting->bool_setting->option_callback(*(bool*)value);
return;
}
else if ((setting->flags & F_INT_SETTING) == F_INT_SETTING)
{
struct int_setting *info = (struct int_setting *)setting->int_setting;
bool neg_step = (info->step < 0);
if (!previous)
{
val = *value + info->step;
if (neg_step ? (val < info->max) : (val > info->max))
val = info->min;
}
else
{
val = *value - info->step;
if (neg_step ? (val > info->min) : (val < info->min))
val = info->max;
}
*value = val;
if (apply && info->option_callback)
info->option_callback(val);
}
else if ((setting->flags & F_T_SOUND) == F_T_SOUND)
{
int setting_id = setting->sound_setting->setting;
int steps = sound_steps(setting_id);
int min = sound_min(setting_id);
int max = sound_max(setting_id);
if (!previous)
{
val = *value + steps;
if (val >= max)
val = min;
}
else
{
val = *value - steps;
if (val < min)
val = max;
}
*value = val;
if (apply)
sound_set(setting_id, val);
}
else if ((setting->flags & F_CHOICE_SETTING) == F_CHOICE_SETTING)
{
struct choice_setting *info = (struct choice_setting *)setting->choice_setting;
if (!previous)
{
val = *value + 1;
if (val >= info->count)
val = 0;
}
else
{
val = *value - 1;
if (val < 0)
val = info->count-1;
}
*value = val;
if (apply && info->option_callback)
info->option_callback(val);
}
else if ((setting->flags & F_TABLE_SETTING) == F_TABLE_SETTING)
{
const struct table_setting *tbl_info = setting->table_setting;
int i, add;
add = previous?tbl_info->count-1:1;
for (i=0; i<tbl_info->count;i++)
{
if ((*value == tbl_info->values[i]) ||
(settings->flags&F_ALLOW_ARBITRARY_VALS &&
*value < tbl_info->values[i]))
{
val = tbl_info->values[(i+add)%tbl_info->count];
break;
}
}
*value = val;
if (apply && tbl_info->option_callback)
tbl_info->option_callback(val);
}
}
#endif
static int selection_to_val(const struct settings_list *setting, int selection)
{
/* rockbox: comment 'set but unused' variables
int min = 0;
*/
int max = 0, step = 1;
if (((setting->flags & F_BOOL_SETTING) == F_BOOL_SETTING) ||
((setting->flags & F_CHOICE_SETTING) == F_CHOICE_SETTING))
return selection;
else if ((setting->flags & F_TABLE_SETTING) == F_TABLE_SETTING)
{
const struct table_setting *info = setting->table_setting;
if (setting->flags&F_ALLOW_ARBITRARY_VALS &&
table_setting_array_position != -1 &&
(selection >= table_setting_array_position))
{
if (selection == table_setting_array_position)
return table_setting_oldval;
return info->values[selection-1];
}
else
return info->values[selection];
}
else if ((setting->flags & F_T_SOUND) == F_T_SOUND)
{
int setting_id = setting->sound_setting->setting;
if(global_settings.list_order == LIST_ORDER_DESCENDING)
{
step = sound_steps(setting_id);
max = (setting_id == SOUND_VOLUME) ?
global_settings.volume_limit : sound_max(setting_id);
/* min = sound_min(setting_id); */
}
else
{
step = -sound_steps(setting_id);
/* min = sound_max(setting_id); */
max = sound_min(setting_id);
}
}
else if ((setting->flags & F_INT_SETTING) == F_INT_SETTING)
{
const struct int_setting *info = setting->int_setting;
if(global_settings.list_order == LIST_ORDER_DESCENDING)
{
/* min = info->min; */
max = info->max;
step = info->step;
}
else
{
max = info->min;
/* min = info->max; */
step = -info->step;
}
}
return max- (selection * step);
}
static const char * value_setting_get_name_cb(int selected_item,
void * data,
char *buffer,
size_t buffer_len)
{
selected_item = selection_to_val(data, selected_item);
return option_get_valuestring(data, buffer, buffer_len, selected_item);
}
/* wrapper to convert from int param to bool param in option_screen */
static void (*boolfunction)(bool);
static void bool_funcwrapper(int value)
{
if (value)
boolfunction(true);
else
boolfunction(false);
}
static void val_to_selection(const struct settings_list *setting, int oldvalue,
int *nb_items, int *selected,
void (**function)(int))
{
int var_type = setting->flags&F_T_MASK;
/* set the number of items and current selection */
if (var_type == F_T_INT || var_type == F_T_UINT)
{
if (setting->flags&F_CHOICE_SETTING)
{
*nb_items = setting->choice_setting->count;
*selected = oldvalue;
*function = setting->choice_setting->option_callback;
}
else if (setting->flags&F_TABLE_SETTING)
{
const struct table_setting *info = setting->table_setting;
int i;
*nb_items = info->count;
*selected = -1;
table_setting_array_position = -1;
for (i=0;*selected==-1 && i<*nb_items;i++)
{
if (setting->flags&F_ALLOW_ARBITRARY_VALS &&
(oldvalue < info->values[i]))
{
table_setting_oldval = oldvalue;
table_setting_array_position = i;
*selected = i;
(*nb_items)++;
}
else if (oldvalue == info->values[i])
*selected = i;
}
*function = info->option_callback;
}
else if (setting->flags&F_T_SOUND)
{
int setting_id = setting->sound_setting->setting;
int steps = sound_steps(setting_id);
int min = sound_min(setting_id);
int max = (setting_id == SOUND_VOLUME) ?
global_settings.volume_limit : sound_max(setting_id);
*nb_items = (max-min)/steps + 1;
if (global_settings.list_order == LIST_ORDER_DESCENDING)
*selected = (max - oldvalue) / steps;
else
*selected = (oldvalue - min) / steps;
*function = sound_get_fn(setting_id);
}
else
{
const struct int_setting *info = setting->int_setting;
int min, max, step;
max = info->max;
min = info->min;
step = info->step;
*nb_items = (max-min)/step + 1;
if(global_settings.list_order == LIST_ORDER_DESCENDING)
*selected = (max - oldvalue) / step;
else
*selected = (oldvalue - min) / step;
*function = info->option_callback;
}
}
else if (var_type == F_T_BOOL)
{
*selected = oldvalue;
*nb_items = 2;
boolfunction = setting->bool_setting->option_callback;
if (boolfunction)
*function = bool_funcwrapper;
}
}
bool option_screen(const struct settings_list *setting,
struct viewport parent[NB_SCREENS],
bool use_temp_var, unsigned char* option_title)
{
int action;
bool done = false;
struct gui_synclist lists;
int oldvalue, nb_items = 0, selected = 0, temp_var;
int *variable;
bool allow_wrap = setting->flags & F_NO_WRAP ? false : true;
bool cb_on_select_only =
((setting->flags & F_CB_ON_SELECT_ONLY) == F_CB_ON_SELECT_ONLY);
int var_type = setting->flags&F_T_MASK;
void (*function)(int) = NULL;
char *title;
if (var_type == F_T_INT || var_type == F_T_UINT)
{
variable = use_temp_var ? &temp_var: (int*)setting->setting;
temp_var = oldvalue = *(int*)setting->setting;
}
else if (var_type == F_T_BOOL)
{
/* bools always use the temp variable...
if use_temp_var is false it will be copied to setting->setting every change */
variable = &temp_var;
temp_var = oldvalue = *(bool*)setting->setting?1:0;
}
else return false; /* only int/bools can go here */
push_current_activity(ACTIVITY_OPTIONSELECT);
gui_synclist_init(&lists, value_setting_get_name_cb,
(void*)setting, false, 1, parent);
if (setting->lang_id == -1)
title = (char*)setting->cfg_vals;
else
title = P2STR(option_title);
gui_synclist_set_title(&lists, title, Icon_Questionmark);
gui_synclist_set_icon_callback(&lists, NULL);
if(global_settings.talk_menu)
gui_synclist_set_voice_callback(&lists, option_talk);
val_to_selection(setting, oldvalue, &nb_items, &selected, &function);
gui_synclist_set_nb_items(&lists, nb_items);
gui_synclist_select_item(&lists, selected);
gui_synclist_draw(&lists);
/* talk the item */
gui_synclist_speak_item(&lists);
while (!done)
{
/* override user wraparound setting; used mainly by EQ settings.
* Not sure this is justified? */
if (!allow_wrap)
lists.wraparound = false;
if (list_do_action(CONTEXT_LIST, HZ, /* HZ so the status bar redraws */
&lists, &action))
{
/* setting changed */
selected = gui_synclist_get_sel_pos(&lists);
*variable = selection_to_val(setting, selected);
if (var_type == F_T_BOOL && !use_temp_var)
*(bool*)setting->setting = (*variable==1);
}
else if (action == ACTION_NONE)
continue;
else if (action == ACTION_STD_CANCEL || action == ACTION_STD_MENU)
{
/* setting canceled, restore old value if changed */
if (*variable != oldvalue)
{
*variable = oldvalue;
if (var_type == F_T_BOOL && !use_temp_var)
*(bool*)setting->setting = (oldvalue==1);
splash(HZ/2, ID2P(LANG_CANCEL));
}
done = true;
}
else if (action == ACTION_STD_CONTEXT)
{
/* reset setting to default */
reset_setting(setting, variable);
if (var_type == F_T_BOOL && !use_temp_var)
*(bool*)setting->setting = (*variable==1);
val_to_selection(setting, *variable, &nb_items,
&selected, &function);
gui_synclist_select_item(&lists, selected);
gui_synclist_draw(&lists);
gui_synclist_speak_item(&lists);
}
else if (action == ACTION_STD_OK)
{
/* setting accepted, store now if it used a temp var */
if (use_temp_var)
{
if (var_type == F_T_INT || var_type == F_T_UINT)
*(int*)setting->setting = *variable;
else
*(bool*)setting->setting = (*variable==1);
}
settings_save();
done = true;
if (cb_on_select_only && function)
function(*variable);
}
else if(default_event_handler(action) == SYS_USB_CONNECTED)
return true;
/* callback */
if (function && !cb_on_select_only)
function(*variable);
/* if the volume is changing we need to let the skins know */
if (function == sound_get_fn(SOUND_VOLUME))
global_status.last_volume_change = current_tick;
}
pop_current_activity(ACTIVITY_REFRESH_NOW);
return false;
}
int get_setting_info_for_bar(const struct settings_list *setting, int *count, int *val)
{
int var_type = setting->flags&F_T_MASK;
void (*function)(int) = NULL;
int oldvalue;
if (var_type == F_T_INT || var_type == F_T_UINT)
{
oldvalue = *(int*)setting->setting;
}
else if (var_type == F_T_BOOL)
{
oldvalue = *(bool*)setting->setting?1:0;
}
else
{
*val = 0;
*count = 1;
return false; /* only int/bools can go here */
}
val_to_selection(setting, oldvalue, count, val, &function);
return true;
}
#ifdef HAVE_TOUCHSCREEN
void update_setting_value_from_touch(const struct settings_list *setting, int selection)
{
int new_val = selection_to_val(setting, selection);
int var_type = setting->flags&F_T_MASK;
if (var_type == F_T_INT || var_type == F_T_UINT)
{
*(int*)setting->setting = new_val;
}
else if (var_type == F_T_BOOL)
{
*(bool*)setting->setting = new_val ? true : false;
}
}
#endif