rockbox/apps/gui/mask_select.c
William Wilgus dc87e9e9f3 Selective Backlight/Advanced Softlock - Selective actions based on context
Selective backlight allows the user to choose actions that will not
enable the backlight when pressed.

Advanced softlock allows user to choose actions that  will not be
blocked by screenlock on devices without a hold button.

Both only occur in FM and WPS Contexts.

Update:
Back from the dead
-Cleaned up code, removed unnecessary calls, re-arranged last filter action
  timeout conditional to work in case last_filtered_action_tick was never set
-Added entries to the manual
-Fixed back button on some menus not activating backlight
-Made menus more intuitive, no actions selected now changes menu item to off.
-Added talk fuctionality.
-Added option to disable selective backlight while on external power.
-Rewrote backlight and softlock handling code to fix issue with scrollwheels
-Menu changed to have toggle(yes/no) and settings
-Optimized selective actions lookup
-Added option to disable notification of 'buttons locked' while softlocked
-Removed uneeded code, consolidated action lookup to single function
-Fixed incorrect name on selective softlock menu
-Added option to disable touch on touchscreen devices
-Fixed backlight on original screenlock without selective screenlock active
-Added text selection in mask_select for when show_icons is off
-Fixed voice in mask_select to speak if voice is defined instead of spelling
-Added more lang defines (play skip seek)
-Added option to disable unknown keys turning on backlight
-Fixed Conditional argument In wrong place causing players without
	backlight to fail to build
-Fixed Disable Unknown blocking detection of context change
-Fixed canceling menu didn't update new settings
-Added Autolock on backlight off
-Removed backlight_on_force from backlight.c, Now sets ignore next to false
	and uses backlight_on
-Cleaned up autolock code added strings to lang file
-Fixed issue where rapid presses would bypass softlock
-Removed old softlock code, Cleaned selective actions code
-Changed menu to match existing RB menus
-Fixed Backlight_on_Hold blocked by backlight_ignore_next
-Fixed ignore_next for ipod
-Fixed bug allowing context with softlock to bypass selective backlight
-Changed mask_select to no longer prompt for changes to be saved
-Changed menu names
-Added ignore timeout to allow ipod scroll wheel to work properly and other
 players to still work properly, removed some previous code including
 ignore_event
-Increased ignore timeout to prevent sd card accesses from interrupting action
 code and turning on backlight
-Changed Unknown action to unmapped action in menu, changed handling code
-Removed unneeded logic and variables for handling unfiltered actions
-Reverted unmapped action code to previous functionality
-Added manual entries (thanks JohnB)
-Removed elusive unhandled unicode character from manual, changed formatting slightly

Actions:
Volume,Play,Seek,Skip

Extras:
Disable unmapped actions
Disable selective backlight on external power
Disable touch during softlock on touchscreen devices
Disable softlock notifications (power button still notifies)
Autolock on backlight off

Method:
Adds a function to ignore backlight on next call
 If selected action occurs backlight is forced on,
 Filter_first_keypress stays intact.

Selective softlock allows selected actions through, bypasses the normal
 softlock routine.

ToDo:
DONE

