mirror of
https://github.com/Rockbox/rockbox.git
synced 2026-10-10 08:03:04 -04:00
Quickscreen will ignore update_delay so that the SBS is updated after each button press, without any lag. The SBS may display settings that are adjusted by the Quickscreen (e.g. cabbiev2's Shuffle and Repeat icons, or the brightness slider included in themes like Adwaitapod and Themify). Change-Id: I28716e81cb77e235eb009bff91c96f510365f435
485 lines
14 KiB
C
485 lines
14 KiB
C
/***************************************************************************
|
|
* __________ __ ___.
|
|
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
|
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
|
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
|
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
|
* \/ \/ \/ \/ \/
|
|
*
|
|
* Copyright (C) 2008 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 <stdio.h>
|
|
#include "config.h"
|
|
#include "system.h"
|
|
#include "icons.h"
|
|
#include "font.h"
|
|
#include "kernel.h"
|
|
#include "misc.h"
|
|
#include "sound.h"
|
|
#include "action.h"
|
|
#include "settings_list.h"
|
|
#include "lang.h"
|
|
#include "playlist.h"
|
|
#include "viewport.h"
|
|
#include "audio.h"
|
|
#include "quickscreen.h"
|
|
#include "talk.h"
|
|
#include "list.h"
|
|
#include "option_select.h"
|
|
#include "debug.h"
|
|
#include "shortcuts.h"
|
|
#include "appevents.h"
|
|
#include "statusbar-skinned.h"
|
|
|
|
/* 2 lines for each of the three vertical sections (top/middle/bottom).
|
|
If less space is available, the top and bottom each lose a line. */
|
|
#define MIN_LINES (3*2)
|
|
#define MAX_NEEDED_LINES 10
|
|
/* pixels between the 2 center items minimum or between text and icons,
|
|
* and between text and parent boundaries */
|
|
#define MARGIN 10
|
|
#define CENTER_ICONAREA_SIZE (MARGIN+8*2)
|
|
|
|
struct quickscreen
|
|
{
|
|
const struct settings_list *items[QUICKSCREEN_ITEM_COUNT];
|
|
struct viewport parent[NB_SCREENS];
|
|
struct viewport vps[NB_SCREENS][QUICKSCREEN_ITEM_COUNT];
|
|
struct viewport vp_icons[NB_SCREENS];
|
|
int button_enter;
|
|
enum quickscreen_return result;
|
|
};
|
|
|
|
static void quickscreen_fix_viewports(struct quickscreen *qs, enum screen_type screen)
|
|
{
|
|
int char_height, width, pad = 0;
|
|
int left_width = 0, right_width = 0, vert_lines;
|
|
unsigned char *s;
|
|
struct screen *display = &screens[screen];
|
|
struct viewport *parent = &qs->parent[screen];
|
|
struct viewport *vps = qs->vps[screen];
|
|
struct viewport *vp_icons = &qs->vp_icons[screen];
|
|
int nb_lines = viewport_get_nb_lines(parent);
|
|
|
|
/* nb_lines only returns the number of fully visible lines, small screens
|
|
or really large fonts could cause problems with the calculation below.
|
|
*/
|
|
if (nb_lines == 0)
|
|
nb_lines++;
|
|
|
|
char_height = parent->height/nb_lines;
|
|
|
|
/* center the icons VP first */
|
|
*vp_icons = *parent;
|
|
vp_icons->width = CENTER_ICONAREA_SIZE; /* abosulte smallest allowed */
|
|
vp_icons->x = parent->x;
|
|
vp_icons->x += (parent->width-CENTER_ICONAREA_SIZE)/2;
|
|
|
|
vps[QUICKSCREEN_BOTTOM] = *parent;
|
|
vps[QUICKSCREEN_TOP] = *parent;
|
|
/* depending on the space the top/buttom items use 1 or 2 lines */
|
|
if (nb_lines < MIN_LINES)
|
|
vert_lines = 1;
|
|
else
|
|
vert_lines = 2;
|
|
vps[QUICKSCREEN_TOP].y = parent->y;
|
|
vps[QUICKSCREEN_TOP].height = vps[QUICKSCREEN_BOTTOM].height
|
|
= vert_lines*char_height;
|
|
vps[QUICKSCREEN_BOTTOM].y
|
|
= parent->y + parent->height - vps[QUICKSCREEN_BOTTOM].height;
|
|
|
|
/* enough space vertically, so put a nice margin */
|
|
if (nb_lines >= MAX_NEEDED_LINES)
|
|
{
|
|
vps[QUICKSCREEN_TOP].y += MARGIN;
|
|
vps[QUICKSCREEN_BOTTOM].y -= MARGIN;
|
|
}
|
|
|
|
vp_icons->y = vps[QUICKSCREEN_TOP].y
|
|
+ vps[QUICKSCREEN_TOP].height;
|
|
vp_icons->height = vps[QUICKSCREEN_BOTTOM].y - vp_icons->y;
|
|
|
|
/* adjust the left/right items widths to fit the screen nicely */
|
|
if (qs->items[QUICKSCREEN_LEFT])
|
|
{
|
|
s = P2STR(ID2P(qs->items[QUICKSCREEN_LEFT]->lang_id));
|
|
left_width = display->getstringsize(s, NULL, NULL);
|
|
}
|
|
if (qs->items[QUICKSCREEN_RIGHT])
|
|
{
|
|
s = P2STR(ID2P(qs->items[QUICKSCREEN_RIGHT]->lang_id));
|
|
right_width = display->getstringsize(s, NULL, NULL);
|
|
}
|
|
|
|
width = MAX(left_width, right_width);
|
|
if (width*2 + vp_icons->width > parent->width)
|
|
{ /* crop text viewports */
|
|
width = (parent->width - vp_icons->width)/2;
|
|
}
|
|
else
|
|
{ /* add more gap in icons vp */
|
|
int excess = parent->width - vp_icons->width - width*2;
|
|
if (excess > MARGIN*4)
|
|
{
|
|
pad = MARGIN;
|
|
excess -= MARGIN*2;
|
|
}
|
|
vp_icons->x -= excess/2;
|
|
vp_icons->width += excess;
|
|
}
|
|
|
|
vps[QUICKSCREEN_LEFT] = *parent;
|
|
vps[QUICKSCREEN_LEFT].x = parent->x + pad;
|
|
vps[QUICKSCREEN_LEFT].width = width;
|
|
|
|
vps[QUICKSCREEN_RIGHT] = *parent;
|
|
vps[QUICKSCREEN_RIGHT].x = parent->x + parent->width - width - pad;
|
|
vps[QUICKSCREEN_RIGHT].width = width;
|
|
|
|
vps[QUICKSCREEN_LEFT].height = vps[QUICKSCREEN_RIGHT].height
|
|
= 2*char_height;
|
|
|
|
vps[QUICKSCREEN_LEFT].y = vps[QUICKSCREEN_RIGHT].y
|
|
= parent->y + (parent->height/2) - char_height;
|
|
|
|
/* shrink the icons vp by a few pixels if there is room so the arrows
|
|
aren't drawn right next to the text */
|
|
if (vp_icons->width > CENTER_ICONAREA_SIZE*2)
|
|
{
|
|
vp_icons->width -= CENTER_ICONAREA_SIZE*2/3;
|
|
vp_icons->x += CENTER_ICONAREA_SIZE*2/6;
|
|
}
|
|
if (vp_icons->height > CENTER_ICONAREA_SIZE*2)
|
|
{
|
|
vp_icons->height -= CENTER_ICONAREA_SIZE*2/3;
|
|
vp_icons->y += CENTER_ICONAREA_SIZE*2/6;
|
|
}
|
|
|
|
/* text alignment */
|
|
vps[QUICKSCREEN_LEFT].flags &= ~VP_FLAG_ALIGNMENT_MASK; /* left-aligned */
|
|
vps[QUICKSCREEN_TOP].flags |= VP_FLAG_ALIGN_CENTER; /* centered */
|
|
vps[QUICKSCREEN_BOTTOM].flags |= VP_FLAG_ALIGN_CENTER; /* centered */
|
|
vps[QUICKSCREEN_RIGHT].flags &= ~VP_FLAG_ALIGNMENT_MASK;/* right aligned*/
|
|
vps[QUICKSCREEN_RIGHT].flags |= VP_FLAG_ALIGN_RIGHT;
|
|
}
|
|
|
|
static void quickscreen_draw(struct quickscreen *qs, enum screen_type screen)
|
|
{
|
|
int temp, i;
|
|
char buf[MAX_PATH];
|
|
unsigned const char *title, *value;
|
|
struct screen *display = &screens[screen];
|
|
struct viewport *parent = &qs->parent[screen];
|
|
struct viewport *vps = qs->vps[screen];
|
|
struct viewport *vp_icons = &qs->vp_icons[screen];
|
|
struct viewport *last_vp = display->set_viewport(parent);
|
|
display->clear_viewport();
|
|
|
|
for (i = 0; i < QUICKSCREEN_ITEM_COUNT; i++)
|
|
{
|
|
struct viewport *vp = &vps[i];
|
|
if (!qs->items[i])
|
|
continue;
|
|
display->set_viewport(vp);
|
|
|
|
title = P2STR(ID2P(qs->items[i]->lang_id));
|
|
temp = option_value_as_int(qs->items[i]);
|
|
value = option_get_valuestring(qs->items[i],
|
|
buf, sizeof buf, temp);
|
|
|
|
if (viewport_get_nb_lines(vp) < 2)
|
|
{
|
|
char text[MAX_PATH];
|
|
snprintf(text, sizeof text, "%s: %s", title, value);
|
|
display->puts_scroll(0, 0, text);
|
|
}
|
|
else
|
|
{
|
|
display->puts_scroll(0, 0, title);
|
|
display->puts_scroll(0, 1, value);
|
|
}
|
|
}
|
|
/* draw the icons */
|
|
display->set_viewport(vp_icons);
|
|
|
|
if (qs->items[QUICKSCREEN_TOP] != NULL)
|
|
{
|
|
display->mono_bitmap(bitmap_icons_7x8[Icon_UpArrow],
|
|
(vp_icons->width/2) - 4, 0, 7, 8);
|
|
}
|
|
if (qs->items[QUICKSCREEN_RIGHT] != NULL)
|
|
{
|
|
display->mono_bitmap(bitmap_icons_7x8[Icon_FastForward],
|
|
vp_icons->width - 8, (vp_icons->height/2) - 4, 7, 8);
|
|
}
|
|
if (qs->items[QUICKSCREEN_LEFT] != NULL)
|
|
{
|
|
display->mono_bitmap(bitmap_icons_7x8[Icon_FastBackward],
|
|
0, (vp_icons->height/2) - 4, 7, 8);
|
|
}
|
|
if (qs->items[QUICKSCREEN_BOTTOM] != NULL)
|
|
{
|
|
display->mono_bitmap(bitmap_icons_7x8[Icon_DownArrow],
|
|
(vp_icons->width/2) - 4, vp_icons->height - 8, 7, 8);
|
|
}
|
|
|
|
skin_mark_dirty(display->screen_type);
|
|
display->set_viewport(last_vp);
|
|
}
|
|
|
|
static void quickscreen_draw_cb(unsigned short id, void *data, void *userdata)
|
|
{
|
|
(void)id;
|
|
(void)data;
|
|
|
|
FOR_NB_SCREENS(i)
|
|
quickscreen_draw((struct quickscreen *) userdata, i);
|
|
}
|
|
|
|
static void talk_qs_option(const struct settings_list *opt, bool enqueue)
|
|
{
|
|
if (!global_settings.talk_menu || !opt)
|
|
return;
|
|
|
|
if (enqueue)
|
|
talk_id(opt->lang_id, enqueue);
|
|
option_talk_value(opt, option_value_as_int(opt), enqueue);
|
|
}
|
|
|
|
/*
|
|
* Does the actions associated to the given button if any
|
|
* - qs : the quickscreen
|
|
* - button : the key we are going to analyse
|
|
* returns : true if the button corresponded to an action, false otherwise
|
|
*/
|
|
static bool quickscreen_do_button(struct quickscreen * qs, int button)
|
|
{
|
|
int item;
|
|
bool previous = false;
|
|
switch(button)
|
|
{
|
|
case ACTION_QS_TOP:
|
|
item = QUICKSCREEN_TOP;
|
|
break;
|
|
|
|
case ACTION_QS_LEFT:
|
|
item = QUICKSCREEN_LEFT;
|
|
previous = true;
|
|
break;
|
|
|
|
case ACTION_QS_DOWN:
|
|
item = QUICKSCREEN_BOTTOM;
|
|
previous = true;
|
|
break;
|
|
|
|
case ACTION_QS_RIGHT:
|
|
item = QUICKSCREEN_RIGHT;
|
|
break;
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
if (qs->items[item] == NULL)
|
|
return false;
|
|
|
|
option_select_next_val(qs->items[item], previous, true);
|
|
talk_qs_option(qs->items[item], false);
|
|
return true;
|
|
}
|
|
|
|
#ifdef HAVE_TOUCHSCREEN
|
|
static int quickscreen_touchscreen_button(void)
|
|
{
|
|
struct gesture_event gevent;
|
|
if (!action_gesture_get_event(&gevent))
|
|
return ACTION_NONE;
|
|
|
|
switch (gevent.id) {
|
|
case GESTURE_TAP:
|
|
case GESTURE_HOLD:
|
|
break;
|
|
default:
|
|
return ACTION_NONE;
|
|
}
|
|
|
|
enum { left=1, right=2, top=4, bottom=8 };
|
|
|
|
int bits = 0;
|
|
|
|
if(gevent.x < LCD_WIDTH/3)
|
|
bits |= left;
|
|
else if(gevent.x > 2*LCD_WIDTH/3)
|
|
bits |= right;
|
|
|
|
if(gevent.y < LCD_HEIGHT/3)
|
|
bits |= top;
|
|
else if(gevent.y > 2*LCD_HEIGHT/3)
|
|
bits |= bottom;
|
|
|
|
switch(bits) {
|
|
case top:
|
|
return ACTION_QS_TOP;
|
|
case bottom:
|
|
return ACTION_QS_DOWN;
|
|
case left:
|
|
return ACTION_QS_LEFT;
|
|
case right:
|
|
return ACTION_QS_RIGHT;
|
|
default:
|
|
return ACTION_STD_CANCEL;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
static void cleanup(void *parameter)
|
|
{
|
|
struct quickscreen *qs = (struct quickscreen *) parameter;
|
|
remove_event_ex(GUI_EVENT_NEED_UI_UPDATE, quickscreen_draw_cb, qs);
|
|
|
|
FOR_NB_SCREENS(i)
|
|
{
|
|
for (int j = 0; j < QUICKSCREEN_ITEM_COUNT; j++)
|
|
screens[i].scroll_stop_viewport(&qs->vps[i][j]);
|
|
viewportmanager_theme_undo(i, !(qs->result & QUICKSCREEN_GOTO_SHORTCUTS_MENU));
|
|
}
|
|
/* Eliminate flashing of parent during transition to Shortcuts */
|
|
if (qs->result & QUICKSCREEN_GOTO_SHORTCUTS_MENU)
|
|
pop_current_activity_without_refresh();
|
|
else
|
|
pop_current_activity();
|
|
}
|
|
|
|
static void quickscreen_run(struct quickscreen * qs)
|
|
{
|
|
int button;
|
|
/* To quit we need either :
|
|
* - a second press on the button that made us enter
|
|
* - an action taken while pressing the enter button,
|
|
* then release the enter button*/
|
|
bool can_quit = false;
|
|
|
|
push_current_activity(ACTIVITY_QUICKSCREEN);
|
|
|
|
FOR_NB_SCREENS(i)
|
|
{
|
|
screens[i].set_viewport(NULL);
|
|
screens[i].scroll_stop();
|
|
viewportmanager_theme_enable(i, true, &qs->parent[i]);
|
|
quickscreen_fix_viewports(qs, i);
|
|
quickscreen_draw(qs, i);
|
|
}
|
|
/* Announce current selection on entering this screen. This is all
|
|
queued up, but can be interrupted as soon as a setting is
|
|
changed. */
|
|
cond_talk_ids(VOICE_QUICKSCREEN);
|
|
talk_qs_option(qs->items[QUICKSCREEN_TOP], true);
|
|
if (qs->items[QUICKSCREEN_TOP] != qs->items[QUICKSCREEN_BOTTOM])
|
|
talk_qs_option(qs->items[QUICKSCREEN_BOTTOM], true);
|
|
talk_qs_option(qs->items[QUICKSCREEN_LEFT], true);
|
|
if (qs->items[QUICKSCREEN_LEFT] != qs->items[QUICKSCREEN_RIGHT])
|
|
talk_qs_option(qs->items[QUICKSCREEN_RIGHT], true);
|
|
|
|
#ifdef HAVE_TOUCHSCREEN
|
|
action_gesture_reset();
|
|
#endif
|
|
add_event_ex(GUI_EVENT_NEED_UI_UPDATE, false, quickscreen_draw_cb, qs);
|
|
while (true)
|
|
{
|
|
button = get_action(CONTEXT_QUICKSCREEN, HZ/5);
|
|
#ifdef HAVE_TOUCHSCREEN
|
|
if (button == ACTION_TOUCHSCREEN)
|
|
button = quickscreen_touchscreen_button();
|
|
#endif
|
|
if (default_event_handler_ex(button, cleanup, qs)
|
|
== SYS_USB_CONNECTED)
|
|
{
|
|
qs->result |= QUICKSCREEN_IN_USB;
|
|
return;
|
|
}
|
|
if (quickscreen_do_button(qs, button))
|
|
{
|
|
qs->result |= QUICKSCREEN_CHANGED;
|
|
can_quit = true;
|
|
FOR_NB_SCREENS(i)
|
|
quickscreen_draw(qs, i);
|
|
}
|
|
else if (button == qs->button_enter)
|
|
can_quit = true;
|
|
else if (button == ACTION_QS_VOLUP)
|
|
adjust_volume(1);
|
|
else if (button == ACTION_QS_VOLDOWN)
|
|
adjust_volume(-1);
|
|
else if (button == ACTION_STD_CONTEXT)
|
|
{
|
|
qs->result |= QUICKSCREEN_GOTO_SHORTCUTS_MENU;
|
|
break;
|
|
}
|
|
if ((button == qs->button_enter) && can_quit)
|
|
break;
|
|
|
|
if (button == ACTION_STD_CANCEL)
|
|
break;
|
|
|
|
/* Ignore SBS update_delay, so that setting
|
|
changes are immediately visible. */
|
|
sb_skin_force_next_update();
|
|
}
|
|
/* Notify that we're exiting this screen */
|
|
cond_talk_ids_fq(VOICE_OK);
|
|
cleanup(qs);
|
|
}
|
|
|
|
int quick_screen_quick(int button_enter)
|
|
{
|
|
struct quickscreen qs;
|
|
qs.button_enter = button_enter;
|
|
qs.result = QUICKSCREEN_OK;
|
|
|
|
for (int i = 0; i < 4; ++i)
|
|
{
|
|
qs.items[i] = global_settings.qs_items[i];
|
|
|
|
if (!is_setting_quickscreenable(qs.items[i]))
|
|
qs.items[i] = NULL;
|
|
}
|
|
|
|
quickscreen_run(&qs);
|
|
|
|
if (qs.result & QUICKSCREEN_CHANGED)
|
|
settings_save();
|
|
|
|
return qs.result & ~QUICKSCREEN_CHANGED;
|
|
}
|
|
|
|
/* stuff to make the quickscreen configurable */
|
|
bool is_setting_quickscreenable(const struct settings_list *setting)
|
|
{
|
|
if (!setting)
|
|
return true;
|
|
|
|
/* to keep things simple, only settings which have a lang_id set are ok */
|
|
if (setting->lang_id < 0 || (setting->flags & F_BANFROMQS))
|
|
return false;
|
|
|
|
switch (setting->flags & F_T_MASK)
|
|
{
|
|
case F_T_BOOL:
|
|
return true;
|
|
case F_T_INT:
|
|
case F_T_UINT:
|
|
return (setting->RESERVED != NULL);
|
|
default:
|
|
return false;
|
|
}
|
|
}
|