2014-10-14 13:33:13 +00:00
local Button = require ( " ui/widget/button " )
2017-04-29 08:38:09 +00:00
local CenterContainer = require ( " ui/widget/container/centercontainer " )
2018-06-13 22:46:52 +00:00
local ConfirmBox = require ( " ui/widget/confirmbox " )
2014-10-30 18:42:18 +00:00
local Device = require ( " device " )
2013-10-22 15:11:31 +00:00
local Event = require ( " ui/event " )
2014-06-10 13:32:49 +00:00
local Font = require ( " ui/font " )
2017-04-29 08:38:09 +00:00
local GestureRange = require ( " ui/gesturerange " )
local Geom = require ( " ui/geometry " )
local InputContainer = require ( " ui/widget/container/inputcontainer " )
local Menu = require ( " ui/widget/menu " )
local UIManager = require ( " ui/uimanager " )
2013-10-18 20:38:07 +00:00
local _ = require ( " gettext " )
2017-04-29 08:38:09 +00:00
local Screen = Device.screen
2013-10-18 20:38:07 +00:00
local ReaderToc = InputContainer : new {
2014-03-13 13:52:43 +00:00
toc = nil ,
2014-10-07 09:09:37 +00:00
ticks = { } ,
2014-10-14 13:33:13 +00:00
toc_indent = " " ,
collapsed_toc = { } ,
collapse_depth = 2 ,
expanded_nodes = { } ,
2014-03-13 13:52:43 +00:00
toc_menu_title = _ ( " Table of contents " ) ,
2018-06-13 22:46:52 +00:00
alt_toc_menu_title = _ ( " Table of contents * " ) ,
2012-05-27 11:47:22 +00:00
}
2012-12-31 00:13:46 +00:00
function ReaderToc : init ( )
2014-03-13 13:52:43 +00:00
if Device : hasKeyboard ( ) then
self.key_events = {
ShowToc = {
{ " T " } ,
2014-06-05 06:58:53 +00:00
doc = " show Table of Content menu " } ,
2014-03-13 13:52:43 +00:00
}
end
if Device : isTouchDevice ( ) then
self.ges_events = {
ShowToc = {
GestureRange : new {
ges = " two_finger_swipe " ,
range = Geom : new {
x = 0 , y = 0 ,
w = Screen : getWidth ( ) ,
h = Screen : getHeight ( ) ,
} ,
direction = " east "
}
} ,
}
end
2014-10-15 12:47:13 +00:00
self : resetToc ( )
2014-03-13 13:52:43 +00:00
self.ui . menu : registerToMainMenu ( self )
2012-12-31 00:13:46 +00:00
end
2012-05-27 11:47:22 +00:00
function ReaderToc : cleanUpTocTitle ( title )
2014-03-13 13:52:43 +00:00
return ( title : gsub ( " \13 " , " " ) )
2012-05-27 11:47:22 +00:00
end
function ReaderToc : onSetDimensions ( dimen )
2014-03-13 13:52:43 +00:00
self.dimen = dimen
2012-05-27 11:47:22 +00:00
end
2014-10-15 12:47:13 +00:00
function ReaderToc : resetToc ( )
2014-03-13 13:52:43 +00:00
self.toc = nil
2014-10-10 10:13:14 +00:00
self.ticks = { }
2014-10-14 13:33:13 +00:00
self.collapsed_toc = { }
2016-12-04 14:13:00 +00:00
self.expanded_nodes = { }
2014-10-15 12:47:13 +00:00
end
function ReaderToc : onUpdateToc ( )
self : resetToc ( )
2014-03-13 13:52:43 +00:00
return true
2013-01-09 08:53:32 +00:00
end
2014-04-04 15:02:29 +00:00
function ReaderToc : onPageUpdate ( pageno )
self.pageno = pageno
2018-05-29 22:28:47 +00:00
if G_reader_settings : readSetting ( " full_refresh_count " ) == - 1 then
if self : isChapterEnd ( pageno , 0 ) then
self.chapter_refresh = true
elseif self : isChapterBegin ( pageno , 0 ) and self.chapter_refresh then
UIManager : setDirty ( " all " , " full " )
self.chapter_refresh = false
else
self.chapter_refresh = false
end
end
2014-04-04 15:02:29 +00:00
end
2017-10-05 19:49:59 +00:00
function ReaderToc : onPosUpdate ( pos , pageno )
self.pageno = pageno
end
2013-01-01 19:37:36 +00:00
function ReaderToc : fillToc ( )
2014-10-07 09:09:37 +00:00
if self.toc and # self.toc > 0 then return end
2018-06-13 22:46:52 +00:00
if self.ui . document : canHaveAlternativeToc ( ) then
if self.ui . doc_settings : readSetting ( " alternative_toc " ) then
-- (if the document has a cache, the previously built alternative
-- TOC was saved and has been reloaded, and this will be avoided)
if not self.ui . document : isTocAlternativeToc ( ) then
self : resetToc ( )
self.ui . document : buildAlternativeToc ( )
end
end
end
2014-03-13 13:52:43 +00:00
self.toc = self.ui . document : getToc ( )
2013-01-01 19:37:36 +00:00
end
2012-05-27 13:24:33 +00:00
2015-02-01 14:50:08 +00:00
function ReaderToc : getTocIndexByPage ( pn_or_xp )
2014-10-07 09:09:37 +00:00
self : fillToc ( )
2015-02-01 14:50:08 +00:00
if # self.toc == 0 then return end
2014-10-07 09:09:37 +00:00
local pageno = pn_or_xp
2014-03-13 13:52:43 +00:00
if type ( pn_or_xp ) == " string " then
2014-10-07 09:09:37 +00:00
pageno = self.ui . document : getPageFromXPointer ( pn_or_xp )
2014-03-13 13:52:43 +00:00
end
2015-02-01 14:50:08 +00:00
local pre_index = 1
2014-03-13 13:52:43 +00:00
for _k , _v in ipairs ( self.toc ) do
if _v.page > pageno then
break
end
2015-02-01 14:50:08 +00:00
pre_index = _k
end
return pre_index
end
function ReaderToc : getTocTitleByPage ( pn_or_xp )
local index = self : getTocIndexByPage ( pn_or_xp )
if index then
return self : cleanUpTocTitle ( self.toc [ index ] . title )
else
return " "
2014-03-13 13:52:43 +00:00
end
2012-05-27 13:24:33 +00:00
end
function ReaderToc : getTocTitleOfCurrentPage ( )
2015-02-01 13:52:46 +00:00
if self.pageno then
return self : getTocTitleByPage ( self.pageno )
end
2012-05-27 13:24:33 +00:00
end
2014-10-07 09:09:37 +00:00
function ReaderToc : getMaxDepth ( )
self : fillToc ( )
local max_depth = 0
for _ , v in ipairs ( self.toc ) do
if v.depth > max_depth then
max_depth = v.depth
2014-07-02 05:07:18 +00:00
end
end
2014-10-07 09:09:37 +00:00
return max_depth
2014-07-02 05:07:18 +00:00
end
2014-10-07 09:09:37 +00:00
--[[
TOC ticks is a list of page number in ascending order of TOC nodes at certain level
positive level counts nodes of the depth level ( level 1 for depth 1 )
2014-10-11 13:10:07 +00:00
negative level counts nodes of reversed depth level ( level - 1 for max_depth )
zero level counts leaf nodes of the toc tree
2014-10-07 09:09:37 +00:00
--]]
function ReaderToc : getTocTicks ( level )
if self.ticks [ level ] then return self.ticks [ level ] end
-- build toc ticks if not found
self : fillToc ( )
local ticks = { }
2014-07-03 16:19:28 +00:00
if # self.toc > 0 then
2014-10-11 13:10:07 +00:00
if level == 0 then
local depth = 0
for i = # self.toc , 1 , - 1 do
local v = self.toc [ i ]
if v.depth >= depth then
table.insert ( ticks , v.page )
end
depth = v.depth
end
2014-10-07 09:09:37 +00:00
else
2016-02-15 09:33:48 +00:00
local depth
2014-10-11 13:10:07 +00:00
if level > 0 then
depth = level
else
depth = self : getMaxDepth ( ) + level + 1
end
for _ , v in ipairs ( self.toc ) do
if v.depth == depth then
table.insert ( ticks , v.page )
end
2014-07-03 16:19:28 +00:00
end
end
2014-10-07 09:09:37 +00:00
-- normally the ticks are sorted already but in rare cases
-- toc nodes may be not in ascending order
table.sort ( ticks )
-- cache ticks only if ticks are available
self.ticks [ level ] = ticks
2014-07-03 16:19:28 +00:00
end
2014-10-07 09:09:37 +00:00
return ticks
2014-07-03 16:19:28 +00:00
end
2014-10-07 09:09:37 +00:00
function ReaderToc : getNextChapter ( cur_pageno , level )
local ticks = self : getTocTicks ( level )
local next_chapter = nil
for i = 1 , # ticks do
if ticks [ i ] > cur_pageno then
next_chapter = ticks [ i ]
break
2014-07-15 10:18:24 +00:00
end
end
2014-10-07 09:09:37 +00:00
return next_chapter
2014-07-15 10:18:24 +00:00
end
2014-10-07 09:09:37 +00:00
function ReaderToc : getPreviousChapter ( cur_pageno , level )
local ticks = self : getTocTicks ( level )
local previous_chapter = nil
for i = 1 , # ticks do
if ticks [ i ] >= cur_pageno then
break
end
previous_chapter = ticks [ i ]
2014-07-15 10:18:24 +00:00
end
2014-10-07 09:09:37 +00:00
return previous_chapter
end
2014-07-15 10:18:24 +00:00
2014-10-07 09:43:48 +00:00
function ReaderToc : isChapterBegin ( cur_pageno , level )
local ticks = self : getTocTicks ( level )
local _begin = false
for i = 1 , # ticks do
if ticks [ i ] == cur_pageno then
_begin = true
break
end
end
return _begin
end
function ReaderToc : isChapterEnd ( cur_pageno , level )
local ticks = self : getTocTicks ( level )
local _end = false
for i = 1 , # ticks do
if ticks [ i ] - 1 == cur_pageno then
_end = true
break
end
end
return _end
end
2014-10-07 09:09:37 +00:00
function ReaderToc : getChapterPagesLeft ( pageno , level )
2014-10-07 09:43:48 +00:00
--if self:isChapterEnd(pageno, level) then return 0 end
2014-10-07 09:09:37 +00:00
local next_chapter = self : getNextChapter ( pageno , level )
if next_chapter then
2014-10-07 09:43:48 +00:00
next_chapter = next_chapter - pageno - 1
2014-07-15 10:18:24 +00:00
end
2014-10-07 09:09:37 +00:00
return next_chapter
end
2014-07-15 10:18:24 +00:00
2014-10-07 09:09:37 +00:00
function ReaderToc : getChapterPagesDone ( pageno , level )
2014-10-07 09:43:48 +00:00
if self : isChapterBegin ( pageno , level ) then return 0 end
2014-10-07 09:09:37 +00:00
local previous_chapter = self : getPreviousChapter ( pageno , level )
if previous_chapter then
previous_chapter = pageno - previous_chapter
2014-07-15 10:18:24 +00:00
end
2014-10-07 09:09:37 +00:00
return previous_chapter
2014-07-15 10:18:24 +00:00
end
2014-10-14 13:33:13 +00:00
function ReaderToc : updateCurrentNode ( )
2015-02-01 13:52:46 +00:00
if # self.collapsed_toc > 0 and self.pageno then
2014-10-14 13:33:13 +00:00
for i , v in ipairs ( self.collapsed_toc ) do
if v.page > self.pageno then
self.collapsed_toc . current = i > 1 and i - 1 or 1
2015-02-01 14:50:08 +00:00
return
end
end
self.collapsed_toc . current = # self.collapsed_toc
end
end
2015-02-03 04:10:25 +00:00
function ReaderToc : expandParentNode ( index )
if index then
local nodes_to_expand = { }
local depth = self.toc [ index ] . depth
for i = index - 1 , 1 , - 1 do
if depth > self.toc [ i ] . depth then
depth = self.toc [ i ] . depth
table.insert ( nodes_to_expand , i )
2014-10-14 13:33:13 +00:00
end
2015-02-03 04:10:25 +00:00
if depth == 1 then break end
end
for i = # nodes_to_expand , 1 , - 1 do
self : expandToc ( nodes_to_expand [ i ] )
2014-10-14 13:33:13 +00:00
end
end
end
2012-05-27 11:47:22 +00:00
function ReaderToc : onShowToc ( )
2014-10-07 09:09:37 +00:00
self : fillToc ( )
2014-03-13 13:52:43 +00:00
-- build menu items
if # self.toc > 0 and not self.toc [ 1 ] . text then
for _ , v in ipairs ( self.toc ) do
2014-10-14 13:33:13 +00:00
v.text = self.toc_indent : rep ( v.depth - 1 ) .. self : cleanUpTocTitle ( v.title )
2014-03-13 13:52:43 +00:00
v.mandatory = v.page
end
end
2014-10-14 13:33:13 +00:00
-- update collapsible state
self.expand_button = Button : new {
icon = " resources/icons/appbar.control.expand.png " ,
2014-11-20 22:07:39 +00:00
width = Screen : scaleBySize ( 30 ) ,
2014-10-14 13:33:13 +00:00
bordersize = 0 ,
show_parent = self ,
}
self.collapse_button = Button : new {
icon = " resources/icons/appbar.control.collapse.png " ,
2014-11-20 22:07:39 +00:00
width = Screen : scaleBySize ( 30 ) ,
2014-10-14 13:33:13 +00:00
bordersize = 0 ,
show_parent = self ,
}
if # self.toc > 0 and # self.collapsed_toc == 0 then
local depth = 0
for i = # self.toc , 1 , - 1 do
local v = self.toc [ i ]
-- node v has child node(s)
if v.depth < depth then
v.state = self.expand_button : new {
callback = function ( ) self : expandToc ( i ) end ,
indent = self.toc_indent : rep ( v.depth - 1 ) ,
}
end
if v.depth < self.collapse_depth then
table.insert ( self.collapsed_toc , 1 , v )
2014-04-04 15:02:29 +00:00
end
2014-10-14 13:33:13 +00:00
depth = v.depth
2014-04-04 15:02:29 +00:00
end
end
2014-03-13 13:52:43 +00:00
2014-10-14 13:33:13 +00:00
local button_size = self.expand_button : getSize ( )
2014-03-13 13:52:43 +00:00
local toc_menu = Menu : new {
title = _ ( " Table of Contents " ) ,
2014-10-14 13:33:13 +00:00
item_table = self.collapsed_toc ,
state_size = button_size ,
2014-03-13 13:52:43 +00:00
ui = self.ui ,
2014-06-10 13:32:49 +00:00
is_borderless = true ,
2015-02-01 13:36:22 +00:00
is_popout = false ,
2014-04-04 15:37:50 +00:00
width = Screen : getWidth ( ) ,
height = Screen : getHeight ( ) ,
2017-04-29 08:38:09 +00:00
cface = Font : getFace ( " x_smallinfofont " ) ,
2018-01-13 22:38:53 +00:00
single_line = true ,
perpage = G_reader_settings : readSetting ( " items_per_page " ) or 14 ,
line_color = require ( " ffi/blitbuffer " ) . COLOR_WHITE ,
2014-06-10 13:13:21 +00:00
on_close_ges = {
GestureRange : new {
ges = " two_finger_swipe " ,
range = Geom : new {
x = 0 , y = 0 ,
w = Screen : getWidth ( ) ,
h = Screen : getHeight ( ) ,
} ,
direction = " west "
}
}
2014-03-13 13:52:43 +00:00
}
2014-04-04 15:37:50 +00:00
local menu_container = CenterContainer : new {
dimen = Screen : getSize ( ) ,
2018-03-17 22:02:32 +00:00
covers_fullscreen = true , -- hint for UIManager:_repaint()
2014-04-04 15:37:50 +00:00
toc_menu ,
}
2014-03-13 13:52:43 +00:00
2014-10-21 14:18:07 +00:00
function toc_menu : onMenuSelect ( item , pos )
-- if toc item has expand/collapse state and tap select on the left side
-- the state switch action is triggered, otherwise goto the linked page
2016-07-30 05:47:44 +00:00
if item.state and pos and pos.x < 0.3 then
2014-10-21 14:18:07 +00:00
item.state . callback ( )
else
toc_menu : close_callback ( )
2018-01-31 12:18:37 +00:00
self.ui . link : addCurrentLocationToStack ( )
2014-10-21 14:18:07 +00:00
self.ui : handleEvent ( Event : new ( " GotoPage " , item.page ) )
end
2014-03-13 13:52:43 +00:00
end
toc_menu.close_callback = function ( )
2014-05-31 12:43:44 +00:00
UIManager : close ( menu_container )
2014-03-13 13:52:43 +00:00
end
2014-05-31 12:43:44 +00:00
toc_menu.show_parent = menu_container
2014-10-14 13:33:13 +00:00
self.toc_menu = toc_menu
2015-02-03 07:34:35 +00:00
self : updateCurrentNode ( )
2015-02-01 14:50:08 +00:00
-- auto expand the parent node of current page
2015-02-03 04:10:25 +00:00
self : expandParentNode ( self : getTocIndexByPage ( self.pageno ) )
2015-02-03 07:34:35 +00:00
-- auto goto page of the current toc entry
2017-02-01 14:24:21 +00:00
self.toc_menu : switchItemTable ( nil , self.collapsed_toc , self.collapsed_toc . current or - 1 )
2015-02-01 14:50:08 +00:00
2014-05-31 12:43:44 +00:00
UIManager : show ( menu_container )
2014-05-30 14:11:46 +00:00
2014-03-13 13:52:43 +00:00
return true
2012-05-27 11:47:22 +00:00
end
2014-10-14 13:33:13 +00:00
-- expand TOC node of index in raw toc table
function ReaderToc : expandToc ( index )
2015-02-01 14:50:08 +00:00
for k , v in ipairs ( self.expanded_nodes ) do
if v == index then return end
end
2014-10-14 13:33:13 +00:00
table.insert ( self.expanded_nodes , index )
local cur_node = self.toc [ index ]
local cur_depth = cur_node.depth
local collapsed_index = nil
for i , v in ipairs ( self.collapsed_toc ) do
if v.page == cur_node.page and v.depth == cur_depth
and v.text == cur_node.text then
collapsed_index = i
break
end
end
2015-02-03 04:10:25 +00:00
-- either the toc entry of index has no child nodes
-- or it's parent nodes are not expanded yet
if not collapsed_index then return end
2014-10-14 13:33:13 +00:00
for i = index + 1 , # self.toc do
local v = self.toc [ i ]
if v.depth == cur_depth + 1 then
collapsed_index = collapsed_index + 1
table.insert ( self.collapsed_toc , collapsed_index , v )
elseif v.depth <= cur_depth then
break
end
end
-- change state of current node to expanded
cur_node.state = self.collapse_button : new {
callback = function ( ) self : collapseToc ( index ) end ,
indent = self.toc_indent : rep ( cur_depth - 1 ) ,
}
self : updateCurrentNode ( )
2017-02-01 14:24:21 +00:00
self.toc_menu : switchItemTable ( nil , self.collapsed_toc , - 1 )
2014-10-14 13:33:13 +00:00
end
-- collapse TOC node of index in raw toc table
function ReaderToc : collapseToc ( index )
2015-02-01 14:50:08 +00:00
for k , v in ipairs ( self.expanded_nodes ) do
if v == index then
table.remove ( self.expanded_nodes , k )
break
end
end
2014-10-14 13:33:13 +00:00
local cur_node = self.toc [ index ]
local cur_depth = cur_node.depth
local i = 1
local is_child_node = false
while i <= # self.collapsed_toc do
local v = self.collapsed_toc [ i ]
if v.page > cur_node.page and v.depth <= cur_depth then
is_child_node = false
end
if is_child_node then
table.remove ( self.collapsed_toc , i )
else
i = i + 1
end
if v.page == cur_node.page and v.depth == cur_depth
and v.text == cur_node.text then
is_child_node = true
end
end
-- change state of current node to collapsed
cur_node.state = self.expand_button : new {
callback = function ( ) self : expandToc ( index ) end ,
indent = self.toc_indent : rep ( cur_depth - 1 ) ,
}
self : updateCurrentNode ( )
2017-02-01 14:24:21 +00:00
self.toc_menu : switchItemTable ( nil , self.collapsed_toc , - 1 )
2014-10-14 13:33:13 +00:00
end
2017-03-04 13:46:38 +00:00
function ReaderToc : addToMainMenu ( menu_items )
2014-03-13 13:52:43 +00:00
-- insert table to main reader menu
2017-03-04 13:46:38 +00:00
menu_items.table_of_contents = {
2018-06-13 22:46:52 +00:00
text_func = function ( )
return self.ui . document : isTocAlternativeToc ( ) and self.alt_toc_menu_title or self.toc_menu_title
end ,
2014-03-13 13:52:43 +00:00
callback = function ( )
self : onShowToc ( )
end ,
2017-02-28 21:46:32 +00:00
}
2018-06-13 22:46:52 +00:00
if self.ui . document : canHaveAlternativeToc ( ) then
2018-09-04 21:55:58 +00:00
menu_items.table_of_contents . hold_callback = function ( touchmenu_instance )
2018-06-13 22:46:52 +00:00
if self.ui . document : isTocAlternativeToc ( ) then
UIManager : show ( ConfirmBox : new {
text = _ ( " The table of content for this book is currently an alternative one built from the document headings. \n Do you want to get back the original table of content? (The book will be reloaded.) " ) ,
ok_callback = function ( )
2018-09-04 21:55:58 +00:00
touchmenu_instance : closeMenu ( )
2018-06-13 22:46:52 +00:00
self.ui . doc_settings : delSetting ( " alternative_toc " )
self.ui . document : invalidateCacheFile ( )
-- Allow for ConfirmBox to be closed before showing
-- "Opening file" InfoMessage
UIManager : scheduleIn ( 0.5 , function ( )
self.ui : reloadDocument ( )
end )
end ,
} )
else
UIManager : show ( ConfirmBox : new {
text = _ ( " Do you want to use an alternative table of content built from the document headings? " ) ,
ok_callback = function ( )
2018-09-04 21:55:58 +00:00
touchmenu_instance : closeMenu ( )
2018-06-13 22:46:52 +00:00
self : resetToc ( )
self.ui . document : buildAlternativeToc ( )
self.ui . doc_settings : saveSetting ( " alternative_toc " , true )
self : onShowToc ( )
self.view . footer : setTocMarkers ( true )
self.view . footer : updateFooter ( )
end ,
} )
end
end
end
2012-12-31 00:13:46 +00:00
end
2013-10-18 20:38:07 +00:00
return ReaderToc