TouchMenu: Search menu to search the menu (#9876)

Fixes #9800.
reviewable/pr9884/r1
Frans de Jonge 1 year ago committed by GitHub
parent 57ef9e6a55
commit e58a12ba04
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1385,4 +1385,11 @@ function FileManager:onRefreshContent()
self:onRefresh()
end
function FileManager:onMenuSearch()
if not self.ui then
self.menu:onShowMenu()
end
self.menu.menu_container[1]:onShowMenuSearch()
end
return FileManager

@ -871,4 +871,11 @@ function ReaderUI:getCurrentPage()
end
end
function ReaderUI:onMenuSearch()
if not self.ui then
self.menu:onShowMenu()
end
self.menu.menu_container[1]:onShowMenuSearch()
end
return ReaderUI

@ -97,6 +97,7 @@ local settingsList = {
fulltext_search = {category="none", event="ShowFulltextSearchInput", title=_("Fulltext search"), general=true},
file_search = {category="none", event="ShowFileSearch", title=_("File search"), general=true, separator=true},
show_menu = {category="none", event="ShowMenu", title=_("Show menu"), general=true},
menu_search = {category="none", event="MenuSearch", title=_("Menu search"), general=true},
favorites = {category="none", event="ShowColl", arg="favorites", title=_("Favorites"), general=true},
screenshot = {category="none", event="Screenshot", title=_("Screenshot"), general=true, separator=true},
@ -227,6 +228,7 @@ local dispatcher_menu_order = {
"file_search",
"show_menu",
"menu_search",
"screenshot",
"exit_screensaver",

@ -609,4 +609,12 @@ common_settings.units = {
},
}
common_settings.search_menu = {
text = _("Menu search"),
callback = function()
UIManager:sendEvent(Event:new("ShowMenuSearch"))
end,
keep_menu_open = true,
}
return common_settings

@ -169,6 +169,8 @@ local order = {
help = {
"quickstart_guide",
"----------------------------",
"search_menu",
"----------------------------",
"report_bug",
"----------------------------",
"system_statistics", -- if enabled (Plugin)

@ -222,6 +222,8 @@ local order = {
help = {
"quickstart_guide",
"----------------------------",
"search_menu",
"----------------------------",
"report_bug",
"----------------------------",
"system_statistics", -- if enabled (Plugin)

@ -26,12 +26,14 @@ local Size = require("ui/size")
local TextWidget = require("ui/widget/textwidget")
local UIManager = require("ui/uimanager")
local UnderlineContainer = require("ui/widget/container/underlinecontainer")
local Utf8Proc = require("ffi/utf8proc")
local VerticalGroup = require("ui/widget/verticalgroup")
local VerticalSpan = require("ui/widget/verticalspan")
local datetime = require("datetime")
local getMenuText = require("ui/widget/menu").getMenuText
local _ = require("gettext")
local T = require("ffi/util").template
local ffiUtil = require("ffi/util")
local T = ffiUtil.template
local Input = Device.input
local Screen = Device.screen
@ -477,6 +479,7 @@ function TouchMenu:init()
end
self.layout = {}
self.search_layout = {}
self.ges_events.TapCloseAllMenus = {
GestureRange:new{
@ -672,8 +675,10 @@ function TouchMenu:updateItems()
self:_recalculatePageLayout()
self.item_group:clear()
self.layout = {}
self.search_layout = {}
table.insert(self.item_group, self.bar)
table.insert(self.layout, self.bar.icon_widgets) -- for the focusmanager
table.insert(self.search_layout, self.bar.icon_widgets) -- for the menu search
for c = 1, self.perpage do
-- calculate index in item_table
@ -681,6 +686,7 @@ function TouchMenu:updateItems()
if i <= #self.item_table then
local item = self.item_table[i]
local item_tmp = TouchMenuItem:new{
name = "touch_menu_item " .. tostring(item.text) .. " xxx", -- xxx testing only, squish later
item = item,
menu = self,
dimen = Geom:new{
@ -693,6 +699,7 @@ function TouchMenu:updateItems()
if item_tmp:isEnabled() then
table.insert(self.layout, {[self.cur_tab] = item_tmp}) -- for the focusmanager
end
table.insert(self.search_layout, {[self.cur_tab] = item_tmp}) -- for the menu search
if item.separator and c ~= self.perpage and i ~= #self.item_table then
-- insert split line
table.insert(self.item_group, self.split_line)
@ -835,6 +842,18 @@ function TouchMenu:onLastPage()
return true
end
function TouchMenu:onGotoPage(nb)
if nb > self.page_num then
self.page = self.page_num
elseif nb < 1 then
self.page = 1
else
self.page = nb
end
self:updateItems()
return true
end
function TouchMenu:onSwipe(arg, ges_ev)
local direction = BD.flipDirectionIfMirroredUILayout(ges_ev.direction)
if direction == "west" then
@ -973,4 +992,365 @@ function TouchMenu:onBack()
self:backToUpperMenu()
end
------ the menu search functionality
function TouchMenu:search(search_for)
local found_menu_items = {}
local MAX_MENU_DEPTH = 20 -- currently our menu needs at least 12 here
local function recurse(val, path, text, icon, depth)
depth = depth + 1
if depth > MAX_MENU_DEPTH then
return
end
for i,v in ipairs(val) do
if type(v) == "table" then
local entry_text = v.text_func and v.text_func() or v.text
local indent = text and ((""):rep(math.min(depth-1, 6)) .. "→ ") or "→ " -- all spaces here are Hair Space U+200A
local next_text = text and (text .. "\n" .. indent .. entry_text) or (indent .. entry_text)
local next_path = path .. "." .. i
recurse(val[i], next_path, next_text, icon, depth)
if Utf8Proc.lowercase(entry_text):find(search_for) then
table.insert(found_menu_items, {entry_text, icon, next_path, next_text})
end
end
end
if val.sub_item_table_func then
local sub_item_table = val.sub_item_table_func()
local perpage = ""
if sub_item_table.max_per_page then
perpage = "[" .. sub_item_table.max_per_page .."]"
end
recurse(sub_item_table, path .. ".sub_item_table_func" .. perpage, text, icon, depth)
elseif val.sub_item_table then
local perpage = ""
if val.sub_item_table.max_per_page then
perpage = "[" .. val.sub_item_table.max_per_page .."]"
end
recurse(val.sub_item_table, path .. ".sub_item_table" .. perpage, text, icon, depth)
end
end -- recurse
-- initial call of recurse
for i = 1, #self.tab_item_table do
recurse(self.tab_item_table[i], i, self.tab_item_table[i].text, self.tab_item_table[i].icon, 0)
end
--[[
for i = 1, #found_menu_items do
print("xxxxxxx->", i,
found_menu_items[i][1],
found_menu_items[i][2],
found_menu_items[i][3])
end
]]
return found_menu_items
end
-- maybe this could be placed in UIManager?
local function highlightWidget(widget, unhilight_in_s)
if not widget then return end
local highlight_dimen = widget.dimen
if highlight_dimen.w == 0 then
highlight_dimen.w = widget.width
end
UIManager:nextTick(function()
-- Highlight
widget.invert = true
UIManager:widgetInvert(widget, highlight_dimen.x, highlight_dimen.y, highlight_dimen.w)
UIManager:setDirty(nil, "fast", highlight_dimen)
UIManager:forceRePaint()
UIManager:yieldToEPDC()
end)
if unhilight_in_s then
-- Unhighlight
UIManager:scheduleIn(unhilight_in_s, function()
widget.invert = false
UIManager:widgetInvert(widget, highlight_dimen.x, highlight_dimen.y, highlight_dimen.w)
UIManager:setDirty(nil, "ui", highlight_dimen)
end)
end
end
function TouchMenu:_get_widget(tab_nb, nb)
return self.search_layout[nb + 1][tab_nb]
end
function TouchMenu:openMenu(path)
local TrapWidget = require("ui/widget/trapwidget")
local animation_time_s = G_reader_settings:readSetting("menu_search_animation_time_s", 1.0)
-- first switch to correct MenuTab
local sep_pos = path:find("%.")
local tab_nb = tonumber(path:sub(1, sep_pos - 1))
if not tab_nb then return end
path = path:sub(sep_pos + 1)
local item = self.tab_item_table[tab_nb]
self:switchMenuTab(tab_nb)
self.bar:switchToTab(tab_nb)
self:onMenuSelect(self.item_table)
-- Now go the menu path down the way
local items_to_show = {}
local dummy, num_of_sep = path:gsub("%.", "%.")
while num_of_sep > 1 do
sep_pos = path:find("%.")
local identifier = path:sub(1, sep_pos -1)
local item_nb = tonumber(identifier)
path = path:sub(sep_pos + 1)
if item_nb then
self:updateItems()
item = item[item_nb]
table.insert(items_to_show, {item_nb, item})
elseif identifier:find("sub_item_table_func") then
item = item.sub_item_table_func()
elseif identifier:find("sub_item_table") then
item = item.sub_item_table
end
num_of_sep = num_of_sep - 1
end
-- Now we are in the right menu, but maybe in the wrong page
sep_pos = path:find("%.")
if not sep_pos then
local logger = require("logger")
logger.err("TouchMenu: search; internal error") -- should not happen
return
end
local identifier = path:sub(sep_pos + 1)
local item_nb = tonumber(identifier)
local perpage
if path:find("sub_item_table_func%[") then
perpage = path:sub(#("sub_item_table_func%["), sep_pos - 2)
elseif path:find("sub_item_table%[") then
perpage = path:sub(#("sub_item_table%["), sep_pos - 2)
end
perpage = tonumber(perpage) or self.perpage
local function open_final_menu()
print("xxx", #items_to_show)
if #items_to_show > 0 then -- can be zero, if in the last menu
self:onMenuSelect(items_to_show[#items_to_show][2])
end
local page_nb = math.floor((item_nb - 1) / perpage) + 1
self:onGotoPage(page_nb)
end
if not (animation_time_s and animation_time_s > 0.0) then
open_final_menu()
else
self.trap_widget = TrapWidget:new{
dismiss_callback = function()
animation_time_s = 0
self.trap_widget = nil
end
}
UIManager:show(self.trap_widget) -- suppress taps during animaton
-- Animation functions
local function open_next_page(pages_to_show, force_first_page)
if animation_time_s == 0.0 then
UIManager.close(self.trap_widget)
self.trap_widget = nil
open_final_menu()
-- end of the animation here!
return
end
print("xxx pages_to_show", pages_to_show, force_first_page)
if force_first_page then
self:onFirstPage()
end
if pages_to_show == 1 then -- if we are on the last page
if self.page_num > 1 then
highlightWidget(self.page_info_right_chev, animation_time_s)
end
highlightWidget(self:_get_widget(tab_nb, (item_nb - 1) % perpage + 1))
-- end of the animation here!
else
if self.page_num ~= 1 then
highlightWidget(self.page_info_right_chev)
end
UIManager:scheduleIn(animation_time_s, function()
self:onNextPage()
if pages_to_show > 1 then
UIManager:nextTick(open_next_page, pages_to_show - 1, false)
end
end)
end
end
local function open_next_menu()
if animation_time_s == 0.0 then
UIManager:close(self.trap_widget)
self.trap_widget = nil
open_final_menu()
-- end of the animation here!
return
end
local x = table.remove(items_to_show, 1)
local next_menu_num, next_menu_item = x and x[1], x and x[2] -- might be nil
if next_menu_item then
highlightWidget(self:_get_widget(tab_nb, next_menu_num))
UIManager:scheduleIn(animation_time_s, function()
self:onMenuSelect(next_menu_item)
UIManager:nextTick(open_next_menu)
end)
else
-- no items left, but maybe on another page
local page_nb = math.floor(item_nb / perpage) + 1
UIManager:nextTick(open_next_page, page_nb, page_nb > 1)
end
end
-- Animate
UIManager:nextTick(open_next_menu)
end
end
function TouchMenu:onShowMenuSearch()
local InputDialog = require("ui/widget/inputdialog")
local CheckButton = require("ui/widget/checkbutton")
local ConfirmBox = require("ui/widget/confirmbox")
local Menu = require("ui/widget/menu")
local function show_search_results(search_string)
local found_menu_items = self:search(search_string)
local function get_current_search_results()
local function item_callback(i)
UIManager:close(self.results_menu_container)
UIManager:setDirty(nil, "ui")
self:openMenu(found_menu_items[i][3])
end
local function item_hold_callback(i)
local confirm_box
confirm_box = ConfirmBox:new{
text = T(_("Open menu entry:\n'%1'\n\n%2"), found_menu_items[i][1], found_menu_items[i][4]),
icon = found_menu_items[i][2],
ok_text = _("Open"),
ok_callback = function()
UIManager:close(confirm_box)
item_callback(i)
end,
cancel_text = _("Dismiss"),
width_percent = 0.95,
}
UIManager:show(confirm_box)
end
local result_items = {}
for i = 1, #found_menu_items do
table.insert(result_items,
{
text = found_menu_items[i][1],
callback = function() item_callback(i) end,
hold_callback = function() item_hold_callback(i) end,
}
)
end
return result_items
end -- get_current_search_results()
if #found_menu_items > 0 then
local results_menu = Menu:new{
title = _("Search results"),
item_table = get_current_search_results(found_menu_items),
item_shortcuts = {},
width = math.floor(Screen:getWidth() * 0.9),
height = math.floor(Screen:getHeight() * 0.9),
single_line = true,
items_per_page = 10,
items_font_size = Menu.getItemFontSize(10),
onMenuSelect = function(item, pos)
if pos.callback then pos.callback() end
end,
onMenuHold = function(item, pos)
if pos.hold_callback then pos.hold_callback() end
end,
close_callback = function()
UIManager:close(self.results_menu_container)
end
}
-- build container
self.results_menu_container = CenterContainer:new{
dimen = Screen:getSize(),
results_menu,
}
results_menu.show_parent = self.results_menu_container
UIManager:show(self.results_menu_container)
else
UIManager:show(InfoMessage:new{
text = T(_("No menus containing '%1' found."), search_string),
})
end
end -- show_search_results()
local search_dialog
search_dialog = InputDialog:new{
title = _("Search menu entry"),
description = _("Search for a menu entry containing the following text (case insensitive)."),
input = G_reader_settings:readSetting("menu_search_string", _("Help")),
buttons = {
{
{
text = _("Cancel"),
id = "close",
callback = function()
UIManager:close(search_dialog)
end,
},
{
text = _("Search"),
callback = function()
local search_for = search_dialog:getInputText()
local status, err = pcall( function() ("test_string"):find(search_for) end)
if status then
search_for = Utf8Proc.lowercase(search_for)
G_reader_settings:saveSetting("menu_search_string", search_for)
UIManager:close(search_dialog)
show_search_results(search_for)
else
err = err:sub(err:find("lua") + 10) -- 10 = strlen("lua:1165: ")
UIManager:show(InfoMessage:new{
text = T(_("Malformed message:\n%1"), err)
})
end
end,
},
}
},
}
local animation_time_s = G_reader_settings:readSetting("menu_search_animation_time_s", 1.0)
local check_button_animation = CheckButton:new{
text = _("Animation"),
checked = animation_time_s ~= 0.0,
parent = search_dialog,
callback = function()
animation_time_s = animation_time_s ~= 0.0 and 0.0 or 1.0
G_reader_settings:saveSetting("menu_search_animation_time_s", animation_time_s)
end,
}
search_dialog:addWidget(check_button_animation)
UIManager:show(search_dialog)
search_dialog:onShowKeyboard()
end
return TouchMenu

Loading…
Cancel
Save