diff options
Diffstat (limited to 'apps/plugins/lib/printcell_helper.c')
-rw-r--r-- | apps/plugins/lib/printcell_helper.c | 681 |
1 files changed, 681 insertions, 0 deletions
diff --git a/apps/plugins/lib/printcell_helper.c b/apps/plugins/lib/printcell_helper.c new file mode 100644 index 0000000000..48b8b2c9d2 --- /dev/null +++ b/apps/plugins/lib/printcell_helper.c @@ -0,0 +1,681 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2021 by William Wilgus + * + * 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. + * + ****************************************************************************/ +/* spreadsheet cells for rockbox lists */ +#include "plugin.h" +#include "lib/printcell_helper.h" + +#define COLUMN_ENDLEN 3 +#define TITLE_FLAG 0xFF +#define SELECTED_FLAG 0x1 + +#if LCD_DEPTH == 1 +#define BAR_WIDTH (1) +#else +#define BAR_WIDTH (COLUMN_ENDLEN) +#endif + +struct printcell_info_t { + struct gui_synclist *gui_list; /* list to display */ + int offw[NB_SCREENS]; /* padding between column boundries and text */ + int iconw[NB_SCREENS]; /* width of an icon */ + int selcol_offw[NB_SCREENS]; /* offset width calculated for selected item */ + int totalcolw[NB_SCREENS]; /* total width of all columns */ + int firstcolxw[NB_SCREENS]; /* first column x + width, save recalculating */ + int ncols; /* number of columns */ + int selcol; /* selected column (-1 to ncols-1) */ + uint32_t hidecol_flags; /*bits 0-31 set bit to 1 to hide a column (1<<col#) */ + uint16_t colw[NB_SCREENS][PRINTCELL_MAX_COLUMNS]; /* width of title text + or MIN(or user defined width / screen width) */ + char title[PRINTCELL_MAXLINELEN]; /* title buffer */ + char titlesep; /* character that separates title column items (ex '$') */ + char colsep; /* character that separates text column items (ex ',') */ + bool separator; /* draw grid */ +}; + +static struct printcell_info_t printcell; + +static void parse_dsptext(char splitchr, int ncols, const char *dsp_text, + char* buffer, size_t bufsz, uint16_t *sidx) +{ + /*Internal function loads sidx with split offsets indexing + the buffer of null terminated strings, splits on 'splitchr' + _assumptions_: + dsp_text[len - 1] = \0, + sidx[PRINTCELL_MAX_COLUMNS] + */ + int i = 0; + size_t j = 0; + int ch = splitchr; /* first column $ is optional */ + if (*dsp_text == splitchr) + dsp_text++; + /* add null to the start of the text buffer */ + buffer[j++] = '\0'; + do + { + if (ch == splitchr) + { + sidx[i] = j; /* save start index and copy next column to the buffer */ + while (*dsp_text != '\0' && *dsp_text != splitchr && j < (bufsz - 1)) + { + buffer[j++] = *dsp_text++; + } + buffer[j++] = '\0'; + + i++; + if (i >= ncols || j >= (bufsz - 1)) + break; + } + ch = *dsp_text++; + } while (ch != '\0'); + while (i < ncols) + sidx[i++] = 0; /* point to null */ +} + +static void draw_selector(struct screen *display, struct line_desc *linedes, + int selected_flag, int selected_col, + int separator_height, int x, int y, int w, int h) +{ + /* Internal function draws the currently selected items row & column styling */ + if (!(separator_height > 0 || (selected_flag & SELECTED_FLAG))) + return; + y--; + h++; + int linestyle = linedes->style & _STYLE_DECO_MASK; + bool invert = (selected_flag == SELECTED_FLAG && linestyle >= STYLE_COLORBAR); + if (invert || (linestyle & STYLE_INVERT) == STYLE_INVERT) + { + display->set_drawmode(DRMODE_SOLID | DRMODE_INVERSEVID); + } + + if (selected_col == printcell.selcol) + { + if (selected_flag & SELECTED_FLAG) + { + /* expand left and right bars to show selected column */ + display->fillrect(x, y, BAR_WIDTH, h); + display->fillrect(x + w - BAR_WIDTH + 1, y, BAR_WIDTH, h); + display->set_drawmode(DRMODE_FG); + } + else + { + /* only draw left and right bars */ + display->drawrect(x + 1, y, 1, h); + display->drawrect(x + w - 1, y, 1, h); + return; + } + } + else if (printcell.selcol < 0) + { + if (selected_flag == SELECTED_FLAG) + { + if (selected_col > 0) + x--; + w++; + } + } + /* draw whole rect outline */ + display->drawrect(x + 1, y, w - 1, h); +} + +static inline void set_cell_width(struct viewport *vp, int max_w, int new_w) +{ + /* Internal function sets cell width if less than the max width */ + if (new_w > max_w) + vp->width = max_w; + else + vp->width = new_w; + vp->width -= COLUMN_ENDLEN; +} + +static inline int printcells(struct screen *display, char* buffer, + uint16_t *sidx, struct line_desc *linedes, + struct viewport *vp, int vp_w, int separator, + int x, int y, int offw, int selected_flag, int last_col, + bool scroll, bool is_title) +{ + /* Internal function prints remaining cells */ + int text_offset = offw + offw; + int screen = display->screen_type; + int height = linedes->height; + int selsep = (selected_flag == 0) ? 0: separator; + uint16_t *screencolwidth = printcell.colw[screen]; + + for(int i = 1; i <= last_col; i++) + { + int ny = y; + int nw = screencolwidth[i] + text_offset; + int offx = 0; + + if (i == last_col || x + nw >= vp_w - offw + 1) + { /* not enough space for next column use up excess */ + if (nw < (vp_w - x)) + { + if (is_title) + offx = ((vp_w - x) - nw) / 2; + nw = vp_w - x; + } + } + + if (!PRINTCELL_COLUMN_IS_VISIBLE(printcell.hidecol_flags, i)) + nw = 0; + + int nx = x + nw; + char *buftext; + + if (nx > 0 && nw > offw && x < vp_w) + { + set_cell_width(vp, vp_w, nx); + + if (i == printcell.selcol) + { + linedes->scroll = (selected_flag > 0); + linedes->separator_height = selsep; + } + else + { + if (vp_w < x + text_offset) + { + scroll = false; + } + linedes->scroll = scroll; + linedes->separator_height = separator; + } + buftext = &buffer[sidx[i]]; + display->put_line(x + offw + offx, ny, linedes, "$t", buftext); + vp->width += COLUMN_ENDLEN + 1; + draw_selector(display, linedes, selected_flag, i, separator, x, ny, nw, height); + } + x = nx; + } + return x; +} + +static inline int calcvisible(int screen, int vp_w, int text_offset, int sbwidth) +{ + /* Internal function determine how many of the previous colums can be shown */ + uint16_t *screencolwidth = printcell.colw[screen]; + int screenicnwidth = printcell.iconw[screen]; + int offset = 0; + int selcellw = 0; + if (printcell.selcol >= 0) + selcellw = screencolwidth[printcell.selcol] + text_offset; + int maxw = vp_w - (sbwidth + selcellw + 1); + + for (int i = printcell.selcol - 1; i >= 0; i--) + { + int cw = screencolwidth[i] + text_offset; + + if (!PRINTCELL_COLUMN_IS_VISIBLE(printcell.hidecol_flags, i)) + cw = 0; + + if (i == 0) + cw += screenicnwidth; + if (offset > 0 || cw > maxw) + offset += cw; /* can not display this cell -- everything left goes here too */ + else + maxw -= cw; /* can display this cell subtract from the max width */ + } + return offset; +} + +static void printcell_listdraw_fn(struct list_putlineinfo_t *list_info) +{ +/* Internal function callback from the list, draws items as they are requested */ +#define ICON_PADDING 1 +#define ICON_PADDING_S "1" + struct screen *display = list_info->display; + int screen = display->screen_type; + int col_offset_width = printcell.offw[screen]; + int text_offset = col_offset_width + col_offset_width; + + static char printcell_buffer[PRINTCELL_MAXLINELEN]; + static uint16_t sidx[PRINTCELL_MAX_COLUMNS]; /*indexes zero terminated strings in buffer*/ + + struct gui_synclist *list = list_info->list; + int offset_pos = list_info->list->offset_position[screen]; + int x = list_info->x - offset_pos; + int y = list_info->y; + int line_indent = list_info->item_indent; + int item_offset = list_info->item_offset; + int icon = list_info->icon; + int icon_w = list_info->icon_width; + bool is_title = list_info->is_title; + bool is_selected = list_info->is_selected; + bool show_cursor = list_info->show_cursor; + bool have_icons = list_info->have_icons; + struct line_desc *linedes = list_info->linedes; + const char *dsp_text = list_info->dsp_text; + struct viewport *vp = list_info->vp; + int line = list_info->line; + + int selected_flag = ((is_selected || is_title) ? + (is_title ? TITLE_FLAG : SELECTED_FLAG) : 0); + bool scroll_items = ((selected_flag == TITLE_FLAG) || + (printcell.selcol < 0 && selected_flag > 0)); + + /* save for restore */ + int vp_w = vp->width; + int saved_separator_height = linedes->separator_height; + bool saved_scroll = linedes->scroll; + + linedes->separator_height = 0; + int separator = saved_separator_height; + + if (printcell.separator || (selected_flag & SELECTED_FLAG)) + separator = 1; + + int nx = x; + int last_col = printcell.ncols - 1; + int hidden_w = 0; + int nw, colxw; + char *buftext; + printcell_buffer[0] = '\0'; + + uint16_t *screencolwidth = printcell.colw[screen]; + if (printcell.hidecol_flags > 0) + { + for (int i = 0; i < printcell.ncols; i++) + { + if (PRINTCELL_COLUMN_IS_VISIBLE(printcell.hidecol_flags, i)) + last_col = i; + else + hidden_w += (screencolwidth[i] + text_offset); + } + } + + if (is_title) + { + parse_dsptext(printcell.titlesep, printcell.ncols, dsp_text, + printcell_buffer, sizeof(printcell_buffer), sidx); + + buftext = &printcell_buffer[sidx[0]]; /* set to first column text */ + int sbwidth = 0; + if (rb->global_settings->scrollbar == SCROLLBAR_LEFT) + sbwidth = rb->global_settings->scrollbar_width; + + printcell.iconw[screen] = have_icons ? ICON_PADDING + icon_w : 0; + + if (printcell.selcol_offw[screen] == 0 && printcell.selcol > 0) + printcell.selcol_offw[screen] = calcvisible(screen, vp_w, text_offset, sbwidth); + + nx -= printcell.selcol_offw[screen]; + + nw = screencolwidth[0] + printcell.iconw[screen] + text_offset; + + if (!PRINTCELL_COLUMN_IS_VISIBLE(printcell.hidecol_flags, 0)) + nw = printcell.iconw[screen] - 1; + nw += sbwidth; + + colxw = nx + nw; + printcell.firstcolxw[screen] = colxw; /* save position of first column for subsequent items */ + + if (colxw > 0) + { + set_cell_width(vp, vp_w, colxw); + linedes->separator_height = separator; + + if (have_icons) + { + display->put_line(nx + (COLUMN_ENDLEN/2), y, linedes, + "$"ICON_PADDING_S"I$t", icon, buftext); + } + else + { + display->put_line(nx + col_offset_width, y, linedes, "$t", buftext); + } + } + } + else + { + parse_dsptext(printcell.colsep, printcell.ncols, dsp_text, + printcell_buffer, sizeof(printcell_buffer), sidx); + + buftext = &printcell_buffer[sidx[0]]; /* set to first column text */ + int cursor = Icon_NOICON; + nx -= printcell.selcol_offw[screen]; + + if (selected_flag & SELECTED_FLAG) + { + cursor = Icon_Cursor; + /* limit length of selection if columns don't reach end */ + int maxw = nx + printcell.totalcolw[screen] + printcell.iconw[screen]; + maxw += text_offset * printcell.ncols; + maxw -= hidden_w; + + if (vp_w > maxw) + vp->width = maxw; + /* display a blank line first to draw selector across all cells */ + display->put_line(x, y, linedes, "$t", ""); + } + + //nw = screencolwidth[0] + printcell.iconw[screen] + text_offset; + colxw = printcell.firstcolxw[screen] - vp->x; /* match title spacing */ + nw = colxw - nx; + if (colxw > 0) + { + set_cell_width(vp, vp_w, colxw); + if (printcell.selcol == 0 && selected_flag == 0) + linedes->separator_height = 0; + else + { + linedes->scroll = printcell.selcol == 0 || scroll_items; + linedes->separator_height = separator; + } + if (show_cursor && have_icons) + { + /* the list can have both, one of or neither of cursor and item icons, + * if both don't apply icon padding twice between the icons */ + display->put_line(nx, y, + linedes, "$*s$"ICON_PADDING_S"I$i$"ICON_PADDING_S"s$*t", + line_indent, cursor, icon, item_offset, buftext); + } + else if (show_cursor || have_icons) + { + display->put_line(nx, y, linedes, "$*s$"ICON_PADDING_S"I$*t", line_indent, + show_cursor ? cursor:icon, item_offset, buftext); + } + else + { + display->put_line(nx + col_offset_width, y, linedes, + "$*s$*t", line_indent, item_offset, buftext); + } + } + } + + if (colxw > 0) /* draw selector for first column (title or items) */ + { + vp->width += COLUMN_ENDLEN + 1; + draw_selector(display, linedes, selected_flag, 0, + separator, nx, y, nw, linedes->height); + } + nx += nw; + /* display remaining cells */ + printcells(display, printcell_buffer, sidx, linedes, vp, vp_w, separator, + nx, y, col_offset_width, selected_flag, last_col, scroll_items, is_title); + + /* draw a line at the bottom of the list */ + if (separator > 0 && line == list->nb_items - 1) + display->hline(x, LCD_WIDTH, y + linedes->height - 1); + + /* restore settings */ + linedes->scroll = saved_scroll; + linedes->separator_height = saved_separator_height; + display->set_drawmode(DRMODE_FG); + vp->width = vp_w; +} + +void printcell_enable(bool enable) +{ + if (!printcell.gui_list) + return; + struct gui_synclist *gui_list = printcell.gui_list; +#ifdef HAVE_LCD_COLOR + static int list_sep_color = INT_MIN; + if (enable) + { + if (list_sep_color == INT_MIN) + list_sep_color = rb->global_settings->list_separator_color; + rb->global_settings->list_separator_color = rb->global_settings->fg_color; + gui_list->callback_draw_item = printcell_listdraw_fn; + } + else + { + gui_list->callback_draw_item = NULL; + if (list_sep_color != INT_MIN) + rb->global_settings->list_separator_color = list_sep_color; + list_sep_color = INT_MIN; + } +#else + if (enable) + gui_list->callback_draw_item = printcell_listdraw_fn; + else + gui_list->callback_draw_item = NULL; +#endif + +} + +int printcell_increment_column(int increment, bool wrap) +{ + if (!printcell.gui_list) + return -1; + struct gui_synclist *gui_list = printcell.gui_list; + int item = printcell.selcol + increment; + int imin = -1; + int imax = printcell.ncols - 1; + if(wrap) + { + imin = imax; + imax = -1; + } + + if (item < -1) + item = imin; + else if (item >= printcell.ncols) + item = imax; + + if (item != printcell.selcol) + { + FOR_NB_SCREENS(n) /* offset needs recalculated */ + printcell.selcol_offw[n] = 0; + printcell.selcol = item; + + rb->gui_synclist_draw(gui_list); + rb->gui_synclist_speak_item(gui_list); + } + return item; +} + +int printcell_get_column_selected(void) +{ + if (!printcell.gui_list) + return -1; + return printcell.selcol; +} + +uint32_t printcell_get_column_visibility(int col) +{ + if (col >= 0) + return (PRINTCELL_COLUMN_IS_VISIBLE(printcell.hidecol_flags, col) ? 0:1); + else /* return flag of all columns */ + return printcell.hidecol_flags; +} + +void printcell_set_column_visible(int col, bool visible) +{ + /* visible columns have 0 for the column bit hidden columns have the bit set */ + if (col >= 0) + { + if (visible) + printcell.hidecol_flags &= ~(PRINTCELL_COLUMN_FLAG(col)); + else + printcell.hidecol_flags |= PRINTCELL_COLUMN_FLAG(col); + } + else + { + if (visible) /* set to everything visible */ + printcell.hidecol_flags = 0; + else /* set to everything hidden */ + printcell.hidecol_flags = ((uint32_t)-1); + } +} + +int printcell_set_columns(struct gui_synclist *gui_list, + struct printcell_settings * pcs, + char * title, enum themable_icons icon) +{ + + if (title == NULL) + title = "$PRINTCELL NOT SETUP"; + + if (pcs == NULL) /* DEFAULTS */ + { +#if LCD_DEPTH > 1 + /* If line sep is set to automatic then outline cells */ + bool sep = (rb->global_settings->list_separator_height < 0); +#else + bool sep = (rb->global_settings->cursor_style == 0); +#endif + pcs = &(struct printcell_settings){ .cell_separator = sep, + .title_delimeter = '$', + .text_delimeter = '$', + .hidecol_flags = 0}; + } + + uint16_t sidx[PRINTCELL_MAX_COLUMNS]; /* starting position of column in title string */ + int width, height, user_minwidth; + int i = 0; + size_t j = 0; + rb->memset(&printcell, 0, sizeof(struct printcell_info_t)); + + printcell.gui_list = gui_list; + printcell.separator = pcs->cell_separator; + printcell.titlesep = pcs->title_delimeter; + printcell.colsep = pcs->text_delimeter; + printcell.hidecol_flags = pcs->hidecol_flags; + + int ch = printcell.titlesep; /* first column $ is optional */ + + FOR_NB_SCREENS(n) + { + rb->screens[n]->getstringsize("W", &width, &height); + printcell.offw[n] = width; /* set column text offset */ + } + + if (*title == printcell.titlesep) + title++; + do + { + if (ch == printcell.titlesep) + { + printcell.title[j++] = ch; + user_minwidth = 0; + if (*title == '*')/* user wants a minimum size for this column */ + { + char *dspst = title++; /* store starting position in case this is wrong */ + while(isdigit(*title)) + { + user_minwidth = 10*user_minwidth + *title - '0'; + title++; + } + if (*title != printcell.titlesep) /* user forgot titlesep or wants to display '*' */ + { + title = dspst; + user_minwidth = 0; + } + else + title++; + } + + sidx[i] = j; + + while (*title != '\0' + && *title != printcell.titlesep + && j < PRINTCELL_MAXLINELEN - 1) + { + printcell.title[j++] = *title++; + } + + FOR_NB_SCREENS(n) + { + rb->screens[n]->getstringsize(&printcell.title[sidx[i]], + &width, &height); + + if (width < user_minwidth) + width = user_minwidth; + + if (width > LCD_WIDTH) + width = LCD_WIDTH; + + printcell.colw[n][i] = width; + printcell.totalcolw[n] += width; + } + if (++i >= PRINTCELL_MAX_COLUMNS - 1) + break; + } + ch = *title++; + } while (ch != '\0'); + printcell.ncols = i; + printcell.title[j] = '\0'; + printcell.selcol = -1; + + rb->gui_synclist_set_title(gui_list, printcell.title, icon); + return printcell.ncols; +} + +char *printcell_get_title_text(int selcol, char *buf, size_t bufsz) +{ + /* note offsets are calculated everytime this function is called + * shouldn't be used in hot code paths */ + int index = 0; + buf[0] = '\0'; + if (selcol < 0) /* return entire string incld col formatting '$'*/ + return printcell.title; + + if (selcol < printcell.ncols) + { + uint16_t sidx[PRINTCELL_MAX_COLUMNS]; /*indexes zero terminated strings in buffer*/ + parse_dsptext(printcell.titlesep, selcol + 1, printcell.title, buf, bufsz, sidx); + index = sidx[selcol]; + } + return &buf[index]; +} + +char *printcell_get_column_text(int selcol, char *buf, size_t bufsz) +{ + int index = 0; + char *bpos; + struct gui_synclist *gui_list = printcell.gui_list; + + if (gui_list && gui_list->callback_draw_item == printcell_listdraw_fn) + { + int col = selcol; + int item = gui_list->selected_item; + void *data = gui_list->data; + + if (col < printcell.ncols + && gui_list->callback_get_item_name(item, data, buf, bufsz) == buf) + { + bpos = buf; + if (col < 0) /* return entire string incld col formatting '$'*/ + { + return bpos; + } + bpos++; /* Skip sep/NULL */ + + while(bpos < &buf[bufsz - 1]) + { + if (*bpos == printcell.colsep || *bpos == '\0') + { + if (col-- == 0) + goto success; + index = bpos - buf + 1; /* Skip sep/NULL */ + } + bpos++; + } + } + } +/*failure*/ + bpos = buf; + index = 0; +success: + *bpos = '\0'; + return &buf[index]; +} |