mirror of
https://github.com/koreader/koreader
synced 2024-10-31 21:20:20 +00:00
eace8d25c1
Noticable with "End of document action > Open next file", and holding PgDn in a directory full of single page html files.
879 lines
35 KiB
Lua
879 lines
35 KiB
Lua
local bit = require("bit")
|
|
local Blitbuffer = require("ffi/blitbuffer")
|
|
local ConfirmBox = require("ui/widget/confirmbox")
|
|
local Device = require("device")
|
|
local InputContainer = require("ui/widget/container/inputcontainer")
|
|
local Event = require("ui/event")
|
|
local ReaderPanning = require("apps/reader/modules/readerpanning")
|
|
local UIManager = require("ui/uimanager")
|
|
local logger = require("logger")
|
|
local _ = require("gettext")
|
|
local Input = Device.input
|
|
local Screen = Device.screen
|
|
local T = require("ffi/util").template
|
|
|
|
local band = bit.band
|
|
|
|
--[[
|
|
Rolling is just like paging in page-based documents except that
|
|
sometimes (in scroll mode) there is no concept of page number to indicate
|
|
current progress.
|
|
There are three kind of progress measurements for credocuments.
|
|
1. page number (in page mode)
|
|
2. progress percentage (in scroll mode)
|
|
3. xpointer (in document dom structure)
|
|
We found that the first two measurements are not suitable for keeping a
|
|
record of the real progress. For example, when switching screen orientation
|
|
from portrait to landscape, or switching view mode from page to scroll, the
|
|
internal xpointer should not be used as the view dimen/mode is changed and
|
|
crengine's pagination mechanism will find a closest xpointer for the new view.
|
|
So if we change the screen orientation or view mode back, we cannot find the
|
|
original place since the internal xpointer is changed, which is counter-
|
|
intuitive as users didn't goto any other page.
|
|
The solution is that we keep a record of the internal xpointer and only change
|
|
it in explicit page turning. And use that xpointer for non-page-turning
|
|
rendering.
|
|
--]]
|
|
local ReaderRolling = InputContainer:new{
|
|
pan_rate = 30, -- default 30 ops, will be adjusted in readerui
|
|
old_doc_height = nil,
|
|
old_page = nil,
|
|
current_pos = 0,
|
|
inverse_reading_order = false,
|
|
-- only used for page view mode
|
|
current_page= nil,
|
|
xpointer = nil,
|
|
panning_steps = ReaderPanning.panning_steps,
|
|
show_overlap_enable = nil,
|
|
cre_top_bar_enabled = false,
|
|
visible_pages = 1,
|
|
-- With visible_pages=2, in 2-pages mode, ensure the first
|
|
-- page is always odd or even (odd is logical to avoid a
|
|
-- same page when turning first 2-pages set of document)
|
|
odd_or_even_first_page = 1 -- 1 = odd, 2 = even, nil or others = free
|
|
}
|
|
|
|
function ReaderRolling:init()
|
|
self.key_events = {}
|
|
if Device:hasKeys() then
|
|
self.key_events.GotoNextView = {
|
|
{ Input.group.PgFwd },
|
|
doc = "go to next view",
|
|
event = "GotoViewRel", args = 1,
|
|
}
|
|
self.key_events.GotoPrevView = {
|
|
{ Input.group.PgBack },
|
|
doc = "go to previous view",
|
|
event = "GotoViewRel", args = -1,
|
|
}
|
|
end
|
|
if Device:hasDPad() then
|
|
self.key_events.MoveUp = {
|
|
{ "Up" },
|
|
doc = "move view up",
|
|
event = "Panning", args = {0, -1},
|
|
}
|
|
self.key_events.MoveDown = {
|
|
{ "Down" },
|
|
doc = "move view down",
|
|
event = "Panning", args = {0, 1},
|
|
}
|
|
end
|
|
if Device:hasKeyboard() then
|
|
self.key_events.GotoFirst = {
|
|
{"1"}, doc = "go to start", event = "GotoPercent", args = 0,
|
|
}
|
|
self.key_events.Goto11 = {
|
|
{"2"}, doc = "go to 11%", event = "GotoPercent", args = 11,
|
|
}
|
|
self.key_events.Goto22 = {
|
|
{"3"}, doc = "go to 22%", event = "GotoPercent", args = 22,
|
|
}
|
|
self.key_events.Goto33 = {
|
|
{"4"}, doc = "go to 33%", event = "GotoPercent", args = 33,
|
|
}
|
|
self.key_events.Goto44 = {
|
|
{"5"}, doc = "go to 44%", event = "GotoPercent", args = 44,
|
|
}
|
|
self.key_events.Goto55 = {
|
|
{"6"}, doc = "go to 55%", event = "GotoPercent", args = 55,
|
|
}
|
|
self.key_events.Goto66 = {
|
|
{"7"}, doc = "go to 66%", event = "GotoPercent", args = 66,
|
|
}
|
|
self.key_events.Goto77 = {
|
|
{"8"}, doc = "go to 77%", event = "GotoPercent", args = 77,
|
|
}
|
|
self.key_events.Goto88 = {
|
|
{"9"}, doc = "go to 88%", event = "GotoPercent", args = 88,
|
|
}
|
|
self.key_events.GotoLast = {
|
|
{"0"}, doc = "go to end", event = "GotoPercent", args = 100,
|
|
}
|
|
end
|
|
|
|
table.insert(self.ui.postInitCallback, function()
|
|
self.ui.document:_readMetadata()
|
|
self.old_doc_height = self.ui.document.info.doc_height
|
|
self.old_page = self.ui.document.info.number_of_pages
|
|
end)
|
|
table.insert(self.ui.postReaderCallback, function()
|
|
self:updatePos()
|
|
-- Disable crengine internal history, with required redraw
|
|
self.ui.document:enableInternalHistory(false)
|
|
self:onRedrawCurrentView()
|
|
end)
|
|
self.ui.menu:registerToMainMenu(self)
|
|
end
|
|
|
|
function ReaderRolling:onReadSettings(config)
|
|
-- 20180503: some fix in crengine has changed the way the DOM is built
|
|
-- for HTML documents and may make XPATHs obtained from previous version
|
|
-- invalid.
|
|
-- We may request the previous (buggy) behaviour though, which we do
|
|
-- if we use a DocSetting previously made that may contain bookmarks
|
|
-- and highlights with old XPATHs.
|
|
-- (EPUB will use the same correct DOM code no matter what DOM version
|
|
-- we request here.)
|
|
if not config:readSetting("cre_dom_version") then
|
|
-- Not previously set, guess which DOM version to use
|
|
if config:readSetting("last_xpointer") then
|
|
-- We have a last_xpointer: this book was previously opened
|
|
-- with possibly a very old version: request the oldest
|
|
config:saveSetting("cre_dom_version", self.ui.document:getOldestDomVersion())
|
|
else
|
|
-- No previous xpointer: book never opened (or sidecar file
|
|
-- purged): we can use the latest DOM version
|
|
config:saveSetting("cre_dom_version", self.ui.document:getLatestDomVersion())
|
|
end
|
|
end
|
|
self.ui.document:requestDomVersion(config:readSetting("cre_dom_version"))
|
|
|
|
local last_xp = config:readSetting("last_xpointer")
|
|
local last_per = config:readSetting("last_percent")
|
|
if last_xp then
|
|
self.xpointer = last_xp
|
|
self.setupXpointer = function()
|
|
self:_gotoXPointer(self.xpointer)
|
|
-- we have to do a real jump in self.ui.document._document to
|
|
-- update status information in CREngine.
|
|
self.ui.document:gotoXPointer(self.xpointer)
|
|
end
|
|
-- we read last_percent just for backward compatibility
|
|
-- FIXME: remove this branch with migration script
|
|
elseif last_per then
|
|
self.setupXpointer = function()
|
|
self:_gotoPercent(last_per)
|
|
-- _gotoPercent calls _gotoPos, which only updates self.current_pos
|
|
-- and self.view.
|
|
-- we need to do a real pos change in self.ui.document._document
|
|
-- to update status information in CREngine.
|
|
self.ui.document:gotoPos(self.current_pos)
|
|
-- _gotoPercent already calls gotoPos, so no need to emit
|
|
-- PosUpdate event in scroll mode
|
|
if self.view.view_mode == "page" then
|
|
self.ui:handleEvent(
|
|
Event:new("PageUpdate", self.ui.document:getCurrentPage()))
|
|
end
|
|
self.xpointer = self.ui.document:getXPointer()
|
|
end
|
|
else
|
|
self.setupXpointer = function()
|
|
self.xpointer = self.ui.document:getXPointer()
|
|
if self.view.view_mode == "page" then
|
|
self.ui:handleEvent(Event:new("PageUpdate", 1))
|
|
end
|
|
end
|
|
end
|
|
self.show_overlap_enable = config:readSetting("show_overlap_enable")
|
|
if self.show_overlap_enable == nil then
|
|
self.show_overlap_enable = DSHOWOVERLAP
|
|
end
|
|
self.inverse_reading_order = config:readSetting("inverse_reading_order") or false
|
|
|
|
-- This self.visible_pages may not be the current nb of visible pages
|
|
-- as crengine may decide to not ensure that in some conditions.
|
|
-- It's the one we got from settings, the one the user has decided on
|
|
-- with config toggle, and the one that we will save for next load.
|
|
-- Use self.ui.document:getVisiblePageCount() to get the current
|
|
-- crengine used value.
|
|
self.visible_pages = config:readSetting("visible_pages") or
|
|
G_reader_settings:readSetting("copt_visible_pages") or 1
|
|
self.ui.document:setVisiblePageCount(self.visible_pages)
|
|
end
|
|
|
|
-- in scroll mode percent_finished must be save before close document
|
|
-- we cannot do it in onSaveSettings() because getLastPercent() uses self.ui.document
|
|
function ReaderRolling:onCloseDocument()
|
|
self.ui.doc_settings:saveSetting("percent_finished", self:getLastPercent())
|
|
local cache_file_path = self.ui.document:getCacheFilePath() -- nil if no cache file
|
|
self.ui.doc_settings:saveSetting("cache_file_path", cache_file_path)
|
|
if self.ui.document:hasCacheFile() then
|
|
-- also checks if DOM is coherent with styles; if not, invalidate the
|
|
-- cache, so a new DOM is built on next opening
|
|
if self.ui.document:isBuiltDomStale() then
|
|
logger.dbg("cre DOM may not be in sync with styles, invalidating cache file for a full reload at next opening")
|
|
self.ui.document:invalidateCacheFile()
|
|
end
|
|
end
|
|
logger.dbg("cre cache used:", cache_file_path or "none")
|
|
end
|
|
|
|
function ReaderRolling:onCheckDomStyleCoherence()
|
|
if self.ui.document and self.ui.document:isBuiltDomStale() then
|
|
UIManager:show(ConfirmBox:new{
|
|
text = _("Styles have changed in such a way that fully reloading the document may be needed for a correct rendering.\nDo you want to reload the document?"),
|
|
ok_callback = function()
|
|
-- Allow for ConfirmBox to be closed before showing
|
|
-- "Opening file" InfoMessage
|
|
UIManager:scheduleIn(0.5, function ()
|
|
self.ui:reloadDocument()
|
|
end)
|
|
end,
|
|
})
|
|
end
|
|
end
|
|
|
|
function ReaderRolling:onSaveSettings()
|
|
-- remove last_percent config since its deprecated
|
|
self.ui.doc_settings:saveSetting("last_percent", nil)
|
|
self.ui.doc_settings:saveSetting("last_xpointer", self.xpointer)
|
|
-- in scrolling mode, the document may already be closed,
|
|
-- so we have to check the condition to avoid crash function self:getLastPercent()
|
|
-- that uses self.ui.document
|
|
if self.ui.document then
|
|
self.ui.doc_settings:saveSetting("percent_finished", self:getLastPercent())
|
|
end
|
|
self.ui.doc_settings:saveSetting("show_overlap_enable", self.show_overlap_enable)
|
|
self.ui.doc_settings:saveSetting("inverse_reading_order", self.inverse_reading_order)
|
|
self.ui.doc_settings:saveSetting("visible_pages", self.visible_pages)
|
|
end
|
|
|
|
function ReaderRolling:onReaderReady()
|
|
self:setupTouchZones()
|
|
self.setupXpointer()
|
|
end
|
|
|
|
function ReaderRolling:setupTouchZones()
|
|
self.ges_events = {}
|
|
self.onGesture = nil
|
|
if not Device:isTouchDevice() then return end
|
|
|
|
local forward_zone = {
|
|
ratio_x = DTAP_ZONE_FORWARD.x, ratio_y = DTAP_ZONE_FORWARD.y,
|
|
ratio_w = DTAP_ZONE_FORWARD.w, ratio_h = DTAP_ZONE_FORWARD.h,
|
|
}
|
|
local backward_zone = {
|
|
ratio_x = DTAP_ZONE_BACKWARD.x, ratio_y = DTAP_ZONE_BACKWARD.y,
|
|
ratio_w = DTAP_ZONE_BACKWARD.w, ratio_h = DTAP_ZONE_BACKWARD.h,
|
|
}
|
|
|
|
local forward_double_tap_zone = {
|
|
ratio_x = DDOUBLE_TAP_ZONE_NEXT_CHAPTER.x, ratio_y = DDOUBLE_TAP_ZONE_NEXT_CHAPTER.y,
|
|
ratio_w = DDOUBLE_TAP_ZONE_NEXT_CHAPTER.w, ratio_h = DDOUBLE_TAP_ZONE_NEXT_CHAPTER.h,
|
|
}
|
|
local backward_double_tap_zone = {
|
|
ratio_x = DDOUBLE_TAP_ZONE_PREV_CHAPTER.x, ratio_y = DDOUBLE_TAP_ZONE_PREV_CHAPTER.y,
|
|
ratio_w = DDOUBLE_TAP_ZONE_PREV_CHAPTER.w, ratio_h = DDOUBLE_TAP_ZONE_PREV_CHAPTER.h,
|
|
}
|
|
|
|
if self.inverse_reading_order then
|
|
forward_zone.ratio_x = 1 - forward_zone.ratio_x - forward_zone.ratio_w
|
|
backward_zone.ratio_x = 1 - backward_zone.ratio_x - backward_zone.ratio_w
|
|
|
|
forward_double_tap_zone.ratio_x =
|
|
1 - forward_double_tap_zone.ratio_x - forward_double_tap_zone.ratio_w
|
|
backward_double_tap_zone.ratio_x =
|
|
1 - backward_double_tap_zone.ratio_x - backward_double_tap_zone.ratio_w
|
|
end
|
|
|
|
self.ui:registerTouchZones({
|
|
{
|
|
id = "tap_forward",
|
|
ges = "tap",
|
|
screen_zone = forward_zone,
|
|
handler = function() return self:onGotoViewRel(1) end,
|
|
},
|
|
{
|
|
id = "tap_backward",
|
|
ges = "tap",
|
|
screen_zone = backward_zone,
|
|
handler = function() return self:onGotoViewRel(-1) end,
|
|
},
|
|
{
|
|
id = "double_tap_forward",
|
|
ges = "double_tap",
|
|
screen_zone = forward_double_tap_zone,
|
|
handler = function() return self:onGotoNextChapter() end
|
|
},
|
|
{
|
|
id = "double_tap_backward",
|
|
ges = "double_tap",
|
|
screen_zone = backward_double_tap_zone,
|
|
handler = function() return self:onGotoPrevChapter() end
|
|
},
|
|
{
|
|
id = "rolling_swipe",
|
|
ges = "swipe",
|
|
screen_zone = {
|
|
ratio_x = 0, ratio_y = 0, ratio_w = 1, ratio_h = 1,
|
|
},
|
|
handler = function(ges) return self:onSwipe(nil, ges) end
|
|
},
|
|
{
|
|
id = "rolling_pan",
|
|
ges = "pan",
|
|
rate = self.pan_rate,
|
|
screen_zone = {
|
|
ratio_x = 0, ratio_y = 0, ratio_w = 1, ratio_h = 1,
|
|
},
|
|
handler = function(ges) return self:onPan(nil, ges) end
|
|
},
|
|
})
|
|
end
|
|
|
|
function ReaderRolling:getLastProgress()
|
|
return self.xpointer
|
|
end
|
|
|
|
function ReaderRolling:addToMainMenu(menu_items)
|
|
-- FIXME: repeated code with page overlap menu for readerpaging
|
|
-- needs to keep only one copy of the logic as for the DRY principle.
|
|
-- The difference between the two menus is only the enabled func.
|
|
local overlap_lines_help_text = _([[
|
|
When page overlap is enabled, some lines from the previous page will be displayed on the next page.
|
|
You can set how many lines are shown.]])
|
|
local page_overlap_menu = {
|
|
{
|
|
text = _("Page overlap"),
|
|
checked_func = function()
|
|
return self.show_overlap_enable
|
|
end,
|
|
callback = function()
|
|
self.show_overlap_enable = not self.show_overlap_enable
|
|
if not self.show_overlap_enable then
|
|
self.view:resetDimArea()
|
|
end
|
|
end
|
|
},
|
|
{
|
|
text_func = function()
|
|
return T(_("Number of lines: %1"), G_reader_settings:readSetting("copt_overlap_lines") or 1)
|
|
end,
|
|
enabled_func = function()
|
|
return self.show_overlap_enable
|
|
end,
|
|
callback = function(touchmenu_instance)
|
|
local SpinWidget = require("ui/widget/spinwidget")
|
|
UIManager:show(SpinWidget:new{
|
|
width = Screen:getWidth() * 0.75,
|
|
value = G_reader_settings:readSetting("copt_overlap_lines") or 1,
|
|
value_min = 1,
|
|
value_max = 10,
|
|
precision = "%d",
|
|
ok_text = _("Set"),
|
|
title_text = _("Set overlapped lines"),
|
|
text = overlap_lines_help_text,
|
|
callback = function(spin)
|
|
G_reader_settings:saveSetting("copt_overlap_lines", spin.value)
|
|
touchmenu_instance:updateItems()
|
|
end,
|
|
})
|
|
end,
|
|
keep_menu_open = true,
|
|
help_text = overlap_lines_help_text,
|
|
separator = true,
|
|
},
|
|
}
|
|
local overlap_enabled_func = function() return self.show_overlap_enable end
|
|
for _, menu_entry in ipairs(self.view:genOverlapStyleMenu(overlap_enabled_func)) do
|
|
table.insert(page_overlap_menu, menu_entry)
|
|
end
|
|
menu_items.page_overlap = {
|
|
text = _("Page overlap"),
|
|
enabled_func = function() return self.view.view_mode ~= "page" end,
|
|
help_text = _([[When page overlap is enabled, some lines from the previous pages are shown on the next page.]]),
|
|
sub_item_table = page_overlap_menu,
|
|
}
|
|
end
|
|
|
|
function ReaderRolling:getLastPercent()
|
|
if self.view.view_mode == "page" then
|
|
return self.current_page / self.old_page
|
|
else
|
|
-- FIXME: the calculated percent is not accurate in "scroll" mode.
|
|
return self.ui.document:getPosFromXPointer(
|
|
self.ui.document:getXPointer()) / self.ui.document.info.doc_height
|
|
end
|
|
end
|
|
|
|
function ReaderRolling:onSwipe(_, ges)
|
|
if ges.direction == "west" then
|
|
if self.inverse_reading_order then
|
|
self:onGotoViewRel(-1)
|
|
else
|
|
self:onGotoViewRel(1)
|
|
end
|
|
elseif ges.direction == "east" then
|
|
if self.inverse_reading_order then
|
|
self:onGotoViewRel(1)
|
|
else
|
|
self:onGotoViewRel(-1)
|
|
end
|
|
else
|
|
-- update footer (time & battery)
|
|
self.view.footer:updateFooter()
|
|
-- trigger full refresh
|
|
UIManager:setDirty(nil, "full")
|
|
end
|
|
end
|
|
|
|
function ReaderRolling:onPan(_, ges)
|
|
if self.view.view_mode == "scroll" then
|
|
local distance_type = "distance"
|
|
if self.ui.gesture and self.ui.gesture.multiswipes_enabled then
|
|
distance_type = "distance_delayed"
|
|
end
|
|
if ges.direction == "north" then
|
|
self:_gotoPos(self.current_pos + ges[distance_type])
|
|
elseif ges.direction == "south" then
|
|
self:_gotoPos(self.current_pos - ges[distance_type])
|
|
end
|
|
end
|
|
return true
|
|
end
|
|
|
|
function ReaderRolling:onPosUpdate(new_pos)
|
|
self.current_pos = new_pos
|
|
self:updateBatteryState()
|
|
end
|
|
|
|
function ReaderRolling:onPageUpdate(new_page)
|
|
self.current_page = new_page
|
|
self:updateBatteryState()
|
|
end
|
|
|
|
function ReaderRolling:onResume()
|
|
self:updateBatteryState()
|
|
end
|
|
|
|
function ReaderRolling:onGotoNextChapter()
|
|
local visible_page_count = self.ui.document:getVisiblePageCount()
|
|
local pageno = self.current_page + (visible_page_count > 1 and 1 or 0)
|
|
local new_page = self.ui.toc:getNextChapter(pageno, 0)
|
|
if new_page then
|
|
self.ui.link:addCurrentLocationToStack()
|
|
self:onGotoPage(new_page)
|
|
end
|
|
return true
|
|
end
|
|
|
|
function ReaderRolling:onGotoPrevChapter()
|
|
local pageno = self.current_page
|
|
local new_page = self.ui.toc:getPreviousChapter(pageno, 0)
|
|
if new_page then
|
|
self.ui.link:addCurrentLocationToStack()
|
|
self:onGotoPage(new_page)
|
|
end
|
|
return true
|
|
end
|
|
|
|
function ReaderRolling:onNotCharging()
|
|
self:updateBatteryState()
|
|
end
|
|
|
|
function ReaderRolling:onGotoPercent(percent)
|
|
logger.dbg("goto document offset in percent:", percent)
|
|
self:_gotoPercent(percent)
|
|
self.xpointer = self.ui.document:getXPointer()
|
|
return true
|
|
end
|
|
|
|
function ReaderRolling:onGotoPage(number)
|
|
if number then
|
|
self:_gotoPage(number)
|
|
end
|
|
self.xpointer = self.ui.document:getXPointer()
|
|
return true
|
|
end
|
|
|
|
function ReaderRolling:onGotoRelativePage(number)
|
|
if number then
|
|
self:_gotoPage(self.current_page + number)
|
|
end
|
|
self.xpointer = self.ui.document:getXPointer()
|
|
return true
|
|
end
|
|
|
|
function ReaderRolling:onGotoXPointer(xp, marker_xp)
|
|
if self.mark_func then
|
|
-- unschedule previous marker as it's no more accurate
|
|
UIManager:unschedule(self.mark_func)
|
|
self.mark_func = nil
|
|
end
|
|
if self.unmark_func then
|
|
-- execute scheduled unmark now to clean previous marker
|
|
self.unmark_func()
|
|
self.unmark_func = nil
|
|
end
|
|
self:_gotoXPointer(xp)
|
|
self.xpointer = xp
|
|
|
|
-- Allow tweaking this marker behaviour with a manual setting:
|
|
-- followed_link_marker = false: no marker shown
|
|
-- followed_link_marker = true: maker shown and not auto removed
|
|
-- followed_link_marker = <number>: removed after <number> seconds
|
|
-- (no real need for a menu item, the default is the finest)
|
|
local marker_setting = G_reader_settings:readSetting("followed_link_marker")
|
|
if marker_setting == nil then
|
|
marker_setting = 1 -- default is: shown and removed after 1 second
|
|
end
|
|
|
|
if marker_xp and marker_setting then
|
|
-- Show a mark on left side of screen to give a visual feedback of
|
|
-- where xpointer target is (and remove if after 1s)
|
|
local screen_y, screen_x = self.ui.document:getScreenPositionFromXPointer(marker_xp)
|
|
local doc_margins = self.ui.document:getPageMargins()
|
|
local marker_h = Screen:scaleBySize(self.ui.font.font_size * 1.1 * self.ui.font.line_space_percent/100.0)
|
|
-- Make it 4/5 of left margin wide (and bigger when huge margin)
|
|
local marker_w = math.floor(math.max(doc_margins["left"] - Screen:scaleBySize(5), doc_margins["left"] * 4/5))
|
|
|
|
if self.ui.document:getVisiblePageCount() > 1 and screen_x > Screen:getWidth() / 2 then
|
|
-- On right page in 2-pages mode
|
|
-- We could show the marker on the right of the page with:
|
|
-- screen_x = Screen:getWidth() - marker_w
|
|
-- But it's best to show it on the left of text, so in
|
|
-- the middle margin, so it still shows just left of a
|
|
-- footnote number.
|
|
-- This is a bit tricky with how the middle margin is sized
|
|
-- by crengine (see LVDocView::updateLayout() in lvdocview.cpp)
|
|
screen_x = Screen:getWidth() / 2
|
|
local page2_x = self.ui.document._document:getPageOffsetX(self.ui.document:getCurrentPage()+1)
|
|
marker_w = page2_x + marker_w - screen_x
|
|
else
|
|
screen_x = 0
|
|
end
|
|
|
|
self.mark_func = function()
|
|
self.mark_func = nil
|
|
Screen.bb:paintRect(screen_x, screen_y, marker_w, marker_h, Blitbuffer.COLOR_BLACK)
|
|
Screen["refreshFast"](Screen, screen_x, screen_y, marker_w, marker_h)
|
|
if type(marker_setting) == "number" then -- hide it
|
|
self.unmark_func = function()
|
|
self.unmark_func = nil
|
|
-- UIManager:setDirty(self.view.dialog, "ui", Geom:new({x=0, y=screen_y, w=marker_w, h=marker_h}))
|
|
-- No need to use setDirty (which would ask crengine to
|
|
-- re-render the page, which may take a few seconds on big
|
|
-- documents): we drew our black marker in the margin, we
|
|
-- can just draw a white one to make it disappear
|
|
Screen.bb:paintRect(screen_x, screen_y, marker_w, marker_h, Blitbuffer.COLOR_WHITE)
|
|
Screen["refreshUI"](Screen, screen_x, screen_y, marker_w, marker_h)
|
|
end
|
|
UIManager:scheduleIn(marker_setting, self.unmark_func)
|
|
end
|
|
end
|
|
UIManager:scheduleIn(0.5, self.mark_func)
|
|
end
|
|
return true
|
|
end
|
|
|
|
function ReaderRolling:getBookLocation()
|
|
return self.xpointer
|
|
end
|
|
|
|
function ReaderRolling:onRestoreBookLocation(saved_location)
|
|
return self:onGotoXPointer(saved_location.xpointer, saved_location.marker_xpointer)
|
|
end
|
|
|
|
function ReaderRolling:onGotoViewRel(diff)
|
|
logger.dbg("goto relative screen:", diff, ", in mode: ", self.view.view_mode)
|
|
if self.view.view_mode == "scroll" then
|
|
local footer_height = (self.view.footer_visible and 1 or 0) * self.view.footer:getHeight()
|
|
local page_visible_height = self.ui.dimen.h - footer_height
|
|
local pan_diff = diff * page_visible_height
|
|
if self.show_overlap_enable then
|
|
local overlap_lines = G_reader_settings:readSetting("copt_overlap_lines") or 1
|
|
local overlap_h = Screen:scaleBySize(self.ui.font.font_size * 1.1 * self.ui.font.line_space_percent/100.0) * overlap_lines
|
|
if pan_diff > overlap_h then
|
|
pan_diff = pan_diff - overlap_h
|
|
elseif pan_diff < -overlap_h then
|
|
pan_diff = pan_diff + overlap_h
|
|
end
|
|
end
|
|
local old_pos = self.current_pos
|
|
-- Only draw dim area when we moved a whole page (not when smaller scroll with Pan)
|
|
local do_dim_area = math.abs(diff) == 1
|
|
self:_gotoPos(self.current_pos + pan_diff, do_dim_area)
|
|
if diff > 0 and old_pos == self.current_pos then
|
|
self.ui:handleEvent(Event:new("EndOfBook"))
|
|
end
|
|
elseif self.view.view_mode == "page" then
|
|
local page_count = self.ui.document:getVisiblePageCount()
|
|
local old_page = self.current_page
|
|
self:_gotoPage(self.current_page + diff*page_count)
|
|
if diff > 0 and old_page == self.current_page then
|
|
self.ui:handleEvent(Event:new("EndOfBook"))
|
|
end
|
|
end
|
|
if self.ui.document ~= nil then
|
|
self.xpointer = self.ui.document:getXPointer()
|
|
end
|
|
return true
|
|
end
|
|
|
|
function ReaderRolling:onPanning(args, _)
|
|
if self.view.view_mode ~= "scroll" then return end
|
|
local _, dy = unpack(args)
|
|
self:_gotoPos(self.current_pos + dy * self.panning_steps.normal)
|
|
self.xpointer = self.ui.document:getXPointer()
|
|
return true
|
|
end
|
|
|
|
function ReaderRolling:onZoom()
|
|
--@TODO re-read doc_height info after font or lineheight changes 05.06 2012 (houqp)
|
|
self:updatePos()
|
|
end
|
|
|
|
--[[
|
|
remember to signal this event when the document has been zoomed,
|
|
font has been changed, or line height has been changed.
|
|
Note that xpointer should not be changed.
|
|
--]]
|
|
function ReaderRolling:onUpdatePos()
|
|
if self.ui.postReaderCallback ~= nil then -- ReaderUI:init() not yet done
|
|
-- Don't schedule any updatePos as long as ReaderUI:init() is
|
|
-- not finished (one will be called in the ui.postReaderCallback
|
|
-- we have set above) to avoid multiple refreshes.
|
|
return true
|
|
end
|
|
-- Calling this now ensures the re-rendering is done by crengine
|
|
-- so the delayed updatePos() has good info and can reposition
|
|
-- the previous xpointer accurately:
|
|
self.ui.document:getCurrentPos()
|
|
-- Otherwise, _readMetadata() would do that, but the positionning
|
|
-- would not work as expected, for some reason (it worked
|
|
-- previously because of some bad setDirty() in ConfigDialog widgets
|
|
-- that were triggering a full repaint of crengine (so, the needed
|
|
-- rerendering) before updatePos() is called.
|
|
UIManager:scheduleIn(0.1, function () self:updatePos() end)
|
|
return true
|
|
end
|
|
|
|
function ReaderRolling:updatePos()
|
|
-- reread document height
|
|
self.ui.document:_readMetadata()
|
|
-- update self.current_pos if the height of document has been changed.
|
|
local new_height = self.ui.document.info.doc_height
|
|
local new_page = self.ui.document.info.number_of_pages
|
|
if self.old_doc_height ~= new_height or self.old_page ~= new_page then
|
|
self:_gotoXPointer(self.xpointer)
|
|
self.old_doc_height = new_height
|
|
self.old_page = new_page
|
|
self.ui:handleEvent(Event:new("UpdateToc"))
|
|
self.view.footer:updateFooter()
|
|
end
|
|
UIManager:setDirty(self.view.dialog, "partial")
|
|
-- Allow for the new rendering to be shown before possibly showing
|
|
-- the "Styles have changes..." ConfirmBox so the user can decide
|
|
-- if it is really needed
|
|
UIManager:scheduleIn(0.1, function ()
|
|
self:onCheckDomStyleCoherence()
|
|
end)
|
|
end
|
|
|
|
--[[
|
|
switching screen mode should not change current page number
|
|
--]]
|
|
function ReaderRolling:onChangeViewMode()
|
|
self.ui.document:_readMetadata()
|
|
self.old_doc_height = self.ui.document.info.doc_height
|
|
self.old_page = self.ui.document.info.number_of_pages
|
|
self.ui:handleEvent(Event:new("UpdateToc"))
|
|
if self.xpointer then
|
|
self:_gotoXPointer(self.xpointer)
|
|
-- Ensure a whole screen refresh is always enqueued
|
|
UIManager:setDirty(self.view.dialog, "partial")
|
|
else
|
|
table.insert(self.ui.postInitCallback, function()
|
|
self:_gotoXPointer(self.xpointer)
|
|
end)
|
|
end
|
|
return true
|
|
end
|
|
|
|
function ReaderRolling:onRedrawCurrentView()
|
|
if self.view.view_mode == "page" then
|
|
self.ui:handleEvent(Event:new("PageUpdate", self.current_page))
|
|
else
|
|
self.ui:handleEvent(Event:new("PosUpdate", self.current_pos))
|
|
end
|
|
return true
|
|
end
|
|
|
|
function ReaderRolling:onSetDimensions(dimen)
|
|
if self.ui.postReaderCallback ~= nil then
|
|
-- ReaderUI:init() not yet done: just set document dimensions
|
|
self.ui.document:setViewDimen(Screen:getSize())
|
|
-- (what's done in the following else is done elsewhere by
|
|
-- the initialization code)
|
|
else
|
|
-- Initialization done: we are called on orientation change
|
|
-- or on window resize (SDL, Android possibly).
|
|
-- We need to temporarily re-enable internal history as crengine
|
|
-- uses it to reposition after resize
|
|
self.ui.document:enableInternalHistory(true)
|
|
-- Set document dimensions
|
|
self.ui.document:setViewDimen(Screen:getSize())
|
|
-- Re-setup previous position
|
|
self:onChangeViewMode()
|
|
self:onUpdatePos()
|
|
-- Re-disable internal history, with required redraw
|
|
self.ui.document:enableInternalHistory(false)
|
|
self:onRedrawCurrentView()
|
|
end
|
|
end
|
|
|
|
function ReaderRolling:onChangeScreenMode(mode, rotation)
|
|
-- Flag it as interactive so we can properly swap to Inverted orientations
|
|
-- (we usurp the second argument, which usually means rotation)
|
|
self.ui:handleEvent(Event:new("SetScreenMode", mode, rotation or true))
|
|
-- (This had the above ReaderRolling:onSetDimensions() called to resize
|
|
-- document dimensions and keep up with current position)
|
|
end
|
|
|
|
function ReaderRolling:onColorRenderingUpdate()
|
|
self.ui.document:updateColorRendering()
|
|
UIManager:setDirty(self.view.dialog, "partial")
|
|
end
|
|
|
|
--[[
|
|
PosUpdate event is used to signal other widgets that pos has been changed.
|
|
--]]
|
|
function ReaderRolling:_gotoPos(new_pos, do_dim_area)
|
|
if new_pos == self.current_pos then return end
|
|
if new_pos < 0 then new_pos = 0 end
|
|
-- Don't go past end of document, and ensure last line of the document
|
|
-- is shown just above the footer, whether footer is visible or not
|
|
local max_pos = self.ui.document.info.doc_height - self.ui.dimen.h + self.view.footer:getHeight()
|
|
if new_pos > max_pos then new_pos = max_pos end
|
|
-- adjust dim_area according to new_pos
|
|
if self.view.view_mode ~= "page" and self.show_overlap_enable and do_dim_area then
|
|
local footer_height = (self.view.footer_visible and 1 or 0) * self.view.footer:getHeight()
|
|
local page_visible_height = self.ui.dimen.h - footer_height
|
|
local panned_step = new_pos - self.current_pos
|
|
self.view.dim_area.x = 0
|
|
self.view.dim_area.h = page_visible_height - math.abs(panned_step)
|
|
self.view.dim_area.w = self.ui.dimen.w
|
|
if panned_step < 0 then
|
|
self.view.dim_area.y = page_visible_height - self.view.dim_area.h
|
|
elseif panned_step > 0 then
|
|
self.view.dim_area.y = 0
|
|
end
|
|
if self.current_pos > max_pos - self.ui.dimen.h/2 then
|
|
-- Avoid a fully dimmed page when reaching end of document
|
|
-- (the scroll would bump and not be a full page long)
|
|
self.view:resetDimArea()
|
|
end
|
|
else
|
|
self.view:resetDimArea()
|
|
end
|
|
self.ui.document:gotoPos(new_pos)
|
|
-- The current page we get in scroll mode may be a bit innacurate,
|
|
-- but we give it anyway to onPosUpdate so footer and statistics can
|
|
-- keep up with page.
|
|
self.ui:handleEvent(Event:new("PosUpdate", new_pos, self.ui.document:getCurrentPage()))
|
|
end
|
|
|
|
function ReaderRolling:_gotoPercent(new_percent)
|
|
self:_gotoPos(new_percent * self.ui.document.info.doc_height / 10000)
|
|
end
|
|
|
|
function ReaderRolling:_gotoPage(new_page, free_first_page)
|
|
if self.ui.document:getVisiblePageCount() > 1 and not free_first_page then
|
|
-- Ensure we always have the first of the two pages odd
|
|
if self.odd_or_even_first_page == 1 then -- odd
|
|
if band(new_page, 1) == 0 then
|
|
-- requested page will be shown as the right page
|
|
new_page = new_page - 1
|
|
end
|
|
elseif self.odd_or_even_first_page == 2 then -- (or 'even' if requested)
|
|
if band(new_page, 1) == 1 then
|
|
-- requested page will be shown as the right page
|
|
new_page = new_page - 1
|
|
end
|
|
end
|
|
end
|
|
self.ui.document:gotoPage(new_page)
|
|
if self.view.view_mode == "page" then
|
|
self.ui:handleEvent(Event:new("PageUpdate", self.ui.document:getCurrentPage()))
|
|
else
|
|
self.ui:handleEvent(Event:new("PosUpdate", self.ui.document:getCurrentPos(), self.ui.document:getCurrentPage()))
|
|
end
|
|
end
|
|
|
|
function ReaderRolling:_gotoXPointer(xpointer)
|
|
if self.view.view_mode == "page" then
|
|
self:_gotoPage(self.ui.document:getPageFromXPointer(xpointer))
|
|
else
|
|
self:_gotoPos(self.ui.document:getPosFromXPointer(xpointer))
|
|
end
|
|
end
|
|
|
|
--[[
|
|
currently we don't need to get page links on each page/pos update
|
|
since we can check link on the fly when tapping on the screen
|
|
function ReaderRolling:updatePageLink()
|
|
logger.dbg("update page link")
|
|
local links = self.ui.document:getPageLinks()
|
|
self.view.links = links
|
|
end
|
|
--]]
|
|
|
|
function ReaderRolling:onSetVisiblePages(visible_pages)
|
|
-- crengine may decide to not ensure the value we request
|
|
-- (for example, in 2-pages mode, it may stop being ensured
|
|
-- when we increase the font size up to a point where a line
|
|
-- would contain less that 20 glyphs).
|
|
-- crengine may enforce visible_page=1 when:
|
|
-- - not in page mode but in scroll mode
|
|
-- - screen w/h < 6/5
|
|
-- - w < 20*em
|
|
-- We nevertheless update the setting (that will saved) with what
|
|
-- the user has requested - and not what crengine has enforced.
|
|
self.visible_pages = visible_pages
|
|
local prev_visible_pages = self.ui.document:getVisiblePageCount()
|
|
self.ui.document:setVisiblePageCount(visible_pages)
|
|
local cur_visible_pages = self.ui.document:getVisiblePageCount()
|
|
if cur_visible_pages ~= prev_visible_pages then
|
|
self.ui:handleEvent(Event:new("UpdatePos"))
|
|
end
|
|
end
|
|
|
|
function ReaderRolling:onSetStatusLine(status_line, on_read_settings)
|
|
-- status_line values:
|
|
-- in crengine: 0=header enabled, 1=disabled
|
|
-- in koreader: 0=top status bar, 1=bottom mini bar
|
|
self.ui.document:setStatusLineProp(status_line)
|
|
self.cre_top_bar_enabled = status_line == 0
|
|
if not on_read_settings then
|
|
-- Ignore this event when it is first sent by ReaderCoptListener
|
|
-- on book loading, so we stay with the saved footer settings
|
|
self.view.footer:setVisible(status_line == 1)
|
|
end
|
|
self.ui:handleEvent(Event:new("UpdatePos"))
|
|
end
|
|
|
|
function ReaderRolling:updateBatteryState()
|
|
if self.view.view_mode == "page" and self.cre_top_bar_enabled then
|
|
logger.dbg("update battery state")
|
|
local powerd = Device:getPowerDevice()
|
|
-- -1 is CR_BATTERY_STATE_CHARGING @ crengine/crengine/include/lvdocview.h
|
|
local state = powerd:isCharging() and -1 or powerd:getCapacity()
|
|
if state then
|
|
self.ui.document:setBatteryState(state)
|
|
end
|
|
end
|
|
end
|
|
|
|
return ReaderRolling
|