From 90118f14cf078358f9ebdee110450b976c9a9e11 Mon Sep 17 00:00:00 2001 From: William Wilgus Date: Fri, 26 Jul 2019 01:30:00 -0500 Subject: lua add demo scripts, atexit handler, gui_scrollbar_draw Change-Id: Ie8794e8a487f73952dae43e036787b6972fdbbee --- apps/plugins/CATEGORIES | 1 + apps/plugins/SOURCES | 1 + apps/plugins/SUBDIRS | 1 + apps/plugins/SUBDIRS.app_build | 1 + apps/plugins/lua/include_lua/printtable.lua | 385 +++++++++++++++++++++++ apps/plugins/lua/loslib.c | 5 +- apps/plugins/lua/lua.make | 2 +- apps/plugins/lua/rocklib.c | 14 +- apps/plugins/lua/rocklib_events.c | 10 +- apps/plugins/lua/rocklib_img.c | 19 +- apps/plugins/lua/rocklua.c | 68 +++- apps/plugins/lua_scripts.lua | 161 ++++++++++ apps/plugins/lua_scripts/dbgettags.lua | 116 +++++++ apps/plugins/lua_scripts/fade2sleep.lua | 107 +++++++ apps/plugins/lua_scripts/filebrowse.lua | 190 ++++++++++++ apps/plugins/lua_scripts/fileview.lua | 79 +++++ apps/plugins/lua_scripts/fileviewers.lua | 465 ++++++++++++++++++++++++++++ apps/plugins/lua_scripts/lua_scripts.make | 24 ++ apps/plugins/lua_scripts/print_lua_func.lua | 304 ++++++++++++++++++ apps/plugins/lua_scripts/printmenu.lua | 83 +++++ apps/plugins/lua_scripts/tagnav.lua | 344 ++++++++++++++++++++ 21 files changed, 2358 insertions(+), 22 deletions(-) create mode 100644 apps/plugins/lua/include_lua/printtable.lua create mode 100644 apps/plugins/lua_scripts.lua create mode 100644 apps/plugins/lua_scripts/dbgettags.lua create mode 100644 apps/plugins/lua_scripts/fade2sleep.lua create mode 100755 apps/plugins/lua_scripts/filebrowse.lua create mode 100755 apps/plugins/lua_scripts/fileview.lua create mode 100755 apps/plugins/lua_scripts/fileviewers.lua create mode 100644 apps/plugins/lua_scripts/lua_scripts.make create mode 100644 apps/plugins/lua_scripts/print_lua_func.lua create mode 100755 apps/plugins/lua_scripts/printmenu.lua create mode 100644 apps/plugins/lua_scripts/tagnav.lua (limited to 'apps/plugins') diff --git a/apps/plugins/CATEGORIES b/apps/plugins/CATEGORIES index ad99f3959f..bb8688347d 100644 --- a/apps/plugins/CATEGORIES +++ b/apps/plugins/CATEGORIES @@ -49,6 +49,7 @@ lamp,apps logo,demos lrcplayer,apps lua,viewers +lua_scripts,demos fractals,demos main_menu_config,apps matrix,demos diff --git a/apps/plugins/SOURCES b/apps/plugins/SOURCES index c3e56af254..6551df71a9 100644 --- a/apps/plugins/SOURCES +++ b/apps/plugins/SOURCES @@ -154,6 +154,7 @@ metronome.c /* Lua needs at least 160 KB to work in */ #if PLUGIN_BUFFER_SIZE >= 0x80000 boomshine.lua +lua_scripts.lua #ifdef HAVE_LCD_COLOR pixel-painter.lua #endif /* HAVE_LCD_COLOR */ diff --git a/apps/plugins/SUBDIRS b/apps/plugins/SUBDIRS index 93cf8297b5..cf2ccff771 100644 --- a/apps/plugins/SUBDIRS +++ b/apps/plugins/SUBDIRS @@ -101,4 +101,5 @@ mpegplayer /* Lua needs at least 160 KB to work in */ #if PLUGIN_BUFFER_SIZE >= 0x80000 lua +lua_scripts #endif diff --git a/apps/plugins/SUBDIRS.app_build b/apps/plugins/SUBDIRS.app_build index 954044146a..48a2d36d87 100644 --- a/apps/plugins/SUBDIRS.app_build +++ b/apps/plugins/SUBDIRS.app_build @@ -7,6 +7,7 @@ * In fact, most of the plugins aren't supposed to be used on a touch(mouse) device */ lua +lua_scripts #ifdef HAVE_LCD_BITMAP #if CONFIG_CODEC == SWCODEC && PLUGIN_BUFFER_SIZE > 0x20000 diff --git a/apps/plugins/lua/include_lua/printtable.lua b/apps/plugins/lua/include_lua/printtable.lua new file mode 100644 index 0000000000..24c4f73b0a --- /dev/null +++ b/apps/plugins/lua/include_lua/printtable.lua @@ -0,0 +1,385 @@ +--[[ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2017 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. + * + ****************************************************************************/ +]] +if not rb.lcd_framebuffer then rb.splash(rb.HZ, "No Support!") return nil end + +require("actions") -- Contains rb.actions & rb.contexts + +local _clr = require("color") +local _print = require("print") +local _timer = require("timer") + +-- Button definitions -- +local EXIT_BUTTON = rb.PLA_EXIT +local CANCEL_BUTTON = rb.actions.PLA_CANCEL +local DOWN_BUTTON = rb.actions.PLA_DOWN +local DOWNR_BUTTON = rb.actions.PLA_DOWN_REPEAT +local EXIT_BUTTON = rb.actions.PLA_EXIT +local LEFT_BUTTON = rb.actions.PLA_LEFT +local LEFTR_BUTTON = rb.actions.PLA_LEFT_REPEAT +local RIGHT_BUTTON = rb.actions.PLA_RIGHT +local RIGHTR_BUTTON = rb.actions.PLA_RIGHT_REPEAT +local SEL_BUTTON = rb.actions.PLA_SELECT +local SELREL_BUTTON = rb.actions.PLA_SELECT_REL +local SELR_BUTTON = rb.actions.PLA_SELECT_REPEAT +local UP_BUTTON = rb.actions.PLA_UP +local UPR_BUTTON = rb.actions.PLA_UP_REPEAT + +-- clamps value to >= min and <= max +local function clamp(iVal, iMin, iMax) + if iMin > iMax then + local swap = iMin + iMin, iMax = iMax, swap + end + + if iVal < iMin then + return iMin + elseif iVal < iMax then + return iVal + end + + return iMax +end + +-------------------------------------------------------------------------------- +--[[ cursor style button routine +-- left / right are x, xi is increment xir is increment when repeat +-- up / down are y, yi is increment yir is increment when repeat +-- cancel is returned as 0,1 +-- select as 0, 1, 2, 3 (none, pressed, repeat, relesed) +-- x_chg and y_chg are the amount x or y changed +-- timeout == nil or -1 loop waits indefinitely till button is pressed +-- time since last button press is returned in ticks.. +-- make xi, xir, yi, yir negative to flip direction... +]] + +local function dpad(x, xi, xir, y, yi, yir, timeout) + _timer("dpad") -- start a persistant timer; keeps time between button events + if timeout == nil then timeout = -1 end + local cancel, select = 0, 0 + local x_chg, y_chg = 0, 0 + local button + while true do + button = rb.get_plugin_action(timeout) + + if button == CANCEL_BUTTON then + cancel = 1 + break; + elseif button == EXIT_BUTTON then + cancel = 1 + break; + elseif button == SEL_BUTTON then + select = 1 + timeout = timeout + 1 + elseif button == SELR_BUTTON then + select = 2 + timeout = timeout + 1 + elseif button == SELREL_BUTTON then + select = -1 + timeout = timeout + 1 + elseif button == LEFT_BUTTON then + x_chg = x_chg - xi + elseif button == LEFTR_BUTTON then + x_chg = x_chg - xir + elseif button == RIGHT_BUTTON then + x_chg = x_chg + xi + elseif button == RIGHTR_BUTTON then + x_chg = x_chg + xir + elseif button == UP_BUTTON then + y_chg = y_chg + yi + elseif button == UPR_BUTTON then + y_chg = y_chg + yir + elseif button == DOWN_BUTTON then + y_chg = y_chg - yi + elseif button == DOWNR_BUTTON then + y_chg = y_chg - yir + elseif timeout >= 0 then--and rb.button_queue_count() < 1 then + break; + end + + if x_chg ~= 0 or y_chg ~= 0 then + timeout = timeout + 1 + end + end + + x = x + x_chg + y = y + y_chg + + return cancel, select, x_chg, x, y_chg, y, _timer.check("dpad", true) +end -- dpad + + + +-------------------------------------------------------------------------------- +--[[ prints a scrollable table to the screen; +-- requires a contiguous table with only strings; +-- 1st item in table is the title if hasheader == true +-- returns select item indice if NOT m_sel.. +-- if m_sel == true a table of selected indices are returned ]] +-------------------------------------------------------------------------------- +-- SECOND MODE OF OPERATION -- if co_routine is defined... +-- prints values returned from a resumable factory in a coroutine this allows +-- very large files etc to be displayed.. the downside is it takes time +-- to load data when scrolling also NO multiple selection is allowed +-- table is passed along with the final count t_count +-------------------------------------------------------------------------------- + +function print_table(t, t_count, settings) +-- (table, t_count, {hasheader, wrap, m_sel, start, curpos, justify, co_routine}) + + if type(t) ~= "table" then + rb.splash(rb.HZ * 5, "table expected got ".. type(t)) + return + end + + local wrap, justify, start, curpos, co_routine, hasheader, m_sel + local header_fgc, header_bgc, item_fgc, item_bgc, item_selc + do + local s = settings or _print.get_settings() + wrap, justify = s.wrap, s.justify + start, curpos = s.start, s.curpos + co_routine = s.co_routine + hasheader = s.hasheader + m_sel = false + if co_routine == nil then + --no multi select in incremental mode + m_sel = s.msel + end + header_fgc = s.hfgc or _clr.set( 0, 000, 000, 000) + header_bgc = s.hbgc or _clr.set(-1, 255, 255, 255) + item_fgc = s.ifgc or _clr.set(-1, 000, 255, 060) + item_bgc = s.ibgc or _clr.set( 0, 000, 000, 000) + item_selc = s.iselc or _clr.set( 1, 000, 200, 100) + end + + local table_p, line, maxline + + local function set_vsb() end -- forward declaration; initialized below + + local function init_position(acc_ticks, acc_steps) + if not acc_ticks then acc_ticks = 15 end-- accelerate scroll every this many ticks + if not acc_steps then acc_steps = 5 end -- default steps for an accelerated scroll + + return {row = 1, row_scrl= acc_steps, + col = 0, col_scrl = acc_steps, + vcursor = 1, vcursor_min = 1, + acc_ticks = acc_ticks, + acc_steps = acc_steps} + end + + local function set_accel(time, scrl, t_p) + if time < t_p.acc_ticks then -- accelerate scroll + scrl = scrl + 1 + else + scrl = t_p.acc_steps + end + return scrl + end + + --adds or removes \0 from end of table entry to mark selected items + local function select_item(item) + if item < 1 then item = 1 end + if not t[item] then return end + if t[item]:sub(-1) == "\0" then + t[item] = t[item]:sub(1, -2) -- de-select + else + t[item] = t[item] .. "\0" -- select + end + end + + -- displays header text at top + local function disp_header(hstr) + local header = header or hstr + local opts = _print.opt.get() + _print.opt.overflow("none") -- don't scroll header; colors change + _print.opt.color(header_fgc, header_bgc) + _print.opt.line(1) + + _print.f() + local line = _print.f(header) + + _print.opt.set(opts) + _print.opt.line(2) + return 2 + end + + -- gets user input to select items, quit, scroll + local function get_input(t_p) + set_vsb(t_p.row + t_p.vcursor - 1)--t_p.row) + rb.lcd_update() + + local quit, select, x_chg, xi, y_chg, yi, timeb = + dpad(t_p.col, -1, -t_p.col_scrl, t_p.row, -1, -t_p.row_scrl) + + t_p.vcursor = t_p.vcursor + y_chg + + if t_p.vcursor > maxline or t_p.vcursor < t_p.vcursor_min then + t_p.row = yi + end + + if wrap == true and (y_chg == 1 or y_chg == -1) then + + -- wraps list, stops at end if accelerated + if t_p.row < t_p.vcursor_min - 1 then + t_p.row = t_count - maxline + 1 + t_p.vcursor = maxline + elseif t_p.row + maxline - 1 > t_count then + t_p.row, t_p.vcursor = t_p.vcursor_min - 1, t_p.vcursor_min - 1 + end + end + + t_p.row = clamp(t_p.row, 1, math.max(t_count - maxline + 1, 1)) + t_p.vcursor = clamp(t_p.vcursor, t_p.vcursor_min, maxline) + + if x_chg ~= 0 then + + if x_chg ~= 1 and x_chg ~= -1 then --stop at the center if accelerated + if (t_p.col <= 0 and xi > 0) or (t_p.col >= 0 and xi < 0) then + xi = 0 + end + end + t_p.col = xi + + t_p.col_scrl = set_accel(timeb, t_p.col_scrl, t_p) + + elseif y_chg ~= 0 then + --t_p.col = 0 -- reset column to the beginning + _print.clear() + _print.opt.sel_line(t_p.vcursor) + + t_p.row_scrl = set_accel(timeb, t_p.row_scrl, t_p) + + end + + if select > 0 and timeb > 15 then --select may be sent multiple times + if m_sel == true then + select_item(t_p.row + t_p.vcursor - 1) + else + return -1, 0, 0, (t_p.row + t_p.vcursor - 1) + end + end + if quit > 0 then return -2, 0, 0, 0 end + return t_p.row, x_chg, y_chg, 0 + end + + -- displays the actual table + local function display_table(table_p, col_c, row_c, sel) + local i = table_p.row + while i >= 1 and i <= t_count do + + -- only print if beginning or user scrolled up/down + if row_c ~= 0 then + + if t[i] == nil and co_routine then + --value has been garbage collected or not created yet + coroutine.resume(co_routine, i) + end + + if t[i] == nil then + rb.splash(1, string.format("ERROR %d is nil", i)) + t[i] = "???" + if rb.get_plugin_action(10) == CANCEL_BUTTON then return 0 end + end + + if m_sel == true and t[i]:sub(-1) == "\0" then + _print.opt.sel_line(line) + end + + if i == 1 and hasheader == true then + line = disp_header(t[1]) + else + line = _print.f("%s", tostring(t[i])) + end + + end + + i = i + 1 -- important! + + if line == 1 or i > t_count or col_c ~= 0 then + _print.opt.column(table_p.col) + i, col_c, row_c, sel = get_input(table_p) + end + + rb.button_clear_queue() -- keep the button queue from overflowing + end + return sel + end -- display_table +--============================================================================-- + + _print.opt.defaults() + _print.opt.autoupdate(false) + _print.opt.color(item_fgc, item_bgc, item_selc) + + table_p = init_position(15, 5) + line, maxline = _print.opt.area(5, 1, rb.LCD_WIDTH - 10, rb.LCD_HEIGHT - 2) + maxline = math.min(maxline, t_count) + + -- allow user to start at a position other than the beginning + if start ~= nil then table_p.row = clamp(start, 1, t_count + 1) end + + if hasheader == true then + table_p.vcursor_min = 2 -- lowest selectable item + table_p.vcursor = 2 + end + + table_p.vcursor = curpos or table_p.vcursor_min + + if table_p.vcursor < 1 or table_p.vcursor > maxline then + table_p.vcursor = table_p.vcursor_min + end + + _print.opt.sel_line(table_p.vcursor) + _print.opt.overflow("manual") + _print.opt.justify(justify) + + -- initialize vertical scrollbar + set_vsb(); do + local vsb =_print.opt.get() + if rb.LCD_DEPTH == 2 then -- invert 2-bit screens + vsb.fg_pattern = 3 - vsb.fg_pattern + vsb.bg_pattern = 3 - vsb.bg_pattern + end + + set_vsb = function (item) + if t_count > (maxline or t_count) then + rb.set_viewport(vsb) + item = item or 0 + local m = maxline / 2 + 1 + rb.gui_scrollbar_draw(vsb.width - 5, vsb.y, 5, vsb.height, + t_count, math.max(0, item - m), + math.min(item + m, t_count), 0) + end + end + end -- set_vsb + local selected = display_table(table_p, 0, 1, 0) + + _print.opt.defaults() + + if m_sel == true then -- walk the table to get selected items + selected = {} + for i = 1, t_count do + if t[i]:sub(-1) == "\0" then table.insert(selected, i) end + end + end + --rb.splash(100, string.format("#1 %d, %d, %d", row, vcursor_pos, sel)) + return selected, table_p.row, table_p.vcursor +end --print_table diff --git a/apps/plugins/lua/loslib.c b/apps/plugins/lua/loslib.c index dce8811fbe..a0ea8e8c24 100644 --- a/apps/plugins/lua/loslib.c +++ b/apps/plugins/lua/loslib.c @@ -172,8 +172,11 @@ static int os_time (lua_State *L) { static int os_exit (lua_State *L) { + lua_settop(L, 2); int status = luaL_optint(L, 1, EXIT_SUCCESS); - lua_close(L); + if (status != EXIT_SUCCESS && lua_type (L, 2) != LUA_TSTRING) + lua_pushfstring(L, "exit (%d)", status); + lua_pushvalue(L, 1); /* put exit status on top of stack */ exit(status); return EXIT_SUCCESS; /* never reached, surpress warning */ } diff --git a/apps/plugins/lua/lua.make b/apps/plugins/lua/lua.make index 16e25c3028..d5fb85afbc 100644 --- a/apps/plugins/lua/lua.make +++ b/apps/plugins/lua/lua.make @@ -19,7 +19,7 @@ LUA_INCLUDEDIR := $(LUA_SRCDIR)/include_lua LUA_INCLUDELIST := $(addprefix $(LUA_BUILDDIR)/,audio.lua blit.lua color.lua draw.lua \ image.lua lcd.lua math_ex.lua print.lua \ timer.lua playlist.lua pcm.lua sound.lua \ - rbcompat.lua ) + rbcompat.lua printtable.lua) ifndef APP_TYPE diff --git a/apps/plugins/lua/rocklib.c b/apps/plugins/lua/rocklib.c index 1e3bc207b7..8c662b7359 100644 --- a/apps/plugins/lua/rocklib.c +++ b/apps/plugins/lua/rocklib.c @@ -771,6 +771,17 @@ RB_WRAP(audio_current_track) return mem_read_write(L, address, maxsize); } +RB_WRAP(restart_lua) +{ + /*close lua state, open a new lua state, load script @ filename */ + luaL_checktype (L, 1, LUA_TSTRING); + lua_settop(L, 1); + lua_pushlightuserdata(L, L); /* signal exit handler */ + exit(1); /* atexit in rocklua.c */ + return -1; +} + + #define RB_FUNC(func) {#func, rock_##func} #define RB_ALIAS(name, func) {name, rock_##func} static const luaL_Reg rocklib[] = @@ -843,6 +854,8 @@ static const luaL_Reg rocklib[] = RB_FUNC(audio_next_track), RB_FUNC(audio_current_track), + RB_FUNC(restart_lua), + {NULL, NULL} }; #undef RB_FUNC @@ -939,4 +952,3 @@ LUALIB_API int luaopen_rock(lua_State *L) #endif return 1; } - diff --git a/apps/plugins/lua/rocklib_events.c b/apps/plugins/lua/rocklib_events.c index 9e363edbdd..270c4ab393 100644 --- a/apps/plugins/lua/rocklib_events.c +++ b/apps/plugins/lua/rocklib_events.c @@ -137,6 +137,7 @@ struct event_data { int thread_state; long *event_stack; long timer_ticks; + short freq_input; short next_input; short next_event; /* callbacks */ @@ -171,8 +172,9 @@ static void init_event_data(lua_State *L, struct event_data *ev_data) ev_data->thread_state = THREAD_YIELD; //ev_data->event_stack = NULL; //ev_data->timer_ticks = 0; - ev_data->next_input = EV_TICKS; - ev_data->next_event = EV_INPUT; + ev_data->freq_input = EV_INPUT; + ev_data->next_input = EV_INPUT; + ev_data->next_event = EV_TICKS; /* callbacks */ for (int i= 0; i < EVENT_CT; i++) ev_data->cb[i] = NULL; @@ -336,7 +338,7 @@ static void rev_timer_isr(void) if (ev_data.next_input <=0) { ev_data.thread_state |= ((ev_data.thread_state & THREAD_INPUTMASK) >> 16); - ev_data.next_input = EV_INPUT; + ev_data.next_input = ev_data.freq_input; } if (ev_data.cb[TIMEREVENT] != NULL && !is_suspend(TIMEREVENT)) @@ -535,6 +537,8 @@ static int rockev_register(lua_State *L) case ACTEVENT: /* fall through */ case BUTEVENT: + ev_data.freq_input = luaL_optinteger(L, 3, EV_INPUT); + if (ev_data.freq_input < HZ / 20) ev_data.freq_input = HZ / 20; ev_data.thread_state |= (ev_flag | (ev_flag << 16)); break; case CUSTOMEVENT: diff --git a/apps/plugins/lua/rocklib_img.c b/apps/plugins/lua/rocklib_img.c index aaecc4d64c..1150b511c3 100644 --- a/apps/plugins/lua/rocklib_img.c +++ b/apps/plugins/lua/rocklib_img.c @@ -1225,6 +1225,8 @@ static const struct luaL_reg rli_lib [] = #define RB_WRAP(func) static int rock_##func(lua_State UNUSED_ATTR *L) #if defined NB_SCREENS && (NB_SCREENS > 1) +#define RB_SCREEN_STRUCT(luastate, narg) \ + rb->screens[get_screen(luastate, narg)] #define RB_SCREENS(luastate, narg, func, ...) \ rb->screens[get_screen(luastate, narg)]->func(__VA_ARGS__) @@ -1240,6 +1242,8 @@ static int get_screen(lua_State *L, int narg) return screen; } #else /* only SCREEN_MAIN exists */ +#define RB_SCREEN_STRUCT(luastate, narg) \ + rb->screens[SCREEN_MAIN] #define RB_SCREENS(luastate, narg, func, ...) \ rb->screens[SCREEN_MAIN]->func(__VA_ARGS__) #endif @@ -1376,7 +1380,6 @@ RB_WRAP(font_getstringsize) } #ifdef HAVE_LCD_BITMAP - RB_WRAP(lcd_framebuffer) { rli_wrap(L, rb->lcd_framebuffer, LCD_WIDTH, LCD_HEIGHT); @@ -1399,6 +1402,19 @@ static void get_rect_bounds(lua_State *L, int narg, int *x, int *y, int *w, int* *h = luaL_checkint(L, narg + 3); } +RB_WRAP(gui_scrollbar_draw) +{ + int x, y, width, height; + get_rect_bounds(L, 1, &x, &y, &width, &height); + int items = luaL_checkint(L, 5); + int min_shown = luaL_checkint(L, 6); + int max_shown = luaL_checkint(L, 7); + unsigned flags = (unsigned) luaL_checkint(L, 8); + rb->gui_scrollbar_draw(RB_SCREEN_STRUCT(L, 9), x, y, width, height, + items, min_shown, max_shown, flags); +return 0; +} + RB_WRAP(lcd_mono_bitmap_part) { struct rocklua_image *src = rli_checktype(L, 1); @@ -1644,6 +1660,7 @@ static const luaL_Reg rocklib_img[] = #ifdef HAVE_LCD_BITMAP R(lcd_framebuffer), R(lcd_setfont), + R(gui_scrollbar_draw), R(lcd_mono_bitmap_part), R(lcd_mono_bitmap), #if LCD_DEPTH > 1 diff --git a/apps/plugins/lua/rocklua.c b/apps/plugins/lua/rocklua.c index 0d0b1f63f7..eb48fa2799 100644 --- a/apps/plugins/lua/rocklua.c +++ b/apps/plugins/lua/rocklua.c @@ -28,6 +28,8 @@ #include "luadir.h" #include "rocklib_events.h" +static lua_State *Ls = NULL; +static int lu_status = 0; static const luaL_Reg lualibs[] = { {"", luaopen_base}, @@ -142,41 +144,77 @@ static int docall (lua_State *L) { return status; } +static void lua_atexit(void); +static int loadfile_newstate(lua_State **L, const char *filename) +{ + *L = luaL_newstate(); + rb_atexit(lua_atexit); + rocklua_openlibs(*L); + return luaL_loadfile(*L, filename); +} + +static void lua_atexit(void) +{ + char *filename; + + if(Ls && lua_gettop(Ls) > 1) + { + if (Ls == lua_touserdata(Ls, -1)) /* signal from restart_lua */ + { + filename = (char *) malloc(MAX_PATH); + + if (filename) /* out of memory? */ + rb->strlcpy(filename, lua_tostring(Ls, -2), MAX_PATH); + lua_close(Ls); /* close old state */ + + lu_status = loadfile_newstate(&Ls, filename); + + free(filename); + plugin_start(NULL); + } + else if (lua_tointeger(Ls, -1) != 0) /* os.exit */ + { + lu_status = LUA_ERRRUN; + lua_pop(Ls, 1); /* put exit string on top of stack */ + plugin_start(NULL); + } + } + _exit(0); /* don't call exit handler */ +} /***************** Plugin Entry Point *****************/ enum plugin_status plugin_start(const void* parameter) { const char* filename; - int status; if (parameter == NULL) { + if (!Ls) rb->splash(HZ, "Play a .lua file!"); - return PLUGIN_ERROR; } else { filename = (char*) parameter; + lu_status = loadfile_newstate(&Ls, filename); + } - lua_State *L = luaL_newstate(); - - rocklua_openlibs(L); - status = luaL_loadfile(L, filename); - if (!status) { + if (Ls) + { + if (!lu_status) { rb->lcd_scroll_stop(); /* rb doesn't like bg change while scroll */ rb->lcd_clear_display(); - status = docall(L); + lu_status= docall(Ls); } - if (status) { - DEBUGF("%s\n", lua_tostring(L, -1)); - rb->splashf(5 * HZ, "%s", lua_tostring(L, -1)); - lua_pop(L, 1); + if (lu_status) { + DEBUGF("%s\n", lua_tostring(Ls, -1)); + rb->splash(5 * HZ, lua_tostring(Ls, -1)); + /*lua_pop(Ls, 1);*/ } - - lua_close(L); + lua_close(Ls); } + else + return PLUGIN_ERROR; return PLUGIN_OK; } - diff --git a/apps/plugins/lua_scripts.lua b/apps/plugins/lua_scripts.lua new file mode 100644 index 0000000000..02fe50b327 --- /dev/null +++ b/apps/plugins/lua_scripts.lua @@ -0,0 +1,161 @@ +--[[ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2017 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. + * + ****************************************************************************/ +]] + +local scrpath = rb.current_path() .. "/lua_scripts/" + +package.path = scrpath .. "/?.lua;" .. package.path --add lua_scripts directory to path +require("printtable") + +rb.actions = nil +package.loaded["actions"] = nil + +local excludedsrc = ";filebrowse.lua;fileviewers.lua;printmenu.lua;dbgettags.lua;" +-------------------------------------------------------------------------------- + +local function get_files(path, norecurse, finddir, findfile, f_t, d_t) + + local quit = false + + local files = f_t or {} + local dirs = d_t or {} + + local function f_filedir(name) + --default find function + -- example: return name:find(".mp3", 1, true) ~= nil + if name:len() <= 2 and (name == "." or name == "..") then + return false + end + if string.find(excludedsrc, ";" .. name .. ";") then + return false + end + if string.sub(name, -4) == ".lua" then + return true + end + return false + end + local function d_filedir(name) + --default discard function + return false + end + + if finddir == nil then + finddir = f_filedir + elseif type(finddir) ~= "function" then + finddir = d_filedir + end + + if findfile == nil then + findfile = f_filedir + elseif type(findfile) ~= "function" then + findfile = d_filedir + end + + local function _get_files(path, cancelbtn) + local sep = "" + if string.sub(path, - 1) ~= "/" then sep = "/" end + for fname, isdir in luadir.dir(path) do + + if isdir and finddir(fname) then + table.insert(dirs, path .. sep ..fname) + elseif not isdir and findfile(fname) then + table.insert(files, path .. sep ..fname) + end + + if rb.get_plugin_action(0) == cancelbtn then + return true + end + end + end + + local function cmp_alphanum (op1, op2) + local type1= type(op1) + local type2 = type(op2) + + if type1 ~= type2 then + return type1 < type2 + else + if type1 == "string" then + op1 = op1:upper() + op2 = op2:upper() + end + return op1 < op2 + end + end + + table.insert(dirs, path) -- root + + for key,value in pairs(dirs) do + --luadir.dir may error out so we need to do the call protected + _, quit = pcall(_get_files, value, CANCEL_BUTTON) + + if quit == true or norecurse then + break; + end + end + + table.sort(files, cmp_alphanum) + table.sort(dirs, cmp_alphanum) + + return dirs, files +end -- get_files +-------------------------------------------------------------------------------- + +-- uses print_table and get_files to display simple file browser +function script_choose(dir, title) + local dstr + local hstr = title + + local norecurse = true + local f_finddir = false -- function to match directories; nil all, false none + local f_findfile = nil -- function to match files; nil all, false none + + local p_settings = {wrap = true, hasheader = true} + local files = {} + local dirs = {} + local item = 1 + rb.lcd_clear_display() + + while item > 0 do + dirs, files = get_files(dir, norecurse, f_finddir, f_findfile, dirs, files) + for i=1, #dirs do dirs[i] = nil end -- empty table for reuse + table.insert(dirs, 1, hstr) + for i = 1, #files do + table.insert(dirs, "\t" .. string.gsub(files[i], ".*/","")) + end + + item = print_table(dirs, #dirs, p_settings) + + -- If item was selected follow directory or return filename + if item > 0 then + dir = files[item - 1] + if not rb.dir_exists("/" .. dir) then + return dir + end + end + + end +end -- file_choose +-------------------------------------------------------------------------------- + +local script_path = script_choose(scrpath, "lua scripts") +if script_path then rb.restart_lua(script_path) end diff --git a/apps/plugins/lua_scripts/dbgettags.lua b/apps/plugins/lua_scripts/dbgettags.lua new file mode 100644 index 0000000000..06fa6e8830 --- /dev/null +++ b/apps/plugins/lua_scripts/dbgettags.lua @@ -0,0 +1,116 @@ +--dbgettags.lua Bilgus 2018 +--[[ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2017 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. + * + ****************************************************************************/ +]] + +require("actions") +local CANCEL_BUTTON = rb.actions.PLA_CANCEL + +local sINVALIDDATABASE = "Invalid Database" +local sERROROPENING = "Error opening" + +-- tag cache header +local sTCVERSION = string.char(0x0F) +local sTCHEADER = string.reverse("TCH" .. sTCVERSION) +local DATASZ = 4 -- int32_t +local TCHSIZE = 3 * DATASZ -- 3 x int32_t + +local function bytesLE_n(str) + str = str or "" + local tbyte={str:byte(1, -1)} + local bpos = 1 + local num = 0 + for k = 1,#tbyte do -- (k = #t, 1, -1 for BE) + num = num + tbyte[k] * bpos + bpos = bpos * 256 + end + return num +end + +-- uses database files to retrieve database tags +-- adds all unique tags into a lua table +function get_tags(filename, hstr) + + if not filename then return end + + hstr = hstr or filename + + local file = io.open('/' .. filename or "", "r") --read + if not file then rb.splash(100, sERROROPENING .. " " .. filename) return end + + local fsz = file:seek("end") + + local posln = 0 + local tag_len = TCHSIZE + local idx + + local function readchrs(count) + if posln >= fsz then return nil end + file:seek("set", posln) + posln = posln + count + return file:read(count) + end + + local tagcache_header = readchrs(DATASZ) or "" + local tagcache_sz = readchrs(DATASZ) or "" + local tagcache_entries = readchrs(DATASZ) or "" + + if tagcache_header ~= sTCHEADER or + bytesLE_n(tagcache_sz) ~= (fsz - TCHSIZE) then + rb.splash(100, sINVALIDDATABASE .. " " .. filename) + return + end + + -- local tag_entries = bytesLE_n(tagcache_entries) + + local ftable = {} + table.insert(ftable, 1, hstr) + + local tline = #ftable + 1 + ftable[tline] = "" + + local str = "" + + while true do + tag_len = bytesLE_n(readchrs(DATASZ)) + readchrs(DATASZ) -- idx = bytesLE_n(readchrs(DATASZ)) + str = readchrs(tag_len) or "" + str = string.match(str, "(%Z+)%z") + + if str then + if ftable[tline - 1] ~= str then -- Remove dupes + ftable[tline] = str + tline = tline + 1 + end + elseif posln >= fsz then + break + end + + if rb.get_plugin_action(0) == CANCEL_BUTTON then + break + end + end + + file:close() + + return ftable +end -- get_tags diff --git a/apps/plugins/lua_scripts/fade2sleep.lua b/apps/plugins/lua_scripts/fade2sleep.lua new file mode 100644 index 0000000000..03a4533591 --- /dev/null +++ b/apps/plugins/lua_scripts/fade2sleep.lua @@ -0,0 +1,107 @@ +--Bilgus 12-2016 +--revisited 8-2019 +require "actions" +require "buttons" +require "sound" +require "audio" +TIMEOUT = 0 + +local SOUND_VOLUME = rb.sound_settings.SOUND_VOLUME +rb.sound_settings = nil +package.loaded["sound_defines"] = nil + +function say_msg(message, timeout) + rb.splash(1, message) + rb.sleep(timeout * rb.HZ) +end + +function say_value(value,message,timeout) + local message = string.format(message .. "%d", value) + say_msg(message, timeout) +end + +function ShowMainMenu() -- we invoke this function every time we want to display the main menu of the script +local s = 0 +local mult = 1 +local unit = " Minutes" + + + while s == 0 or s == 5 do -- don't exit of program until user selects Exit + if mult < 1 then + mult = 1 + s = 0 + end + mainmenu = {"More", mult * 1 .. unit, mult * 5 .. unit, mult * 10 .. unit, mult * 15 .. unit, "Less", "Exit"} -- define the items of the menu + s = rb.do_menu("Reduce volume + sleep over", mainmenu, s, false) -- actually tell Rockbox to draw the menu + + -- In the line above: "Test" is the title of the menu, mainmenu is an array with the items + -- of the menu, nil is a null value that needs to be there, and the last parameter is + -- whether the theme should be drawn on the menu or not. + -- the variable s will hold the index of the selected item on the menu. + -- the index is zero based. This means that the first item is 0, the second one is 1, etc. + if s == 0 then mult = mult + 1 + elseif s == 1 then TIMEOUT = mult + elseif s == 2 then TIMEOUT = mult * 5 + elseif s == 3 then TIMEOUT = mult * 10 + elseif s == 4 then TIMEOUT = mult * 15 + elseif s == 5 then mult = mult - 1 -- User selected to exit + elseif s == 6 then os.exit() -- User selected to exit + elseif s == -2 then os.exit() -- -2 index is returned from do_menu() when user presses the key to exit the menu (on iPods, it's the left key). + -- In this case, user probably wants to exit (or go back to last menu). + else rb.splash(2 * rb.HZ, "Error! Selected index: " .. s) -- something strange happened. The program shows this message when + -- the selected item is not on the index from 0 to 3 (in this case), and displays + -- the selected index. Having this type of error handling is not + -- required, but it might be nice to have Especially while you're still + -- developing the plugin. + end + end +end + +ShowMainMenu() +rb.set_sleeptimer_duration(TIMEOUT) +rb.lcd_clear_display() +rb.lcd_update() + +local volume = rb.sound_current(SOUND_VOLUME) +local vol_min = rb.sound_min(SOUND_VOLUME) +local volsteps = -(vol_min - volume) +local seconds = (TIMEOUT * 60) / volsteps +local sec_left = (TIMEOUT * 60) +local hb = 0 +local action = rb.get_action(rb.contexts.CONTEXT_STD, 0) + if rb.audio_status() == 1 then + while ((volume > vol_min) and (action ~= rb.actions.ACTION_STD_CANCEL)) do + rb.lcd_clear_display() + say_value(volume,sec_left .. " Sec, Volume: ", 1) + local i = seconds * 2 + while ((i > 0) and (action ~= rb.actions.ACTION_STD_CANCEL)) do + i = i - 1 + rb.lcd_drawline(hb, 1, hb, 1) + rb.lcd_update() + if hb >= rb.LCD_WIDTH then + hb = 0 + rb.lcd_clear_display() + say_value(volume,sec_left .. " Sec, Volume: ", 1) + end + hb = hb + 1 + rb.sleep(rb.HZ / 2) + action = rb.get_action(rb.contexts.CONTEXT_STD, 0) + rb.yield() + end + volume = volume - 1 + rb.sound_set(SOUND_VOLUME, volume); + sec_left = sec_left - seconds + + end + rb.audio_stop() + rb.lcd_clear_display() + rb.lcd_update() + + os.exit(1, "Playback Stopped") + + else + rb.lcd_clear_display() + rb.lcd_update() + + os.exit(2, "Nothing is playing") + end diff --git a/apps/plugins/lua_scripts/filebrowse.lua b/apps/plugins/lua_scripts/filebrowse.lua new file mode 100755 index 0000000000..5e75365cf8 --- /dev/null +++ b/apps/plugins/lua_scripts/filebrowse.lua @@ -0,0 +1,190 @@ +--[[ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2017 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. + * + ****************************************************************************/ +]] +if ... == nil then rb.splash(rb.HZ * 3, "use 'require'") end +require("printtable") +local _lcd = require("lcd") +local _timer = require("timer") + +-------------------------------------------------------------------------------- +--[[ returns a sorted tables of directories and (another) of files +-- path is the starting path; norecurse == true.. only that path will be searched +-- findfile & finddir are definable search functions +-- if not defined all files/dirs are returned if false is passed.. none +-- or you can provide your own function see below.. +-- f_t and d_t allow you to pass your own tables for re-use but isn't necessary +]] +local function get_files(path, norecurse, finddir, findfile, f_t, d_t) + + local quit = false + + local files = f_t or {} + local dirs = d_t or {} + + local function f_filedir(name) + --default find function + -- example: return name:find(".mp3", 1, true) ~= nil + if name:len() <= 2 and (name == "." or name == "..") then + return false + end + return true + end + local function d_filedir(name) + --default discard function + return false + end + + if finddir == nil then + finddir = f_filedir + elseif type(finddir) ~= "function" then + finddir = d_filedir + end + + if findfile == nil then + findfile = f_filedir + elseif type(findfile) ~= "function" then + findfile = d_filedir + end + + local function _get_files(path, cancelbtn) + local sep = "" + if string.sub(path, - 1) ~= "/" then sep = "/" end + for fname, isdir in luadir.dir(path) do + + if isdir and finddir(fname) then + table.insert(dirs, path .. sep ..fname) + elseif not isdir and findfile(fname) then + table.insert(files, path .. sep ..fname) + end + + if rb.get_plugin_action(0) == cancelbtn then + return true + end + end + end + + local function cmp_alphanum (op1, op2) + local type1= type(op1) + local type2 = type(op2) + + if type1 ~= type2 then + return type1 < type2 + else + if type1 == "string" then + op1 = op1:upper() + op2 = op2:upper() + end + return op1 < op2 + end + end + + _lcd:splashf(1, "Searching for Files") + + table.insert(dirs, path) -- root + + for key,value in pairs(dirs) do + --luadir.dir may error out so we need to do the call protected + _, quit = pcall(_get_files, value, CANCEL_BUTTON) + + if quit == true or norecurse then + break; + end + end + + table.sort(files, cmp_alphanum) + table.sort(dirs, cmp_alphanum) + + return dirs, files +end -- get_files +-------------------------------------------------------------------------------- + +-- uses print_table and get_files to display simple file browser +function file_choose(dir, title) + local dstr, hstr = "" + if not title then + dstr = "%d items found in %0d.%02d seconds" + else + hstr = title + end + + -- returns whole seconds and remainder + local function tick2seconds(ticks) + local secs = (ticks / rb.HZ) + local csecs = (ticks - (secs * rb.HZ)) + return secs, csecs + end + + local norecurse = true + local f_finddir = nil -- function to match directories; nil all, false none + local f_findfile = nil -- function to match files; nil all, false none + + local p_settings = {wrap = true, hasheader = true} + + local timer + local files = {} + local dirs = {} + local item = 1 + _lcd:clear() + + while item > 0 do + if not title then + timer = _timer() + end + + dirs, files = get_files(dir, norecurse, f_finddir, f_findfile, dirs, files) + + local parentdir = dirs[1] + for i = 1, #dirs do + dirs[i] = "\t" .. dirs[i] + end + + for i = 1, #files do + table.insert(dirs, "\t" .. files[i]) + end + + for i=1, #files do files[i] = nil end -- empty table for reuse + + if not title then + hstr = string.format(dstr, #dirs - 1, tick2seconds(timer:stop())) + end + + table.insert(dirs, 1, hstr) + + item = print_table(dirs, #dirs, p_settings) + + -- If item was selected follow directory or return filename + if item > 0 then + dir = string.gsub(dirs[item], "%c+","") + if not rb.dir_exists("/" .. dir) then + return dir + end + end + + if dir == parentdir then + dir = dir:sub(1, dir:match(".*()/") - 1) + if dir == "" then dir = "/" end + end + for i=1, #dirs do dirs[i] = nil end -- empty table for reuse + + end +end -- file_choose +-------------------------------------------------------------------------------- diff --git a/apps/plugins/lua_scripts/fileview.lua b/apps/plugins/lua_scripts/fileview.lua new file mode 100755 index 0000000000..920281bbef --- /dev/null +++ b/apps/plugins/lua_scripts/fileview.lua @@ -0,0 +1,79 @@ +--[[ + __________ __ ___. + Open \______ \ ____ ____ | | _\_ |__ _______ ___ + Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + \/ \/ \/ \/ \/ + $Id$ + Example Lua File Viewer script + Copyright (C) 2017 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. +]]-- + +require("actions") -- Contains rb.actions & rb.contexts +-- require("buttons") -- Contains rb.buttons -- not needed for this example + +--local _timer = require("timer") +--local _clr = require("color") -- clrset, clrinc provides device independent colors +local _lcd = require("lcd") -- lcd helper functions +--local _print = require("print") -- advanced text printing +--local _img = require("image") -- image manipulation save, rotate, resize, tile, new, load +--local _blit = require("blit") -- handy list of blit operations +--local _draw = require("draw") -- draw all the things (primitives) +--local _math = require("math_ex") -- missing math sine cosine, sqrt, clamp functions + + +local scrpath = rb.current_path()--rb.PLUGIN_DIR .. "/demos/lua_scripts/" + +package.path = scrpath .. "/?.lua;" .. package.path --add lua_scripts directory to path + +require("printmenu") --menu +require("filebrowse") -- file browser +require("fileviewers") -- fileviewer, hexviewer + +rb.actions = nil +package.loaded["actions"] = nil + +-- uses print_table to display a menu +function main_menu() + + local mt = { + [1] = "Rocklua File View Example", + [2] = "File View", + [3] = "File Hex View", + [4] = "Simple Browser", + [5] = "Exit" + } + + local ft = { + [0] = exit_now, --if user cancels do this function + [1] = function(TITLE) return true end, -- shouldn't happen title occupies this slot + [2] = function(VIEWF) -- view file + print_file_increment(file_choose("/", "Choose File")) + end, + [3] = function(VHEXF) -- view hex + print_file_hex(file_choose("/", "Choose File"), 8) + end, + [4] = function(BROWS) -- file browser + _lcd:splashf(rb.HZ, "%s", file_choose("/") or "None") + end, + [5] = function(EXIT_) return true end + } + + print_menu(mt, ft) + +end + +function exit_now() + _lcd:update() + os.exit() +end -- exit_now + +main_menu() +exit_now() diff --git a/apps/plugins/lua_scripts/fileviewers.lua b/apps/plugins/lua_scripts/fileviewers.lua new file mode 100755 index 0000000000..5a9c417d36 --- /dev/null +++ b/apps/plugins/lua_scripts/fileviewers.lua @@ -0,0 +1,465 @@ +--[[ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2017 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. + * + ****************************************************************************/ +]] +if ... == nil then rb.splash(rb.HZ * 3, "use 'require'") end +require("printtable") +local _clr = require("color") +local _lcd = require("lcd") +local _print = require("print") +local _timer = require("timer") + +require("actions") +local CANCEL_BUTTON = rb.actions.PLA_CANCEL +-------------------------------------------------------------------------------- +-- builds an index of byte position of every line at each bufsz increment +-- in filename; bufsz == 1 would be every line; saves to filename.ext.idx_ext +-- lnbyte should be nil for text files and number of bytes per line for binary +local function build_file_index(filename, idx_ext, bufsz, lnbyte) + + if not filename then return end + local file = io.open('/' .. filename, "r") --read + if not file then _lcd:splashf(100, "Can't open %s", filename) return end + local fsz = file:seek("end") + local fsz_kb = fsz / 1024 + local count + local ltable = {0} --first index is the beginning of the file + local timer = _timer() + local fread + _lcd:splashf(100, "Indexing file %d Kb", (fsz / 1024)) + + if lnbyte then + fread = function(f) return f:read(lnbyte) end + else + lnbyte = -1 + fread = function(f) return f:read("*l") end + end + + file:seek("set", 0) + for i = 1, fsz do + if i % bufsz == 0 then + local loc = file:seek() + ltable[#ltable + 1] = loc + _lcd:splashf(1, "Parsing %d of %d Kb", loc / 1024, fsz_kb) + end + if rb.get_plugin_action(0) == CANCEL_BUTTON then + return + end + if not fread(file) then + count = i + break + end + end + + local fileidx = io.open('/' .. filename .. idx_ext, "w+") -- write/erase + if fileidx then + fileidx:write(fsz .. "\n") + fileidx:write(count .. "\n") + fileidx:write(bufsz .. "\n") + fileidx:write(lnbyte .. "\n") + fileidx:write(table.concat(ltable, "\n")) + fileidx:close() + _lcd:splashf(100, "Finished in %d seconds", timer.stop() / rb.HZ) + collectgarbage("collect") + else + error("unable to save index file") + end +end -- build_file_index +-------------------------------------------------------------------------------- + +--- returns size of original file, total lines buffersize, and table filled +-- with line offsets in index file -> filename +local function load_index_file(filename) + local filesz, count, bufsz, lnbyte + local ltable + local fileidx = io.open('/' .. filename, "r") --read + if fileidx then + local idx = -3 + ltable = {} + fileidx:seek("set", 0) + for line in fileidx:lines() do + if idx == -3 then + filesz = tonumber(line) + elseif idx == -2 then + count = tonumber(line) + elseif idx == -1 then + bufsz = tonumber(line) + elseif idx == 0 then + lnbyte = tonumber(line) + else + ltable[idx] = tonumber(line) + end + idx = idx + 1 + end + fileidx:close() + end + return lnbyte, filesz, count, bufsz, ltable +end -- load_index_file +-------------------------------------------------------------------------------- + +-- creates a fixed index with fixed line lengths, perfect for viewing hex files +-- not so great for reading text files but works as a fallback +local function load_fixed_index(bytesperline, filesz, bufsz) + local lnbyte = bytesperline + local count = (filesz + lnbyte - 1) / lnbyte + 1 + local idx_t = {} -- build index + for i = 0, filesz, bufsz do + idx_t[#idx_t + 1] = lnbyte * i + end + return lnbyte, filesz, count, bufsz, idx_t +end -- load_fixed_index +-------------------------------------------------------------------------------- + +-- uses print_table to display a whole file +function print_file(filename, maxlinelen, settings) + + if not filename then return end + local file = io.open('/' .. filename or "", "r") --read + if not file then _lcd:splashf(100, "Can't open %s", filename) return end + maxlinelen = 33 + local hstr = filename + local ftable = {} + table.insert(ftable, 1, hstr) + + local tline = #ftable + 1 + local remln = maxlinelen + local posln = 1 + + for line in file:lines() do + if line then + if maxlinelen then + if line == "" then + ftable[tline] = ftable[tline] or "" + tline = tline + 1 + remln = maxlinelen + else + line = line:match("%w.+") or "" + end + local linelen = line:len() + while linelen > 0 do + + local fsp = line:find("%s", posln + remln - 5) or 0x0 + fsp = fsp - (posln + remln) + if fsp >= 0 then + local fspr = fsp + fsp = line:find("%s", posln + remln) or linelen + fsp = fsp - (posln + remln) + if math.abs(fspr) < fsp then fsp = fspr end + end + if fsp > 5 or fsp < -5 then fsp = 0 end + + local str = line:sub(posln, posln + remln + fsp) + local slen = str:len() + ftable[tline] = ftable[tline] or "" + ftable[tline] = ftable[tline] .. str + linelen = linelen - slen + if linelen > 0 then + tline = tline + 1 + posln = posln + slen + remln = maxlinelen + --loop continues + else + ftable[tline] = ftable[tline] .. " " + remln = maxlinelen - slen + posln = 1 + --loop ends + end + + end + else + ftable[#ftable + 1] = line + end + + + end + end + + file:close() + + _lcd:clear() + _print.clear() + + if not settings then + settings = {} + settings.justify = "center" + settings.wrap = true + settings.msel = true + end + settings.hasheader = true + settings.co_routine = nil + + local sel = + print_table(ftable, #ftable, settings) + + _lcd:splashf(rb.HZ * 2, "%d items {%s}", #sel, table.concat(sel, ", ")) + ftable = nil +end -- print_file +-------------------------------------------------------------------------------- + +-- uses print_table to display a portion of a file +function print_file_increment(filename, settings) + + if not filename then return end + local file = io.open('/' .. filename, "r") --read + if not file then _lcd:splashf(100, "Can't open %s", filename) return end + local fsz = file:seek("end") + local bsz = 1023 + --if small file do it the easier way and load whole file to table + if fsz < 60 * 1024 then + file:close() + print_file(filename, settings) + return + end + + local ext = ".idx" + local lnbyte, filesz, count, bufsz, idx_t = load_index_file(filename .. ext) + + if not idx_t or fsz ~= filesz then -- build file index + build_file_index(filename, ext, bsz) + lnbyte, filesz, count, bufsz, idx_t = load_index_file(filename .. ext) + end + + -- if invalid or user canceled creation fallback to a fixed index + if not idx_t or fsz ~= filesz or count <= 0 then + _lcd:splashf(rb.HZ * 5, "Unable to read file index %s", filename .. ext) + lnbyte, filesz, count, bufsz, idx_t = load_fixed_index(32, fsz, bsz) + end + + if not idx_t or fsz ~= filesz or count <= 0 then + _lcd:splashf(rb.HZ * 5, "Unable to load file %s", filename) + return + end + + local hstr = filename + local file_t = setmetatable({},{__mode = "kv"}) --weak keys and values + -- this allows them to be garbage collected as space is needed + -- rebuilds when needed + local ovf = 0 + local lpos = 1 + local timer = _timer() + file:seek("set", 0) + + function print_co() + while true do + collectgarbage("step") + file_t[1] = hstr --position 1 is ALWAYS header/title + + for i = 1, bufsz + ovf do + file_t[lpos + i] = file:read ("*l") + end + ovf = 0 + lpos = lpos + bufsz + + local bpos = coroutine.yield() + + if bpos <= lpos then -- roll over or scroll up + bpos = (bpos - bufsz) + bpos % bufsz + timer:check(true) + end + + lpos = bpos - bpos % bufsz + + if lpos < 1 then + lpos = 1 + elseif lpos > count - bufsz then -- partial fill + ovf = count - bufsz - lpos + end + --get position in file of the nearest indexed line + file:seek("set", idx_t[bpos / bufsz + 1]) + + -- on really large files if it has been more than 10 minutes + -- since the user scrolled up the screen wipe out the prior + -- items to free memory + if lpos % 5000 == 0 and timer:check() > rb.HZ * 600 then + for i = 1, lpos - 100 do + file_t[i] = nil + end + end + + end + end + + co = coroutine.create(print_co) + _lcd:clear() + _print.clear() + + if not settings then + settings = {} + settings.justify = "center" + settings.wrap = true + end + settings.hasheader = true + settings.co_routine = co + settings.msel = false + + table.insert(file_t, 1, hstr) --position 1 is header/title + local sel = + print_table(file_t, count, settings) + file:close() + idx_t = nil + file_t = nil + return sel +end --print_file_increment +-------------------------------------------------------------------------------- +function print_file_hex(filename, bytesperline, settings) + + if not filename then return end + local file = io.open('/' .. filename, "r") --read + if not file then _lcd:splashf(100, "Can't open %s", filename) return end + local hstr = filename + local bpl = bytesperline + local fsz = file:seek("end") +--[[ + local filesz = file:seek("end") + local bufsz = 1023 + local lnbyte = bytesperline + local count = (filesz + lnbyte - 1) / lnbyte + 1 + + local idx_t = {} -- build index + for i = 0, filesz, bufsz do + idx_t[#idx_t + 1] = lnbyte * i + end]] + + local lnbyte, filesz, count, bufsz, idx_t = load_fixed_index(bpl, fsz, 1023) + + local file_t = setmetatable({},{__mode = "kv"}) --weak keys and values + -- this allows them to be garbage collected as space is needed + -- rebuilds when needed + local ovf = 0 + local lpos = 1 + local timer = _timer() + file:seek("set", 0) + + function hex_co() + while true do + collectgarbage("step") + file_t[1] = hstr --position 1 is ALWAYS header/title + + for i = 1, bufsz + ovf do + local pos = file:seek() + local s = file:read (lnbyte) + if not s then -- EOF + file_t[lpos + i] = "" + break; + end + local s_len = s:len() + + if s_len > 0 then + local fmt = "0x%04X: " .. string.rep("%02X ", s_len) + local schrs = " " .. s:gsub("(%c)", " . ") + file_t[lpos + i] = string.format(fmt, pos, s:byte(1, s_len)) .. + schrs + else + file_t[lpos + i] = string.format("0x%04X: ", pos) + end + end + ovf = 0 + lpos = lpos + bufsz + + local bpos = coroutine.yield() + + if bpos < lpos then -- roll over or scroll up + bpos = (bpos - bufsz) + bpos % bufsz + timer:check(true) + end + + lpos = bpos - bpos % bufsz + + if lpos < 1 then + lpos = 1 + elseif lpos > count - bufsz then -- partial fill + ovf = count - bufsz - lpos + end + --get position in file of the nearest indexed line + file:seek("set", idx_t[bpos / bufsz + 1]) + + -- on really large files if it has been more than 10 minutes + -- since the user scrolled up the screen wipe out the prior + -- items to free memory + if lpos % 10000 == 0 and timer:check() > rb.HZ * 600 then + for i = 1, lpos - 100 do + file_t[i] = nil + end + end + + end + end + + co = coroutine.create(hex_co) + + local function repl(char) + local ret = "" + if char:sub(1,2) == "0x" then + return string.format("%dd:", tonumber(char:sub(3, -2), 16)) + else + return string.format("%03d ", tonumber(char, 16)) + end + end + + + _lcd:clear() + _print.clear() + + local sel, start, vcur = 1 + table.insert(file_t, 1, hstr) --position 1 is header/title + + if not settings then + settings = {} + settings.justify = "left" + settings.wrap = true + settings.msel = false + settings.hfgc = _clr.set( 0, 000, 000, 000) + settings.hbgc = _clr.set(-1, 255, 255, 255) + settings.ifgc = _clr.set(-1, 255, 255, 255) + settings.ibgc = _clr.set( 0, 000, 000, 000) + settings.iselc = _clr.set( 1, 000, 200, 100) + end + + settings.hasheader = true + settings.co_routine = co + settings.start = start + settings.curpos = vcur + + while sel > 0 do + settings.start = start + settings.curpos = vcur + + sel, start, vcur = print_table(file_t, count, settings) + + if sel > 1 and file_t[sel] then -- flips between hex and decimal + local s = file_t[sel] + if s:sub(-1) == "\b" then + file_t[sel] = nil + ovf = -(bufsz - 1) + coroutine.resume(co, sel) --rebuild this item + else + s = s:gsub("(0x%x+:)", repl) .. "\b" + file_t[sel] = s:gsub("(%x%x%s)", repl) .. "\b" + end + end + end + + file:close() + idx_t = nil + file_t = nil + return sel +end -- print_file_hex +-------------------------------------------------------------------------------- diff --git a/apps/plugins/lua_scripts/lua_scripts.make b/apps/plugins/lua_scripts/lua_scripts.make new file mode 100644 index 0000000000..2f46f9d74a --- /dev/null +++ b/apps/plugins/lua_scripts/lua_scripts.make @@ -0,0 +1,24 @@ +# __________ __ ___. +# Open \______ \ ____ ____ | | _\_ |__ _______ ___ +# Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / +# Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < +# Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ +# \/ \/ \/ \/ \/ +# $Id$ +# + +LUASCR_SRCDIR := $(APPSDIR)/plugins/lua_scripts +LUASCR_BUILDDIR := $(BUILDDIR)/apps/plugins/lua_scripts +LUASCRS := $(wildcard $(LUASCR_SRCDIR)/*.lua) + +#DUMMY := $(info [${LUASCRS}]) + +DUMMY : all + +all: $(subst $(LUASCR_SRCDIR)/,$(LUASCR_BUILDDIR)/,$(LUASCRS)) + +$(LUASCR_BUILDDIR)/%.lua: $(LUASCR_SRCDIR)/%.lua | $(LUASCR_BUILDDIR) + $(call PRINTS,CP $(subst $(LUASCR_SRCDIR)/,,$<))cp $< $@ + +$(LUASCR_BUILDDIR): + $(call PRINTS,MKDIR $@)mkdir -p $(LUASCR_BUILDDIR)/ diff --git a/apps/plugins/lua_scripts/print_lua_func.lua b/apps/plugins/lua_scripts/print_lua_func.lua new file mode 100644 index 0000000000..f2642a187b --- /dev/null +++ b/apps/plugins/lua_scripts/print_lua_func.lua @@ -0,0 +1,304 @@ +--RB LUA show all global variables; BILGUS +require "actions" +require "audio" +require "buttons" +require "color" +require "draw" +require "image" +require "lcd" +require "math_ex" +require "pcm" +require "playlist" +require "print" +--require "settings" --uses a lot of memory +require "sound" +collectgarbage("collect") + +local sDumpFile = "/rb-lua_functions.txt" +local filehandle + +local function a2m_m2a(addr_member) + --turns members into addresses; addresses back into members + return addr_member +end + +local function dtTag(sType) +--convert named type; 'number'.. to short type '[n]...' +--if '?' supplied print out datatype key; number = [n]... + local retType = "?" + local typ = { + ["nil"] = "nil", + ["boolean"] = "b", + ["number"] = "n", + ["string"] = "s", + ["userdata"] = "u", + ["function"] = "f", + ["thread"] = "thr", + ["table"] = "t" + } + if sType == "?" then retType = "Datatypes: " end + for k,v in pairs(typ) do + if sType == k then + retType = v break + elseif (sType == "?") then + retType = retType .. " [" ..v.. "] = " .. k + end + end + return " [" ..retType.. "] " +end + +local function tableByName(tName) + --find the longest match possible to an actual table + --Name comes in as (table) tName.var so we can pass back out the name found PITA + --returns the table found (key and value) + local ld = {} + local sMatch = "" + local kMatch = nil + local vMatch = nil + +----FUNCTIONS for tableByName ----------------------------------------------------- + local function search4Str(n, k, v) + local sKey = tostring(k) + if string.find (n, sKey,1,true) then + if sKey:len() > sMatch:len() then sMatch = sKey kMatch = k vMatch = v end + --find the longest match we can + end + end +----END FUNCTIONS for tableByName ------------------------------------------------- + + if tName.val ~= nil and tName.val ~= "" then + for k, v in pairs(_G) do + --_G check both since some tables are only in _G or package.loaded + search4Str(tName.val, k, v) + end + for k, v in pairs(package.loaded) do --package.loaded + search4Str(tName.val, k, v) + end + if not string.find (sMatch, "_G",1,true) then sMatch = "_G." .. sMatch end + -- put the root _G in if not exist + if kMatch and vMatch then ld[kMatch] = vMatch tName.val = sMatch return ld end + end + tName.val = "_G" + return package.loaded --Not Found return default +end + +local function dump_Tables(tBase, sFunc, tSeen, tRet) + --Based on: http://www.lua.org/cgi-bin/demo?globals + --Recurse through tBase tables copying all found Tables + local sSep="" + local ld={} + local tNameBuf = {} + local sName + if sFunc ~= "" then sSep = "." end + + for k, v in pairs(tBase) do + k = tostring(k) + tNameBuf[1] = sFunc + tNameBuf[2] = sSep + tNameBuf[3] = k + + + if k ~= "loaded" and type(v) == "table" and not tSeen[v] then + tSeen[v]=sFunc + sName = table.concat(tNameBuf) + tRet[sName] = a2m_m2a(v) --place all keys into ld[i]=value + dump_Tables(v, sName, tSeen, tRet) + elseif type(v) == "table" and not tSeen[v] then + tSeen[v]=sFunc + tRet[table.concat(tNameBuf)] = a2m_m2a(v) -- dump 'loaded' table + for k1, v1 in pairs(v) do + if not _G[k1] and type(v1) == "table" and not tSeen[v1] then + -- dump tables that are loaded but not global + tSeen[v1]=sFunc + tNameBuf[3] = k1 + sName = table.concat(tNameBuf) + tRet[sName] = a2m_m2a(v1) --place all keys into ld[i]=value + dump_Tables(v1, sName, tSeen, tRet) + end + end + end + end +end + +local function dump_Functions(tBase) + --Based on: http://www.lua.org/cgi-bin/demo?globals + --We already recursed through tBase copying all found tables + --we look up the table by name and then (ab)use a2m_m2a() to load the address + --after finding the table by address in tBase we will + --put the table address of tFuncs in its place + local tFuncBuf = {} + for k,v in pairs(tBase) do + local tTable = a2m_m2a(v) + local tFuncs = {} + + for key, val in pairs(tTable) do + if key ~= "loaded" then + tFuncBuf[1] = dtTag(type(val)) + tFuncBuf[2] = tostring(key) + tFuncs[table.concat(tFuncBuf)]= val + --put the name and value in our tFuncs table + end + end + tBase[k] = a2m_m2a(tFuncs) -- copy the address back to tBase + end + +end + +local function get_common_branches(t, tRet) + --load t 'names(values)' into keys + --strip off long paths then iterate value if it exists + --local tRet={} + local sBranch = "" + local tName = {} + for k in pairs(t) do + tName["val"]=k + tableByName(tName) + sBranch = tName.val + if tRet[sBranch] == nil then + tRet[sBranch] = 1 --first instance of this branch + else + tRet[sBranch] = tRet[sBranch] + 1 + end + end +end + +local function pairsByPairs (t, tkSorted) + --tkSorted should be an already sorted (i)table with t[keys] in the values + --https://www.lua.org/pil/19.3.html + --!!Note: table sort default function does not like numbers as [KEY]!! + --see *sortbyKeys*cmp_alphanum* + + local i = 0 -- iterator variable + local iter = function () -- iterator function + i = i + 1 + if tkSorted[i] == nil then return nil + else return tkSorted[i], t[tkSorted[i]] + end + end + return iter +end + +local function sortbyKeys(t, tkSorted) + --loads keys of (t) into values of tkSorted + --and then sorts them + --tkSorted has integer keys (see ipairs) +----FUNCTIONS for sortByKeys ------------- + local cmp_alphanum = function (op1, op2) + local type1= type(op1) + local type2 = type(op2) + if type1 ~= type2 then + return type1 < type2 + else + return op1 < op2 + end + end +----END FUNCTIONS for sortByKeys --------- + for n in pairs(t) do table.insert(tkSorted, n) end + table.sort(tkSorted, cmp_alphanum)--table.sort(tkSorted) +end + +local function funcprint(tBuf, strName, value) + local sType = type(value) + local sVal = "" + local sHex = "" + tBuf[#tBuf + 1] = "\t" + tBuf[#tBuf + 1] = strName + if nil ~= string.find (";string;number;userdata;boolean;", sType, 1, true) then + --If any of the above types print the contents of variable + sVal = tostring(value) + + if type(value) == "number" then + sHex = " = 0x" .. string.format("%x", value) + else + sHex = "" + sVal = string.gsub(sVal, "\n", "\\n") --replace newline with \n + end + tBuf[#tBuf + 1] = " : " + tBuf[#tBuf + 1] = sVal + tBuf[#tBuf + 1] = sHex + end + tBuf[#tBuf + 1] = "\r\n" +end + +local function errorHandler( err ) + filehandle:write(" ERROR:" .. err .. "\n") +end + + +------------MAIN---------------------------------------------------------------- + local _NIL = nil + local tSeen= {} + local tcBase = {} + local tkSortCbase = {} + local tMods= {} + local tkSortMods = {} + local tWriteBuf = {} + local n = 0 -- count of how many items were found + + filehandle = io.open(sDumpFile, "w+") --overwrite + tWriteBuf[#tWriteBuf + 1] = "*Loaded Modules* \n" + + xpcall( function() + dump_Tables(tableByName({["val"] = "_G"}),"", tSeen, tMods) + --you can put a table name here if you just wanted to display + --only its items, ex. "os" or "rb" or "io" + --However, it has to be accessible directly from _G + --so "rb.actions" wouldn't return anything since its technically + --enumerated through _G.rb + end , errorHandler ) + tSeen = nil + + xpcall( function()dump_Functions(tMods)end , errorHandler ) + + get_common_branches(tMods, tcBase) + + sortbyKeys(tcBase, tkSortCbase) + sortbyKeys(tMods, tkSortMods) + + for k, v in pairsByPairs(tcBase, tkSortCbase ) do + n = n + 1 + if n ~= 1 then + tWriteBuf[#tWriteBuf + 1] = ", " + end + tWriteBuf[#tWriteBuf + 1] = tostring(k) + if n >= 3 then -- split loaded modules to multiple lines + n = 0 + tWriteBuf[#tWriteBuf + 1] = "\r\n" + end + if #tWriteBuf > 25 then + filehandle:write(table.concat(tWriteBuf)) + for i=1, #tWriteBuf do tWriteBuf[i] = _NIL end -- reuse table + end + end + + tcBase= nil tkSortCbase= nil + tWriteBuf[#tWriteBuf + 1] = "\r\n" + tWriteBuf[#tWriteBuf + 1] = dtTag("?") + tWriteBuf[#tWriteBuf + 1] = "\r\n\r\n" + tWriteBuf[#tWriteBuf + 1] = "Functions: \r\n" + + n = 0 + for key, val in pairsByPairs(tMods, tkSortMods) do + local tkSorted = {} + local tFuncs = a2m_m2a(val) + sortbyKeys(tFuncs, tkSorted) + tWriteBuf[#tWriteBuf + 1] = "\r\n" + tWriteBuf[#tWriteBuf + 1] = tostring(key) + tWriteBuf[#tWriteBuf + 1] = "\r\n" + for k, v in pairsByPairs(tFuncs, tkSorted) do + n = n + 1 + funcprint(tWriteBuf, k,v) + if #tWriteBuf > 25 then + filehandle:write(table.concat(tWriteBuf)) + for i=1, #tWriteBuf do tWriteBuf[i] = _NIL end -- reuse table + end + end + end + tWriteBuf[#tWriteBuf + 1] = "\r\n\r\n" + tWriteBuf[#tWriteBuf + 1] = n + tWriteBuf[#tWriteBuf + 1] = " Items Found \r\n" + filehandle:write(table.concat(tWriteBuf)) + for i=1, #tWriteBuf do tWriteBuf[i] = _NIL end -- empty table + filehandle:close() + rb.splash(rb.HZ * 5, n .. " Items dumped to : " .. sDumpFile) + --rb.splash(500, collectgarbage("count")) diff --git a/apps/plugins/lua_scripts/printmenu.lua b/apps/plugins/lua_scripts/printmenu.lua new file mode 100755 index 0000000000..3cb17d8026 --- /dev/null +++ b/apps/plugins/lua_scripts/printmenu.lua @@ -0,0 +1,83 @@ +--[[ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2017 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. + * + ****************************************************************************/ +]] +if not rb.lcd_framebuffer then rb.splash(rb.HZ, "No Support!") return nil end + +require("printtable") +local _clr = require("color") + +local _LCD = rb.lcd_framebuffer() +-------------------------------------------------------------------------------- +-- displays text in menu_t calls function in same indice of func_t when selected +function print_menu(menu_t, func_t, selected, settings, copy_screen) + + local i, start, vcur, screen_img + + if selected then vcur = selected + 1 end + if vcur and vcur <= 1 then vcur = 2 end + + if not settings then + settings = {} + settings.justify = "center" + settings.wrap = true + settings.hfgc = _clr.set( 0, 000, 000, 000) + settings.hbgc = _clr.set(-1, 255, 255, 255) + settings.ifgc = _clr.set(-1, 000, 255, 060) + settings.ibgc = _clr.set( 0, 000, 000, 000) + settings.iselc = _clr.set( 1, 000, 200, 100) + settings.default = true + end + + settings.hasheader = true + settings.co_routine = nil + settings.msel = false + settings.start = start + settings.curpos = vcur + + while not i or i > 0 do + if copy_screen == true then + --make a copy of screen for restoration + screen_img = screen_img or rb.new_image() + screen_img:copy(_LCD) + else + screen_img = nil + end + + _LCD:clear(settings.ibgc) + + settings.start = start + settings.curpos = vcur + + i, start, vcur = print_table(menu_t, #menu_t, settings) + --vcur = vcur + 1 + collectgarbage("collect") + if copy_screen == true then _LCD:copy(screen_img) end + + if func_t and func_t[i] then + if func_t[i](i, menu_t) == true then break end + else + break + end + end + if settings.default == true then settings = nil end + return screen_img +end diff --git a/apps/plugins/lua_scripts/tagnav.lua b/apps/plugins/lua_scripts/tagnav.lua new file mode 100644 index 0000000000..35e691045d --- /dev/null +++ b/apps/plugins/lua_scripts/tagnav.lua @@ -0,0 +1,344 @@ +-- BILGUS 2018 + +--local scrpath = rb.current_path()" + +--package.path = scrpath .. "/?.lua;" .. package.path --add lua_scripts directory to path +require("printmenu") +require("printtable") +require("dbgettags") + +local _print = require("print") + +rb.actions = nil +package.loaded["actions"] = nil + +local sERROROPENING = "Error opening" +local sERRORMENUENTRY = "Error finding menu entry" + +local sBLANKLINE = "##sBLANKLINE##" +local sDEFAULTMENU = "customfilter" + +local sFILEOUT = "/.rockbox/tagnavi_custom.config" +local sFILEHEADER = "#! rockbox/tagbrowser/2.0" +local sMENUSTART = "%menu_start \"custom\" \"Database\"" +local sMENUTITLE = "title = \"fmt_title\"" + +local TAG_ARTIST, TAG_ALBARTIST, TAG_ALBUM, TAG_GENRE, TAG_COMPOSER = 1, 2, 3, 4, 5 +local ts_TAGTYPE = {"Artist", "AlbumArtist", "Album", "Genre", "Composer"} +local ts_DBPATH = {"database_0.tcd", "database_7.tcd", "database_1.tcd", "database_2.tcd", "database_5.tcd"} + +local COND_OR, COND_AND, COND_NOR, COND_NAND = 1, 2, 3, 4 +local ts_CONDITIONALS = {"OR", "AND", "!, OR", "!, AND"} +local ts_CONDSYMBOLS = {"|", "&", "|", "&"} + +local ts_YESNO = {"", "Yes", "No"} +local s_OVERWRITE = "Overwrite" +local s_EXISTS = "Exists" + + +local function question(tInquiry, start) + settings = {} + settings.justify = "center" + settings.wrap = true + settings.msel = false + settings.hasheader = true + settings.co_routine = nil + settings.curpos = start or 1 + local sel = print_table(tInquiry, #tInquiry, settings) + return sel +end + +local function find_linepos(t_lines, search, startline) + startline = startline or 1 + + for i = startline, #t_lines do + if string.match (t_lines[i], search) then + return i + end + end + + return -1 +end + +local function replacelines(t_lines, search, replace, startline) + startline = startline or 1 + repcount = 0 + for i = startline, #t_lines do + if string.match (t_lines[i], search) then + t_lines[i] = replace + repcount = repcount + 1 + end + end + return repcount +end + +local function replaceemptylines(t_lines, replace, startline) + startline = startline or 1 + replace = replace or nil + repcount = 0 + for i = startline, #t_lines do + if t_lines[i] == "" then + t_lines[i] = replace + repcount = repcount + 1 + end + end + return repcount +end + +local function checkexistingmenu(t_lines, menuname) + local pos = find_linepos(t_lines, "^\"" .. menuname .. "\"%s*%->.+") + local sel = 0 + if pos > 0 then + ts_YESNO[1] = menuname .. " " .. s_EXISTS .. ", " .. s_OVERWRITE .. "?" + sel = question(ts_YESNO, 3) + if sel == 3 then + pos = nil + elseif sel < 2 then + pos = 0 + end + else + pos = nil + end + return pos +end + +local function savedata(filename, ts_tags, cond, menuname) + menuname = menuname or sDEFAULTMENU + + local lines = {} + local curline = 0 + local function lines_next(str, pos) + pos = pos or #lines + 1 + lines[pos] = str or "" + curline = pos + end + + local function lines_append(str, pos) + pos = pos or curline or #lines + lines[pos] = lines[pos] .. str or "" + end + + if rb.file_exists(filename) ~= true then + lines_next(sFILEHEADER) + lines_next("#") + lines_next("# MAIN MENU") + lines_next(sMENUSTART) + else + local file = io.open(filename, "r") -- read + if not file then + rb.splash(rb.HZ, "Error opening" .. " " .. filename) + return + end + + for line in file:lines() do + lines_next(line) + end + file:close() + end + + local menupos = find_linepos(lines, sMENUSTART) + if menupos < 1 then + rb.splash(rb.HZ, sERRORMENUENTRY) + return + end + + replaceemptylines(lines, sBLANKLINE, menupos) + + local existmenupos = checkexistingmenu(lines, menuname) + if existmenupos and existmenupos < 1 then return end -- user canceled + + local lastcond = "" + local n_cond = COND_OR + local tags, tagtype + + local function buildtag(e_tagtype) + if ts_tags[e_tagtype] then + n_cond = (cond[e_tagtype] or COND_OR) + if e_tagtype > 1 then + lines_append(" " .. ts_CONDSYMBOLS[n_cond]) + end + tags = ts_tags[e_tagtype] + tagtype = string.lower(ts_TAGTYPE[e_tagtype]) + + if n_cond <= COND_AND then + lines_append(" " .. tagtype) + lines_append(" @ \"".. table.concat(tags, "|") .. "\"") + else + for k = 1, #tags do + lines_append(" " .. tagtype) + lines_append(" !~ \"".. tags[k] .. "\"") + if k < #tags then lines_append(" &") end + end + end + end + end + + if ts_tags[TAG_ARTIST] or ts_tags[TAG_ALBARTIST] or ts_tags[TAG_ALBUM] or + ts_tags[TAG_GENRE] or ts_tags[TAG_COMPOSER] then + + lines_next("\"" .. menuname .. "\" -> " .. sMENUTITLE .. " ?", existmenupos) + + buildtag(TAG_ARTIST) + buildtag(TAG_ALBARTIST) + buildtag(TAG_ALBUM) + buildtag(TAG_GENRE) + buildtag(TAG_COMPOSER) + + lines_next("\n") + else + rb.splash(rb.HZ, "Nothing to save") + end + + local file = io.open(filename, "w+") -- overwrite + if not file then + rb.splash(rb.HZ, "Error writing " .. filename) + return + end + + for i = 1, #lines do + if lines[i] and lines[i] ~= sBLANKLINE then + file:write(lines[i], "\n") + end + end + + file:close() +end + +-- uses print_table to display database tags +local function print_tags(ftable, settings, t_selected) + if not s_cond then s_sep = "|" end + ftable = ftable or {} + + if t_selected then + for k = 1, #t_selected do + ftable[t_selected[k]] = ftable[t_selected[k]] .. "\0" + end + end + rb.lcd_clear_display() + _print.clear() + + if not settings then + settings = {} + settings.justify = "center" + settings.wrap = true + settings.msel = true + end + + settings.hasheader = true + settings.co_routine = nil + + local sel = print_table(ftable, #ftable, settings) + + --_lcd:splashf(rb.HZ * 2, "%d items {%s}", #sel, table.concat(sel, ", ")) + local selected = {} + local str = "" + for k = 1,#sel do + str = ftable[sel[k]] or "" + selected[#selected + 1] = string.sub(str, 1, -2) -- REMOVE \0 + end + + ftable = nil + + if #sel == 0 then + return nil, nil + end + + return sel, selected +end -- print_tags + +-- uses print_table to display a menu +function main_menu() + local menuname = sDEFAULTMENU + local t_tags + local ts_tags = {} + local cond = {} + local sel = {} + local mt = { + [1] = "TagNav Customizer", + [2] = "", --ts_CONDITIONALS[cond[TAG_ARTIST] or COND_OR], + [3] = ts_TAGTYPE[TAG_ARTIST], + [4] = ts_CONDITIONALS[cond[TAG_ALBARTIST] or COND_OR], + [5] = ts_TAGTYPE[TAG_ALBARTIST], + [6] = ts_CONDITIONALS[cond[TAG_ALBUM] or COND_OR], + [7] = ts_TAGTYPE[TAG_ALBUM], + [8] = ts_CONDITIONALS[cond[TAG_GENRE] or COND_OR], + [9] = ts_TAGTYPE[TAG_GENRE], + [10] = ts_CONDITIONALS[cond[TAG_COMPOSER] or COND_OR], + [11] = ts_TAGTYPE[TAG_COMPOSER], + [12] = "Save to Tagnav", + [13] = "Exit" + } + + local function sel_cond(item, item_mt) + cond[item] = cond[item] or 1 + cond[item] = cond[item] + 1 + if cond[item] > #ts_CONDITIONALS then cond[item] = 1 end + mt[item_mt] = ts_CONDITIONALS[cond[item]] + end + + local function sel_tag(item, item_mt, t_tags) + t_tags = get_tags(rb.ROCKBOX_DIR .. "/" .. ts_DBPATH[item], ts_TAGTYPE[item]) + sel[item], ts_tags[item] = print_tags(t_tags, nil, sel[item]) + if ts_tags[item] then + mt[item_mt] = ts_TAGTYPE[item] .. " [" .. #sel[item] .. "]" + else + mt[item_mt] = ts_TAGTYPE[item] + end + end + + local ft = { + [0] = exit_now, --if user cancels do this function + [1] = function(TITLE) return true end, -- shouldn't happen title occupies this slot + [2] = function(ARTCOND) + sel_cond(TAG_ARTIST, ARTCOND) + end, + [3] = function(ART) + sel_tag(TAG_ARTIST, ART, t_tags) + end, + [4] = function(ALBARTCOND) + sel_cond(TAG_ALBARTIST, ALBARTCOND) + end, + [5] = function(ALBART) + sel_tag(TAG_ALBARTIST, ALBART, t_tags) + end, + [6] = function(ALBCOND) + sel_cond(TAG_ALBUM, ALBCOND) + end, + [7] = function(ALB) + sel_tag(TAG_ALBUM, ALB, t_tags) + end, + [8] = function(GENRECOND) + sel_cond(TAG_GENRE, GENRECOND) + end, + [9] = function(GENRE) + sel_tag(TAG_GENRE, GENRE, t_tags) + end, + [10] = function(COMPCOND) + sel_cond(TAG_COMPOSER, COMPCOND) + end, + [11] = function(COMP) + sel_tag(TAG_COMPOSER, COMP, t_tags) + end, + + [12] = function(SAVET) + menuname = menuname or sDEFAULTMENU + menuname = rb.kbd_input(menuname) + menuname = string.match(menuname, "%w+") + if menuname == "" then menuname = nil end + menuname = menuname or sDEFAULTMENU + savedata(sFILEOUT, ts_tags, cond, menuname) + end, + [13] = function(EXIT_) return true end + } + + print_menu(mt, ft, 2) --start at item 2 + +end + +function exit_now() + rb.lcd_update() + os.exit() +end -- exit_now + +main_menu() +exit_now() -- cgit v1.2.3