I've got a plugin that implements it in progress but I still haven't finished it yet but I will eventually get interested in it again
/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2017 William Wilgus
*
* 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 "plugin.h"
#include "lib/pluginlib_actions.h"
#define settings_filename PLUGIN_APPS_DIR "/fade2sleep_settings.f2s"
#define MSTR_CT(x) (sizeof(x)/sizeof(0[x]))
#define MFMT_PLAYLIST "%s %s %s;"
#define MFMT_TIME "%s %s;"
#define MFMT_VOL "%s %d %s;"
#define MFMT_STR "%s;"
#define MFMT_SEPARATOR " ;"
#define MENU_SETTINGS_BEGIN "",
#define MENUSETTING_VOLUME(item) setting_strings[item],settings->table[item],\
rb->sound_unit(SOUND_VOLUME)
#define MENUSETTING_TRIGGER(item) setting_strings[item],\
trigger_strings[settings->table[item]]
#define MENUSETTING_ACTION(item) action_strings[settings->table[item]]
#define MENUSETTING_PLAY(item) setting_strings[item],\
play_strings[settings->table[item]],\
(settings->table[item])? settings->play : "\0"
#define WATCHED_EV(flag) ((watched_events & flag) == flag) ? flag : 0
#define THREAD_STACK_SIZE DEFAULT_STACK_SIZE
/* use long for aligning */
static unsigned long thread_stack[THREAD_STACK_SIZE/sizeof(long)];
#define EV_EXIT MAKE_SYS_EVENT(SYS_EVENT_CLS_PRIVATE, 1)
#define TIMEOUT_VOL_CHANGE HZ/2
static unsigned int thread_id;
static struct event_queue thread_q SHAREDBSS_ATTR;
const struct button_mapping *plugin_contexts[] = { pla_main_ctx };
//#define MAX_MENU_ENTRIES 10 /* menu string pointers statically allocated */
//const char* menu_strings[MAX_MENU_ENTRIES];// * sizeof(const char*)
static unsigned int triggered_events = 0;
static unsigned int watched_events = 0;
static bool quit = false, in_usb_mode = false;
/*Time setting formatting (NOT RTC)*/
#define MS_IN_TICK (1000U/HZ)
enum e_fmt_time_auto_idx
{
UNIT_IDX_HR = 0,
UNIT_IDX_MIN,
UNIT_IDX_SEC,
UNIT_IDX_MS,
UNIT_IDX_TIME_COUNT,
};
/* format_time_auto */
#define UNIT_IDX_MASK 0x01FFU /*Return only Unit_IDX*/
#define UNIT_TRIM_ZERO 0x0200U /*Don't show leading zero on max_idx*/
#define UNIT_LOCK_HR 0x0400U /*Don't Auto Range below this field*/
#define UNIT_LOCK_MIN 0x0800U /*Don't Auto Range below this field*/
#define UNIT_LOCK_SEC 0x1000U /*Don't Auto Range below this field*/
static const char *unit_strings[] =
{
[UNIT_INT] = "", [UNIT_MS] = "ms",
[UNIT_SEC] = "s", [UNIT_MIN] = "min",
[UNIT_HOUR]= "hr", [UNIT_KHZ] = "kHz",
[UNIT_DB] = "dB", [UNIT_PERCENT] = "%",
[UNIT_MAH] = "mAh", [UNIT_PIXEL] = "px",
[UNIT_PER_SEC] = "per sec",
[UNIT_HERTZ] = "Hz",
[UNIT_MB] = "MB", [UNIT_KBIT] = "kb/s",
[UNIT_PM_TICK] = "units/10ms",
};
enum e_self_triggered_events
{ /* TRIGGERED WITHIN THIS PLUGIN */
SELF_EVENT_CHECK_TRIGGERED = 0,
SELF_EVENT_TIMEOUT_REACHED = 1,
SELF_EVENT_FINALVOL_REACHED = 2,
};
enum e_triggered_events
{
//TRIG_EVENT_=0x0001,
TRIG_EVENT_START_PLAYBACK = 0x0002,
TRIG_EVENT_TRACK_SKIP = 0x0004,
TRIG_EVENT_TRACK_CHANGE = 0x0008,
TRIG_EVENT_TRACK_FINISH = 0x0010,
TRIG_EVENT_VOL_CHANGED = 0x0020,
TRIG_EVENT_SCREEN_CHANGED = 0x0040,
TRIG_EVENT_SCREEN_ON = 0x0080,
TRIG_EVENT_VOL_FINAL = 0x0100,
TRIG_EVENT_PAUSE = 0x0200,
TRIG_EVENT_RESUME = 0x0400,
TRIG_EVENT_PLAY = 0x0800,
TRIG_EVENT_STOP = 0x1000,
TRIG_EVENT_TIMEOUT = 0x2000,
//TRIG_EVENT_=0x4000,
//TRIG_EVENT_=0x8000,
};
enum e_play_str
{
PLAY_USECURRENT = 0,
PLAY_LOAD,
};
static const char * play_strings[] =
{
[PLAY_USECURRENT]"Use Current",
[PLAY_LOAD] "Load",
};
enum e_save_str
{
SAV_SAVE = 0,
SAV_SAVED,
};
static const char * saved_strings[] =
{
[SAV_SAVE] "Save",
[SAV_SAVED]"Saved",
};
enum e_trigger_str
{
TRIG_NONE = 0,
TRIG_TIMEOUT,
TRIG_FINALVOL,
TRIG_PAUSE,
TRIG_VOLCHANGE,
TRIG_TRACKCHANGE,
TRIG_ANY,
};
static const char * trigger_strings[] =
{
[TRIG_NONE] "None",
[TRIG_TIMEOUT] "Timeout",
[TRIG_FINALVOL] "Final Volume",
[TRIG_PAUSE] "Pause",
[TRIG_VOLCHANGE] "Volume Change",
[TRIG_TRACKCHANGE]"Track Change",
[TRIG_ANY] "Any Action",
};
enum e_delay_str
{
DLY_NONE = 0,
DLY_TIMEOUT,
DLY_TRACKCHANGE,
DLY_PAUSERESUME,
DLY_PLAYSTOP,
};
static const char * delay_strings[] =
{
[DLY_NONE] "None",
[DLY_TIMEOUT] "Timeout",
[DLY_TRACKCHANGE]"Track Change",
[DLY_PAUSERESUME]"Pause/Resume",
[DLY_PLAYSTOP] "Play/Stop",
};
enum e_curvol_str
{
CURVOL_NONE = 0,
CURVOL_START,
CURVOL_END,
};
static const char * cur_vol_strings[] =
{
[CURVOL_NONE] "None",
[CURVOL_START]"Start",
[CURVOL_END] "End",
};
enum e_action_str
{
ACT_NONE = 0,
ACT_SLEEP,
ACT_STARTOVER,
ACT_INVERTFADE,
ACT_EXIT,
};
static const char * action_strings[] =
{
[ACT_NONE] "None",
[ACT_SLEEP] "Sleep",
[ACT_STARTOVER] "Start Over",
[ACT_INVERTFADE]"Invert Fade",
[ACT_EXIT] "Exit",
};
enum e_ny_str
{
NY_NO = 0,
NY_YES,
};
static const char * ny_strings[] =
{
[NY_NO] "No",
[NY_YES]"Yes",
};
enum enum_menu
{ /*same order as they will be displayed in the menu*/
M_PLAY = 0,
M_DELAY,
M_ACTDELAY,
M_USECURVOL,
M_STARTVOL,
M_ENDVOL,
M_TIMEOUT,
M_TRIG1,
M_ACT1,
M_TRIG2,
M_ACT2,
M_TRIG3,
M_ACT3,
M_TSR,
M_SEP,
M_SAVE,
M_RUN,
M_EXIT,
M_ITEM_COUNT,
};
static const char * setting_strings[M_ITEM_COUNT] =
{//[]"",
[M_PLAY] "Play",
[M_DELAY] "Delay",
[M_ACTDELAY] "After",
[M_USECURVOL] "Use Cur Volume",
[M_STARTVOL] "Fade from :",
[M_ENDVOL] "to :",
[M_TIMEOUT] "Timeout",
[M_TRIG1] "After",
[M_ACT1] "Action1",
[M_TRIG2] "After",
[M_ACT2] "Action2",
[M_TRIG3] "After",
[M_ACT3] "Action3",
[M_TSR] "In Background",
/*[M_SEP]*/
[M_SAVE] "Save",
[M_RUN] "Run",
[M_EXIT] "Exit",
};
static const int setting_icons[M_ITEM_COUNT] =
{//Icon_NOICON
[M_DELAY]Icon_Menu_setting,
[M_ACTDELAY]Icon_Playback_menu,
[M_USECURVOL]Icon_Questionmark,
[M_STARTVOL]Icon_EQ,
[M_ENDVOL]Icon_Audio,
[M_TIMEOUT]Icon_Config,
[M_TRIG1]Icon_Menu_setting,
[M_ACT1]Icon_Menu_functioncall,
[M_TRIG2]Icon_NOICON,
[M_ACT2]Icon_NOICON,
[M_TRIG3]Icon_NOICON,
[M_ACT3]Icon_NOICON,
[M_SEP]Icon_NOICON,
[M_SAVE]Icon_Submenu,
[M_EXIT]Icon_Reverse_Cursor,
};
//static int setting_table[M_ITEM_COUNT]={0};
static struct viewport viewport[NB_SCREENS];
static struct fade_settings
{
int vol_orig;
int vol_min;
int vol_max;
int vol_step;
int vol_cur;
bool fade_out;
long timeout_ticks;
long ticks_remaining;
int audio_status;
int duration;
int delay_duration;
int vol_init;
bool init_use_cur;
int vol_final;
bool final_use_cur;
int actions;
int sleep;
bool tsr;
} fade_set;
static struct settings
{
uint32_t crc;
char play[MAX_PATH];
int table[M_ITEM_COUNT];
} settings;
static const char * dyn_menu_get_entry(int selected_item, void * data,
char * buffer, size_t buffer_len)
{
//(void)data;
(void)buffer;
(void)buffer_len;
char * menustr = ((char**) data)[selected_item];
return menustr;
}
static enum themable_icons menu_get_icon(int selected_item, void * data)
{
(void)data;
if ((unsigned) selected_item >= M_ITEM_COUNT)
return Icon_NOICON;
return setting_icons[selected_item];//data[selected_item];// : Icon_NOICON;
}
static int do_dyn_menu(const char* title,
char* strentry, const char ** entrylist, int entries,
int *cur_selection, int *list_start,
list_get_icon *icon_callback,
struct viewport parent[NB_SCREENS], bool hide_theme)
{
/*
Creates a menu, can use non constant strings by supplying a comma
separated list of menu items via strentry ex. "item1;item2;item3;item4"
strentry may be a buffer filled by any means (sprintf) or a string literal
but NOT const since every separator ';' will be replaced by a '\0'
ALTERNATIVELY can use a list of const strings via entrylist
ex. static const char * menu_str[] ={"item1", "item2", "item3"};
do_dyn_menu(titlestr, menustr, entrylist, entries, start, hidetheme)
titlestr - title for the menu
menustr - comma separated list of menu entries
entrylist - ALTERNATIVE to menustr pointer to const string list
entries - number of entries in menustr if > MAX_MENU_ENTRIES will be truncated.
cur_sel - sets menu item selected, returns users selection
list_start - sets menu entry at top of list, keeps position between calls
hide_theme - hides theme elements such as the status bar and background
returns selected item index, -2 if canceled, <0 on other error
*/
enum{MAX_DYN_MENU_ENTRIES=M_ITEM_COUNT +1 };
static const char* menu_strings[MAX_DYN_MENU_ENTRIES];
char *remain, *token = NULL;
const char * const sep= ";";
struct gui_synclist list;
int result = 0;
int action;
entries = MIN(MAX_DYN_MENU_ENTRIES, entries);
if(menu_strings != NULL && entrylist == NULL)
{
for(int i=0; i < entries; i++)
{
token = rb->strtok_r(strentry, sep, &remain);
strentry = NULL; /*strtok will continue with sequence*/
menu_strings[i] = (token != NULL)? token : "???";
}
entrylist = menu_strings;
}
rb->gui_synclist_init(&list, dyn_menu_get_entry, entrylist, true, 1, parent);
rb->gui_synclist_set_title(&list, (unsigned char*) title, Icon_Rockbox);
if (icon_callback)
rb->gui_synclist_set_icon_callback(&list, *icon_callback);
rb->gui_synclist_set_nb_items(&list, entries);
rb->gui_synclist_select_item(&list, (cur_selection) ? *cur_selection : 0);
FOR_NB_SCREENS(i)
{
if (list_start)
list.start_item[i] = *list_start;
rb->viewportmanager_theme_enable(i, !hide_theme, parent);
}
rb->gui_synclist_draw(&list);
while (result == 0)
{
action = rb->get_action(CONTEXT_LIST, TIMEOUT_BLOCK);
if (rb->gui_synclist_do_button(&list, &action, LIST_WRAP_UNLESS_HELD))
continue;
switch (action)
{
case ACTION_STD_OK:
{
result = 1;
break;
}
case ACTION_STD_CANCEL:
{
result = -1;
break;
}
}
}
if (cur_selection)
*cur_selection = rb->gui_synclist_get_sel_pos(&list);
FOR_NB_SCREENS(i)
{
rb->viewportmanager_theme_undo(i, false);
if (list_start)
{
if (i == 0)
*list_start = 0;
*list_start |= list.start_item[i];
}
}
return result;
}
/*
unsigned int ms_to_ticks(unsigned int ms)
{
return ms/MS_IN_TICK;
}
unsigned int ticks_to_ms(unsigned int ticks)
{
return ticks * MS_IN_TICK;
}
*/
/* time_split_units()
split time values depending on base unit
unit_idx: UNIT_HOUR, UNIT_MIN, UNIT_SEC, UNIT_MS
abs_value: absolute time value
units_in: array of unsigned ints with UNIT_IDX_TIME_COUNT fields
*/
static unsigned int time_split_units(int unit_idx, unsigned long abs_val,
unsigned int (*units_in)[UNIT_IDX_TIME_COUNT])
{
unsigned int base_idx = UNIT_IDX_HR;
int hours;
int minutes = 0;
int seconds = 0;
int millisec = 0;
switch (unit_idx & UNIT_IDX_MASK) /*Mask off upper bits*/
{
case UNIT_MS:
base_idx = UNIT_IDX_MS;
millisec = abs_val;
abs_val = abs_val / 1000U;
millisec = millisec - (1000U * abs_val);
/* fallthrough and calculate the rest of the units */
case UNIT_SEC:
if (base_idx == UNIT_IDX_HR)
base_idx = UNIT_IDX_SEC;
seconds = abs_val;
abs_val = abs_val / 60U;
seconds = seconds - (60U * abs_val);
/* fallthrough and calculate the rest of the units */
case UNIT_MIN:
if (base_idx == UNIT_IDX_HR)
base_idx = UNIT_IDX_MIN;
minutes = abs_val;
abs_val = abs_val / 60U;
minutes = minutes -(60U * abs_val);
/* fallthrough and calculate the rest of the units */
case UNIT_HOUR:
default:
hours = abs_val;
break;
}
(*units_in)[UNIT_IDX_HR] = hours;
(*units_in)[UNIT_IDX_MIN] = minutes;
(*units_in)[UNIT_IDX_SEC] = seconds;
(*units_in)[UNIT_IDX_MS] = millisec;
return base_idx;
}
/* format_time_auto - return an auto ranged time string;
buffer: needs to be at least 64 characters
unit_idx: specifies lowest or base index of the value
add | UNIT_LOCK_ to prevent autorange below this index
add | UNIT_TRIM_ZERO to supress leading zero on the largest unit
value: should be passed in the same form as unit_idx
supress_unit: if true unit string is NOT printed
idx_pos[2]: (if !NULL) [0] specifies an index of interest,
the offset and width for that index will be returned.
In field [0] offset, field [1] length
Ex: given 12:34:56.78 if you pass the idx_pos UNIT_IDX_MIN
idx_pos returns -> {3,2}.. offset(3) and length(2) = '34'
*/
static const char *format_time_auto(char *buffer, int buf_len, const long value,
int unit_idx, bool supress_unit,
unsigned char (*idx_pos)[2])
{
const char * const sign = &"-"[value < 0 ? 0 : 1];
bool is_rtl = rb->lang_is_rtl();
unsigned int timebuf_len = 30; /* -2147483648:00:00.00\0 */
char *timebuf;/*timebuf[24]; shared with buffer instead*/
int len, left_offset;
unsigned char base_idx, max_idx;
unsigned int units_in[UNIT_IDX_TIME_COUNT];
char fwidth[UNIT_IDX_TIME_COUNT] =
{
[UNIT_IDX_HR] = 0, /* hr is variable length */
[UNIT_IDX_MIN] = 2,
[UNIT_IDX_SEC] = 2,
[UNIT_IDX_MS] = 3,
}; /* {0,2,2,3}; Field Widths*/
unsigned char offsets[UNIT_IDX_TIME_COUNT] =
{
[UNIT_IDX_HR] = 10,/* ?:59:59.999 Std offsets */
[UNIT_IDX_MIN] = 7, /*0?:+1:+4.+7 need calculated */
[UNIT_IDX_SEC] = 4,/* 999.59:59:0 RTL offsets */
[UNIT_IDX_MS] = 0,/* 0 .4 :7 :10 won't change */
}; /* {10,7,4,0}; Offsets*/
static const int unitlock[UNIT_IDX_TIME_COUNT] =
{
[UNIT_IDX_HR] = UNIT_LOCK_HR,
[UNIT_IDX_MIN] = UNIT_LOCK_MIN,
[UNIT_IDX_SEC] = UNIT_LOCK_SEC,
[UNIT_IDX_MS] = 0,
}; /* unitlock*/
static const int units[UNIT_IDX_TIME_COUNT] =
{
[UNIT_IDX_HR] = UNIT_HOUR,
[UNIT_IDX_MIN] = UNIT_MIN,
[UNIT_IDX_SEC] = UNIT_SEC,
[UNIT_IDX_MS] = UNIT_MS,
}; /* units*/
buf_len = buf_len - (timebuf_len + 2);
timebuf = &buffer[buf_len + 1]; /* use part of the supplied buffer */
if (buf_len < 32)
return buffer;
if (idx_pos != NULL)
{
(*idx_pos)[0] = MIN((*idx_pos)[0], UNIT_IDX_TIME_COUNT - 1);
unit_idx |= unitlock[(*idx_pos)[0]];
}
base_idx = time_split_units(unit_idx, abs(value), &units_in);
if (units_in[UNIT_IDX_HR] || (unit_idx & unitlock[UNIT_IDX_HR]))
max_idx = UNIT_IDX_HR;
else if (units_in[UNIT_IDX_MIN] || (unit_idx & unitlock[UNIT_IDX_MIN]))
max_idx = UNIT_IDX_MIN;
else if (units_in[UNIT_IDX_SEC] || (unit_idx & unitlock[UNIT_IDX_SEC]))
max_idx = UNIT_IDX_SEC;
else if (units_in[UNIT_IDX_MS])
max_idx = UNIT_IDX_MS;
else /* value is 0*/
max_idx = base_idx;
if (!is_rtl)
{
len = snprintf(timebuf, timebuf_len,
"%02d:%02d:%02d.%03d",
units_in[UNIT_IDX_HR],
units_in[UNIT_IDX_MIN],
units_in[UNIT_IDX_SEC],
units_in[UNIT_IDX_MS]);
fwidth[UNIT_IDX_HR] = len - offsets[UNIT_IDX_HR];
offsets[UNIT_IDX_MS] = fwidth[UNIT_IDX_HR] + offsets[UNIT_IDX_MIN];
offsets[UNIT_IDX_SEC] = fwidth[UNIT_IDX_HR] + offsets[UNIT_IDX_SEC];
offsets[UNIT_IDX_MIN] = fwidth[UNIT_IDX_HR] + 1;
offsets[UNIT_IDX_HR] = 0;
timebuf[offsets[base_idx] + fwidth[base_idx]] = '\0';
left_offset = -(offsets[max_idx]);
left_offset += rb->strlcpy(buffer, sign, buf_len);
/* trim leading zero on the max_idx */
if ((unit_idx & UNIT_TRIM_ZERO) == UNIT_TRIM_ZERO &&
timebuf[offsets[max_idx]] == '0')
{
offsets[max_idx]++;
}
rb->strlcat(buffer, &timebuf[offsets[max_idx]], buf_len);
if (!supress_unit)
{
rb->strlcat(buffer, " ", buf_len);
rb->strlcat(buffer, unit_strings[units[max_idx]], buf_len);
}
}
else /*RTL Languages*/
{
len = snprintf(timebuf, timebuf_len,
"%03d.%02d:%02d:%02d",
units_in[UNIT_IDX_MS],
units_in[UNIT_IDX_SEC],
units_in[UNIT_IDX_MIN],
units_in[UNIT_IDX_HR]);
fwidth[UNIT_IDX_HR] = len - offsets[UNIT_IDX_HR];
left_offset = -(offsets[base_idx]);
/* trim leading zero on the max_idx */
if ((unit_idx & UNIT_TRIM_ZERO) == UNIT_TRIM_ZERO &&
timebuf[offsets[max_idx]] == '0')
{
timebuf[offsets[max_idx]] = timebuf[offsets[max_idx]+1];
fwidth[max_idx]--;
}
timebuf[offsets[max_idx] + fwidth[max_idx]] = '\0';
if (!supress_unit)
{
rb->strlcpy(buffer, unit_strings[units[max_idx]], buf_len);
left_offset += rb->strlcat(buffer, " ", buf_len);
rb->strlcat(buffer, &timebuf[offsets[base_idx]], buf_len);
}
else
rb->strlcpy(buffer, &timebuf[offsets[base_idx]], buf_len);
rb->strlcat(buffer, sign, buf_len);
}
if (idx_pos != NULL)
{
(*idx_pos)[1]= fwidth[*(idx_pos)[0]];
(*idx_pos)[0]= left_offset + offsets[(*idx_pos)[0]];
}
return buffer;
}
static void draw_horiz_scrollbar(struct screen * display, int top, int height, int cur_pct)
{
rb->gui_scrollbar_draw(display, /* screen */
5, /* x */
top, /* y */
LCD_WIDTH-10, /* width */
height , /* height */
100, /* items */
0, /* min_shown */
cur_pct, /* max_shown */
HORIZONTAL); /* flags */
}
static inline int get_button(void)
{
return pluginlib_getaction(HZ/10, plugin_contexts,
ARRAYLEN(plugin_contexts));
}
static bool can_play(void)
{
int audio_status = rb->audio_status();
if ((!audio_status && rb->global_status->resume_index != -1)
&& (rb->playlist_resume() != -1)) {
return true;
}
else if (audio_status & AUDIO_STATUS_PLAY)
return true;
return false;
}
static void resume_audio(bool from_stop)
{
int audio_status = rb->audio_status();
//rb->playlist_resume()
if (from_stop && !audio_status && rb->global_status->resume_index != -1) {
if (rb->playlist_resume() != -1) {
rb->playlist_resume_track(rb->global_status->resume_index,
rb->global_status->resume_crc32,
rb->global_status->resume_elapsed,
rb->global_status->resume_offset);
}
}
else if (audio_status & AUDIO_STATUS_PLAY)
rb->audio_resume();
}
static void pause_audio(void)
{
if (rb->audio_status() & AUDIO_STATUS_PLAY)
rb->audio_pause();
}
int value_to_pct(int value, int min, int max)
{
int pct = 0;
pct = ((value * 100 - min *100)/(abs(max-min)));
return pct;
}
static int clamp_int(int value, int min, int max)
{
int tmp = 0;
if (min > max)
{ /* Swap max and min if needed */
tmp = max;
max = min;
min = tmp;
}
if (value < min)
value = min;
else if (value > max)
value = max;
return value;
}
static int do_select_clamped(int v1, int v2, bool use_v2, int v_min, int v_max)
{
/*
selects val1 or val2 by bool use_v2
false = val1, true = val2
0 = val1 1 = val2
returns selected clamped to v_min/v_max
*/
int value;
if (use_v2)
value = v2;
else
value = v1;
return clamp_int(value, v_min, v_max);
}
static int clamp_int_rollover(int value, int min, int max)
{
int tmp = 0;
if (min > max)
{ /* Swap max and min if needed */
tmp = max;
max = min;
min = tmp;
}
if (value < min)
value = max;
else if (value > max)
value = min;
return value;
}
static int do_volume_change(int vol_step, int vol_min, int vol_max)
{
/*
Negative numbers lower volume
Positive numbers raise volume
clamps to vol_min/volmax
returns current volume
*/
rb->button_clear_queue(); //clear any pending buttons
int vol = rb->sound_get_current(SOUND_VOLUME);
vol = clamp_int(vol + vol_step, vol_min, vol_max);
rb->sound_set_current(SOUND_VOLUME, vol);
return vol;
}
static void do_set_fade(struct fade_settings * fade_set)
{
int duration = fade_set->duration;
int vol_min = rb->sound_min(SOUND_VOLUME);
int vol_max = rb->sound_max(SOUND_VOLUME);
int vol_orig = rb->sound_get_current(SOUND_VOLUME); /* get current volume */
int vol_cur; /*vol_orig is the global volume not necessarily current volume*/
int vol_init;
int vol_final;
int vol_step;
int vol_range;
bool fade_out;
long ticks_remaining;
long timeout_ticks;
vol_init = do_select_clamped(vol_orig, fade_set->vol_init,
!fade_set->init_use_cur, vol_min, vol_max);
fade_set->vol_init = vol_init;
vol_cur = vol_init;
vol_final = do_select_clamped(vol_orig, fade_set->vol_final,
!fade_set->final_use_cur, vol_min, vol_max);
fade_set->vol_final = vol_final;
if (vol_init > vol_final)
{
fade_out = true;
vol_range = vol_init - vol_final; //abs()
vol_step = -1;
}
else
{
fade_out = false;
vol_range = vol_final - vol_init;//abs()
vol_step = 1;
}
fade_set->fade_out = fade_out;
fade_set->vol_step = vol_step;
ticks_remaining = HZ * duration;
if (ticks_remaining < HZ)
ticks_remaining = HZ;
fade_set->ticks_remaining = ticks_remaining;
timeout_ticks = (ticks_remaining / vol_range);
if (timeout_ticks < HZ/10)
timeout_ticks = HZ/10;
fade_set->timeout_ticks = timeout_ticks;
fade_set->vol_min = vol_min;
fade_set->vol_max = vol_max;
fade_set->vol_cur = vol_cur;
fade_set->vol_orig = vol_orig;
}
static bool do_fade(struct fade_settings * fade_set)
{
int vol_min = fade_set->vol_init;
int vol_max = fade_set->vol_final;
int vol_step = fade_set->vol_step;
int vol_cur = fade_set->vol_cur;
int ret = true;
vol_cur = do_volume_change(vol_step, vol_min, vol_max);
if (fade_set->vol_cur == vol_cur)
{
rb->splashf(HZ, "usr = %d", vol_cur);
}
if (vol_cur == vol_max)
{
ret = false;
rb->splashf(HZ, "v = %d", vol_cur);
}
fade_set->vol_cur = vol_cur;
return ret;
}
static int get_button_dpad(int *value, int *h_pos, int * v_pos, unsigned int multiplier)
{
/* Allow user to use left,right, up, down, and (scroll) buttons to set a value
single button press increments/decrements value by 1 * multiplier
repeat button press accelerates inc/dec by multiplier * 2
if h_pos is not NULL then left decrements h_pos, right increments
if v_pos is not NULL then down decrements v_pos, up increments
h_pos/v_pos being defined blocks actions for respective
single press inc/dec of value
Returns 0 if no button matched,
-1 if canceled,
1 if value selected
*/
int button;
int exit_val = 0;
button = get_button();
switch (button)
{
case PLA_RIGHT:
if (h_pos) { *h_pos += 1; break; }
case PLA_UP:
if (v_pos) { *v_pos += 1; break; }
*value+=1 * multiplier;
break;
case PLA_RIGHT_REPEAT:
case PLA_UP_REPEAT:
*value+=2 * multiplier;
break;
#ifdef HAVE_SCROLLWHEEL
case PLA_SCROLL_FWD:
*value+=1 * multiplier;
break;
case PLA_SCROLL_FWD_REPEAT:
*value+=2 * multiplier;
break;
#endif
case PLA_LEFT:
if (h_pos) { *h_pos -= 1; break; }
case PLA_DOWN:
if (v_pos) { *v_pos -= 1; break; }
*value-=1 * multiplier;
break;
case PLA_LEFT_REPEAT:
case PLA_DOWN_REPEAT:
*value-=2 * multiplier;
break;
#ifdef HAVE_SCROLLWHEEL
case PLA_SCROLL_BACK:
*value-=1 * multiplier;
break;
case PLA_SCROLL_BACK_REPEAT:
*value -=2 * multiplier;
break;
#endif
case PLA_SELECT:
case PLA_SELECT_REPEAT:
exit_val = 1;
break;
case PLA_EXIT:
case PLA_CANCEL:
exit_val = -1;
break;
default:
exit_val = 0;
break;
}
return exit_val;
}
void pop_up_frame(struct screen * display, int x, int y, int width, int height)
{
/*
|___________|
| |
| |
|___________|
*/
//int title_height = display->getcharheight() + 5;
//display->puts_scroll(1, 0, title_buf);
//void (*lcd_fillrect)(int x, int y, int width, int height);
display->set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
display->fillrect(x, y, width, height);
display->set_drawmode(DRMODE_SOLID);
display->hline(0, width, height/3);
display->hline(1, width-1, height);
display->vline(x, 0, height-1);
display->vline(width-x, 0, height-1);
}
int do_set_value(const char * title, int val, int val_min, int val_max, const char * val_lbl)
{
struct screen * display = rb->screens[SCREEN_MAIN];
int title_height = display->getcharheight() + 5;
char title_buf[48]= "\0";
int initial_val = val;
int ret = 0;
FOR_NB_SCREENS(i)
rb->viewportmanager_theme_enable(i, false, NULL);
//rb->lcd_clear_display();
pop_up_frame(display, 0, 0, LCD_WIDTH-1, title_height * 3);
while(ret == 0)
{
ret = get_button_dpad(&val, NULL, NULL, 1); /* allow user to set value*/
val = clamp_int_rollover(val, val_min, val_max);
draw_horiz_scrollbar(display, title_height * 2, title_height/2,
value_to_pct(val, val_min, val_max));
rb->snprintf(title_buf, sizeof(title_buf), "%s %d %s ",
title, val, val_lbl);
display->puts_scroll(1, 0, title_buf);
display->update();
rb->sleep(HZ/100);
}
if (ret == -1)
{
val = initial_val;
rb->splash(HZ/2, "Canceled");
}
display->clear_display();
FOR_NB_SCREENS(i)
rb->viewportmanager_theme_undo(i, true);
//display->update();
return val;
}
int do_set_time(const char * title, int sec, int sec_min, int sec_max)
{
char time_buf[64]= "\0";
const int time_buf_sz = sizeof(time_buf);
struct screen *display = rb->screens[SCREEN_MAIN];
int title_height = display->getcharheight() + 5;
int is_rtl = rb->lang_is_rtl();
const int initial_sec = sec; /* on cancel original time returned*/
int ret = 0;
enum {FIELD_ST = 0, FIELD_CT = 3};
unsigned char idx_pos[2];
int field_pos = (is_rtl? FIELD_ST : FIELD_CT - 1);
int rel_field; /* If lang is RTL then fields/cols are reversed */
static const unsigned char field_cols[FIELD_CT] =
{UNIT_IDX_HR, UNIT_IDX_MIN, UNIT_IDX_SEC};
static const unsigned int multiplier_cols[FIELD_CT] = {3600, 60, 1};
int offset = 1;
const int lcd_chr_width = (LCD_WIDTH/rb->lcd_getstringsize("W", NULL, NULL));
//rb->lcd_clear_display();
pop_up_frame(display, 0, 0, LCD_WIDTH-1, title_height * 3);
while(ret == 0)
{
field_pos = clamp_int_rollover(field_pos, FIELD_ST, FIELD_CT - 1);
/* if lang is RTL then columns flip */
rel_field = (is_rtl? FIELD_CT - field_pos - 1 : field_pos);
idx_pos[0] = field_cols[rel_field];
ret = get_button_dpad(&sec, &field_pos, NULL, multiplier_cols[rel_field]);
/* allow user to set value*/
sec = clamp_int_rollover(sec, sec_min, sec_max);
rb->lcd_puts_scroll(offset, 2, " ");
format_time_auto(time_buf, time_buf_sz, sec, UNIT_SEC, false, &idx_pos);
offset = (lcd_chr_width - rb->strlen(time_buf))/2;
rb->lcd_puts_scroll(offset, 2, time_buf);
rb->lcd_set_drawmode(DRMODE_SOLID | DRMODE_INVERSEVID);
time_buf[idx_pos[0] + idx_pos[1]] = '\0';
/*draw selected position inverse*/
rb->lcd_puts_scroll(offset + idx_pos[0], 2, &time_buf[idx_pos[0]]);
rb->lcd_set_drawmode(DRMODE_SOLID);
rb->lcd_puts_scroll(1,0,title);
rb->lcd_update();
rb->sleep(HZ/100);
}
if (ret == -1)
{
sec = initial_sec;
rb->splash(HZ/2, "Canceled");
}
rb->lcd_clear_display();
rb->lcd_update();
return sec;
}
uint32_t crc_settings(struct settings * set)
{
//struct settings set_tmp
/* some fields are ignored in crc calculation */
uint32_t current_crc = set->crc;
set->crc = 0;
int vol_start = set->table[M_STARTVOL];
int vol_end = set->table[M_ENDVOL];
if (set->table[M_USECURVOL] == CURVOL_START)
set->table[M_STARTVOL] = 0;
if (set->table[M_USECURVOL] == CURVOL_END)
set->table[M_ENDVOL] = 0;
set->table[M_SAVE] = 0;
uint32_t crc = rb->crc_32(set, sizeof(*set), 0xffffffff);
/* put back ignored fields */
set->crc = current_crc;
set->table[M_STARTVOL] = vol_start;
set->table[M_ENDVOL] = vol_end;
return crc;
}
void load_settings(struct settings * settings, char* filename)
{
int fh = rb->open(filename, O_RDONLY);
struct settings settings_load;
/* defaults */
settings->play[0] = '\0';
if(fh >= 0)
{ /* does file exist? */
/* basic consistency check */
if(rb->filesize(fh) == sizeof(*settings))
{
rb->read(fh, &settings_load, sizeof(struct settings));
if (settings_load.crc == crc_settings(&settings_load))
{
rb->memcpy(settings, &settings_load, sizeof(struct settings));
}
else
rb->splashf(HZ * 5, "CRC Check Failed %s", filename);
}
else
rb->splashf(HZ * 5, "Incorrect File Size %s", filename);
rb->close(fh);
}
}
void save_settings(struct settings * settings, char* filename)
{
int fd = rb->creat(filename, 0666);
settings->crc = crc_settings(settings);
if(fd >= 0)
{ /* does file exist? */
rb->write (fd, settings, sizeof(*settings));
rb->close(fd);
}
}
void scroll_table_rollover(int * p_sel, int entries, int direction)
{
int sel = *p_sel;
if (direction < 0)
(sel)--;
else
(sel)++;
*p_sel = clamp_int_rollover(sel, 0, entries - 1);
}
static bool play_load(const char* filename)
{
struct browse_context browse;
if (rb->file_exists(filename))
{
rb->browse_context_init(&browse, 0, BROWSE_RUNFILE, "", 0, filename, "");
rb->rockbox_browse(&browse);
return true;
}
else
rb->splashf(HZ * 2, "Error: %s Does Not Exist", filename);
return false;
}
static bool play_picker(struct settings *settings)
{
bool ret = false;
struct browse_context browse;
rb->browse_context_init(&browse, SHOW_PLAYLIST,
BROWSE_SELECTONLY|BROWSE_NO_CONTEXT_MENU,
"Play", Icon_Menu_setting, "/", "");
browse.buf = settings->play;
browse.bufsize = sizeof(settings->play);
rb->rockbox_browse(&browse);
if (browse.flags & BROWSE_SELECTED)
{
rb->splashf(HZ * 2, "%s", settings->play);
if (rb->file_exists(settings->play))
ret = true;
//play_load("/test.txt");
}
return ret;
}
static void do_set_volume_table_values(struct settings *set)
{
/* user can elect to use the current volume level as the start or end volume */
int vol = rb->sound_get_current(SOUND_VOLUME);
int vol_min = rb->sound_min(SOUND_VOLUME);
int vol_max = rb->sound_max(SOUND_VOLUME);
set->table[M_STARTVOL] = do_select_clamped(set->table[M_STARTVOL], vol,
set->table[M_USECURVOL] == CURVOL_START,
vol_min, vol_max);
set->table[M_ENDVOL] = do_select_clamped(set->table[M_ENDVOL], vol,
set->table[M_USECURVOL] == CURVOL_END,
vol_min, vol_max);
}
static void do_set_action_table_values(struct settings *set)
{
if (set->table[M_TRIG1] == TRIG_NONE)
set->table[M_ACT1] = ACT_NONE;
if (set->table[M_TRIG2] == TRIG_NONE)
set->table[M_ACT2] = ACT_NONE;
if (set->table[M_TRIG3] == TRIG_NONE)
set->table[M_ACT3] = ACT_NONE;
}
static void init_table_settings(struct settings *set)
{
do_set_volume_table_values(set);
do_set_action_table_values(set);
set->table[M_SAVE] = (set->crc == crc_settings(set))? SAV_SAVED : SAV_SAVE;
}
static void event_callback(unsigned short id, void *param)
{
static int last_audio = AUDIO_STATUS_ERROR;
static int last_screen = 0;
static bool backlight_on = true;
int audio_status;
int screen;
int last_vol_change;
if (((struct track_event *)param)->flags & TEF_REWIND)//TEF_AUTO_SKIP
return; /* Not a true track end */
switch(id)
{
case PLAYBACK_EVENT_START_PLAYBACK:
triggered_events |= WATCHED_EV(TRIG_EVENT_START_PLAYBACK);
break;
case PLAYBACK_EVENT_TRACK_SKIP:
if (!(((struct track_event *)param)->flags & TEF_AUTO_SKIP))
triggered_events |= WATCHED_EV(TRIG_EVENT_TRACK_SKIP);
break;
case PLAYBACK_EVENT_TRACK_CHANGE:
triggered_events |= WATCHED_EV(TRIG_EVENT_TRACK_CHANGE);
break;
case PLAYBACK_EVENT_TRACK_FINISH:
if (!(((struct track_event *)param)->flags & TEF_REWIND))//TEF_AUTO_SKIP
triggered_events |= WATCHED_EV(TRIG_EVENT_TRACK_FINISH);
break;
case SELF_EVENT_FINALVOL_REACHED:
triggered_events |= WATCHED_EV(TRIG_EVENT_VOL_FINAL);
break;
case SELF_EVENT_TIMEOUT_REACHED:
triggered_events |= WATCHED_EV(TRIG_EVENT_TIMEOUT);
break;
case SELF_EVENT_CHECK_TRIGGERED:
last_vol_change = rb->global_status->last_volume_change;
if (last_vol_change + TIMEOUT_VOL_CHANGE < *rb->current_tick)
triggered_events |= WATCHED_EV(TRIG_EVENT_VOL_CHANGED