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) 2021 by William Wilgus
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/* spreadsheet cells for rockbox lists */
22#include "plugin.h"
23#include "lib/printcell_helper.h"
24
25#define COLUMN_ENDLEN 3
26#define TITLE_FLAG 0xFF
27#define SELECTED_FLAG 0x1
28
29#if LCD_DEPTH == 1
30#define BAR_WIDTH (1)
31#else
32#define BAR_WIDTH (COLUMN_ENDLEN)
33#endif
34
35struct printcell_info_t {
36 struct gui_synclist *gui_list; /* list to display */
37 int offw[NB_SCREENS]; /* padding between column boundries and text */
38 int iconw[NB_SCREENS]; /* width of an icon */
39 int selcol_offw[NB_SCREENS]; /* offset width calculated for selected item */
40 int totalcolw[NB_SCREENS]; /* total width of all columns */
41 int firstcolxw[NB_SCREENS]; /* first column x + width, save recalculating */
42 int ncols; /* number of columns */
43 int selcol; /* selected column (-1 to ncols-1) */
44 uint32_t hidecol_flags; /*bits 0-31 set bit to 1 to hide a column (1<<col#) */
45 uint16_t colw[NB_SCREENS][PRINTCELL_MAX_COLUMNS]; /* width of title text
46 or MIN(or user defined width / screen width) */
47 char title[PRINTCELL_MAXLINELEN]; /* title buffer */
48 char titlesep; /* character that separates title column items (ex '$') */
49 char colsep; /* character that separates text column items (ex ',') */
50 bool separator; /* draw grid */
51};
52
53static struct printcell_info_t printcell;
54
55static void parse_dsptext(char splitchr, int ncols, const char *dsp_text,
56 char* buffer, size_t bufsz, uint16_t *sidx)
57{
58 /*Internal function loads sidx with split offsets indexing
59 the buffer of null terminated strings, splits on 'splitchr'
60 _assumptions_:
61 dsp_text[len - 1] = \0,
62 sidx[PRINTCELL_MAX_COLUMNS]
63 */
64 int i = 0;
65 size_t j = 0;
66 int ch = splitchr; /* first column $ is optional */
67 if (*dsp_text == splitchr)
68 dsp_text++;
69 /* add null to the start of the text buffer */
70 buffer[j++] = '\0';
71 do
72 {
73 if (ch == splitchr)
74 {
75 sidx[i] = j; /* save start index and copy next column to the buffer */
76 while (*dsp_text != '\0' && *dsp_text != splitchr && j < (bufsz - 1))
77 {
78 buffer[j++] = *dsp_text++;
79 }
80 buffer[j++] = '\0';
81
82 i++;
83 if (i >= ncols || j >= (bufsz - 1))
84 break;
85 }
86 ch = *dsp_text++;
87 } while (ch != '\0');
88 while (i < ncols)
89 sidx[i++] = 0; /* point to null */
90}
91
92static void draw_selector(struct screen *display, struct line_desc *linedes,
93 int selected_flag, int selected_col,
94 int separator_height, int x, int y, int w, int h)
95{
96 /* Internal function draws the currently selected items row & column styling */
97 if (!(separator_height > 0 || (selected_flag & SELECTED_FLAG)))
98 return;
99 y--;
100 h++;
101 int linestyle = linedes->style & _STYLE_DECO_MASK;
102 bool invert = (selected_flag == SELECTED_FLAG && linestyle >= STYLE_COLORBAR);
103 if (invert || (linestyle & STYLE_INVERT) == STYLE_INVERT)
104 {
105 display->set_drawmode(DRMODE_SOLID | DRMODE_INVERSEVID);
106 }
107
108 if (selected_col == printcell.selcol)
109 {
110 if (selected_flag & SELECTED_FLAG)
111 {
112 /* expand left and right bars to show selected column */
113 display->fillrect(x, y, BAR_WIDTH, h);
114 display->fillrect(x + w - BAR_WIDTH + 1, y, BAR_WIDTH, h);
115 display->set_drawmode(DRMODE_FG);
116 }
117 else
118 {
119 /* only draw left and right bars */
120 display->drawrect(x + 1, y, 1, h);
121 display->drawrect(x + w - 1, y, 1, h);
122 return;
123 }
124 }
125 else if (printcell.selcol < 0)
126 {
127 if (selected_flag == SELECTED_FLAG)
128 {
129 if (selected_col > 0)
130 x--;
131 w++;
132 }
133 }
134 /* draw whole rect outline */
135 display->drawrect(x + 1, y, w - 1, h);
136}
137
138static inline void set_cell_width(struct viewport *vp, int max_w, int new_w)
139{
140 /* Internal function sets cell width if less than the max width */
141 if (new_w > max_w)
142 vp->width = max_w;
143 else
144 vp->width = new_w;
145 vp->width -= COLUMN_ENDLEN;
146}
147
148static inline int printcells(struct screen *display, char* buffer,
149 uint16_t *sidx, struct line_desc *linedes,
150 struct viewport *vp, int vp_w, int separator,
151 int x, int y, int offw, int selected_flag, int last_col,
152 bool scroll, bool is_title)
153{
154 /* Internal function prints remaining cells */
155 int text_offset = offw + offw;
156 int screen = display->screen_type;
157 int height = linedes->height;
158 int selsep = (selected_flag == 0) ? 0: separator;
159 uint16_t *screencolwidth = printcell.colw[screen];
160
161 for(int i = 1; i <= last_col; i++)
162 {
163 int ny = y;
164 int nw = screencolwidth[i] + text_offset;
165 int offx = 0;
166
167 if (i == last_col || x + nw >= vp_w - offw + 1)
168 { /* not enough space for next column use up excess */
169 if (nw < (vp_w - x))
170 {
171 if (is_title)
172 offx = ((vp_w - x) - nw) / 2;
173 nw = vp_w - x;
174 }
175 }
176
177 if (!PRINTCELL_COLUMN_IS_VISIBLE(printcell.hidecol_flags, i))
178 nw = 0;
179
180 int nx = x + nw;
181 char *buftext;
182
183 if (nx > 0 && nw > offw && x < vp_w)
184 {
185 set_cell_width(vp, vp_w, nx);
186
187 if (i == printcell.selcol)
188 {
189 linedes->scroll = (selected_flag > 0);
190 linedes->separator_height = selsep;
191 }
192 else
193 {
194 if (vp_w < x + text_offset)
195 {
196 scroll = false;
197 }
198 linedes->scroll = scroll;
199 linedes->separator_height = separator;
200 }
201 buftext = &buffer[sidx[i]];
202 display->put_line(x + offw + offx, ny, linedes, "$t", buftext);
203 vp->width += COLUMN_ENDLEN + 1;
204 if (vp->width > vp_w)
205 vp->width = vp_w;
206
207 draw_selector(display, linedes, selected_flag, i, separator, x, ny, nw, height);
208 }
209 x = nx;
210 }
211 return x;
212}
213
214static inline int calcvisible(int screen, int vp_w, int text_offset, int sbwidth)
215{
216 /* Internal function determine how many of the previous colums can be shown */
217 uint16_t *screencolwidth = printcell.colw[screen];
218 int screenicnwidth = printcell.iconw[screen];
219 int offset = 0;
220 int selcellw = 0;
221 if (printcell.selcol >= 0)
222 selcellw = screencolwidth[printcell.selcol] + text_offset;
223 int maxw = vp_w - (sbwidth + selcellw + 1);
224
225 for (int i = printcell.selcol - 1; i >= 0; i--)
226 {
227 int cw = screencolwidth[i] + text_offset;
228
229 if (!PRINTCELL_COLUMN_IS_VISIBLE(printcell.hidecol_flags, i))
230 cw = 0;
231
232 if (i == 0)
233 cw += screenicnwidth;
234 if (offset > 0 || cw > maxw)
235 offset += cw; /* can not display this cell -- everything left goes here too */
236 else
237 maxw -= cw; /* can display this cell subtract from the max width */
238 }
239 return offset;
240}
241
242static void printcell_listdraw_fn(struct list_putlineinfo_t *list_info)
243{
244/* Internal function callback from the list, draws items as they are requested */
245#define ICON_PADDING 1
246#define ICON_PADDING_S "1"
247 struct screen *display = list_info->display;
248 int screen = display->screen_type;
249 int col_offset_width = printcell.offw[screen];
250 int text_offset = col_offset_width + col_offset_width;
251
252 static char printcell_buffer[PRINTCELL_MAXLINELEN];
253 static uint16_t sidx[PRINTCELL_MAX_COLUMNS]; /*indexes zero terminated strings in buffer*/
254
255 struct gui_synclist *list = list_info->list;
256 int offset_pos = list_info->list->offset_position[screen];
257 int x = list_info->x - offset_pos;
258 int y = list_info->y;
259 int line_indent = list_info->item_indent;
260 int item_offset = list_info->item_offset;
261 int icon = list_info->icon;
262 int icon_w = list_info->icon_width;
263 bool is_title = list_info->is_title;
264 bool is_selected = list_info->is_selected;
265 bool show_cursor = list_info->show_cursor;
266 bool have_icons = list_info->have_icons;
267 struct line_desc *linedes = list_info->linedes;
268 const char *dsp_text = list_info->dsp_text;
269 struct viewport *vp = list_info->vp;
270 int line = list_info->line;
271
272 int selected_flag = ((is_selected || is_title) ?
273 (is_title ? TITLE_FLAG : SELECTED_FLAG) : 0);
274 bool scroll_items = ((selected_flag == TITLE_FLAG) ||
275 (printcell.selcol < 0 && selected_flag > 0));
276
277 /* save for restore */
278 int vp_w = vp->width;
279 int saved_separator_height = linedes->separator_height;
280 bool saved_scroll = linedes->scroll;
281
282 linedes->separator_height = 0;
283 int separator = saved_separator_height;
284
285 if (printcell.separator || (selected_flag & SELECTED_FLAG))
286 separator = 1;
287
288 int nx = x;
289 int last_col = printcell.ncols - 1;
290 int hidden_w = 0;
291 int nw, colxw;
292 char *buftext;
293 printcell_buffer[0] = '\0';
294
295 uint16_t *screencolwidth = printcell.colw[screen];
296 if (printcell.hidecol_flags > 0)
297 {
298 for (int i = 0; i < printcell.ncols; i++)
299 {
300 if (PRINTCELL_COLUMN_IS_VISIBLE(printcell.hidecol_flags, i))
301 last_col = i;
302 else
303 hidden_w += (screencolwidth[i] + text_offset);
304 }
305 }
306
307 if (is_title)
308 {
309 parse_dsptext(printcell.titlesep, printcell.ncols, dsp_text,
310 printcell_buffer, sizeof(printcell_buffer), sidx);
311
312 buftext = &printcell_buffer[sidx[0]]; /* set to first column text */
313 int sbwidth = 0;
314 if (rb->global_settings->scrollbar == SCROLLBAR_LEFT)
315 sbwidth = rb->global_settings->scrollbar_width;
316
317 printcell.iconw[screen] = have_icons ? ICON_PADDING + icon_w : 0;
318
319 if (printcell.selcol_offw[screen] == 0 && printcell.selcol > 0)
320 printcell.selcol_offw[screen] = calcvisible(screen, vp_w, text_offset, sbwidth);
321
322 nx -= printcell.selcol_offw[screen];
323
324 nw = screencolwidth[0] + printcell.iconw[screen] + text_offset;
325
326 if (!PRINTCELL_COLUMN_IS_VISIBLE(printcell.hidecol_flags, 0))
327 nw = printcell.iconw[screen] - 1;
328 nw += sbwidth;
329
330 colxw = nx + nw;
331 printcell.firstcolxw[screen] = colxw; /* save position of first column for subsequent items */
332
333 if (colxw > 0)
334 {
335 set_cell_width(vp, vp_w, colxw);
336 linedes->separator_height = separator;
337
338 if (have_icons)
339 {
340 display->put_line(nx + (COLUMN_ENDLEN/2), y, linedes,
341 "$"ICON_PADDING_S"I$t", icon, buftext);
342 }
343 else
344 {
345 display->put_line(nx + col_offset_width, y, linedes, "$t", buftext);
346 }
347 }
348 }
349 else
350 {
351 parse_dsptext(printcell.colsep, printcell.ncols, dsp_text,
352 printcell_buffer, sizeof(printcell_buffer), sidx);
353
354 buftext = &printcell_buffer[sidx[0]]; /* set to first column text */
355 int cursor = Icon_NOICON;
356 nx -= printcell.selcol_offw[screen];
357
358 if (selected_flag & SELECTED_FLAG)
359 {
360 cursor = Icon_Cursor;
361 /* limit length of selection if columns don't reach end */
362 int maxw = nx + printcell.totalcolw[screen] + printcell.iconw[screen];
363 maxw += text_offset * printcell.ncols;
364 maxw -= hidden_w;
365
366 if (vp_w > maxw)
367 vp->width = maxw;
368 /* display a blank line first to draw selector across all cells */
369 display->put_line(x, y, linedes, "$t", "");
370 }
371
372 //nw = screencolwidth[0] + printcell.iconw[screen] + text_offset;
373 colxw = printcell.firstcolxw[screen] - vp->x; /* match title spacing */
374 nw = colxw - nx;
375 if (colxw > 0)
376 {
377 set_cell_width(vp, vp_w, colxw);
378 if (printcell.selcol == 0 && selected_flag == 0)
379 linedes->separator_height = 0;
380 else
381 {
382 linedes->scroll = printcell.selcol == 0 || scroll_items;
383 linedes->separator_height = separator;
384 }
385 if (show_cursor && have_icons)
386 {
387 /* the list can have both, one of or neither of cursor and item icons,
388 * if both don't apply icon padding twice between the icons */
389 display->put_line(nx, y,
390 linedes, "$*s$"ICON_PADDING_S"I$i$"ICON_PADDING_S"s$*t",
391 line_indent, cursor, icon, item_offset, buftext);
392 }
393 else if (show_cursor || have_icons)
394 {
395 display->put_line(nx, y, linedes, "$*s$"ICON_PADDING_S"I$*t", line_indent,
396 show_cursor ? cursor:icon, item_offset, buftext);
397 }
398 else
399 {
400 display->put_line(nx + col_offset_width, y, linedes,
401 "$*s$*t", line_indent, item_offset, buftext);
402 }
403 }
404 }
405
406 if (colxw > 0) /* draw selector for first column (title or items) */
407 {
408 vp->width += COLUMN_ENDLEN + 1;
409 if (vp->width > vp_w)
410 vp->width = vp_w;
411 draw_selector(display, linedes, selected_flag, 0,
412 separator, nx, y, nw, linedes->height);
413 }
414 nx += nw;
415 /* display remaining cells */
416 printcells(display, printcell_buffer, sidx, linedes, vp, vp_w, separator,
417 nx, y, col_offset_width, selected_flag, last_col, scroll_items, is_title);
418
419 /* draw a line at the bottom of the list */
420 if (separator > 0 && line == list->nb_items - 1)
421 display->hline(x, LCD_WIDTH, y + linedes->height - 1);
422
423 /* restore settings */
424 linedes->scroll = saved_scroll;
425 linedes->separator_height = saved_separator_height;
426 display->set_drawmode(DRMODE_FG);
427 vp->width = vp_w;
428}
429
430void printcell_enable(bool enable)
431{
432 if (!printcell.gui_list)
433 return;
434 struct gui_synclist *gui_list = printcell.gui_list;
435#ifdef HAVE_LCD_COLOR
436 static int list_sep_color = INT_MIN;
437 if (enable)
438 {
439 if (list_sep_color == INT_MIN)
440 list_sep_color = rb->global_settings->list_separator_color;
441 rb->global_settings->list_separator_color = rb->global_settings->fg_color;
442 gui_list->callback_draw_item = printcell_listdraw_fn;
443 }
444 else
445 {
446 gui_list->callback_draw_item = NULL;
447 if (list_sep_color != INT_MIN)
448 rb->global_settings->list_separator_color = list_sep_color;
449 list_sep_color = INT_MIN;
450 }
451#else
452 if (enable)
453 gui_list->callback_draw_item = printcell_listdraw_fn;
454 else
455 gui_list->callback_draw_item = NULL;
456#endif
457
458}
459
460int printcell_increment_column(int increment, bool wrap)
461{
462 if (!printcell.gui_list)
463 return -1;
464 struct gui_synclist *gui_list = printcell.gui_list;
465 int item = printcell.selcol + increment;
466 int imin = -1;
467 int imax = printcell.ncols - 1;
468 if(wrap)
469 {
470 imin = imax;
471 imax = -1;
472 }
473
474 if (item < -1)
475 item = imin;
476 else if (item >= printcell.ncols)
477 item = imax;
478
479 if (item != printcell.selcol)
480 {
481 FOR_NB_SCREENS(n) /* offset needs recalculated */
482 printcell.selcol_offw[n] = 0;
483 printcell.selcol = item;
484
485 rb->gui_synclist_draw(gui_list);
486 rb->gui_synclist_speak_item(gui_list);
487 }
488 return item;
489}
490
491int printcell_get_column_selected(void)
492{
493 if (!printcell.gui_list)
494 return -1;
495 return printcell.selcol;
496}
497
498uint32_t printcell_get_column_visibility(int col)
499{
500 if (col >= 0)
501 return (PRINTCELL_COLUMN_IS_VISIBLE(printcell.hidecol_flags, col) ? 0:1);
502 else /* return flag of all columns */
503 return printcell.hidecol_flags;
504}
505
506void printcell_set_column_visible(int col, bool visible)
507{
508 /* visible columns have 0 for the column bit hidden columns have the bit set */
509 if (col >= 0)
510 {
511 if (visible)
512 printcell.hidecol_flags &= ~(PRINTCELL_COLUMN_FLAG(col));
513 else
514 printcell.hidecol_flags |= PRINTCELL_COLUMN_FLAG(col);
515 }
516 else
517 {
518 if (visible) /* set to everything visible */
519 printcell.hidecol_flags = 0;
520 else /* set to everything hidden */
521 printcell.hidecol_flags = ((uint32_t)-1);
522 }
523}
524
525int printcell_set_columns(struct gui_synclist *gui_list,
526 struct printcell_settings * pcs,
527 char * title, enum themable_icons icon)
528{
529
530 if (title == NULL)
531 title = "$PRINTCELL NOT SETUP";
532
533 uint16_t sidx[PRINTCELL_MAX_COLUMNS]; /* starting position of column in title string */
534 int width, height, user_minwidth;
535 int i = 0;
536 size_t j = 0;
537 rb->memset(&printcell, 0, sizeof(struct printcell_info_t));
538
539 if (pcs == NULL) /* DEFAULTS */
540 {
541#if LCD_DEPTH > 1
542 /* If line sep is set to automatic then outline cells */
543 bool sep = (rb->global_settings->list_separator_height < 0);
544#else
545 bool sep = (rb->global_settings->cursor_style == 0);
546#endif
547 printcell.separator = sep;
548 printcell.titlesep = '$';
549 printcell.colsep = '$';
550 printcell.hidecol_flags = 0;
551 }
552 else
553 {
554 printcell.separator = pcs->cell_separator;
555 printcell.titlesep = pcs->title_delimeter;
556 printcell.colsep = pcs->text_delimeter;
557 printcell.hidecol_flags = pcs->hidecol_flags;
558 }
559 printcell.gui_list = gui_list;
560
561
562 int ch = printcell.titlesep; /* first column $ is optional */
563
564 FOR_NB_SCREENS(n)
565 {
566 rb->screens[n]->getstringsize("W", &width, &height);
567 printcell.offw[n] = width; /* set column text offset */
568 }
569
570 if (*title == printcell.titlesep)
571 title++;
572 do
573 {
574 if (ch == printcell.titlesep)
575 {
576 printcell.title[j++] = ch;
577 user_minwidth = 0;
578 if (*title == '*')/* user wants a minimum size for this column */
579 {
580 char *dspst = title++; /* store starting position in case this is wrong */
581 while(isdigit(*title))
582 {
583 user_minwidth = 10*user_minwidth + *title - '0';
584 title++;
585 }
586 if (*title != printcell.titlesep) /* user forgot titlesep or wants to display '*' */
587 {
588 title = dspst;
589 user_minwidth = 0;
590 }
591 else
592 title++;
593 }
594
595 sidx[i] = j;
596
597 while (*title != '\0'
598 && *title != printcell.titlesep
599 && j < PRINTCELL_MAXLINELEN - 1)
600 {
601 printcell.title[j++] = *title++;
602 }
603
604 FOR_NB_SCREENS(n)
605 {
606 rb->screens[n]->getstringsize(&printcell.title[sidx[i]],
607 &width, &height);
608
609 if (width < user_minwidth)
610 width = user_minwidth;
611
612 if (width > LCD_WIDTH)
613 width = LCD_WIDTH;
614
615 printcell.colw[n][i] = width;
616 printcell.totalcolw[n] += width;
617 }
618 if (++i >= PRINTCELL_MAX_COLUMNS - 1)
619 break;
620 }
621 ch = *title++;
622 } while (ch != '\0');
623 printcell.ncols = i;
624 printcell.title[j] = '\0';
625 printcell.selcol = -1;
626
627 rb->gui_synclist_set_title(gui_list, printcell.title, icon);
628 return printcell.ncols;
629}
630
631char *printcell_get_title_text(int selcol, char *buf, size_t bufsz)
632{
633 /* note offsets are calculated everytime this function is called
634 * shouldn't be used in hot code paths */
635 int index = 0;
636 buf[0] = '\0';
637 if (selcol < 0) /* return entire string incld col formatting '$'*/
638 return printcell.title;
639
640 if (selcol < printcell.ncols)
641 {
642 uint16_t sidx[PRINTCELL_MAX_COLUMNS]; /*indexes zero terminated strings in buffer*/
643 parse_dsptext(printcell.titlesep, selcol + 1, printcell.title, buf, bufsz, sidx);
644 index = sidx[selcol];
645 }
646 return &buf[index];
647}
648
649char *printcell_get_column_text(int selcol, char *buf, size_t bufsz)
650{
651 int index = 0;
652 char *bpos;
653 struct gui_synclist *gui_list = printcell.gui_list;
654
655 if (gui_list && gui_list->callback_draw_item == printcell_listdraw_fn)
656 {
657 int col = selcol;
658 int item = gui_list->selected_item;
659 void *data = gui_list->data;
660
661 if (col < printcell.ncols
662 && gui_list->callback_get_item_name(item, data, buf, bufsz) == buf)
663 {
664 bpos = buf;
665 if (col < 0) /* return entire string incld col formatting '$'*/
666 {
667 return bpos;
668 }
669 bpos++; /* Skip sep/NULL */
670
671 while(bpos < &buf[bufsz - 1])
672 {
673 if (*bpos == printcell.colsep || *bpos == '\0')
674 {
675 if (col-- == 0)
676 goto success;
677 index = bpos - buf + 1; /* Skip sep/NULL */
678 }
679 bpos++;
680 }
681 }
682 }
683/*failure*/
684 bpos = buf;
685 index = 0;
686success:
687 *bpos = '\0';
688 return &buf[index];
689}