previous commit (#1) has attribution for folder_select.c which mask_select
is based from.

Change-Id: I08132ddcfd64c81751ef23b720f3ec6d68695fe4
2017-01-17 23:06:17 +01:00

287 lines
8.3 KiB
C

/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* Copyright (C) 2016 William Wilgus
* Derivative of folder_select.c by:
* Copyright (C) 2012 Jonathan Gordon
* Copyright (C) 2012 Thomas Martitz
*
* 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.
*
****************************************************************************/
/* TODO add language defines */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "lang.h"
#include "language.h"
#include "list.h"
#include "plugin.h"
#include "mask_select.h"
#include "talk.h"
/*
* Order for changing child states:
* 1) expand folder
* 2) collapse and select
* 3) deselect (skip to 1)
*/
enum child_state {
EXPANDED,
SELECTED,
COLLAPSED,
DESELECTED,
};
/* Children of main categories */
struct child {
const char* name;
struct category *category;
enum child_state state;
int key_value;
};
/* Main Categories in root */
struct category {
const char *name;
struct child *children;
int children_count;
int depth;
struct category* previous;
int key_value; /*values of all children OR|D*/
};
/* empty category for children of root only one level needed */
static struct category empty = {
.name = "",
.children = NULL,
.children_count = 0,
.depth = 1,
.previous = NULL,
};
/* Or | all keyvalues that user selected */
static int calculate_mask_r(struct category *root, int mask)
{
int i = 0;
while (i < root->children_count)
{
struct child *this = &root->children[i];
if (this->state == SELECTED)
mask |= this->key_value;
else if (this->state == EXPANDED)
mask = calculate_mask_r(this->category, mask);
i++;
}
return mask;
}
static int count_items(struct category *start)
{
int count = 0;
int i;
for (i=0; i<start->children_count; i++)
{
struct child *foo = &start->children[i];
if (foo->state == EXPANDED)
count += count_items(foo->category);
count++;
}
return count;
}
static struct child* find_index(struct category *start,
int index,struct category **parent)
{
int i = 0;
*parent = NULL;
while (i < start->children_count)
{
struct child *foo = &start->children[i];
if (i == index)
{
*parent = start;
return foo;
}
i++;
if (foo->state == EXPANDED)
{
struct child *bar = find_index(foo->category, index - i, parent);
if (bar)
{
return bar;
}
index -= count_items(foo->category);
}
}
return NULL;
}
/* simplelist uses this callback to change
the states of the categories/children */
static int item_action_callback(int action, struct gui_synclist *list)
{
struct category *root = (struct category*)list->data;
struct category *parent;
struct child *this = find_index(root, list->selected_item, &parent);
if (action == ACTION_STD_OK)
{
switch (this->state)
{
case EXPANDED:
this->state = SELECTED;
if (global_settings.talk_menu)
talk_id(LANG_ON, false);
break;
case SELECTED:
this->state = this->category->children_count == 0 ?
DESELECTED : COLLAPSED;
if (global_settings.talk_menu && this->category->children_count == 0)
talk_id(LANG_OFF, false);
break;
case COLLAPSED:
if (this->category == NULL)
this->category = root;
this->state = this->category->children_count == 0 ?
SELECTED : EXPANDED;
if (global_settings.talk_menu && this->category->children_count == 0)
talk_id(LANG_ON, false);
break;
case DESELECTED:
this->state = SELECTED;
if (global_settings.talk_menu)
talk_id(LANG_ON, false);
break;
default:
/* do nothing */
return action;
}
list->nb_items = count_items(root);
return ACTION_REDRAW;
}
return action;
}
static const char * item_get_name(int selected_item, void * data,
char * buffer, size_t buffer_len)
{
struct category *root = (struct category*)data;
struct category *parent;
struct child *this = find_index(root, selected_item , &parent);
buffer[0] = '\0';
if (parent->depth >= 0)
for(int i = 0; i <= parent->depth; i++)
strcat(buffer, "\t\0");
/* state of selection needs icons so if icons are disabled use text*/
if (!global_settings.show_icons)
{
if (this->state == SELECTED)
strcat(buffer, "+\0");
else
strcat(buffer," \0");
}
strlcat(buffer, P2STR((const unsigned char *)this->name), buffer_len);
return buffer;
}
static int item_get_talk(int selected_item, void *data)
{
struct category *root = (struct category*)data;
struct category *parent;
struct child *this = find_index(root, selected_item , &parent);
if (global_settings.talk_menu)
{
long id = P2ID((const unsigned char *)(this->name));
if(id>=0)
talk_id(id, true);
else
talk_spell(this->name, true);
talk_id(VOICE_PAUSE,true);
if (this->state == SELECTED)
talk_id(LANG_ON, true);
else if (this->state == DESELECTED)
talk_id(LANG_OFF, true);
}
return 0;
}
static enum themable_icons item_get_icon(int selected_item, void * data)
{
struct category *root = (struct category*)data;
struct category *parent;
struct child *this = find_index(root, selected_item, &parent);
switch (this->state)
{
case SELECTED:
return Icon_Submenu;
case COLLAPSED:
return Icon_NOICON;
case EXPANDED:
return Icon_Submenu_Entered;
default:
return Icon_NOICON;
}
return Icon_NOICON;
}
/* supply your original mask,the page header (ie. User do this..), mask items
and count, they will be selected automatically if the mask includes
them. If user selects new items and chooses to save settings
new mask returned otherwise, original is returned
*/
int mask_select(int mask, const unsigned char* headermsg,
struct s_mask_items *mask_items, size_t items)
{
struct simplelist_info info;
struct child action_child[items];
for (unsigned i = 0; i < items; i++)
{
action_child[i].name = mask_items[i].name;
action_child[i].category = &empty;
action_child[i].key_value = mask_items[i].bit_value;
action_child[i].state = mask_items[i].bit_value & mask ?
SELECTED : DESELECTED;
}
struct category root = {
.name = "",
.children = (struct child*) &action_child,
.children_count = items,
.depth = -1,
.previous = NULL,
};
simplelist_info_init(&info, P2STR(headermsg), count_items(&root), &root);
info.get_name = item_get_name;
info.action_callback = item_action_callback;
info.get_icon = item_get_icon;
info.get_talk = item_get_talk;
simplelist_show_list(&info);
return calculate_mask_r(&root,0);
}