A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita audio rust zig deno mpris rockbox mpd

[Feature, Plugin] lastfm_scrobbler_viewer

a plugin to view lastfm scrobbler logs

uses print cell to give a spreadsheet view of scrobbler logs

buffers the whole file if possible otherwise it reads entries from disk

rudimentary text searching for columns
include / exclude; all/any and case sensitive

Change-Id: Id9616e5796658952fba4ea747f596cb77d6f34c0

authored by

William Wilgus and committed by
William Wilgus
dfe12252 43b0fba7

+1358 -111
+1
apps/filetypes.c
··· 129 129 { "fmr", FILE_ATTR_FMR }, 130 130 { "fms", FILE_ATTR_FMS }, 131 131 #endif 132 + { "log", FILE_ATTR_LOG }, 132 133 { "lng", FILE_ATTR_LNG }, 133 134 { "rock", FILE_ATTR_ROCK }, 134 135 { "lua", FILE_ATTR_LUA },
+1
apps/filetypes.h
··· 48 48 #define FILE_ATTR_FMS 0x1200 /* FM screen skin file */ 49 49 #define FILE_ATTR_RFMS 0x1300 /* FM screen skin file */ 50 50 #define FILE_ATTR_OPX 0x1400 /* open plugins shortcut */ 51 + #define FILE_ATTR_LOG 0x1500 /* log file */ 51 52 #define FILE_ATTR_MASK 0xFF00 /* which bits tree.c uses for file types */ 52 53 53 54 struct filetype {
+1
apps/plugins/CATEGORIES
··· 50 50 keyremap,apps 51 51 lamp,apps 52 52 lastfm_scrobbler,apps 53 + lastfm_scrobbler_viewer,viewers 53 54 logo,demos 54 55 lrcplayer,apps 55 56 lua,viewers
+1
apps/plugins/SOURCES
··· 13 13 keybox.c 14 14 keyremap.c 15 15 lastfm_scrobbler.c 16 + lastfm_scrobbler_viewer.c 16 17 logo.c 17 18 lrcplayer.c 18 19 mosaique.c
+20 -9
apps/plugins/keyremap.c
··· 1422 1422 else 1423 1423 rb->talk_spell(name, true); 1424 1424 } 1425 + else if(data == MENU_ID(M_SETKEYS)) 1426 + { 1427 + char buf[MAX_MENU_NAME]; 1428 + int selcol = printcell_get_column_selected(); 1429 + const char* name = printcell_get_column_text(selcol, buf, sizeof(buf)); 1430 + long id = P2ID((const unsigned char *)name); 1431 + if(id>=0) 1432 + rb->talk_id(id, true); 1433 + else 1434 + rb->talk_spell(name, true); 1435 + } 1425 1436 else 1426 1437 { 1427 1438 char buf[MAX_MENU_NAME]; ··· 1606 1617 } 1607 1618 else 1608 1619 { 1609 - keyset.view_lastcol = printcell_increment_column(lists, 1, true); 1620 + keyset.view_lastcol = printcell_increment_column(1, true); 1610 1621 *action = ACTION_NONE; 1611 1622 } 1612 1623 } 1613 1624 } 1614 1625 else if (*action == ACTION_STD_CANCEL) 1615 1626 { 1616 - keyset.view_lastcol = printcell_increment_column(lists, -1, true); 1627 + keyset.view_lastcol = printcell_increment_column(-1, true); 1617 1628 if (keyset.view_lastcol != keyset.view_columns - 1) 1618 1629 { 1619 1630 *action = ACTION_NONE; ··· 2038 2049 rb->gui_synclist_set_voice_callback(&lists, list_voice_cb); 2039 2050 rb->gui_synclist_set_nb_items(&lists,items); 2040 2051 rb->gui_synclist_select_item(&lists, selected_item); 2041 - printcell_enable(&lists, false, false); 2052 + printcell_enable(false); 2042 2053 2043 2054 if (menu_id == MENU_ID(M_ROOT)) 2044 2055 { ··· 2047 2058 } 2048 2059 else if (menu_id == MENU_ID(M_SETKEYS)) 2049 2060 { 2050 - printcell_enable(&lists, true, true); 2051 - keyset.view_columns = printcell_set_columns(&lists, ACTVIEW_HEADER, Icon_Rockbox); 2052 - int curcol = printcell_increment_column(&lists, 0, true); 2061 + keyset.view_columns = printcell_set_columns(&lists, NULL, 2062 + ACTVIEW_HEADER, Icon_Rockbox); 2063 + printcell_enable(true); 2064 + int curcol = printcell_get_column_selected(); 2053 2065 if (keyset.view_lastcol >= keyset.view_columns) 2054 2066 keyset.view_lastcol = -1; 2055 2067 /* restore column position */ 2056 2068 while (keyset.view_lastcol > -1 && curcol != keyset.view_lastcol) 2057 2069 { 2058 - curcol = printcell_increment_column(&lists, 1, true); 2070 + curcol = printcell_increment_column(1, true); 2059 2071 } 2060 2072 keyset.view_lastcol = curcol; 2061 2073 } ··· 2065 2077 PEEK_MENU_ID(id); 2066 2078 lang_strlcpy(menu_title, mainitem(id)->name, sizeof(menu_title)); 2067 2079 rb->gui_synclist_set_title(&lists, menu_title, Icon_Submenu_Entered); 2068 - /* if (menu_title[0] == '$'){ printcell_enable(&lists, true, true); } */ 2080 + /* if (menu_title[0] == '$'){ printcell_enable(true); } */ 2069 2081 } 2070 - 2071 2082 } 2072 2083 2073 2084 static void keyremap_set_buffer(void* buffer, size_t buf_size)
+2
apps/plugins/lastfm_scrobbler.c
··· 160 160 case 3: 161 161 rb->set_int("Beep Level", "", UNIT_INT, 162 162 &gConfig.beeplvl, NULL, 1, 0, 10, NULL); 163 + if (gConfig.beeplvl > 0) 164 + rb->beep_play(1500, 100, 100 * gConfig.beeplvl); 163 165 case 4: /*sep*/ 164 166 continue; 165 167 case 5:
+1033
apps/plugins/lastfm_scrobbler_viewer.c
··· 1 + /*************************************************************************** 2 + * __________ __ ___. 3 + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ 4 + * Source | _// __ \_/ ___\| |/ /| __ \ / __ \ \/ / 5 + * Jukebox | | ( (__) ) \___| ( | \_\ ( (__) ) ( 6 + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ 7 + * \/ \/ \/ \/ \/ 8 + * $Id$ 9 + * 10 + * Copyright (C) 2023 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 + 22 + #include "plugin.h" 23 + #include "lang_enum.h" 24 + 25 + #include "lib/printcell_helper.h" 26 + 27 + #include "lib/configfile.h" 28 + #define CFG_FILE "/lastfm_scrobbler_viewer.cfg" 29 + #define TMP_FILE ""PLUGIN_DATA_DIR "/lscrobbler_viewer_%d.tmp" 30 + #define CFG_VER 1 31 + 32 + #ifdef ROCKBOX_HAS_LOGF 33 + #define logf rb->logf 34 + #else 35 + #define logf(...) do { } while(0) 36 + #endif 37 + 38 + /*#ARTIST #ALBUM #TITLE #TRACKNUM #LENGTH #RATING #TIMESTAMP #MUSICBRAINZ_TRACKID*/ 39 + 40 + #define SCROBBLE_HDR_FMT "$*%d$%s$*%d$%s$*%d$%s$Track#$Length$Rating$TimeStamp$TrackId" 41 + //#define SCROBBLE_HDR "$*128$Artist$*128$Album$*128$Title$Track#$Length$Rating$TimeStamp$TrackId" 42 + 43 + #define SCROBBLER_MIN_COLUMNS (6) /* a valid scrobbler file should have at least this many columns */ 44 + 45 + #define GOTO_ACTION_DEFAULT_HANDLER (PLUGIN_OK + 1) 46 + #define CANCEL_CONTEXT_MENU (PLUGIN_OK + 2) 47 + #define QUIT_CONTEXT_MENU (PLUGIN_OK + 3) 48 + /* global lists, for everything */ 49 + static struct gui_synclist lists; 50 + 51 + /* printcell data for the current file */ 52 + struct printcell_data_t { 53 + int view_columns; 54 + int view_lastcol; 55 + 56 + int items_buffered; 57 + int items_total; 58 + int fd_cur; 59 + char *filename; 60 + 61 + char *buf; 62 + size_t buf_size; 63 + off_t buf_used; 64 + char header[PRINTCELL_MAXLINELEN]; 65 + 66 + }; 67 + 68 + enum e_find_type { 69 + FIND_ALL = 0, 70 + FIND_EXCLUDE, 71 + FIND_EXCLUDE_CASE, 72 + FIND_EXCLUDE_ANY, 73 + FIND_INCLUDE, 74 + FIND_INCLUDE_CASE, 75 + FIND_INCLUDE_ANY, 76 + FIND_CUSTOM, 77 + }; 78 + 79 + static void synclist_set(int selected_item, int items, int sel_size, struct printcell_data_t *pc_data); 80 + static void pc_data_set_header(struct printcell_data_t *pc_data); 81 + 82 + static void browse_file(char *buf, size_t bufsz) 83 + { 84 + struct browse_context browse = { 85 + .dirfilter = SHOW_ALL, 86 + .flags = BROWSE_SELECTONLY, 87 + .title = "Select a scrobbler log file", 88 + .icon = Icon_Playlist, 89 + .buf = buf, 90 + .bufsize = bufsz, 91 + .root = "/", 92 + }; 93 + 94 + if (rb->rockbox_browse(&browse) != GO_TO_PREVIOUS) 95 + { 96 + buf[0] = '\0'; 97 + } 98 + } 99 + 100 + static struct plugin_config 101 + { 102 + bool separator; 103 + bool talk; 104 + int col_width; 105 + uint32_t hidecol_flags; 106 + } gConfig; 107 + 108 + static struct configdata config[] = 109 + { 110 + {TYPE_BOOL, 0, 1, { .bool_p = &gConfig.separator }, "Cell Separator", NULL}, 111 + {TYPE_BOOL, 0, 1, { .bool_p = &gConfig.talk }, "Voice", NULL}, 112 + {TYPE_INT, 32, LCD_WIDTH, { .int_p = &gConfig.col_width }, "Cell Width", NULL}, 113 + {TYPE_INT, INT_MIN, INT_MAX, { .int_p = &gConfig.hidecol_flags }, "Hidden Columns", NULL}, 114 + }; 115 + const int gCfg_sz = sizeof(config)/sizeof(*config); 116 + /****************** config functions *****************/ 117 + static void config_set_defaults(void) 118 + { 119 + gConfig.col_width = MIN(LCD_WIDTH, 128); 120 + gConfig.hidecol_flags = 0; 121 + gConfig.separator = true; 122 + gConfig.talk = rb->global_settings->talk_menu; 123 + } 124 + 125 + static void config_save(void) 126 + { 127 + configfile_save(CFG_FILE, config, gCfg_sz, CFG_VER); 128 + } 129 + 130 + static int config_settings_menu(struct printcell_data_t *pc_data) 131 + { 132 + int selection = 0; 133 + 134 + struct viewport parentvp[NB_SCREENS]; 135 + FOR_NB_SCREENS(l) 136 + { 137 + rb->viewport_set_defaults(&parentvp[l], l); 138 + rb->viewport_set_fullscreen(&parentvp[l], l); 139 + } 140 + 141 + MENUITEM_STRINGLIST(settings_menu, ID2P(LANG_SETTINGS), NULL, 142 + ID2P(LANG_LIST_SEPARATOR), 143 + "Cell Width", 144 + ID2P(LANG_VOICE), 145 + ID2P(VOICE_BLANK), 146 + ID2P(VOICE_BLANK), 147 + ID2P(LANG_CANCEL_0), 148 + ID2P(LANG_SAVE_EXIT)); 149 + 150 + do { 151 + selection=rb->do_menu(&settings_menu,&selection, parentvp, true); 152 + switch(selection) { 153 + 154 + case 0: 155 + rb->set_bool(rb->str(LANG_LIST_SEPARATOR), &gConfig.separator); 156 + break; 157 + case 1: 158 + rb->set_int("Cell Width", "", UNIT_INT, 159 + &gConfig.col_width, NULL, 1, 32, LCD_WIDTH, NULL ); 160 + break; 161 + case 2: 162 + rb->set_bool(rb->str(LANG_VOICE), &gConfig.talk); 163 + break; 164 + case 3: 165 + continue; 166 + case 4: /*sep*/ 167 + continue; 168 + case 5: 169 + return -1; 170 + break; 171 + case 6: 172 + { 173 + pc_data_set_header(pc_data); 174 + synclist_set(0, pc_data->items_total, 1, pc_data); 175 + int res = configfile_save(CFG_FILE, config, gCfg_sz, CFG_VER); 176 + if (res >= 0) 177 + { 178 + logf("Cfg saved %s %d bytes", CFG_FILE, gCfg_sz); 179 + return PLUGIN_OK; 180 + } 181 + logf("Cfg FAILED (%d) %s", res, CFG_FILE); 182 + return PLUGIN_ERROR; 183 + } 184 + case MENU_ATTACHED_USB: 185 + return PLUGIN_USB_CONNECTED; 186 + default: 187 + return PLUGIN_OK; 188 + } 189 + } while ( selection >= 0 ); 190 + return 0; 191 + } 192 + 193 + /* open file pass back file descriptor and return file size */ 194 + static size_t file_open(const char *filename, int *fd) 195 + { 196 + size_t fsize = 0; 197 + 198 + if (filename && fd) 199 + { 200 + *fd = rb->open(filename, O_RDONLY); 201 + if (*fd >= 0) 202 + { 203 + fsize = rb->filesize(*fd); 204 + } 205 + } 206 + return fsize; 207 + } 208 + 209 + static const char* list_get_name_cb(int selected_item, void* data, 210 + char* buf, size_t buf_len) 211 + { 212 + const int slush_pos = 32; /* entries around the current entry to keep buffered */ 213 + /* keep track of the last position in the buffer */ 214 + static int buf_line_num = 0; 215 + static off_t buf_last_pos = 0; 216 + /* keep track of the last position in the file */ 217 + static int file_line_num = 0; 218 + static off_t file_last_seek = 0; 219 + 220 + if (selected_item < 0) 221 + { 222 + logf("[%s] Reset positions", __func__); 223 + buf_line_num = 0; 224 + buf_last_pos = 0; 225 + file_line_num = 0; 226 + file_last_seek = 0; 227 + return ""; 228 + } 229 + 230 + int line_num; 231 + struct printcell_data_t *pc_data = (struct printcell_data_t*) data; 232 + bool found = false; 233 + 234 + if (pc_data->buf && selected_item < pc_data->items_buffered) 235 + { 236 + int buf_pos; 237 + 238 + if (buf_line_num > selected_item || buf_last_pos >= pc_data->buf_used 239 + || buf_line_num < 0) 240 + { 241 + logf("[%s] Set pos buffer 0", __func__); 242 + buf_line_num = 0; 243 + buf_last_pos = 0; 244 + } 245 + buf_pos = buf_last_pos; 246 + line_num = buf_line_num; 247 + 248 + while (buf_pos < pc_data->buf_used 249 + && line_num < pc_data->items_buffered) 250 + { 251 + size_t len = rb->strlen(&pc_data->buf[buf_pos]); 252 + if(line_num == selected_item) 253 + { 254 + rb->strlcpy(buf, &pc_data->buf[buf_pos], MIN(buf_len, len)); 255 + logf("(%d) in buffer: %s", line_num, buf); 256 + found = true; 257 + break; 258 + } 259 + else 260 + { 261 + buf_pos += len + 1; /* need to go past the NULL terminator */ 262 + line_num++; 263 + 264 + if (buf_line_num + slush_pos < selected_item) 265 + { 266 + logf("[%s] Set pos buffer %d", __func__, line_num); 267 + buf_line_num = line_num; 268 + buf_last_pos = buf_pos; 269 + } 270 + } 271 + } 272 + } 273 + 274 + /* didn't find the item try the file */ 275 + if(!found && pc_data->fd_cur >= 0) 276 + { 277 + int fd = pc_data->fd_cur; 278 + 279 + if (file_line_num < 0 || file_line_num > selected_item) 280 + { 281 + logf("[%s] Set seek file 0", __func__); 282 + file_line_num = 0; 283 + file_last_seek = 0; 284 + } 285 + 286 + rb->lseek(fd, file_last_seek, SEEK_SET); 287 + line_num = file_line_num; 288 + 289 + while ((rb->read_line(fd, buf, buf_len)) > 0) 290 + { 291 + if(buf[0] == '#') 292 + continue; 293 + if(line_num == selected_item) 294 + { 295 + logf("(%d) in file: %s", line_num, buf); 296 + found = true; 297 + break; 298 + } 299 + else 300 + { 301 + line_num++; 302 + 303 + if (file_line_num + slush_pos < selected_item) 304 + { 305 + logf("[%s] Set seek file %d", __func__, line_num); 306 + file_line_num = line_num; 307 + file_last_seek = rb->lseek(fd, 0, SEEK_CUR); 308 + } 309 + } 310 + } 311 + } 312 + 313 + if(!found) 314 + { 315 + logf("(%d) Not Found!", selected_item); 316 + buf_line_num = -1; 317 + file_line_num = -1; 318 + buf[0] = '\0'; 319 + } 320 + return buf; 321 + } 322 + 323 + static int list_voice_cb(int list_index, void* data) 324 + { 325 + (void) list_index; 326 + struct printcell_data_t *pc_data = (struct printcell_data_t*) data; 327 + if (!gConfig.talk) 328 + return -1; 329 + 330 + int selcol = printcell_get_column_selected(); 331 + char buf[MAX_PATH]; 332 + char* name; 333 + long id; 334 + 335 + if (pc_data->view_lastcol != selcol) 336 + { 337 + name = printcell_get_title_text(selcol, buf, sizeof(buf)); 338 + 339 + id = P2ID((const unsigned char *)name); 340 + 341 + if(id>=0) 342 + rb->talk_id(id, true); 343 + else if (selcol >= 0) 344 + { 345 + switch (selcol) 346 + { 347 + case 0: 348 + rb->talk_id(LANG_ID3_ARTIST, true); 349 + break; 350 + case 1: 351 + rb->talk_id(LANG_ID3_ALBUM, true); 352 + break; 353 + case 2: 354 + rb->talk_id(LANG_ID3_TITLE, true); 355 + break; 356 + case 3: 357 + rb->talk_id(LANG_ID3_TRACKNUM, true); 358 + break; 359 + case 4: 360 + rb->talk_id(LANG_ID3_LENGTH, true); 361 + break; 362 + 363 + default: 364 + rb->talk_spell(name, true); 365 + break; 366 + } 367 + } 368 + else 369 + rb->talk_id(LANG_ALL, true); 370 + 371 + rb->talk_id(VOICE_PAUSE, true); 372 + } 373 + 374 + name = printcell_get_column_text(selcol, buf, sizeof(buf)); 375 + 376 + id = P2ID((const unsigned char *)name); 377 + 378 + if(id>=0) 379 + rb->talk_id(id, true); 380 + else if (selcol >= 0) 381 + rb->talk_spell(name, true); 382 + 383 + return 0; 384 + } 385 + 386 + static enum themable_icons list_icon_cb(int selected_item, void *data) 387 + { 388 + struct printcell_data_t *pc_data = (struct printcell_data_t*) data; 389 + if (lists.selected_item == selected_item) 390 + { 391 + if (pc_data->view_lastcol < 0) 392 + return Icon_Config; 393 + return Icon_Audio; 394 + } 395 + return Icon_NOICON; 396 + } 397 + 398 + /* load file entries into pc_data buffer, file should already be opened 399 + * and will be closed if the whole file was buffered */ 400 + static int file_load_entries(struct printcell_data_t *pc_data) 401 + { 402 + int items = 0; 403 + int count = 0; 404 + int buffered = 0; 405 + unsigned int pos = 0; 406 + bool comment = false; 407 + char ch; 408 + int fd = pc_data->fd_cur; 409 + if (fd < 0) 410 + return 0; 411 + 412 + rb->lseek(fd, 0, SEEK_SET); 413 + 414 + while (rb->read(fd, &ch, 1) > 0) 415 + { 416 + if (count++ == 0 && ch == '#') /* skip comments */ 417 + comment = true; 418 + else if (!comment && ch != '\r' && pc_data->buf_size > pos) 419 + pc_data->buf[pos++] = ch; 420 + 421 + if (items == 0 && pos > PRINTCELL_MAXLINELEN * 2) 422 + break; 423 + 424 + if (ch == '\n') 425 + { 426 + if (!comment) 427 + { 428 + pc_data->buf[pos] = '\0'; 429 + if (pc_data->buf_size > pos) 430 + { 431 + pos++; 432 + buffered++; 433 + } 434 + items++; 435 + } 436 + comment = false; 437 + count = 0; 438 + rb->yield(); 439 + } 440 + } 441 + 442 + logf("[%s] items: %d buffered: %d", __func__, items, buffered); 443 + 444 + pc_data->items_total = items; 445 + pc_data->items_buffered = buffered; 446 + pc_data->buf[pos] = '\0'; 447 + pc_data->buf_used = pos; 448 + 449 + if (items == buffered) /* whole file fit into buffer; close file */ 450 + { 451 + rb->close(pc_data->fd_cur); 452 + pc_data->fd_cur = -1; 453 + } 454 + 455 + list_get_name_cb(-1, NULL, NULL, 0); /* prime name cb */ 456 + return items; 457 + } 458 + 459 + static int filter_items(struct printcell_data_t *pc_data, 460 + enum e_find_type find_type, int col) 461 + { 462 + /* saves filtered items to a temp file and loads it */ 463 + int fd; 464 + bool reload = false; 465 + char buf[PRINTCELL_MAXLINELEN]; 466 + char find_exclude_buf[PRINTCELL_MAXLINELEN]; 467 + int selcol = printcell_get_column_selected(); 468 + char *find_exclude = printcell_get_column_text(selcol, find_exclude_buf, 469 + sizeof(find_exclude_buf)); 470 + const char colsep = '\t'; 471 + int find_len = strlen(find_exclude); 472 + 473 + if (find_type == FIND_CUSTOM || find_len == 0) 474 + { 475 + int option = 0; 476 + struct opt_items find_types[] = { 477 + {"Exclude", -1}, {"Exclude Case Sensitive", -1}, 478 + {"Exclude Any", -1}, {"Include", -1}, 479 + {"Include Case Sensitive", -1}, {"Include Any", -1} 480 + }; 481 + if (rb->set_option("Find Type", &option, INT, 482 + find_types, 6, NULL)) 483 + { 484 + return 0; 485 + } 486 + switch (option) 487 + { 488 + case 0: 489 + find_type = FIND_EXCLUDE; 490 + break; 491 + case 1: 492 + find_type = FIND_EXCLUDE_CASE; 493 + break; 494 + case 2: 495 + find_type = FIND_EXCLUDE_ANY; 496 + break; 497 + case 3: 498 + find_type = FIND_INCLUDE; 499 + break; 500 + case 4: 501 + find_type = FIND_INCLUDE_CASE; 502 + break; 503 + case 5: 504 + find_type = FIND_INCLUDE_ANY; 505 + break; 506 + default: 507 + find_type = FIND_ALL; 508 + break; 509 + } 510 + 511 + /* copy data to beginning of buf */ 512 + rb->memmove(find_exclude_buf, find_exclude, find_len); 513 + find_exclude_buf[find_len] = '\0'; 514 + 515 + if (rb->kbd_input(find_exclude_buf, sizeof(find_exclude_buf), NULL) < 0) 516 + return -1; 517 + find_exclude = find_exclude_buf; 518 + find_len = strlen(find_exclude); 519 + } 520 + 521 + char tmp_filename[MAX_PATH]; 522 + static int tmp_num = 0; 523 + rb->snprintf(tmp_filename, sizeof(tmp_filename), TMP_FILE, tmp_num); 524 + tmp_num++; 525 + 526 + fd = rb->open(tmp_filename, O_RDWR | O_CREAT | O_TRUNC, 0666); 527 + if(fd >= 0) 528 + { 529 + rb->splash_progress_set_delay(HZ * 5); 530 + for (int i = 0; i < pc_data->items_total; i++) 531 + { 532 + rb->splash_progress(i, pc_data->items_total, "Filtering..."); 533 + const char * data = list_get_name_cb(i, pc_data, buf, sizeof(buf)); 534 + 535 + if (find_type != FIND_ALL) 536 + { 537 + int index = col; 538 + 539 + if (index < 0) 540 + index = 0; 541 + if (find_len > 0) 542 + { 543 + char *bcol = buf; 544 + while (*bcol != '\0' && index > 0) 545 + { 546 + if (*bcol == colsep) 547 + index--; 548 + bcol++; 549 + } 550 + if (index > 0) 551 + continue; 552 + 553 + if (find_type == FIND_EXCLUDE) 554 + { 555 + if (rb->strncasecmp(bcol, find_exclude, find_len) == 0) 556 + { 557 + logf("[%s] exclude [%s]", find_exclude, bcol); 558 + continue; 559 + } 560 + } 561 + else if (find_type == FIND_INCLUDE) 562 + { 563 + if (rb->strncasecmp(bcol, find_exclude, find_len) != 0) 564 + { 565 + logf("%s include %s", find_exclude, bcol); 566 + continue; 567 + } 568 + } 569 + else if (find_type == FIND_EXCLUDE_CASE) 570 + { 571 + if (rb->strncmp(bcol, find_exclude, find_len) == 0) 572 + { 573 + logf("[%s] exclude case [%s]", find_exclude, bcol); 574 + continue; 575 + } 576 + } 577 + else if (find_type == FIND_INCLUDE_CASE) 578 + { 579 + if (rb->strncmp(bcol, find_exclude, find_len) != 0) 580 + { 581 + logf("%s include case %s", find_exclude, bcol); 582 + continue; 583 + } 584 + } 585 + else if (find_type == FIND_EXCLUDE_ANY) 586 + { 587 + bool found = false; 588 + while (*bcol != '\0' && *bcol != colsep) 589 + { 590 + if (rb->strncasecmp(bcol, find_exclude, find_len) == 0) 591 + { 592 + logf("[%s] exclude any [%s]", find_exclude, bcol); 593 + found = true; 594 + break; 595 + } 596 + bcol++; 597 + } 598 + if (found) 599 + continue; 600 + } 601 + else if (find_type == FIND_INCLUDE_ANY) 602 + { 603 + bool found = false; 604 + while (*bcol != '\0' && *bcol != colsep) 605 + { 606 + if (rb->strncasecmp(bcol, find_exclude, find_len) == 0) 607 + { 608 + found = true; 609 + logf("[%s] include any [%s]", find_exclude, bcol); 610 + break; 611 + } 612 + bcol++; 613 + } 614 + if (!found) 615 + continue; 616 + } 617 + } 618 + } 619 + int len = strlen(data); 620 + if (len > 0) 621 + { 622 + logf("writing [%d bytes][%s]", len + 1, data); 623 + rb->write(fd, data, len); 624 + rb->write(fd, "\n", 1); 625 + } 626 + } 627 + reload = true; 628 + } 629 + 630 + if (reload) 631 + { 632 + if (pc_data->fd_cur >= 0) 633 + rb->close(pc_data->fd_cur); 634 + 635 + pc_data->fd_cur = fd; 636 + int items = file_load_entries(pc_data); 637 + if (items >= 0) 638 + { 639 + lists.selected_item = 0; 640 + rb->gui_synclist_set_nb_items(&lists, items); 641 + } 642 + } 643 + logf("Col text '%s'", find_exclude); 644 + logf("text '%s'", find_exclude_buf); 645 + 646 + return pc_data->items_total; 647 + } 648 + 649 + static int scrobbler_context_menu(struct printcell_data_t *pc_data) 650 + { 651 + struct viewport parentvp[NB_SCREENS]; 652 + FOR_NB_SCREENS(l) 653 + { 654 + rb->viewport_set_defaults(&parentvp[l], l); 655 + rb->viewport_set_fullscreen(&parentvp[l], l); 656 + } 657 + 658 + int col = printcell_get_column_selected(); 659 + int selection = 0; 660 + int items = 0; 661 + uint32_t visible = printcell_get_column_visibility(-1); 662 + bool hide_col = PRINTCELL_COLUMN_IS_VISIBLE(visible, col); 663 + 664 + char namebuf[PRINTCELL_MAXLINELEN]; 665 + enum e_find_type find_type = FIND_ALL; 666 + 667 + char *colname = pc_data->filename; 668 + if (col >= 0) 669 + colname = printcell_get_title_text(col, namebuf, sizeof(namebuf)); 670 + 671 + #define MENUITEM_STRINGLIST_CUSTOM(name, str, callback, ... ) \ 672 + const char *name##_[] = {__VA_ARGS__}; \ 673 + const struct menu_callback_with_desc name##__ = \ 674 + {callback,str, Icon_NOICON}; \ 675 + const struct menu_item_ex name = \ 676 + {MT_RETURN_ID|MENU_HAS_DESC| \ 677 + MENU_ITEM_COUNT(sizeof( name##_)/sizeof(*name##_)), \ 678 + { .strings = name##_},{.callback_and_desc = & name##__}}; 679 + 680 + const char *menu_item[4]; 681 + 682 + menu_item[0]= hide_col ? "Hide":"Show"; 683 + menu_item[1]= "Exclude"; 684 + menu_item[2]= "Include"; 685 + menu_item[3]= "Custom Filter"; 686 + 687 + if (col == -1) 688 + { 689 + menu_item[0]= hide_col ? "Hide All":"Show All"; 690 + menu_item[1]= "Open"; 691 + menu_item[2]= "Reload"; 692 + menu_item[3]= ID2P(LANG_SETTINGS); 693 + } 694 + 695 + MENUITEM_STRINGLIST_CUSTOM(context_menu, colname, NULL, 696 + menu_item[0], 697 + menu_item[1], 698 + menu_item[2], 699 + menu_item[3], 700 + ID2P(VOICE_BLANK), 701 + ID2P(LANG_CANCEL_0), 702 + ID2P(LANG_MENU_QUIT)); 703 + 704 + #undef MENUITEM_STRINGLIST_CUSTOM 705 + do { 706 + selection=rb->do_menu(&context_menu,&selection, parentvp, true); 707 + switch(selection) { 708 + 709 + case 0: 710 + { 711 + printcell_set_column_visible(col, !hide_col); 712 + if (hide_col) 713 + { 714 + do 715 + { 716 + col = printcell_increment_column(1, true); 717 + } while (col >= 0 && printcell_get_column_visibility(col) == 1); 718 + pc_data->view_lastcol = col; 719 + } 720 + gConfig.hidecol_flags = printcell_get_column_visibility(-1); 721 + break; 722 + } 723 + case 1: /* Exclude / Open */ 724 + { 725 + if (col == -1)/*Open*/ 726 + { 727 + char buf[MAX_PATH]; 728 + browse_file(buf, sizeof(buf)); 729 + if (rb->file_exists(buf)) 730 + { 731 + rb->strlcpy(pc_data->filename, buf, MAX_PATH); 732 + if (pc_data->fd_cur >= 0) 733 + rb->close(pc_data->fd_cur); 734 + if (file_open(pc_data->filename, &pc_data->fd_cur) > 0) 735 + items = file_load_entries(pc_data); 736 + if (items >= 0) 737 + synclist_set(0, items, 1, pc_data); 738 + } 739 + else 740 + rb->splash(HZ *2, "Error Opening"); 741 + 742 + return CANCEL_CONTEXT_MENU; 743 + } 744 + 745 + find_type = FIND_EXCLUDE; 746 + } 747 + /* fall-through */ 748 + case 2: /* Include / Reload */ 749 + { 750 + if (col == -1) /*Reload*/ 751 + { 752 + if (pc_data->fd_cur >= 0) 753 + rb->close(pc_data->fd_cur); 754 + if (file_open(pc_data->filename, &pc_data->fd_cur) > 0) 755 + items = file_load_entries(pc_data); 756 + if (items >= 0) 757 + rb->gui_synclist_set_nb_items(&lists, items); 758 + return CANCEL_CONTEXT_MENU; 759 + } 760 + 761 + if (find_type == FIND_ALL) 762 + find_type = FIND_INCLUDE; 763 + /* fall-through */ 764 + } 765 + case 3: /*Custom Filter / Settings */ 766 + { 767 + if (col == -1)/*Settings*/ 768 + return config_settings_menu(pc_data); 769 + 770 + if (find_type == FIND_ALL) 771 + find_type = FIND_CUSTOM; 772 + items = filter_items(pc_data, find_type, col); 773 + 774 + if (items >= 0) 775 + rb->gui_synclist_set_nb_items(&lists, items); 776 + break; 777 + } 778 + case 4: /*sep*/ 779 + continue; 780 + case 5: 781 + return CANCEL_CONTEXT_MENU; 782 + break; 783 + case 6: /* Quit */ 784 + return QUIT_CONTEXT_MENU; 785 + break; 786 + case MENU_ATTACHED_USB: 787 + return PLUGIN_USB_CONNECTED; 788 + default: 789 + return PLUGIN_OK; 790 + } 791 + } while ( selection < 0 ); 792 + return 0; 793 + } 794 + 795 + static void cleanup(void *parameter) 796 + { 797 + struct printcell_data_t *pc_data = (struct printcell_data_t*) parameter; 798 + if (pc_data->fd_cur >= 0) 799 + rb->close(pc_data->fd_cur); 800 + pc_data->fd_cur = -1; 801 + } 802 + 803 + static void menu_action_printcell(int *action, int selected_item, bool* exit, struct gui_synclist *lists) 804 + { 805 + (void) exit; 806 + struct printcell_data_t *pc_data = (struct printcell_data_t*) lists->data; 807 + if (*action == ACTION_STD_OK) 808 + { 809 + if (selected_item < lists->nb_items) 810 + { 811 + pc_data->view_lastcol = printcell_increment_column(1, true); 812 + *action = ACTION_NONE; 813 + } 814 + } 815 + else if (*action == ACTION_STD_CANCEL) 816 + { 817 + pc_data->view_lastcol = printcell_increment_column(-1, true); 818 + if (pc_data->view_lastcol != pc_data->view_columns - 1) 819 + { 820 + *action = ACTION_NONE; 821 + } 822 + } 823 + else if (*action == ACTION_STD_CONTEXT) 824 + { 825 + int ctxret = scrobbler_context_menu(pc_data); 826 + if (ctxret == QUIT_CONTEXT_MENU) 827 + *exit = true; 828 + } 829 + } 830 + 831 + int menu_action_cb(int *action, int selected_item, bool* exit, struct gui_synclist *lists) 832 + { 833 + 834 + menu_action_printcell(action, selected_item, exit, lists); 835 + 836 + if (rb->default_event_handler_ex(*action, cleanup, lists->data) == SYS_USB_CONNECTED) 837 + { 838 + *exit = true; 839 + return PLUGIN_USB_CONNECTED; 840 + } 841 + return PLUGIN_OK; 842 + } 843 + 844 + static int count_max_columns(int items, char delimeter, 845 + int expected_cols, struct printcell_data_t *pc_data) 846 + { 847 + int max_cols = 0; 848 + int cols = 0; 849 + char buf[PRINTCELL_MAXLINELEN]; 850 + for (int i = 0; i < items; i++) 851 + { 852 + const char *txt = list_get_name_cb(i, pc_data, buf, sizeof(buf)); 853 + while (*txt != '\0') 854 + { 855 + if (*txt == delimeter) 856 + { 857 + cols++; 858 + if (cols == expected_cols) 859 + { 860 + max_cols = cols; 861 + break; 862 + } 863 + } 864 + txt++; 865 + } 866 + 867 + if(max_cols < expected_cols && i > 32) 868 + break; 869 + 870 + if (cols > max_cols) 871 + max_cols = cols; 872 + cols = 0; 873 + } 874 + return max_cols; 875 + } 876 + 877 + static void synclist_set(int selected_item, int items, int sel_size, struct printcell_data_t *pc_data) 878 + { 879 + if (items <= 0) 880 + return; 881 + if (selected_item < 0) 882 + selected_item = 0; 883 + 884 + rb->gui_synclist_init(&lists,list_get_name_cb, 885 + pc_data, false, sel_size, NULL); 886 + 887 + rb->gui_synclist_set_icon_callback(&lists, list_icon_cb); 888 + rb->gui_synclist_set_voice_callback(&lists, list_voice_cb); 889 + rb->gui_synclist_set_nb_items(&lists,items); 890 + rb->gui_synclist_select_item(&lists, selected_item); 891 + 892 + struct printcell_settings pcs = {.cell_separator = gConfig.separator, 893 + .title_delimeter = '$', 894 + .text_delimeter = '\t', 895 + .hidecol_flags = gConfig.hidecol_flags}; 896 + 897 + pc_data->view_columns = printcell_set_columns(&lists, &pcs, 898 + pc_data->header, Icon_Rockbox); 899 + printcell_enable(true); 900 + 901 + 902 + int max_cols = count_max_columns(items, pcs.text_delimeter, 903 + SCROBBLER_MIN_COLUMNS, pc_data); 904 + if (max_cols < SCROBBLER_MIN_COLUMNS) /* not a scrobbler file? */ 905 + { 906 + rb->gui_synclist_set_voice_callback(&lists, NULL); 907 + pc_data->view_columns = printcell_set_columns(&lists, NULL, 908 + "$*512$", Icon_Questionmark); 909 + } 910 + 911 + int curcol = printcell_get_column_selected(); 912 + while (curcol >= 0) 913 + curcol = printcell_increment_column(-1, false); 914 + 915 + if (pc_data->view_lastcol >= pc_data->view_columns) 916 + pc_data->view_lastcol = -1; 917 + 918 + /* restore column position */ 919 + while (pc_data->view_lastcol > -1 && curcol != pc_data->view_lastcol) 920 + { 921 + curcol = printcell_increment_column(1, true); 922 + } 923 + pc_data->view_lastcol = curcol; 924 + list_voice_cb(0, pc_data); 925 + } 926 + 927 + static void pc_data_set_header(struct printcell_data_t *pc_data) 928 + { 929 + int col_w = gConfig.col_width; 930 + rb->snprintf(pc_data->header, PRINTCELL_MAXLINELEN, 931 + SCROBBLE_HDR_FMT, col_w, rb->str(LANG_ID3_ARTIST), 932 + col_w, rb->str(LANG_ID3_ALBUM), 933 + col_w, rb->str(LANG_ID3_TITLE)); 934 + } 935 + 936 + enum plugin_status plugin_start(const void* parameter) 937 + { 938 + int ret = PLUGIN_OK; 939 + int selected_item = -1; 940 + int action; 941 + int items = 0; 942 + #if CONFIG_RTC 943 + static char filename[MAX_PATH] = HOME_DIR "/.scrobbler.log"; 944 + #else /* !CONFIG_RTC */ 945 + static char filename[MAX_PATH] = HOME_DIR "/.scrobbler-timeless.log"; 946 + #endif /* CONFIG_RTC */ 947 + bool redraw = true; 948 + bool exit = false; 949 + 950 + static struct printcell_data_t printcell_data; 951 + 952 + if (parameter) 953 + { 954 + rb->strlcpy(filename, (const char*)parameter, MAX_PATH); 955 + filename[MAX_PATH - 1] = '\0'; 956 + } 957 + 958 + if (!rb->file_exists(filename)) 959 + { 960 + browse_file(filename, sizeof(filename)); 961 + if (!rb->file_exists(filename)) 962 + { 963 + rb->splash(HZ, "No Scrobbler file Goodbye."); 964 + return PLUGIN_ERROR; 965 + } 966 + 967 + } 968 + 969 + config_set_defaults(); 970 + if (configfile_load(CFG_FILE, config, gCfg_sz, CFG_VER) < 0) 971 + { 972 + /* If the loading failed, save a new config file */ 973 + config_set_defaults(); 974 + configfile_save(CFG_FILE, config, gCfg_sz, CFG_VER); 975 + 976 + rb->splash(HZ, ID2P(LANG_REVERT_TO_DEFAULT_SETTINGS)); 977 + } 978 + 979 + rb->memset(&printcell_data, 0, sizeof (struct printcell_data_t)); 980 + printcell_data.fd_cur = -1; 981 + printcell_data.view_lastcol = -1; 982 + 983 + if (rb->file_exists(filename)) 984 + { 985 + if (file_open(filename, &printcell_data.fd_cur) == 0) 986 + printcell_data.fd_cur = -1; 987 + else 988 + { 989 + size_t buf_size; 990 + printcell_data.buf = rb->plugin_get_buffer(&buf_size); 991 + printcell_data.buf_size = buf_size; 992 + printcell_data.buf_used = 0; 993 + printcell_data.filename = filename; 994 + items = file_load_entries(&printcell_data); 995 + } 996 + } 997 + int col_w = gConfig.col_width; 998 + rb->snprintf(printcell_data.header, PRINTCELL_MAXLINELEN, 999 + SCROBBLE_HDR_FMT, col_w, rb->str(LANG_ID3_ARTIST), 1000 + col_w, rb->str(LANG_ID3_ALBUM), 1001 + col_w, rb->str(LANG_ID3_TITLE)); 1002 + 1003 + if (!exit && items > 0) 1004 + { 1005 + synclist_set(0, items, 1, &printcell_data); 1006 + rb->gui_synclist_draw(&lists); 1007 + 1008 + while (!exit) 1009 + { 1010 + action = rb->get_action(CONTEXT_LIST, HZ / 10); 1011 + 1012 + if (action == ACTION_NONE) 1013 + { 1014 + if (redraw) 1015 + { 1016 + action = ACTION_REDRAW; 1017 + redraw = false; 1018 + } 1019 + } 1020 + else 1021 + redraw = true; 1022 + 1023 + ret = menu_action_cb(&action, selected_item, &exit, &lists); 1024 + if (rb->gui_synclist_do_button(&lists, &action)) 1025 + continue; 1026 + selected_item = rb->gui_synclist_get_sel_pos(&lists); 1027 + 1028 + } 1029 + } 1030 + config_save(); 1031 + cleanup(&printcell_data); 1032 + return ret; 1033 + }
+222 -75
apps/plugins/lib/printcell_helper.c
··· 22 22 #include "plugin.h" 23 23 #include "lib/printcell_helper.h" 24 24 25 - #ifndef PRINTCELL_MAX_COLUMNS 26 - #define PRINTCELL_MAX_COLUMNS 16 27 - #endif 28 - 29 25 #define COLUMN_ENDLEN 3 30 26 #define TITLE_FLAG 0xFF 31 27 #define SELECTED_FLAG 0x1 ··· 37 33 #endif 38 34 39 35 struct printcell_info_t { 40 - int offw[NB_SCREENS]; 41 - int iconw[NB_SCREENS]; 42 - int selcol_offw[NB_SCREENS]; 43 - int totalcolw[NB_SCREENS]; 44 - int firstcolxw[NB_SCREENS]; 45 - uint16_t colw[NB_SCREENS][PRINTCELL_MAX_COLUMNS]; 46 - int ncols; 47 - int selcol; 48 - int selcol_index; 49 - char title[PRINTCELL_MAXLINELEN]; 50 - bool separator; 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 51 }; 52 + 52 53 static struct printcell_info_t printcell; 53 54 54 - static void parse_dsptext(int ncols, const char *dsp_text, char* buffer, uint16_t *sidx) 55 + static void parse_dsptext(char splitchr, int ncols, const char *dsp_text, 56 + char* buffer, size_t bufsz, uint16_t *sidx) 55 57 { 56 58 /*Internal function loads sidx with split offsets indexing 57 - the buffer of null terminated strings, splits on '$' 59 + the buffer of null terminated strings, splits on 'splitchr' 58 60 _assumptions_: 59 61 dsp_text[len - 1] = \0, 60 - buffer[PRINTCELL_MAXLINELEN], 61 62 sidx[PRINTCELL_MAX_COLUMNS] 62 63 */ 63 64 int i = 0; 64 - int j = 0; 65 - int ch = '$'; /* first column $ is optional */ 66 - if (*dsp_text == '$') 65 + size_t j = 0; 66 + int ch = splitchr; /* first column $ is optional */ 67 + if (*dsp_text == splitchr) 67 68 dsp_text++; 68 69 /* add null to the start of the text buffer */ 69 70 buffer[j++] = '\0'; 70 71 do 71 72 { 72 - if (ch == '$') 73 + if (ch == splitchr) 73 74 { 74 - sidx[i] = j; 75 - if (*dsp_text == '$') /* $$ escaped user must want to display $*/ 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 + { 76 78 buffer[j++] = *dsp_text++; 77 - while (*dsp_text != '\0' && *dsp_text != '$' && j < PRINTCELL_MAXLINELEN - 1) 78 - buffer[j++] = *dsp_text++; 79 + } 79 80 buffer[j++] = '\0'; 81 + 80 82 i++; 81 - if (i >= ncols || j >= (PRINTCELL_MAXLINELEN - 1)) 83 + if (i >= ncols || j >= (bufsz - 1)) 82 84 break; 83 85 } 84 86 ch = *dsp_text++; ··· 146 148 static inline int printcells(struct screen *display, char* buffer, 147 149 uint16_t *sidx, struct line_desc *linedes, 148 150 struct viewport *vp, int vp_w, int separator, 149 - int x, int y, int offw, int selected_flag, bool scroll) 151 + int x, int y, int offw, int selected_flag, int last_col, 152 + bool scroll, bool is_title) 150 153 { 151 154 /* Internal function prints remaining cells */ 152 155 int text_offset = offw + offw; 153 - int ncols = printcell.ncols; 154 156 int screen = display->screen_type; 155 157 int height = linedes->height; 156 158 int selsep = (selected_flag == 0) ? 0: separator; 157 159 uint16_t *screencolwidth = printcell.colw[screen]; 158 160 159 - for(int i = 1; i < ncols; i++) 161 + for(int i = 1; i <= last_col; i++) 160 162 { 161 163 int ny = y; 162 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 + 163 180 int nx = x + nw; 164 181 char *buftext; 165 - if (nx > 0 && x < vp_w) 182 + 183 + if (nx > 0 && nw > offw && x < vp_w) 166 184 { 167 185 set_cell_width(vp, vp_w, nx); 168 186 ··· 173 191 } 174 192 else 175 193 { 194 + if (vp_w < x + text_offset) 195 + { 196 + scroll = false; 197 + } 176 198 linedes->scroll = scroll; 177 199 linedes->separator_height = separator; 178 200 } 179 201 buftext = &buffer[sidx[i]]; 180 - display->put_line(x + offw, ny, linedes, "$t", buftext); 202 + display->put_line(x + offw + offx, ny, linedes, "$t", buftext); 181 203 vp->width += COLUMN_ENDLEN + 1; 182 204 draw_selector(display, linedes, selected_flag, i, separator, x, ny, nw, height); 183 205 } ··· 200 222 for (int i = printcell.selcol - 1; i >= 0; i--) 201 223 { 202 224 int cw = screencolwidth[i] + text_offset; 225 + 226 + if (!PRINTCELL_COLUMN_IS_VISIBLE(printcell.hidecol_flags, i)) 227 + cw = 0; 228 + 203 229 if (i == 0) 204 230 cw += screenicnwidth; 205 231 if (offset > 0 || cw > maxw) ··· 257 283 separator = 1; 258 284 259 285 int nx = x; 286 + int last_col = printcell.ncols - 1; 287 + int hidden_w = 0; 260 288 int nw, colxw; 289 + char *buftext; 290 + printcell_buffer[0] = '\0'; 261 291 262 - printcell_buffer[0] = '\0'; 263 - parse_dsptext(printcell.ncols, dsp_text, printcell_buffer, sidx); 264 - char *buftext = &printcell_buffer[sidx[0]]; 265 292 uint16_t *screencolwidth = printcell.colw[screen]; 293 + if (printcell.hidecol_flags > 0) 294 + { 295 + for (int i = 0; i < printcell.ncols; i++) 296 + { 297 + if (PRINTCELL_COLUMN_IS_VISIBLE(printcell.hidecol_flags, i)) 298 + last_col = i; 299 + else 300 + hidden_w += (screencolwidth[i] + text_offset); 301 + } 302 + } 266 303 267 304 if (is_title) 268 305 { 306 + parse_dsptext(printcell.titlesep, printcell.ncols, dsp_text, 307 + printcell_buffer, sizeof(printcell_buffer), sidx); 308 + 309 + buftext = &printcell_buffer[sidx[0]]; /* set to first column text */ 269 310 int sbwidth = 0; 270 311 if (rb->global_settings->scrollbar == SCROLLBAR_LEFT) 271 312 sbwidth = rb->global_settings->scrollbar_width; ··· 278 319 nx -= printcell.selcol_offw[screen]; 279 320 280 321 nw = screencolwidth[0] + printcell.iconw[screen] + text_offset; 322 + 323 + if (!PRINTCELL_COLUMN_IS_VISIBLE(printcell.hidecol_flags, 0)) 324 + nw = printcell.iconw[screen] - 1; 281 325 nw += sbwidth; 282 326 283 327 colxw = nx + nw; ··· 301 345 } 302 346 else 303 347 { 348 + parse_dsptext(printcell.colsep, printcell.ncols, dsp_text, 349 + printcell_buffer, sizeof(printcell_buffer), sidx); 350 + 351 + buftext = &printcell_buffer[sidx[0]]; /* set to first column text */ 304 352 int cursor = Icon_NOICON; 305 353 nx -= printcell.selcol_offw[screen]; 306 354 307 355 if (selected_flag & SELECTED_FLAG) 308 356 { 309 - if (printcell.selcol >= 0) 310 - printcell.selcol_index = sidx[printcell.selcol]; /* save the item offset*/ 311 - else 312 - printcell.selcol_index = -1; 313 - 314 357 cursor = Icon_Cursor; 315 358 /* limit length of selection if columns don't reach end */ 316 359 int maxw = nx + printcell.totalcolw[screen] + printcell.iconw[screen]; 317 360 maxw += text_offset * printcell.ncols; 361 + maxw -= hidden_w; 362 + 318 363 if (vp_w > maxw) 319 364 vp->width = maxw; 320 365 /* display a blank line first to draw selector across all cells */ ··· 364 409 nx += nw; 365 410 /* display remaining cells */ 366 411 printcells(display, printcell_buffer, sidx, linedes, vp, vp_w, separator, 367 - nx, y, col_offset_width, selected_flag, scroll_items); 412 + nx, y, col_offset_width, selected_flag, last_col, scroll_items, is_title); 368 413 369 414 /* draw a line at the bottom of the list */ 370 415 if (separator > 0 && line == list->nb_items - 1) ··· 377 422 vp->width = vp_w; 378 423 } 379 424 380 - void printcell_enable(struct gui_synclist *gui_list, bool enable, bool separator) 425 + void printcell_enable(bool enable) 381 426 { 382 - printcell.separator = separator; 427 + if (!printcell.gui_list) 428 + return; 429 + struct gui_synclist *gui_list = printcell.gui_list; 383 430 #ifdef HAVE_LCD_COLOR 384 431 static int list_sep_color = INT_MIN; 385 432 if (enable) 386 433 { 387 - list_sep_color = rb->global_settings->list_separator_color; 434 + if (list_sep_color == INT_MIN) 435 + list_sep_color = rb->global_settings->list_separator_color; 388 436 rb->global_settings->list_separator_color = rb->global_settings->fg_color; 389 437 gui_list->callback_draw_item = printcell_listdraw_fn; 390 438 } ··· 404 452 405 453 } 406 454 407 - int printcell_increment_column(struct gui_synclist *gui_list, int increment, bool wrap) 455 + int printcell_increment_column(int increment, bool wrap) 408 456 { 457 + if (!printcell.gui_list) 458 + return -1; 459 + struct gui_synclist *gui_list = printcell.gui_list; 409 460 int item = printcell.selcol + increment; 410 461 int imin = -1; 411 462 int imax = printcell.ncols - 1; ··· 425 476 FOR_NB_SCREENS(n) /* offset needs recalculated */ 426 477 printcell.selcol_offw[n] = 0; 427 478 printcell.selcol = item; 428 - printcell.selcol_index = 0; 479 + 429 480 rb->gui_synclist_draw(gui_list); 481 + rb->gui_synclist_speak_item(gui_list); 430 482 } 431 483 return item; 432 484 } 433 485 486 + int printcell_get_column_selected(void) 487 + { 488 + if (!printcell.gui_list) 489 + return -1; 490 + return printcell.selcol; 491 + } 492 + 493 + uint32_t printcell_get_column_visibility(int col) 494 + { 495 + if (col >= 0) 496 + return (PRINTCELL_COLUMN_IS_VISIBLE(printcell.hidecol_flags, col) ? 0:1); 497 + else /* return flag of all columns */ 498 + return printcell.hidecol_flags; 499 + } 500 + 501 + void printcell_set_column_visible(int col, bool visible) 502 + { 503 + /* visible columns have 0 for the column bit hidden columns have the bit set */ 504 + if (col >= 0) 505 + { 506 + if (visible) 507 + printcell.hidecol_flags &= ~(PRINTCELL_COLUMN_FLAG(col)); 508 + else 509 + printcell.hidecol_flags |= PRINTCELL_COLUMN_FLAG(col); 510 + } 511 + else 512 + { 513 + if (visible) /* set to everything visible */ 514 + printcell.hidecol_flags = 0; 515 + else /* set to everything hidden */ 516 + printcell.hidecol_flags = ((uint32_t)-1); 517 + } 518 + } 519 + 434 520 int printcell_set_columns(struct gui_synclist *gui_list, 435 - char * title, enum themable_icons icon) 521 + struct printcell_settings * pcs, 522 + char * title, enum themable_icons icon) 436 523 { 524 + 437 525 if (title == NULL) 438 526 title = "$PRINTCELL NOT SETUP"; 527 + 528 + if (pcs == NULL) /* DEFAULTS */ 529 + { 530 + #if LCD_DEPTH > 1 531 + /* If line sep is set to automatic then outline cells */ 532 + bool sep = (rb->global_settings->list_separator_height < 0); 533 + #else 534 + bool sep = (rb->global_settings->cursor_style == 0); 535 + #endif 536 + pcs = &(struct printcell_settings){ .cell_separator = sep, 537 + .title_delimeter = '$', 538 + .text_delimeter = '$', 539 + .hidecol_flags = 0}; 540 + } 541 + 439 542 uint16_t sidx[PRINTCELL_MAX_COLUMNS]; /* starting position of column in title string */ 440 543 int width, height, user_minwidth; 441 544 int i = 0; 442 - int j = 0; 443 - int ch = '$'; /* first column $ is optional */ 545 + size_t j = 0; 546 + rb->memset(&printcell, 0, sizeof(struct printcell_info_t)); 547 + 548 + printcell.gui_list = gui_list; 549 + printcell.separator = pcs->cell_separator; 550 + printcell.titlesep = pcs->title_delimeter; 551 + printcell.colsep = pcs->text_delimeter; 552 + printcell.hidecol_flags = pcs->hidecol_flags; 444 553 445 - rb->memset(&printcell, 0, sizeof(struct printcell_info_t)); 554 + int ch = printcell.titlesep; /* first column $ is optional */ 446 555 447 556 FOR_NB_SCREENS(n) 448 557 { ··· 450 559 printcell.offw[n] = width; /* set column text offset */ 451 560 } 452 561 453 - if (*title == '$') 562 + if (*title == printcell.titlesep) 454 563 title++; 455 564 do 456 565 { 457 - if (ch == '$') 566 + if (ch == printcell.titlesep) 458 567 { 459 568 printcell.title[j++] = ch; 460 569 user_minwidth = 0; ··· 466 575 user_minwidth = 10*user_minwidth + *title - '0'; 467 576 title++; 468 577 } 469 - if (*title != '$') /* user forgot $ or wants to display '*' */ 578 + if (*title != printcell.titlesep) /* user forgot titlesep or wants to display '*' */ 470 579 { 471 580 title = dspst; 472 581 user_minwidth = 0; ··· 476 585 } 477 586 478 587 sidx[i] = j; 479 - if (*title == '$') /* $$ escaped user must want to display $*/ 480 - printcell.title[j++] = *title++; 481 588 482 - while (*title != '\0' && *title != '$' && j < PRINTCELL_MAXLINELEN - 1) 589 + while (*title != '\0' 590 + && *title != printcell.titlesep 591 + && j < PRINTCELL_MAXLINELEN - 1) 592 + { 483 593 printcell.title[j++] = *title++; 594 + } 484 595 485 596 FOR_NB_SCREENS(n) 486 597 { 487 - rb->screens[n]->getstringsize(&printcell.title[sidx[i]], &width, &height); 598 + rb->screens[n]->getstringsize(&printcell.title[sidx[i]], 599 + &width, &height); 600 + 488 601 if (width < user_minwidth) 489 602 width = user_minwidth; 603 + 604 + if (width > LCD_WIDTH) 605 + width = LCD_WIDTH; 606 + 490 607 printcell.colw[n][i] = width; 491 608 printcell.totalcolw[n] += width; 492 609 } ··· 498 615 printcell.ncols = i; 499 616 printcell.title[j] = '\0'; 500 617 printcell.selcol = -1; 501 - printcell.selcol_index = 0; 502 618 503 619 rb->gui_synclist_set_title(gui_list, printcell.title, icon); 504 620 return printcell.ncols; 505 621 } 506 622 507 - char *printcell_get_selected_column_text(struct gui_synclist *gui_list, char *buf, size_t bufsz) 623 + char *printcell_get_title_text(int selcol, char *buf, size_t bufsz) 508 624 { 509 - int selected = gui_list->selected_item; 510 - int index = printcell.selcol_index - 1; 625 + /* note offsets are calculated everytime this function is called 626 + * shouldn't be used in hot code paths */ 627 + int index = 0; 628 + buf[0] = '\0'; 629 + if (selcol < 0) /* return entire string incld col formatting '$'*/ 630 + return printcell.title; 631 + 632 + if (selcol < printcell.ncols) 633 + { 634 + uint16_t sidx[PRINTCELL_MAX_COLUMNS]; /*indexes zero terminated strings in buffer*/ 635 + parse_dsptext(printcell.titlesep, selcol + 1, printcell.title, buf, bufsz, sidx); 636 + index = sidx[selcol]; 637 + } 638 + return &buf[index]; 639 + } 511 640 512 - if (index < 0) 513 - index = 0; 641 + char *printcell_get_column_text(int selcol, char *buf, size_t bufsz) 642 + { 643 + int index = 0; 514 644 char *bpos; 645 + struct gui_synclist *gui_list = printcell.gui_list; 515 646 516 - if (gui_list->callback_get_item_name(selected, gui_list->data, buf, bufsz) == buf) 647 + if (gui_list && gui_list->callback_draw_item == printcell_listdraw_fn) 517 648 { 518 - bpos = &buf[index]; 519 - if (printcell.selcol < 0) /* return entire string incld col formatting '$'*/ 520 - return bpos; 521 - while(bpos < &buf[bufsz - 1]) 649 + int col = selcol; 650 + int item = gui_list->selected_item; 651 + void *data = gui_list->data; 652 + 653 + if (col < printcell.ncols 654 + && gui_list->callback_get_item_name(item, data, buf, bufsz) == buf) 522 655 { 523 - if (*bpos == '$' || *bpos == '\0') 524 - goto success; 525 - bpos++; 656 + bpos = buf; 657 + if (col < 0) /* return entire string incld col formatting '$'*/ 658 + { 659 + return bpos; 660 + } 661 + bpos++; /* Skip sep/NULL */ 662 + 663 + while(bpos < &buf[bufsz - 1]) 664 + { 665 + if (*bpos == printcell.colsep || *bpos == '\0') 666 + { 667 + if (col-- == 0) 668 + goto success; 669 + index = bpos - buf + 1; /* Skip sep/NULL */ 670 + } 671 + bpos++; 672 + } 526 673 } 527 674 } 528 675 /*failure*/ 529 - bpos = buf; 530 - index = 0; 676 + bpos = buf; 677 + index = 0; 531 678 success: 532 - *bpos = '\0'; 533 - return &buf[index]; 679 + *bpos = '\0'; 680 + return &buf[index]; 534 681 }
+55 -13
apps/plugins/lib/printcell_helper.h
··· 21 21 22 22 #ifndef _PRINTCELL_LIST_H_ 23 23 #define _PRINTCELL_LIST_H_ 24 + #ifndef PRINTCELL_MAX_COLUMNS 25 + #define PRINTCELL_MAX_COLUMNS 16 /* Max 32 (hidecol_flags)*/ 26 + #endif 27 + 24 28 #define PRINTCELL_MAXLINELEN MAX_PATH 29 + #define PC_COL_FLAG(col) ((uint32_t)(col >= 0 \ 30 + && col < PRINTCELL_MAX_COLUMNS) ? 1u<<col : -1u) 25 31 26 - /* sets the printcell function enabled */ 27 - void printcell_enable(struct gui_synclist *gui_list, bool enable, bool separator); 32 + #define PRINTCELL_COLUMN_IS_VISIBLE(flag, col) ((flag & PC_COL_FLAG(col)) == 0) 33 + #define PRINTCELL_COLUMN_FLAG(col) (PC_COL_FLAG(col)) 28 34 29 - /* sets title and calculates cell widths each column is identified by '$' character 30 - ex 3 columns title = "Col1$Col2$Col3" also accepts $*WIDTH$ 31 - ex 3 columns varying width title = "$*64$Col1$*128$Col2$Col3 32 - returns number of columns 35 + struct printcell_settings 36 + { 37 + bool cell_separator; 38 + char title_delimeter; 39 + char text_delimeter; 40 + uint32_t hidecol_flags; 41 + }; 42 + 43 + /* Printcell initialization - Sets title and calculates cell widths 44 + * by default each column is identified by '$' character 45 + * ex 3 columns title = "Col1$Col2$Col3" also accepts $*WIDTH$ 46 + * ex 3 columns varying width title = "$*64$Col1$*128$Col2$Col3 47 + * supplying struct printcell_settings pcs allows changing default settings 48 + * supply NULL to use defaults 49 + * 50 + * Returns number of columns 33 51 */ 34 - int printcell_set_columns(struct gui_synclist *gui_list, 35 - char * title, enum themable_icons icon); 52 + int printcell_set_columns(struct gui_synclist *gui_list, 53 + struct printcell_settings * pcs, 54 + char * title, enum themable_icons icon); 55 + 56 + /* Sets the printcell function enabled (use after initializing with set_column) 57 + * Note you should call printcell_enable(false) if the list might be reused */ 58 + void printcell_enable(bool enable); 36 59 37 - /* increments the current selected column negative increment is allowed 38 - returns the selected column 60 + /* Increments the current selected column negative increment is allowed 61 + returns the selected column 39 62 range: -1(no selection) to ncols - 1 */ 40 - int printcell_increment_column(struct gui_synclist *gui_list, int increment, bool wrap); 63 + int printcell_increment_column(int increment, bool wrap); 41 64 42 - /* return the text of currently selected column buffer should be sized 65 + /* Return index of the currently selected column (-1 to ncols - 1) */ 66 + int printcell_get_column_selected(void); 67 + 68 + /* Return the text of currently selected column buffer should be sized 43 69 * for max item len, buf[PRINTCELL_MAXLINELEN] is a safe bet */ 44 - char *printcell_get_selected_column_text(struct gui_synclist *gui_list, char *buf, size_t bufsz); 70 + char *printcell_get_column_text(int selcol, char *buf, size_t bufsz); 71 + 72 + /* Return the text of currently selected column title should be sized 73 + * for max item len, buf[PRINTCELL_MAXLINELEN] is a safe bet */ 74 + char *printcell_get_title_text(int selcol, char *buf, size_t bufsz); 75 + 76 + 77 + /* Hide or show a specified column - supply col = -1 to affect all columns */ 78 + void printcell_set_column_visible(int col, bool visible); 79 + 80 + /* Return visibility of a specified column 81 + * returns (1 visible or 0 hidden) 82 + * if supply col == -1 a flag with visibility of all columns will be returned 83 + * NOTE: flag denotes a hidden column by a 1 in the column bit (1 << col#) 84 + * PRINTCELL_COLUMN_IS_VISIBLE(flag,col) macro will convert to bool 85 + */ 86 + uint32_t printcell_get_column_visibility(int col); 45 87 #endif /*_PRINTCELL_LIST_H_*/
+21 -14
apps/plugins/rb_info.c
··· 335 335 rb->talk_spell(name, true); 336 336 } 337 337 } 338 + else if (data == MENU_ID(M_TESTPUT)) 339 + { 340 + char buf[64]; 341 + const char* name = printcell_get_column_text(printcell_get_column_selected(), 342 + buf, sizeof(buf)); 343 + long id = P2ID((const unsigned char *)name); 344 + if(id>=0) 345 + rb->talk_id(id, true); 346 + else 347 + rb->talk_spell(name, true); 348 + } 338 349 else 339 350 { 340 351 char buf[64]; ··· 354 365 { 355 366 if (*action == ACTION_STD_OK) 356 367 { 357 - printcell_increment_column(lists, 1, true); 368 + printcell_increment_column(1, true); 358 369 *action = ACTION_NONE; 359 370 } 360 371 else if (*action == ACTION_STD_CANCEL) 361 372 { 362 - if (printcell_increment_column(lists, -1, true) != testput_cols - 1) 373 + if (printcell_increment_column(-1, true) != testput_cols - 1) 363 374 { 364 375 *action = ACTION_NONE; 365 376 } ··· 368 379 { 369 380 char buf[PRINTCELL_MAXLINELEN]; 370 381 char* bufp = buf; 371 - bufp = printcell_get_selected_column_text(lists, bufp, PRINTCELL_MAXLINELEN); 382 + int selcol = printcell_get_column_selected(); 383 + bufp = printcell_get_column_text(selcol, bufp, PRINTCELL_MAXLINELEN); 372 384 rb->splashf(HZ * 2, "Item: %s", bufp); 373 385 } 374 386 } ··· 429 441 if (cur->menuid == MENU_ID(M_TESTPUT)) 430 442 { 431 443 synclist_set(cur->menuid, 0, cur->items, 1); 432 - #if LCD_DEPTH > 1 433 - /* If line sep is set to automatic then outline cells */ 434 - bool showlinesep = (rb->global_settings->list_separator_height < 0); 435 - #else 436 - bool showlinesep = (rb->global_settings->cursor_style == 0); 437 - #endif 438 - printcell_enable(lists, true, showlinesep); 444 + printcell_enable(true); 439 445 //lists->callback_draw_item = test_listdraw_fn; 440 446 } 441 447 else 442 448 { 443 - printcell_enable(lists, false, false); 449 + printcell_enable(false); 444 450 synclist_set(cur->menuid, 1, cur->items, 1); 445 451 } 446 452 rb->gui_synclist_draw(lists); ··· 473 479 if (lists->data == MENU_ID(M_TESTPUT)) 474 480 { 475 481 //lists->callback_draw_item = NULL; 476 - printcell_enable(lists, false, false); 482 + printcell_enable(false); 477 483 } 478 484 if (lists->data != MENU_ID(M_ROOT)) 479 485 { ··· 505 511 menu_id, false, sel_size, NULL); 506 512 if (menu_id == MENU_ID(M_TESTPUT)) 507 513 { 508 - testput_cols = printcell_set_columns(&lists, TESTPUT_HEADER, Icon_Rockbox); 514 + testput_cols = printcell_set_columns(&lists, NULL, 515 + TESTPUT_HEADER, Icon_Rockbox); 509 516 } 510 517 else 511 518 { ··· 562 569 selected_item = rb->gui_synclist_get_sel_pos(&lists); 563 570 } 564 571 } 565 - 572 + printcell_enable(false); 566 573 return ret; 567 574 }
+1
apps/plugins/viewers.config
··· 105 105 lnk,viewers/windows_lnk,- 106 106 #ifdef HAVE_TAGCACHE 107 107 *,demos/pictureflow,- 108 + log,viewers/lastfm_scrobbler_viewer,4 108 109 #endif