@@ -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