A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita audio rust zig deno mpris rockbox mpd
at master 689 lines 23 kB view raw
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}