mirror of https://github.com/koreader/koreader
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
parent
74c1813a82
commit
8403154d4d
@ -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
|
||||
|
Loading…
Reference in New Issue