rockbox/apps/gui/quickscreen.c
Christian Soffke bc5a638594 Option to switch off album art or to prefer file over embedded
Large embedded album art can cause pauses during
playback or when skipping between tracks, especially
on older devices, but embedded art is currently loaded
even when separately stored smaller image files would be
available.

A workaround is to remove large album art from the
metadata of files.

This now adds a setting to either turn off loading of
album art completely, or to prefer loading the album art
from a separate image file and thus ignore the embedded
versions.

Change-Id: I22fb581abf56072e35e6c29d72e553747ec1a96a
2022-01-22 08:29:40 -05:00

492 lines
16 KiB
C

/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* 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"
#ifdef HAVE_ALBUMART
#include "playback.h"
#endif
/* 1 top, 1 bottom, 2 on either side, 1 for the icons
* if enough space, top and bottom have 2 lines */
#define MIN_LINES 5
#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)
static void quickscreen_fix_viewports(struct gui_quickscreen *qs,
struct screen *display,
struct viewport *parent,
struct viewport
vps[QUICKSCREEN_ITEM_COUNT],
struct viewport *vp_icons)
{
int char_height, width, pad = 0;
int left_width = 0, right_width = 0, vert_lines;
unsigned char *s;
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 gui_quickscreen_draw(const struct gui_quickscreen *qs,
struct screen *display,
struct viewport *parent,
struct viewport vps[QUICKSCREEN_ITEM_COUNT],
struct viewport *vp_icons)
{
int i;
char buf[MAX_PATH];
unsigned const char *title, *value;
int temp;
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, MAX_PATH, temp);
if (viewport_get_nb_lines(vp) < 2)
{
char text[MAX_PATH];
snprintf(text, MAX_PATH, "%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);
}
display->set_viewport(parent);
display->update_viewport();
display->set_viewport(last_vp);
}
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 gui_quickscreen_do_button(struct gui_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)
{
short x,y;
/* only hitting the text counts, everything else is exit */
if (action_get_touchscreen_press(&x, &y) != BUTTON_REL)
return ACTION_NONE;
enum { left=1, right=2, top=4, bottom=8 };
int bits = (x < LCD_WIDTH/3 ? left : (x > 2*LCD_WIDTH/3 ? 2 : right)) |
(y < LCD_WIDTH/3 ? top : (y > 2*LCD_WIDTH/3 ? 8 : 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 bool gui_syncquickscreen_run(struct gui_quickscreen * qs, int button_enter, bool *usb)
{
int button;
struct viewport parent[NB_SCREENS];
struct viewport vps[NB_SCREENS][QUICKSCREEN_ITEM_COUNT];
struct viewport vp_icons[NB_SCREENS];
bool changed = false;
/* 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, &parent[i]);
quickscreen_fix_viewports(qs, &screens[i], &parent[i], vps[i], &vp_icons[i]);
gui_quickscreen_draw(qs, &screens[i], &parent[i], vps[i], &vp_icons[i]);
}
*usb = false;
/* 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);
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(button) == SYS_USB_CONNECTED)
{
*usb = true;
break;
}
if (gui_quickscreen_do_button(qs, button))
{
changed = true;
can_quit = true;
FOR_NB_SCREENS(i)
gui_quickscreen_draw(qs, &screens[i], &parent[i],
vps[i], &vp_icons[i]);
if (qs->callback)
qs->callback(qs);
}
else if (button == button_enter)
can_quit = true;
else if (button == ACTION_QS_VOLUP) {
global_settings.volume += sound_steps(SOUND_VOLUME);
setvol();
FOR_NB_SCREENS(i)
skin_update(CUSTOM_STATUSBAR, i, SKIN_REFRESH_NON_STATIC);
}
else if (button == ACTION_QS_VOLDOWN) {
global_settings.volume -= sound_steps(SOUND_VOLUME);
setvol();
FOR_NB_SCREENS(i)
skin_update(CUSTOM_STATUSBAR, i, SKIN_REFRESH_NON_STATIC);
}
if ((button == button_enter) && can_quit)
break;
if (button == ACTION_STD_CANCEL)
break;
}
/* Notify that we're exiting this screen */
cond_talk_ids_fq(VOICE_OK);
FOR_NB_SCREENS(i)
{ /* stop scrolling before exiting */
for (int j = 0; j < QUICKSCREEN_ITEM_COUNT; j++)
screens[i].scroll_stop_viewport(&vps[i][j]);
viewportmanager_theme_undo(i, true);
}
pop_current_activity();
return changed;
}
static const struct settings_list *get_setting(int gs_value,
const struct settings_list *defaultval)
{
if (gs_value != -1 && gs_value < nb_settings &&
is_setting_quickscreenable(&settings[gs_value]))
return &settings[gs_value];
return defaultval;
}
int quick_screen_quick(int button_enter)
{
struct gui_quickscreen qs;
bool oldshuffle = global_settings.playlist_shuffle;
int oldrepeat = global_settings.repeat_mode;
#ifdef HAVE_ALBUMART
int old_album_art = global_settings.album_art;
#endif
bool usb = false;
if (global_settings.shortcuts_replaces_qs)
return do_shortcut_menu(NULL);
qs.items[QUICKSCREEN_TOP] =
get_setting(global_settings.qs_items[QUICKSCREEN_TOP], NULL);
qs.items[QUICKSCREEN_LEFT] =
get_setting(global_settings.qs_items[QUICKSCREEN_LEFT],
find_setting(&global_settings.playlist_shuffle, NULL));
qs.items[QUICKSCREEN_RIGHT] =
get_setting(global_settings.qs_items[QUICKSCREEN_RIGHT],
find_setting(&global_settings.repeat_mode, NULL));
qs.items[QUICKSCREEN_BOTTOM] =
get_setting(global_settings.qs_items[QUICKSCREEN_BOTTOM], NULL);
qs.callback = NULL;
if (gui_syncquickscreen_run(&qs, button_enter, &usb))
{
settings_save();
/* make sure repeat/shuffle/any other nasty ones get updated */
if ( oldrepeat != global_settings.repeat_mode &&
(audio_status() & AUDIO_STATUS_PLAY) )
{
audio_flush_and_reload_tracks();
}
if (oldshuffle != global_settings.playlist_shuffle
&& audio_status() & AUDIO_STATUS_PLAY)
{
replaygain_update();
if (global_settings.playlist_shuffle)
playlist_randomise(NULL, current_tick, true);
else
playlist_sort(NULL, true);
}
#ifdef HAVE_ALBUMART
if (old_album_art != global_settings.album_art)
set_albumart_mode(global_settings.album_art);
#endif
}
return (usb ? 1:0);
}
/* stuff to make the quickscreen configurable */
bool is_setting_quickscreenable(const struct settings_list *setting)
{
/* 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;
}
}
void set_as_qs_item(const struct settings_list *setting,
enum quickscreen_item item)
{
int i;
for (i = 0; i < nb_settings; i++)
{
if (&settings[i] == setting)
break;
}
global_settings.qs_items[item] = i;
}