Reader: rationalize "Back" key/action handling (#6840)

Have ReaderBack be the sole handler of onBack.
Add 4 mutually exclusive options for the Back key,
to avoid ReaderLink and ReaderBack location stacks
from interfering (ReaderBack's stack being always a
superset of ReaderLink's stack).
So, remove "Enable back history", which is replaced
by Back option "Go to previous read page".
Fix a few possible crashes and inconsistencies (when
zoom, scroll or reflow have changed) with ReaderPaging
and ReaderView when restoring previous page/view.
reviewable/pr6878/r1
poire-z 4 years ago committed by GitHub
parent 74c1813a82
commit 8403154d4d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -497,8 +497,12 @@ function FileManager:init()
if Device:hasKeys() then
self.key_events.Home = { {"Home"}, doc = "go home" }
--Override the menu.lua way of handling the back key
-- Override the menu.lua way of handling the back key
self.file_chooser.key_events.Back = { {"Back"}, doc = "go back" }
if not Device:hasFewKeys() then
-- Also remove the handler assigned to the "Back" key by menu.lua
self.file_chooser.key_events.Close = nil
end
end
self:handleEvent(Event:new("SetDimensions", self.dimen))

@ -1,89 +1,193 @@
local ConfirmBox = require("ui/widget/confirmbox")
local Device = require("device")
local Event = require("ui/event")
local EventListener = require("ui/widget/eventlistener")
local Notification = require("ui/widget/notification")
local UIManager = require("ui/uimanager")
local logger = require("logger")
local util = require("util")
local _ = require("gettext")
-- This module handles the "Back" key (and the "Back" gesture action).
-- When global setting "back_in_reader" == "previous_read_page", it
-- additionally handles a location stack for each visited page or
-- page view change (when scrolling in a same page)
local ReaderBack = EventListener:new{
location_stack = {},
-- a limit not intended to be a practical limit but just a failsafe
max_stack = 5000,
-- allow for disabling back history, and having Back key
-- quit immediately (useful for some developers)
disabled = G_reader_settings:isFalse("enable_back_history"),
}
function ReaderBack:init()
if Device:hasKeys() then
self.ui.key_events.Back = { {"Back"}, doc = "Reader back" }
end
-- Regular function wrapping our method, to avoid re-creating
-- an anonymous function at each page turn
self._addPreviousLocationToStackCallback = function()
self:_addPreviousLocationToStack()
end
end
function ReaderBack:_getCurrentLocation()
local current_location
if self.ui.document.info.has_pages then
current_location = self.ui.paging:getBookLocation()
local current_location = self.ui.paging:getBookLocation()
if current_location then
-- We need a copy, as we're getting references to
-- objects ReaderPaging/ReaderView may still modify
local res = {}
for i=1, #current_location do
res[i] = util.tableDeepCopy(current_location[i])
end
return res
end
else
current_location = {
return {
xpointer = self.ui.rolling:getBookLocation(),
}
end
return current_location
end
local ignore_location
function ReaderBack:_areLocationsSimilar(location1, location2)
if self.ui.document.info.has_pages then
-- locations are arrays of k/v tables
if #location1 ~= #location2 then
return false
end
for i=1, #location1 do
if not util.tableEquals(location1[i], location2[i]) then
return false
end
end
return true
else
return location1.xpointer == location2.xpointer
end
end
function ReaderBack:addCurrentLocationToStack()
local location_stack = self.location_stack
function ReaderBack:_addPreviousLocationToStack()
local new_location = self:_getCurrentLocation()
if util.tableEquals(ignore_location, new_location) then return end
table.insert(location_stack, new_location)
if #location_stack > self.max_stack then
table.remove(location_stack, 1)
if self.cur_location and new_location then
if self:_areLocationsSimilar(self.cur_location, new_location) then
-- Unchanged, don't add it yet
return
end
table.insert(self.location_stack, self.cur_location)
if #self.location_stack > self.max_stack then
table.remove(self.location_stack, 1)
end
end
end
-- Scroll mode crengine
function ReaderBack:onPosUpdate()
if self.disabled then return end
self:addCurrentLocationToStack()
end
-- Paged media
function ReaderBack:onPageUpdate()
if self.disabled then return end
self:addCurrentLocationToStack()
if new_location then
self.cur_location = new_location
end
end
-- Called when loading new document
function ReaderBack:onReadSettings(config)
self.location_stack = {}
self.cur_location = nil
end
function ReaderBack:onBack()
local location_stack = self.location_stack
function ReaderBack:_onViewPossiblyUpdated()
if G_reader_settings:readSetting("back_in_reader") == "previous_read_page" then
-- As multiple modules will have their :onPageUpdate()/... called,
-- and some of them will set up the new page with it, we need to
-- delay our handling after all of them are called (otherwise,
-- depending on the order of the calls, we may be have the location
-- of either the previous page or the current one).
UIManager:nextTick(self._addPreviousLocationToStackCallback)
end
self.back_resist = nil
end
if self.disabled then
self.ui:handleEvent(Event:new("Close"))
elseif #location_stack > 1 then
local saved_location = table.remove(location_stack)
-- Hook to events that do/may change page/view (more than one of these events
-- may be sent on a single page turn/scroll, _addPreviousLocationToStack()
-- will ignore those for the same book location):
-- Called after initial page is set up
ReaderBack.onReaderReady = ReaderBack._onViewPossiblyUpdated
-- New page on paged media or crengine in page mode
ReaderBack.onPageUpdate = ReaderBack._onViewPossiblyUpdated
-- New page on crengine in scroll mode
ReaderBack.onPosUpdate = ReaderBack._onViewPossiblyUpdated
-- View updated (possibly on the same page) on paged media
ReaderBack.onViewRecalculate = ReaderBack._onViewPossiblyUpdated
-- View updated (possibly on the same page) on paged media (needed in Reflow mode)
ReaderBack.onPagePositionUpdated = ReaderBack._onViewPossiblyUpdated
if saved_location then
ignore_location = self:_getCurrentLocation()
logger.dbg("[ReaderBack] restoring:", saved_location)
self.ui:handleEvent(Event:new('RestoreBookLocation', saved_location))
function ReaderBack:onBack()
local back_in_reader = G_reader_settings:readSetting("back_in_reader") or "previous_location"
local back_to_exit = G_reader_settings:readSetting("back_to_exit") or "prompt"
if back_in_reader == "previous_read_page" then
if #self.location_stack > 0 then
local saved_location = table.remove(self.location_stack)
if saved_location then
-- Reset self.cur_location, which will be updated with the restored
-- saved_location, which will then not be added to the stack
self.cur_location = nil
logger.dbg("[ReaderBack] restoring:", saved_location)
self.ui:handleEvent(Event:new('RestoreBookLocation', saved_location))
-- Ensure we always have self.cur_location updated, as in some
-- cases (same page), no event that we handle might be sent.
UIManager:nextTick(self._addPreviousLocationToStackCallback)
return true
end
elseif not self.back_resist or back_to_exit == "disable" then
-- Show a one time notification when location stack is empty.
-- On next "Back" only, proceed with the default behaviour (unless
-- it's disabled, in which case we always show this notification)
self.back_resist = true
UIManager:show(Notification:new{
text = _("Location history is empty."),
timeout = 2,
})
return true
else
self.back_resist = nil
end
else
logger.dbg("[ReaderBack] no location history, closing")
elseif back_in_reader == "previous_location" then
-- ReaderLink maintains its own location_stack of less frequent jumps
-- (links or TOC entries followed, skim document...)
if back_to_exit == "disable" then
-- Let ReaderLink always show its notification if empty
self.ui.link:onGoBackLink(true) -- show_notification_if_empty=true
return true
end
if self.back_resist then
-- Notification "Location history is empty" previously shown by ReaderLink
self.back_resist = nil
elseif self.ui.link:onGoBackLink(true) then -- show_notification_if_empty=true
return true -- some location restored
else
-- ReaderLink has shown its notification that location stack is empty.
-- On next "Back" only, proceed with the default behaviour
self.back_resist = true
return true
end
elseif back_in_reader == "filebrowser" then
self.ui:handleEvent(Event:new("Home"))
-- Filebrowser will handle next "Back" and ensure back_to_exit
return true
end
-- location stack empty, or back_in_reader == "default"
if back_to_exit == "always" then
self.ui:handleEvent(Event:new("Close"))
elseif back_to_exit == "disable" then
return true
elseif back_to_exit == "prompt" then
UIManager:show(ConfirmBox:new{
text = _("Exit KOReader?"),
ok_text = _("Exit"),
ok_callback = function()
self.ui:handleEvent(Event:new("Close"))
end
})
end
return true
end
return ReaderBack

@ -41,11 +41,8 @@ function ReaderLink:init()
doc = "go to selected page link",
event = "GotoSelectedPageLink",
}
self.key_events.GoBackLink = {
{ "Back" },
doc = "go back from link",
event = "GoBackLink",
}
-- "Back" is handled by ReaderBack, which will call our onGoBackLink()
-- when G_reader_settings:readSetting("back_in_reader") == "previous_location"
end
if Device:isTouchDevice() then
self.ui:registerTouchZones({

@ -292,6 +292,7 @@ book, the page view will be roughly the same.
function ReaderPaging:setPagePosition(page, pos)
logger.dbg("set page position", pos)
self.page_positions[page] = pos
self.ui:handleEvent(Event:new("PagePositionUpdated"))
end
--[[
@ -532,16 +533,34 @@ end
function ReaderPaging:onRestoreBookLocation(saved_location)
if self.view.page_scroll then
self.view:restoreViewContext(saved_location)
self:_gotoPage(self.view.page_states[1].page, "scrolling")
if self.view:restoreViewContext(saved_location) then
self:_gotoPage(saved_location[1].page, "scrolling")
else
-- If context is unusable (not from scroll mode), trigger
-- this to go at least to its page and redraw it
self.ui:handleEvent(Event:new("PageUpdate", saved_location[1].page))
end
else
-- gotoPage will emit PageUpdate event, which will trigger recalculate
-- gotoPage may emit PageUpdate event, which will trigger recalculate
-- in ReaderView and resets the view context. So we need to call
-- restoreViewContext after gotoPage
-- restoreViewContext after gotoPage.
-- But if we're restoring to the same page, it will not emit
-- PageUpdate event - so we need to do it for a correct redrawing
local send_PageUpdate = saved_location[1].page == self.current_page
self:_gotoPage(saved_location[1].page)
self.view:restoreViewContext(saved_location)
if not self.view:restoreViewContext(saved_location) then
-- If context is unusable (not from page mode), also
-- send PageUpdate event to go to its page and redraw it
send_PageUpdate = true
end
if send_PageUpdate then
self.ui:handleEvent(Event:new("PageUpdate", saved_location[1].page))
end
end
self:setPagePosition(self:getTopPage(), self:getTopPosition())
-- In some cases (same page, different offset), doing the above
-- might not redraw the screen. Ensure it is.
UIManager:setDirty(self.view.dialog, "partial")
return true
end

@ -668,13 +668,25 @@ function ReaderView:getViewContext()
end
function ReaderView:restoreViewContext(ctx)
-- The format of the context is different depending on page_scroll.
-- If we're asked to restore the other format, just ignore it
-- (our only caller, ReaderPaging:onRestoreBookLocation(), will
-- at least change to the page of the context, which is all that
-- can be done when restoring from a different mode)
if self.page_scroll then
self.page_states = ctx
if ctx[1] and ctx[1].visible_area then
self.page_states = ctx
return true
end
else
self.state = ctx[1]
self.visible_area = ctx[2]
self.page_area = ctx[3]
if ctx[1] and ctx[1].pos then
self.state = ctx[1]
self.visible_area = ctx[2]
self.page_area = ctx[3]
return true
end
end
return false
end
function ReaderView:onSetRotationMode(rotation)

@ -356,14 +356,52 @@ common_settings.back_in_filemanager = {
},
},
}
common_settings.enable_back_history = {
text = _("Enable back history"),
checked_func = function()
return G_reader_settings:nilOrTrue("enable_back_history")
end,
callback = function()
G_reader_settings:flipNilOrTrue("enable_back_history")
end,
common_settings.back_in_reader = {
-- All these options are managed by ReaderBack
text = _("Back in reader"),
sub_item_table = {
{
text_func = function()
local back_to_exit = G_reader_settings:readSetting("back_to_exit") or "prompt"
return T(_("Back to exit (%1)"),
back_to_exit_str[back_to_exit][2])
end,
checked_func = function()
return G_reader_settings:readSetting("back_in_reader") == "default"
end,
callback = function()
G_reader_settings:saveSetting("back_in_reader", "default")
end,
},
{
text = _("Go to file browser"),
checked_func = function()
return G_reader_settings:readSetting("back_in_reader") == "filebrowser"
end,
callback = function()
G_reader_settings:saveSetting("back_in_reader", "filebrowser")
end,
},
{
text = _("Go to previous location"),
checked_func = function()
local back_in_reader = G_reader_settings:readSetting("back_in_reader")
return back_in_reader == "previous_location" or back_in_reader == nil
end,
callback = function()
G_reader_settings:saveSetting("back_in_reader", "previous_location")
end,
},
{
text = _("Go to previous read page"),
checked_func = function()
return G_reader_settings:readSetting("back_in_reader") == "previous_read_page"
end,
callback = function()
G_reader_settings:saveSetting("back_in_reader", "previous_read_page")
end,
},
},
}
if Device:hasKeys() then
common_settings.invert_page_turn_buttons = {

@ -54,7 +54,7 @@ local order = {
navigation = {
"back_to_exit",
"back_in_filemanager",
"enable_back_history",
"back_in_reader",
"android_volume_keys",
"android_camera_key",
"android_haptic_feedback",

@ -75,7 +75,7 @@ local order = {
navigation = {
"back_to_exit",
"back_in_filemanager",
"enable_back_history",
"back_in_reader",
"android_volume_keys",
"android_camera_key",
"android_haptic_feedback",

@ -479,7 +479,7 @@ Default value: %1]]), Screen.low_pan_rate and 5.0 or 30.0),
info_text = T(_([[
Any other taps made within this interval after a first tap will be considered accidental and ignored.
The interval value is in milliseconds and can range from 0 (0 second) to 2000 (2 seconds).
The interval value is in milliseconds and can range from 0 (0 seconds) to 2000 (2 seconds).
Default value: %1]]), GestureDetector.TAP_INTERVAL/1000),
width = math.floor(Screen:getWidth() * 0.75),
value = GestureDetector:getInterval("ges_tap_interval")/1000,
@ -507,7 +507,7 @@ Default value: %1]]), GestureDetector.TAP_INTERVAL/1000),
info_text = _([[
Any other taps made within this interval after a first tap will be considered accidental and ignored.
The interval value is in milliseconds and can range from 0 (0 second) to 2000 (2 seconds).
The interval value is in milliseconds and can range from 0 (0 seconds) to 2000 (2 seconds).
Default value: 0]]),
width = math.floor(Screen:getWidth() * 0.75),
value = (G_reader_settings:readSetting("ges_tap_interval_on_keyboard") or 0)/1000,

Loading…
Cancel
Save