2
0
mirror of https://github.com/koreader/koreader synced 2024-11-18 03:25:46 +00:00
koreader/frontend/ui/menusorter.lua

223 lines
8.3 KiB
Lua

--[[--
This module is responsible for constructing the KOReader menu based on a list of
menu_items and a separate menu order.
]]
local DataStorage = require("datastorage")
local FFIUtil = require("ffi/util")
local lfs = require("libs/libkoreader-lfs")
local logger = require("logger")
local _ = require("gettext")
local separator_id = "----------------------------"
local MenuSorter = {
orphaned_prefix = _("NEW: "),
separator = {
id = separator_id,
text = "KOMenu:separator",
},
}
function MenuSorter:readMSSettings(config_prefix)
if config_prefix then
local menu_order = string.format(
"%s/%s_menu_order", DataStorage:getSettingsDir(), config_prefix)
if lfs.attributes(menu_order..".lua") then
return require(menu_order) or {}
end
end
return {}
end
function MenuSorter:mergeAndSort(config_prefix, item_table, order)
local user_order = self:readMSSettings(config_prefix)
if user_order then
for user_order_id, user_order_item in pairs(user_order) do
order[user_order_id] = user_order_item
end
end
return self:sort(item_table, order)
end
--- Sorts a flat table of menu items into a hierarchical menu based on supplied order.
---- @tparam table item_table menu item table
---- @tparam table order sorting order
---- @treturn table the sorted menu item table
function MenuSorter:sort(item_table, order)
local menu_table = {}
local sub_menus = {}
-- the actual sorting of menu items
for order_id, order_item in pairs(order) do
-- user might define non-existing menu item
if item_table[order_id] ~= nil then
local tmp_menu_table = {}
menu_table[order_id] = item_table[order_id]
menu_table[order_id].id = order_id
for order_number, order_number_id in ipairs(order_item) do
-- this is a submenu, mark it for later
if item_table[order_number_id] ~= nil and order[order_number_id] then
table.insert(sub_menus, order_number_id)
tmp_menu_table[order_number] = {
id = order_number_id,
}
-- regular, just insert a menu action
else
if order_number_id == separator_id then
-- it's a separator
tmp_menu_table[order_number] = self.separator
elseif item_table[order_number_id] ~= nil then
item_table[order_number_id].id = order_number_id
tmp_menu_table[order_number] = item_table[order_number_id]
-- remove reference from item_table so it won't show up as orphaned
item_table[order_number_id] = nil
end
end
end
-- compress menus
-- if menu_items were missing we might have a table with gaps
-- but ipairs doesn't like that and quits when it hits nil
local i = 1
local new_index = 1
while i <= table.maxn(tmp_menu_table) do
local v = tmp_menu_table[i]
if v then
if v.id == separator_id then
new_index = new_index - 1
-- ignore separator if the menu starts with it
if new_index > 0 then
menu_table[order_id][new_index].separator = true
end
else
-- fix the index
menu_table[order_id][new_index] = tmp_menu_table[i]
end
new_index = new_index + 1
end
i = i + 1
end
else
if order_id ~= "KOMenu:disabled" and order_id ~="plus_menu" then
--"plus_menu" break an assumption of the menu_sorter, but it's ok, so ignore it.
--See : https://github.com/koreader/koreader/pull/3844#issuecomment-383092219
logger.warn("menu id not found:", order_id)
end
end
end
-- We should not rely on Lua to magically order the items as we expected:
-- Some menu items cannot be referred until its parent menu item is inserted into
-- menu_table["KOMenu:menu_buttons"].
-- So we loop until nothing changed anymore.
local changed = true
while changed do
changed = false
-- now do the submenus
for i, sub_menu in ipairs(sub_menus) do
if menu_table[sub_menu] ~= nil then
local sub_menu_position = self:findById(menu_table["KOMenu:menu_buttons"], sub_menu)
if sub_menu_position then
changed = true
local sub_menu_content = menu_table[sub_menu]
sub_menu_position.text = sub_menu_content.text
sub_menu_position.hold_callback = sub_menu_content.hold_callback
sub_menu_position.sub_item_table = sub_menu_content
-- remove reference from top level output
menu_table[sub_menu] = nil
-- remove reference from input so it won't show up as orphaned
item_table[sub_menu] = nil
end
end
end
end
-- cleanup, top-level items shouldn't have sub_item_table
-- they should, however have one going in
-- Also, compress the menu table.
local menu_buttons_offset = 0
for i, top_menu in ipairs(menu_table["KOMenu:menu_buttons"]) do
local menu_button = menu_table["KOMenu:menu_buttons"][i].sub_item_table
menu_table["KOMenu:menu_buttons"][i] = nil
if menu_button then
menu_table["KOMenu:menu_buttons"][i-menu_buttons_offset] = menu_button
else
menu_buttons_offset = menu_buttons_offset + 1
end
end
-- handle disabled
if order["KOMenu:disabled"] then
for _, item in ipairs(order["KOMenu:disabled"]) do
if item_table[item] then
-- remove reference from input so it won't show up as orphaned
item_table[item] = nil
end
end
end
-- remove top level reference before orphan handling
item_table["KOMenu:menu_buttons"] = nil
-- attach orphans based on sorting_hint, or with a NEW prefix in the first menu if none found
for k, v in FFIUtil.orderedPairs(item_table) do
local sorting_hint = v.sorting_hint
-- normally there should be menu text but check to be sure
if v.text and v.new ~= true then
v.id = k
if not sorting_hint then v.text = self.orphaned_prefix .. v.text end
-- prevent text being prepended to item on menu reload, i.e., on switching between reader and filemanager
v.new = true
-- deal with orphaned submenus
if #v > 0 then
v.sub_item_table = {}
for i=1, #v do
v.sub_item_table[i] = v[i]
end
end
end
if sorting_hint then
local sorting_hint_menu = self:findById(menu_table["KOMenu:menu_buttons"], sorting_hint)
sorting_hint_menu = sorting_hint_menu.sub_item_table or sorting_hint_menu
table.insert(sorting_hint_menu, v)
else
table.insert(menu_table["KOMenu:menu_buttons"][1], v)
end
end
return menu_table["KOMenu:menu_buttons"]
end
--- Returns a menu item by ID.
---- @tparam table tbl Lua table
---- @tparam string needle_id Menu item ID string
---- @treturn table a reference to the table item if found
function MenuSorter:findById(tbl, needle_id)
local items = {}
for _, item in pairs(tbl) do
if item ~= "KOMenu:menu_buttons" then
table.insert(items, item)
end
end
local k, v
k, v = next(items, nil)
while k do
local id_match = v.id == needle_id
local sub_table = v.sub_item_table or type(v) == "table" and v
if id_match then
return v
elseif sub_table then
for _, item in pairs(sub_table) do
if type(item) == "table" and item.id then
table.insert(items, item)
end
end
end
k, v = next(items, k)
end
end
return MenuSorter