A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita
audio
rust
zig
deno
mpris
rockbox
mpd
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2008 by Jonathan Gordon
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21
22#include <stdio.h>
23#include "config.h"
24#include "system.h"
25#include "icons.h"
26#include "font.h"
27#include "kernel.h"
28#include "misc.h"
29#include "sound.h"
30#include "action.h"
31#include "settings_list.h"
32#include "lang.h"
33#include "playlist.h"
34#include "viewport.h"
35#include "audio.h"
36#include "quickscreen.h"
37#include "talk.h"
38#include "list.h"
39#include "option_select.h"
40#include "debug.h"
41#include "shortcuts.h"
42#include "appevents.h"
43
44 /* 1 top, 1 bottom, 2 on either side, 1 for the icons
45 * if enough space, top and bottom have 2 lines */
46#define MIN_LINES 5
47#define MAX_NEEDED_LINES 10
48 /* pixels between the 2 center items minimum or between text and icons,
49 * and between text and parent boundaries */
50#define MARGIN 10
51#define CENTER_ICONAREA_SIZE (MARGIN+8*2)
52
53static bool redraw;
54
55static void quickscreen_update_callback(unsigned short id,
56 void *data, void *userdata)
57{
58 (void)id;
59 (void)data;
60 (void)userdata;
61
62 redraw = true;
63}
64
65static void quickscreen_fix_viewports(struct gui_quickscreen *qs,
66 struct screen *display,
67 struct viewport *parent,
68 struct viewport
69 vps[QUICKSCREEN_ITEM_COUNT],
70 struct viewport *vp_icons)
71{
72 int char_height, width, pad = 0;
73 int left_width = 0, right_width = 0, vert_lines;
74 unsigned char *s;
75 int nb_lines = viewport_get_nb_lines(parent);
76
77 /* nb_lines only returns the number of fully visible lines, small screens
78 or really large fonts could cause problems with the calculation below.
79 */
80 if (nb_lines == 0)
81 nb_lines++;
82
83 char_height = parent->height/nb_lines;
84
85 /* center the icons VP first */
86 *vp_icons = *parent;
87 vp_icons->width = CENTER_ICONAREA_SIZE; /* abosulte smallest allowed */
88 vp_icons->x = parent->x;
89 vp_icons->x += (parent->width-CENTER_ICONAREA_SIZE)/2;
90
91 vps[QUICKSCREEN_BOTTOM] = *parent;
92 vps[QUICKSCREEN_TOP] = *parent;
93 /* depending on the space the top/buttom items use 1 or 2 lines */
94 if (nb_lines < MIN_LINES)
95 vert_lines = 1;
96 else
97 vert_lines = 2;
98 vps[QUICKSCREEN_TOP].y = parent->y;
99 vps[QUICKSCREEN_TOP].height = vps[QUICKSCREEN_BOTTOM].height
100 = vert_lines*char_height;
101 vps[QUICKSCREEN_BOTTOM].y
102 = parent->y + parent->height - vps[QUICKSCREEN_BOTTOM].height;
103
104 /* enough space vertically, so put a nice margin */
105 if (nb_lines >= MAX_NEEDED_LINES)
106 {
107 vps[QUICKSCREEN_TOP].y += MARGIN;
108 vps[QUICKSCREEN_BOTTOM].y -= MARGIN;
109 }
110
111 vp_icons->y = vps[QUICKSCREEN_TOP].y
112 + vps[QUICKSCREEN_TOP].height;
113 vp_icons->height = vps[QUICKSCREEN_BOTTOM].y - vp_icons->y;
114
115 /* adjust the left/right items widths to fit the screen nicely */
116 if (qs->items[QUICKSCREEN_LEFT])
117 {
118 s = P2STR(ID2P(qs->items[QUICKSCREEN_LEFT]->lang_id));
119 left_width = display->getstringsize(s, NULL, NULL);
120 }
121 if (qs->items[QUICKSCREEN_RIGHT])
122 {
123 s = P2STR(ID2P(qs->items[QUICKSCREEN_RIGHT]->lang_id));
124 right_width = display->getstringsize(s, NULL, NULL);
125 }
126
127 width = MAX(left_width, right_width);
128 if (width*2 + vp_icons->width > parent->width)
129 { /* crop text viewports */
130 width = (parent->width - vp_icons->width)/2;
131 }
132 else
133 { /* add more gap in icons vp */
134 int excess = parent->width - vp_icons->width - width*2;
135 if (excess > MARGIN*4)
136 {
137 pad = MARGIN;
138 excess -= MARGIN*2;
139 }
140 vp_icons->x -= excess/2;
141 vp_icons->width += excess;
142 }
143
144 vps[QUICKSCREEN_LEFT] = *parent;
145 vps[QUICKSCREEN_LEFT].x = parent->x + pad;
146 vps[QUICKSCREEN_LEFT].width = width;
147
148 vps[QUICKSCREEN_RIGHT] = *parent;
149 vps[QUICKSCREEN_RIGHT].x = parent->x + parent->width - width - pad;
150 vps[QUICKSCREEN_RIGHT].width = width;
151
152 vps[QUICKSCREEN_LEFT].height = vps[QUICKSCREEN_RIGHT].height
153 = 2*char_height;
154
155 vps[QUICKSCREEN_LEFT].y = vps[QUICKSCREEN_RIGHT].y
156 = parent->y + (parent->height/2) - char_height;
157
158 /* shrink the icons vp by a few pixels if there is room so the arrows
159 aren't drawn right next to the text */
160 if (vp_icons->width > CENTER_ICONAREA_SIZE*2)
161 {
162 vp_icons->width -= CENTER_ICONAREA_SIZE*2/3;
163 vp_icons->x += CENTER_ICONAREA_SIZE*2/6;
164 }
165 if (vp_icons->height > CENTER_ICONAREA_SIZE*2)
166 {
167 vp_icons->height -= CENTER_ICONAREA_SIZE*2/3;
168 vp_icons->y += CENTER_ICONAREA_SIZE*2/6;
169 }
170
171 /* text alignment */
172 vps[QUICKSCREEN_LEFT].flags &= ~VP_FLAG_ALIGNMENT_MASK; /* left-aligned */
173 vps[QUICKSCREEN_TOP].flags |= VP_FLAG_ALIGN_CENTER; /* centered */
174 vps[QUICKSCREEN_BOTTOM].flags |= VP_FLAG_ALIGN_CENTER; /* centered */
175 vps[QUICKSCREEN_RIGHT].flags &= ~VP_FLAG_ALIGNMENT_MASK;/* right aligned*/
176 vps[QUICKSCREEN_RIGHT].flags |= VP_FLAG_ALIGN_RIGHT;
177}
178
179static void gui_quickscreen_draw(const struct gui_quickscreen *qs,
180 struct screen *display,
181 struct viewport *parent,
182 struct viewport vps[QUICKSCREEN_ITEM_COUNT],
183 struct viewport *vp_icons)
184{
185 int i;
186 char buf[MAX_PATH];
187 unsigned const char *title, *value;
188 int temp;
189 struct viewport *last_vp = display->set_viewport(parent);
190 display->clear_viewport();
191
192 for (i = 0; i < QUICKSCREEN_ITEM_COUNT; i++)
193 {
194 struct viewport *vp = &vps[i];
195 if (!qs->items[i])
196 continue;
197 display->set_viewport(vp);
198
199 title = P2STR(ID2P(qs->items[i]->lang_id));
200 temp = option_value_as_int(qs->items[i]);
201 value = option_get_valuestring(qs->items[i],
202 buf, MAX_PATH, temp);
203
204 if (viewport_get_nb_lines(vp) < 2)
205 {
206 char text[MAX_PATH];
207 snprintf(text, MAX_PATH, "%s: %s", title, value);
208 display->puts_scroll(0, 0, text);
209 }
210 else
211 {
212 display->puts_scroll(0, 0, title);
213 display->puts_scroll(0, 1, value);
214 }
215 }
216 /* draw the icons */
217 display->set_viewport(vp_icons);
218
219 if (qs->items[QUICKSCREEN_TOP] != NULL)
220 {
221 display->mono_bitmap(bitmap_icons_7x8[Icon_UpArrow],
222 (vp_icons->width/2) - 4, 0, 7, 8);
223 }
224 if (qs->items[QUICKSCREEN_RIGHT] != NULL)
225 {
226 display->mono_bitmap(bitmap_icons_7x8[Icon_FastForward],
227 vp_icons->width - 8, (vp_icons->height/2) - 4, 7, 8);
228 }
229 if (qs->items[QUICKSCREEN_LEFT] != NULL)
230 {
231 display->mono_bitmap(bitmap_icons_7x8[Icon_FastBackward],
232 0, (vp_icons->height/2) - 4, 7, 8);
233 }
234 if (qs->items[QUICKSCREEN_BOTTOM] != NULL)
235 {
236 display->mono_bitmap(bitmap_icons_7x8[Icon_DownArrow],
237 (vp_icons->width/2) - 4, vp_icons->height - 8, 7, 8);
238 }
239
240 display->set_viewport(parent);
241 display->update_viewport();
242 display->set_viewport(last_vp);
243}
244
245static void talk_qs_option(const struct settings_list *opt, bool enqueue)
246{
247 if (!global_settings.talk_menu || !opt)
248 return;
249
250 if (enqueue)
251 talk_id(opt->lang_id, enqueue);
252 option_talk_value(opt, option_value_as_int(opt), enqueue);
253}
254
255/*
256 * Does the actions associated to the given button if any
257 * - qs : the quickscreen
258 * - button : the key we are going to analyse
259 * returns : true if the button corresponded to an action, false otherwise
260 */
261static bool gui_quickscreen_do_button(struct gui_quickscreen * qs, int button)
262{
263 int item;
264 bool previous = false;
265 switch(button)
266 {
267 case ACTION_QS_TOP:
268 item = QUICKSCREEN_TOP;
269 break;
270
271 case ACTION_QS_LEFT:
272 item = QUICKSCREEN_LEFT;
273 previous = true;
274 break;
275
276 case ACTION_QS_DOWN:
277 item = QUICKSCREEN_BOTTOM;
278 previous = true;
279 break;
280
281 case ACTION_QS_RIGHT:
282 item = QUICKSCREEN_RIGHT;
283 break;
284
285 default:
286 return false;
287 }
288
289 if (qs->items[item] == NULL)
290 return false;
291
292 option_select_next_val(qs->items[item], previous, true);
293 talk_qs_option(qs->items[item], false);
294 return true;
295}
296
297#ifdef HAVE_TOUCHSCREEN
298static int quickscreen_touchscreen_button(void)
299{
300 short x,y;
301 if (action_get_touchscreen_press(&x, &y) != BUTTON_REL)
302 return ACTION_NONE;
303
304 enum { left=1, right=2, top=4, bottom=8 };
305
306 int bits = 0;
307
308 if(x < LCD_WIDTH/3)
309 bits |= left;
310 else if(x > 2*LCD_WIDTH/3)
311 bits |= right;
312
313 if(y < LCD_HEIGHT/3)
314 bits |= top;
315 else if(y > 2*LCD_HEIGHT/3)
316 bits |= bottom;
317
318 switch(bits) {
319 case top:
320 return ACTION_QS_TOP;
321 case bottom:
322 return ACTION_QS_DOWN;
323 case left:
324 return ACTION_QS_LEFT;
325 case right:
326 return ACTION_QS_RIGHT;
327 default:
328 return ACTION_STD_CANCEL;
329 }
330}
331#endif
332
333static int gui_syncquickscreen_run(struct gui_quickscreen * qs, int button_enter, bool *usb)
334{
335 int button;
336 struct viewport parent[NB_SCREENS];
337 struct viewport vps[NB_SCREENS][QUICKSCREEN_ITEM_COUNT];
338 struct viewport vp_icons[NB_SCREENS];
339 int ret = QUICKSCREEN_OK;
340 /* To quit we need either :
341 * - a second press on the button that made us enter
342 * - an action taken while pressing the enter button,
343 * then release the enter button*/
344 bool can_quit = false;
345
346 push_current_activity(ACTIVITY_QUICKSCREEN);
347
348 add_event_ex(GUI_EVENT_NEED_UI_UPDATE, false, quickscreen_update_callback, NULL);
349
350 FOR_NB_SCREENS(i)
351 {
352 screens[i].set_viewport(NULL);
353 screens[i].scroll_stop();
354 viewportmanager_theme_enable(i, true, &parent[i]);
355 quickscreen_fix_viewports(qs, &screens[i], &parent[i], vps[i], &vp_icons[i]);
356 gui_quickscreen_draw(qs, &screens[i], &parent[i], vps[i], &vp_icons[i]);
357 }
358 *usb = false;
359 /* Announce current selection on entering this screen. This is all
360 queued up, but can be interrupted as soon as a setting is
361 changed. */
362 cond_talk_ids(VOICE_QUICKSCREEN);
363 talk_qs_option(qs->items[QUICKSCREEN_TOP], true);
364 if (qs->items[QUICKSCREEN_TOP] != qs->items[QUICKSCREEN_BOTTOM])
365 talk_qs_option(qs->items[QUICKSCREEN_BOTTOM], true);
366 talk_qs_option(qs->items[QUICKSCREEN_LEFT], true);
367 if (qs->items[QUICKSCREEN_LEFT] != qs->items[QUICKSCREEN_RIGHT])
368 talk_qs_option(qs->items[QUICKSCREEN_RIGHT], true);
369 while (true) {
370 if (redraw)
371 {
372 redraw = false;
373 FOR_NB_SCREENS(i)
374 gui_quickscreen_draw(qs, &screens[i], &parent[i],
375 vps[i], &vp_icons[i]);
376 }
377 button = get_action(CONTEXT_QUICKSCREEN, HZ/5);
378#ifdef HAVE_TOUCHSCREEN
379 if (button == ACTION_TOUCHSCREEN)
380 button = quickscreen_touchscreen_button();
381#endif
382 if (default_event_handler(button) == SYS_USB_CONNECTED)
383 {
384 *usb = true;
385 break;
386 }
387 if (gui_quickscreen_do_button(qs, button))
388 {
389 ret |= QUICKSCREEN_CHANGED;
390 can_quit = true;
391 redraw = true;
392 }
393 else if (button == button_enter)
394 can_quit = true;
395 else if (button == ACTION_QS_VOLUP) {
396 adjust_volume(1);
397 FOR_NB_SCREENS(i)
398 skin_update(CUSTOM_STATUSBAR, i, SKIN_REFRESH_NON_STATIC);
399 }
400 else if (button == ACTION_QS_VOLDOWN) {
401 adjust_volume(-1);
402 FOR_NB_SCREENS(i)
403 skin_update(CUSTOM_STATUSBAR, i, SKIN_REFRESH_NON_STATIC);
404 }
405 else if (button == ACTION_STD_CONTEXT)
406 {
407 ret |= QUICKSCREEN_GOTO_SHORTCUTS_MENU;
408 break;
409 }
410 if ((button == button_enter) && can_quit)
411 break;
412
413 if (button == ACTION_STD_CANCEL)
414 break;
415 }
416 /* Notify that we're exiting this screen */
417 cond_talk_ids_fq(VOICE_OK);
418 FOR_NB_SCREENS(i)
419 { /* stop scrolling before exiting */
420 for (int j = 0; j < QUICKSCREEN_ITEM_COUNT; j++)
421 screens[i].scroll_stop_viewport(&vps[i][j]);
422 viewportmanager_theme_undo(i, !(ret & QUICKSCREEN_GOTO_SHORTCUTS_MENU));
423 }
424
425 if (ret & QUICKSCREEN_GOTO_SHORTCUTS_MENU) /* Eliminate flashing of parent during */
426 pop_current_activity_without_refresh(); /* transition to Shortcuts */
427 else
428 pop_current_activity();
429
430 remove_event_ex(GUI_EVENT_NEED_UI_UPDATE, quickscreen_update_callback, NULL);
431
432 return ret;
433}
434
435int quick_screen_quick(int button_enter)
436{
437 struct gui_quickscreen qs;
438 bool usb = false;
439
440 for (int i = 0; i < 4; ++i)
441 {
442 qs.items[i] = global_settings.qs_items[i];
443
444 if (!is_setting_quickscreenable(qs.items[i]))
445 qs.items[i] = NULL;
446 }
447
448 int ret = gui_syncquickscreen_run(&qs, button_enter, &usb);
449 if (ret & QUICKSCREEN_CHANGED)
450 settings_save();
451 if (usb)
452 return QUICKSCREEN_IN_USB;
453 return ret & QUICKSCREEN_GOTO_SHORTCUTS_MENU ? QUICKSCREEN_GOTO_SHORTCUTS_MENU :
454 QUICKSCREEN_OK;
455}
456
457/* stuff to make the quickscreen configurable */
458bool is_setting_quickscreenable(const struct settings_list *setting)
459{
460 if (!setting)
461 return true;
462
463 /* to keep things simple, only settings which have a lang_id set are ok */
464 if (setting->lang_id < 0 || (setting->flags & F_BANFROMQS))
465 return false;
466
467 switch (setting->flags & F_T_MASK)
468 {
469 case F_T_BOOL:
470 return true;
471 case F_T_INT:
472 case F_T_UINT:
473 return (setting->RESERVED != NULL);
474 default:
475 return false;
476 }
477}