Merge pull request #1817 from koreader/statistics_ui

migrate statistics ui to use KeyValuePage
pull/1819/head
Huang Xin 8 years ago
commit 271b79b039

@ -71,8 +71,8 @@ script:
- make all - make all
- travis_retry make testfront - travis_retry make testfront
- luajit $(which luacheck) --no-color -q frontend | tee ./luacheck.out - luajit $(which luacheck) --no-color -q frontend | tee ./luacheck.out
- test $(grep Total ./luacheck.out | awk '{print $2}') -le 238 - test $(grep Total ./luacheck.out | awk '{print $2}') -le 196
after_success: after_success:
- make coverage - make coverage
- cd koreader-*/koreader && luacov-coveralls -v - cd koreader-*/koreader && luajit $(which luacov-coveralls) -v

@ -1 +1 @@
Subproject commit fe0a527ddb8918127917b244c8c2ceb54b6a2705 Subproject commit 8a305a81a7a04e2f6df5cb336ad79cb17432fb4d

@ -22,6 +22,10 @@ function DataStorage:getDataDir()
return data_dir return data_dir
end end
function DataStorage:getHistoryDir()
return self:getDataDir() .. "/history"
end
local function initDataDir() local function initDataDir()
local data_dir = DataStorage:getDataDir() local data_dir = DataStorage:getDataDir()
local sub_data_dirs = {"cache", "clipboard", "data", "history", "ota", "screenshots"} local sub_data_dirs = {"cache", "clipboard", "data", "history", "ota", "screenshots"}

@ -6,10 +6,11 @@ local DataStorage = require("datastorage")
local UIManager = require("ui/uimanager") local UIManager = require("ui/uimanager")
local DocSettings = require("docsettings") local DocSettings = require("docsettings")
local Menu = require("ui/widget/menu") local Menu = require("ui/widget/menu")
local joinPath = require("ffi/util").joinPath
local Screen = require("device").screen local Screen = require("device").screen
local _ = require("gettext") local _ = require("gettext")
local history_dir = DataStorage:getDataDir() .. "/history/" local history_dir = DataStorage:getHistoryDir()
local FileManagerHistory = InputContainer:extend{ local FileManagerHistory = InputContainer:extend{
hist_menu_title = _("History"), hist_menu_title = _("History"),
@ -30,7 +31,7 @@ function FileManagerHistory:onMenuHold(item)
{ {
text = _("Remove this item from history"), text = _("Remove this item from history"),
callback = function() callback = function()
os.remove(history_dir..item.histfile) os.remove(joinPath(history_dir, item.histfile))
self._manager:updateItemTable() self._manager:updateItemTable()
UIManager:close(self.histfile_dialog) UIManager:close(self.histfile_dialog)
end, end,
@ -82,7 +83,7 @@ function FileManagerHistory:updateItemTable()
self.hist = {} self.hist = {}
for f in lfs.dir(history_dir) do for f in lfs.dir(history_dir) do
local path = history_dir..f local path = joinPath(history_dir, f)
if lfs.attributes(path, "mode") == "file" then if lfs.attributes(path, "mode") == "file" then
local name = DocSettings:getNameFromHistory(f) local name = DocSettings:getNameFromHistory(f)
table.insert(self.hist, { table.insert(self.hist, {

@ -12,7 +12,6 @@ local BBoxWidget = require("ui/widget/bboxwidget")
local HorizontalSpan = require("ui/widget/horizontalspan") local HorizontalSpan = require("ui/widget/horizontalspan")
local Button = require("ui/widget/button") local Button = require("ui/widget/button")
local Math = require("optmath") local Math = require("optmath")
local DEBUG = require("dbg")
local Blitbuffer = require("ffi/blitbuffer") local Blitbuffer = require("ffi/blitbuffer")
local PageCropDialog = VerticalGroup:new{ local PageCropDialog = VerticalGroup:new{

@ -2,7 +2,6 @@ local InputContainer = require("ui/widget/container/inputcontainer")
local RightContainer = require("ui/widget/container/rightcontainer") local RightContainer = require("ui/widget/container/rightcontainer")
local ImageWidget = require("ui/widget/imagewidget") local ImageWidget = require("ui/widget/imagewidget")
local GestureRange = require("ui/gesturerange") local GestureRange = require("ui/gesturerange")
local UIManager = require("ui/uimanager")
local Device = require("device") local Device = require("device")
local Geom = require("ui/geometry") local Geom = require("ui/geometry")
local Screen = require("device").screen local Screen = require("device").screen

@ -8,7 +8,6 @@ local Screen = require("device").screen
local Input = require("device").input local Input = require("device").input
local Event = require("ui/event") local Event = require("ui/event")
local UIManager = require("ui/uimanager") local UIManager = require("ui/uimanager")
local DEBUG = require("dbg")
local T = require("ffi/util").template local T = require("ffi/util").template
local _ = require("gettext") local _ = require("gettext")

@ -14,7 +14,6 @@ local Screen = require("device").screen
local Geom = require("ui/geometry") local Geom = require("ui/geometry")
local Event = require("ui/event") local Event = require("ui/event")
local Font = require("ui/font") local Font = require("ui/font")
local DEBUG = require("dbg")
local _ = require("gettext") local _ = require("gettext")
local util = require("util") local util = require("util")
@ -54,7 +53,7 @@ function ReaderFooter:init()
book_time_to_read = true, book_time_to_read = true,
chapter_time_to_read = true, chapter_time_to_read = true,
} }
local text_default = "" local text_default
if self.settings.all_at_once then if self.settings.all_at_once then
local info = {} local info = {}
if self.settings.battery then if self.settings.battery then

@ -1,5 +1,6 @@
local InputContainer = require("ui/widget/container/inputcontainer") local InputContainer = require("ui/widget/container/inputcontainer")
local UIManager = require("ui/uimanager") local UIManager = require("ui/uimanager")
local JSON = require("json")
local InfoMessage = require("ui/widget/infomessage") local InfoMessage = require("ui/widget/infomessage")
local T = require("ffi/util").template local T = require("ffi/util").template
local _ = require("gettext") local _ = require("gettext")
@ -11,32 +12,35 @@ local ReaderHyphenation = InputContainer:new{
} }
function ReaderHyphenation:init() function ReaderHyphenation:init()
local lang_data_file = assert(io.open("./data/hyph/languages.json"), "r")
lang_data = json.decode(lang_data_file:read("*all"))
self.lang_table = {} self.lang_table = {}
self.hyph_table = {} self.hyph_table = {}
self.hyph_alg = cre.getSelectedHyphDict() self.hyph_alg = cre.getSelectedHyphDict()
for k,v in ipairs(lang_data) do
table.insert(self.hyph_table, {
text = v.name,
callback = function()
self.hyph_alg = v.filename
UIManager:show(InfoMessage:new{
text = T( _("Changed hyphenation to %1."), v.name),
})
self.ui.document:setHyphDictionary(v.filename)
self.ui.toc:onUpdateToc()
end,
checked_func = function()
return v.filename == self.hyph_alg
end
})
self.lang_table[v.language] = v.filename local lang_data_file = assert(io.open("./data/hyph/languages.json"), "r")
if v.aliases then local ok, lang_data = pcall(JSON.decode, lang_data_file:read("*all"))
for i,alias in ipairs(v.aliases) do
self.lang_table[alias] = v.filename if ok and lang_data then
for k,v in ipairs(lang_data) do
table.insert(self.hyph_table, {
text = v.name,
callback = function()
self.hyph_alg = v.filename
UIManager:show(InfoMessage:new{
text = T(_("Changed hyphenation to %1."), v.name),
})
self.ui.document:setHyphDictionary(v.filename)
self.ui.toc:onUpdateToc()
end,
checked_func = function()
return v.filename == self.hyph_alg
end
})
self.lang_table[v.language] = v.filename
if v.aliases then
for i,alias in ipairs(v.aliases) do
self.lang_table[alias] = v.filename
end
end end
end end
end end

@ -6,7 +6,6 @@ local Geom = require("ui/geometry")
local Screen = require("device").screen local Screen = require("device").screen
local Device = require("device") local Device = require("device")
local Event = require("ui/event") local Event = require("ui/event")
local DEBUG = require("dbg")
local _ = require("gettext") local _ = require("gettext")
local ReaderLink = InputContainer:new{ local ReaderLink = InputContainer:new{

@ -318,17 +318,9 @@ function ReaderPaging:onSwipe(arg, ges)
elseif self.page_flipping_mode and self.original_page then elseif self.page_flipping_mode and self.original_page then
self:gotoPage(self.original_page) self:gotoPage(self.original_page)
elseif ges.direction == "west" then elseif ges.direction == "west" then
if DCHANGE_WEST_SWIPE_TO_EAST then self:onPagingRel(1)
self:onPagingRel(-1)
else
self:onPagingRel(1)
end
elseif ges.direction == "east" then elseif ges.direction == "east" then
if DCHANGE_EAST_SWIPE_TO_WEST then self:onPagingRel(-1)
self:onPagingRel(1)
else
self:onPagingRel(-1)
end
else else
-- trigger full refresh -- trigger full refresh
UIManager:setDirty(nil, "full") UIManager:setDirty(nil, "full")

@ -14,8 +14,7 @@ local ReaderPanning = InputContainer:new{
} }
function ReaderPanning:init() function ReaderPanning:init()
if Device:isTouchDevice() then if Device:hasKeyboard() then
else
self.key_events = { self.key_events = {
-- these will all generate the same event, just with different arguments -- these will all generate the same event, just with different arguments
MoveUp = { MoveUp = {

@ -261,17 +261,9 @@ end
function ReaderRolling:onSwipe(arg, ges) function ReaderRolling:onSwipe(arg, ges)
if ges.direction == "west" or ges.direction == "north" then if ges.direction == "west" or ges.direction == "north" then
if DCHANGE_WEST_SWIPE_TO_EAST then self:onGotoViewRel(1)
self:onGotoViewRel(-1)
else
self:onGotoViewRel(1)
end
elseif ges.direction == "east" or ges.direction == "south" then elseif ges.direction == "east" or ges.direction == "south" then
if DCHANGE_EAST_SWIPE_TO_WEST then self:onGotoViewRel(-1)
self:onGotoViewRel(1)
else
self:onGotoViewRel(-1)
end
end end
end end

@ -38,14 +38,14 @@ function ReaderStatus:addToMainMenu(tab_item_table)
end end
function ReaderStatus:showStatus() function ReaderStatus:showStatus()
local statusWidget = StatusWidget:new { local status_page = StatusWidget:new {
thumbnail = self.document:getCoverPageImage(), thumbnail = self.document:getCoverPageImage(),
props = self.document:getProps(), props = self.document:getProps(),
document = self.document, document = self.document,
settings = self.settings, settings = self.settings,
view = self.view, view = self.view,
} }
UIManager:show(statusWidget) UIManager:show(status_page)
end end
function ReaderStatus:onPageUpdate(pageno) function ReaderStatus:onPageUpdate(pageno)
@ -66,4 +66,3 @@ function ReaderStatus:onReadSettings(config)
end end
return ReaderStatus return ReaderStatus

@ -9,7 +9,6 @@ local Screen = require("device").screen
local Device = require("device") local Device = require("device")
local Event = require("ui/event") local Event = require("ui/event")
local Font = require("ui/font") local Font = require("ui/font")
local DEBUG = require("dbg")
local _ = require("gettext") local _ = require("gettext")
local ReaderToc = InputContainer:new{ local ReaderToc = InputContainer:new{
@ -143,7 +142,7 @@ function ReaderToc:getTocTicks(level)
depth = v.depth depth = v.depth
end end
else else
local depth = nil local depth
if level > 0 then if level > 0 then
depth = level depth = level
else else
@ -261,7 +260,6 @@ end
function ReaderToc:onShowToc() function ReaderToc:onShowToc()
self:fillToc() self:fillToc()
local max_depth = self:getMaxDepth()
-- build menu items -- build menu items
if #self.toc > 0 and not self.toc[1].text then if #self.toc > 0 and not self.toc[1].text then
for _,v in ipairs(self.toc) do for _,v in ipairs(self.toc) do

@ -4,7 +4,6 @@ local lfs = require("libs/libkoreader-lfs")
local UIManager = require("ui/uimanager") local UIManager = require("ui/uimanager")
local Screen = require("device").screen local Screen = require("device").screen
local Event = require("ui/event") local Event = require("ui/event")
local DEBUG = require("dbg")
local T = require("ffi/util").template local T = require("ffi/util").template
local _ = require("gettext") local _ = require("gettext")
@ -100,7 +99,6 @@ end
function ReaderTypeset:setStyleSheet(new_css) function ReaderTypeset:setStyleSheet(new_css)
if new_css ~= self.css then if new_css ~= self.css then
--DEBUG("setting css to ", new_css)
self.css = new_css self.css = new_css
self.ui.document:setStyleSheet(new_css) self.ui.document:setStyleSheet(new_css)
self.ui:handleEvent(Event:new("UpdatePos")) self.ui:handleEvent(Event:new("UpdatePos"))

@ -185,7 +185,7 @@ function ReaderView:screenToPageTransform(pos)
end end
else else
pos.page = self.ui.document:getCurrentPage() pos.page = self.ui.document:getCurrentPage()
local last_y = self.ui.document:getCurrentPos() -- local last_y = self.ui.document:getCurrentPos()
DEBUG("document has no pages at", pos) DEBUG("document has no pages at", pos)
return pos return pos
end end
@ -293,8 +293,8 @@ function ReaderView:getCurrentPageList()
end end
function ReaderView:getScrollPagePosition(pos) function ReaderView:getScrollPagePosition(pos)
local x_p, y_p
local x_s, y_s = pos.x, pos.y local x_s, y_s = pos.x, pos.y
local x_p, y_p = nil, nil
for _, state in ipairs(self.page_states) do for _, state in ipairs(self.page_states) do
if y_s < state.visible_area.h + state.offset.y then if y_s < state.visible_area.h + state.offset.y then
y_p = (state.visible_area.y + y_s - state.offset.y) / state.zoom y_p = (state.visible_area.y + y_s - state.offset.y) / state.zoom
@ -482,7 +482,6 @@ end
This method is supposed to be only used by ReaderPaging This method is supposed to be only used by ReaderPaging
--]] --]]
function ReaderView:recalculate() function ReaderView:recalculate()
local page_size = nil
if self.ui.document.info.has_pages and self.state.page then if self.ui.document.info.has_pages and self.state.page then
self.page_area = self:getPageArea( self.page_area = self:getPageArea(
self.state.page, self.state.page,

@ -1,7 +1,6 @@
local ReaderDictionary = require("apps/reader/modules/readerdictionary") local ReaderDictionary = require("apps/reader/modules/readerdictionary")
local Translator = require("ui/translator") local Translator = require("ui/translator")
local Wikipedia = require("ui/wikipedia") local Wikipedia = require("ui/wikipedia")
local Screen = require("device").screen
local DEBUG = require("dbg") local DEBUG = require("dbg")
local _ = require("gettext") local _ = require("gettext")
@ -29,7 +28,8 @@ function ReaderWikipedia:onLookupWikipedia(word, box)
-- seems lower case phrase has higher hit rate -- seems lower case phrase has higher hit rate
word = string.lower(word) word = string.lower(word)
local results = {} local results = {}
local ok, pages = pcall(Wikipedia.wikintro, Wikipedia, word, lang) local pages
ok, pages = pcall(Wikipedia.wikintro, Wikipedia, word, lang)
if ok and pages then if ok and pages then
for pageid, page in pairs(pages) do for pageid, page in pairs(pages) do
local result = { local result = {

@ -424,6 +424,12 @@ function GestureDetector:handleSwipe(tev)
y = self.first_tevs[slot].y, y = self.first_tevs[slot].y,
w = 0, h = 0, w = 0, h = 0,
} }
-- TODO: dirty hack for some weird devices, replace it with better solution
if swipe_direction == "west" and DCHANGE_WEST_SWIPE_TO_EAST then
swipe_direction = "east"
elseif swipe_direction == "east" and DCHANGE_EAST_SWIPE_TO_WEST then
swipe_direction = "west"
end
DEBUG("swipe", swipe_direction, swipe_distance, "detected in slot", slot) DEBUG("swipe", swipe_direction, swipe_distance, "detected in slot", slot)
self:clearState(slot) self:clearState(slot)
return { return {

@ -104,7 +104,7 @@ function NickelConf.frontLightLevel.set(new_intensity)
kobo_conf:close() kobo_conf:close()
end end
kobo_conf_w = assert(io.open(kobo_conf_path, "w")) local kobo_conf_w = assert(io.open(kobo_conf_path, "w"))
for i, line in ipairs(lines) do for i, line in ipairs(lines) do
kobo_conf_w:write(line, "\n") kobo_conf_w:write(line, "\n")
end end

@ -1,6 +1,6 @@
local BasePowerD = require("device/generic/powerd") local BasePowerD = require("device/generic/powerd")
local ffi = require("ffi") local ffi = require("ffi")
local inkview = ffi.load("inkview") -- local inkview = ffi.load("inkview")
ffi.cdef[[ ffi.cdef[[
int IsCharging(); int IsCharging();

@ -4,14 +4,14 @@ local dump = require("dump")
local DocSettings = {} local DocSettings = {}
local history_dir = DataStorage:getDataDir() .. "/history/" local history_dir = DataStorage:getHistoryDir()
function DocSettings:getSidecarDir(doc_path) function DocSettings:getSidecarDir(doc_path)
return doc_path:match("(.*)%.")..".sdr" return doc_path:match("(.*)%.")..".sdr"
end end
function DocSettings:getHistoryPath(fullpath) function DocSettings:getHistoryPath(fullpath)
return history_dir .. "[" .. fullpath:gsub("(.*/)([^/]+)","%1] %2"):gsub("/","#") .. ".lua" return history_dir .. "/[" .. fullpath:gsub("(.*/)([^/]+)","%1] %2"):gsub("/","#") .. ".lua"
end end
function DocSettings:getPathFromHistory(hist_name) function DocSettings:getPathFromHistory(hist_name)

@ -1,11 +1,9 @@
local CreOptions = require("ui/data/creoptions") local CreOptions = require("ui/data/creoptions")
local Document = require("document/document") local Document = require("document/document")
local Configurable = require("configurable")
local Blitbuffer = require("ffi/blitbuffer") local Blitbuffer = require("ffi/blitbuffer")
local lfs = require("libs/libkoreader-lfs") local lfs = require("libs/libkoreader-lfs")
local DataStorage = require("datastorage") local DataStorage = require("datastorage")
local Geom = require("ui/geometry") local Geom = require("ui/geometry")
local Device = require("device")
local Screen = require("device").screen local Screen = require("device").screen
local Font = require("ui/font") local Font = require("ui/font")
local DEBUG = require("dbg") local DEBUG = require("dbg")
@ -190,7 +188,7 @@ function CreDocument:getTextFromPositions(pos0, pos1)
local text_range = self._document:getTextFromPositions(pos0.x, pos0.y, pos1.x, pos1.y) local text_range = self._document:getTextFromPositions(pos0.x, pos0.y, pos1.x, pos1.y)
DEBUG("CreDocument: get text range", text_range) DEBUG("CreDocument: get text range", text_range)
if text_range then if text_range then
local line_boxes = self:getScreenBoxesFromPositions(text_range.pos0, text_range.pos1) -- local line_boxes = self:getScreenBoxesFromPositions(text_range.pos0, text_range.pos1)
return { return {
text = text_range.text, text = text_range.text,
pos0 = text_range.pos0, pos0 = text_range.pos0,

@ -1,10 +1,6 @@
local Geom = require("ui/geometry")
local Cache = require("cache")
local CacheItem = require("cacheitem")
local KoptOptions = require("ui/data/koptoptions") local KoptOptions = require("ui/data/koptoptions")
local Document = require("document/document") local Document = require("document/document")
local DrawContext = require("ffi/drawcontext") local DrawContext = require("ffi/drawcontext")
local Configurable = require("configurable")
local DjvuDocument = Document:new{ local DjvuDocument = Document:new{
_document = false, _document = false,

@ -33,8 +33,8 @@ local Document = {
is_edited = false, is_edited = false,
} }
function Document:new(o) function Document:new(from_o)
local o = o or {} local o = from_o or {}
setmetatable(o, self) setmetatable(o, self)
self.__index = self self.__index = self
if o._init then o:_init() end if o._init then o:_init() end
@ -205,7 +205,7 @@ function Document:getUsedBBoxDimensions(pageno, zoom, rotation)
if bbox.y0 < 0 then bbox.y0 = 0 end if bbox.y0 < 0 then bbox.y0 = 0 end
if bbox.x1 < 0 then bbox.x1 = 0 end if bbox.x1 < 0 then bbox.x1 = 0 end
if bbox.y1 < 0 then bbox.y1 = 0 end if bbox.y1 < 0 then bbox.y1 = 0 end
local ubbox_dimen = nil local ubbox_dimen
if (bbox.x0 >= bbox.x1) or (bbox.y0 >= bbox.y1) then if (bbox.x0 >= bbox.x1) or (bbox.y0 >= bbox.y1) then
-- if document's bbox info is corrupted, we use the page size -- if document's bbox info is corrupted, we use the page size
ubbox_dimen = self:getPageDimensions(pageno, zoom, rotation) ubbox_dimen = self:getPageDimensions(pageno, zoom, rotation)
@ -282,7 +282,7 @@ function Document:renderPage(pageno, rect, zoom, rotation, gamma, render_mode)
end end
-- prepare cache item with contained blitbuffer -- prepare cache item with contained blitbuffer
local tile = TileCacheItem:new{ tile = TileCacheItem:new{
persistent = true, persistent = true,
size = size.w * size.h + 64, -- estimation size = size.w * size.h + 64, -- estimation
excerpt = size, excerpt = size,

@ -31,9 +31,10 @@ function PdfDocument:init()
else else
self:_readMetadata() self:_readMetadata()
end end
if not (self.info.number_of_pages > 0) then -- TODO: handle this
-- if not (self.info.number_of_pages > 0) then
--error("No page found in PDF file") --error("No page found in PDF file")
end -- end
end end
function PdfDocument:unlock(password) function PdfDocument:unlock(password)

@ -10,6 +10,7 @@ local PicDocument = Document:new{
function PicDocument:init() function PicDocument:init()
if not pic then pic = require("ffi/pic") end if not pic then pic = require("ffi/pic") end
local ok
ok, self._document = pcall(pic.openDocument, self.file) ok, self._document = pcall(pic.openDocument, self.file)
if not ok then if not ok then
error("Failed to open jpeg image") error("Failed to open jpeg image")

@ -11,8 +11,8 @@ local GestureRange = {
scale = nil, scale = nil,
} }
function GestureRange:new(o) function GestureRange:new(from_o)
local o = o or {} local o = from_o or {}
setmetatable(o, self) setmetatable(o, self)
self.__index = self self.__index = self
return o return o
@ -29,7 +29,7 @@ function GestureRange:match(gs)
-- e.g. range = function() return self.dimen end -- e.g. range = function() return self.dimen end
-- for inputcontainer given that the x and y field of `self.dimen` is only -- for inputcontainer given that the x and y field of `self.dimen` is only
-- filled when the inputcontainer is painted into blitbuffer -- filled when the inputcontainer is painted into blitbuffer
local range = nil local range
if type(self.range) == "function" then if type(self.range) == "function" then
range = self.range() range = self.range()
else else

@ -18,7 +18,7 @@ local GlyphCache = Cache:new{
} }
-- iterator over UTF8 encoded characters in a string -- iterator over UTF8 encoded characters in a string
local function utf8Chars(input) local function utf8Chars(input_text)
local function read_next_glyph(input, pos) local function read_next_glyph(input, pos)
if string.len(input) < pos then return nil end if string.len(input) < pos then return nil end
local value = string.byte(input, pos) local value = string.byte(input, pos)
@ -60,7 +60,7 @@ local function utf8Chars(input)
return pos+bytes_left+1, glyph, string.sub(input, pos, pos+bytes_left) return pos+bytes_left+1, glyph, string.sub(input, pos, pos+bytes_left)
end end
end end
return read_next_glyph, input, 1 return read_next_glyph, input_text, 1
end end
function RenderText:getGlyph(face, charcode, bold) function RenderText:getGlyph(face, charcode, bold)

@ -9,7 +9,6 @@ local UIManager = require("ui/uimanager")
local Geom = require("ui/geometry") local Geom = require("ui/geometry")
local Device = require("device") local Device = require("device")
local Font = require("ui/font") local Font = require("ui/font")
local DEBUG = require("dbg")
local _ = require("gettext") local _ = require("gettext")
--[[ --[[

@ -1,14 +1,24 @@
--[[--
Button widget that shows an "×" and handles closing window when tapped
Example:
local parent_widget = HorizontalGroup:new{}
table.insert(parent_widget, CloseButton:new{
window = parent_widget,
})
UIManager:show(parent_widget)
]]
local InputContainer = require("ui/widget/container/inputcontainer") local InputContainer = require("ui/widget/container/inputcontainer")
local FrameContainer = require("ui/widget/container/framecontainer") local FrameContainer = require("ui/widget/container/framecontainer")
local TextWidget = require("ui/widget/textwidget") local TextWidget = require("ui/widget/textwidget")
local GestureRange = require("ui/gesturerange") local GestureRange = require("ui/gesturerange")
local Font = require("ui/font") local Font = require("ui/font")
--[[
a button widget that shows an "×" and handles closing window when tapped
--]]
local CloseButton = InputContainer:new{ local CloseButton = InputContainer:new{
align = "right", overlap_align = "right",
window = nil, window = nil,
} }
@ -23,7 +33,7 @@ function CloseButton:init()
text_widget text_widget
} }
self.dimen = text_widget:getSize():copy() self.dimen = text_widget:getSize()
self.ges_events.Close = { self.ges_events.Close = {
GestureRange:new{ GestureRange:new{

@ -8,11 +8,15 @@ local WidgetContainer = Widget:new()
function WidgetContainer:init() function WidgetContainer:init()
if self.dimen then if self.dimen then
if not self.dimen.w then if self.initDimen then
self.dimen.w = self[1].getSize().w self:initDimen()
end else
if not self.dimen.h then if not self.dimen.w then
self.dimen.h = self[1].getSize().h self.dimen.w = self[1].getSize().w
end
if not self.dimen.h then
self.dimen.h = self[1].getSize().h
end
end end
end end
end end

@ -91,9 +91,7 @@ function DictQuickLookup:init()
}, },
} }
table.insert(self.dict_bar, table.insert(self.dict_bar,
CloseButton:new{ CloseButton:new{ window = self, })
window = self,
})
end end
end end

@ -39,6 +39,8 @@ function HorizontalGroup:paintTo(bb, x, y)
widget:paintTo(bb, x + self._offsets[i].x, y) widget:paintTo(bb, x + self._offsets[i].x, y)
elseif self.align == "bottom" then elseif self.align == "bottom" then
widget:paintTo(bb, x + self._offsets[i].x, y + size.h - self._offsets[i].y) widget:paintTo(bb, x + self._offsets[i].x, y + size.h - self._offsets[i].y)
else
print("[!] invalid alignment for HorizontalGroup", self.align)
end end
end end
end end

@ -0,0 +1,273 @@
--[[--
Widget that presents a multi-page to show key value pairs.
Example:
local Foo = KeyValuePage:new{
title = "Statistics",
kv_pairs = {
{"Current period", "00:00:00"},
-- single or more "-" will generate a solid line
"----------------------------",
{"Page to read", "5"},
{"Time to read", "00:01:00"},
{"Press me", "will invoke the callback",
callback = function() print("hello") end },
},
}
UIManager:show(Foo)
]]
local InputContainer = require("ui/widget/container/inputcontainer")
local FrameContainer = require("ui/widget/container/framecontainer")
local VerticalGroup = require("ui/widget/verticalgroup")
local VerticalSpan = require("ui/widget/verticalspan")
local OverlapGroup = require("ui/widget/overlapgroup")
local LeftContainer = require("ui/widget/container/leftcontainer")
local RightContainer = require("ui/widget/container/rightcontainer")
local LineWidget = require("ui/widget/linewidget")
local Blitbuffer = require("ffi/blitbuffer")
local CloseButton = require("ui/widget/closebutton")
local UIManager = require("ui/uimanager")
local TextWidget = require("ui/widget/textwidget")
local GestureRange = require("ui/gesturerange")
local Geom = require("ui/geometry")
local Font = require("ui/font")
local Device = require("device")
local Screen = Device.screen
local KeyValueTitle = VerticalGroup:new{
kv_page = nil,
title = "",
align = "left",
}
function KeyValueTitle:init()
self.close_button = CloseButton:new{ window = self }
table.insert(self, OverlapGroup:new{
dimen = { w = self.width },
TextWidget:new{
text = self.title,
face = Font:getFace("tfont", 26),
},
self.close_button,
})
self.page_cnt = FrameContainer:new{
padding = 4,
margin = 0,
bordersize = 0,
background = Blitbuffer.COLOR_WHITE,
overlap_offset = {0, -18},
TextWidget:new{
text = "", -- page count
fgcolor = Blitbuffer.COLOR_GREY,
face = Font:getFace("ffont", 16),
},
}
self.title_bottom = OverlapGroup:new{
dimen = { w = self.width, h = Screen:scaleBySize(2) },
LineWidget:new{
dimen = Geom:new{ w = self.width, h = Screen:scaleBySize(2) },
background = Blitbuffer.COLOR_GREY,
style = "solid",
},
self.page_cnt,
}
table.insert(self, self.title_bottom)
table.insert(self, VerticalSpan:new{ width = Screen:scaleBySize(5) })
end
function KeyValueTitle:setPageCount(curr, total)
if total == 1 then
-- remove page count if there is only one page
table.remove(self.title_bottom, 2)
return
end
self.page_cnt[1]:setText(curr .. "/" .. total)
self.page_cnt.overlap_offset[1] = (self.width - self.page_cnt:getSize().w
- self.close_button:getSize().w)
self.title_bottom[2] = self.page_cnt
end
function KeyValueTitle:onClose()
self.kv_page:onClose()
return true
end
local KeyValueItem = InputContainer:new{
key = nil,
value = nil,
cface = Font:getFace("cfont", 24),
width = nil,
height = nil,
}
function KeyValueItem:init()
self.dimen = Geom:new{w = self.width, h = self.height}
if self.callback and Device:isTouchDevice() then
self.ges_events.Tap = {
GestureRange:new{
ges = "tap",
range = self.dimen,
}
}
end
self[1] = OverlapGroup:new{
dimen = self.dimen:copy(),
LeftContainer:new{
dimen = self.dimen:copy(),
TextWidget:new{
text = self.key,
face = self.cface,
}
},
RightContainer:new{
dimen = self.dimen:copy(),
TextWidget:new{
text = self.value,
face = self.cface,
}
}
}
end
function KeyValueItem:onTap()
self.callback()
return true
end
local KeyValuePage = InputContainer:new{
title = "",
width = nil,
height = nil,
-- index for the first item to show
show_page = 1,
}
function KeyValuePage:init()
self.dimen = Geom:new{
w = self.width or Screen:getWidth(),
h = self.height or Screen:getHeight(),
}
if Device:isTouchDevice() then
self.ges_events.Swipe = {
GestureRange:new{
ges = "swipe",
range = self.dimen,
}
}
end
local padding = Screen:scaleBySize(10)
self.item_width = self.dimen.w - 2 * padding
self.item_height = Screen:scaleBySize(30)
-- setup title bar
self.title_bar = KeyValueTitle:new{
title = self.title,
width = self.item_width,
height = self.item_height,
kv_page = self,
}
-- setup main content
self.item_padding = self.item_height / 4
local line_height = self.item_height + 2 * self.item_padding
local content_height = self.dimen.h - self.title_bar:getSize().h
self.items_per_page = math.floor(content_height / line_height)
self.pages = math.ceil(#self.kv_pairs / self.items_per_page)
self.main_content = VerticalGroup:new{}
self:_populateItems()
-- assemble page
self[1] = FrameContainer:new{
height = self.dimen.h,
padding = padding,
bordersize = 0,
background = Blitbuffer.COLOR_WHITE,
VerticalGroup:new{
self.title_bar,
self.main_content,
},
}
end
function KeyValuePage:nextPage()
local new_page = math.min(self.show_page+1, self.pages)
if new_page > self.show_page then
self.show_page = new_page
self:_populateItems()
end
end
function KeyValuePage:prevPage()
local new_page = math.max(self.show_page-1, 1)
if new_page < self.show_page then
self.show_page = new_page
self:_populateItems()
end
end
-- make sure self.item_padding and self.item_height are set before calling this
function KeyValuePage:_populateItems()
self.main_content:clear()
local idx_offset = (self.show_page - 1) * self.items_per_page
for idx = 1, self.items_per_page do
local entry = self.kv_pairs[idx_offset + idx]
if entry == nil then break end
table.insert(self.main_content,
VerticalSpan:new{ width = self.item_padding })
if type(entry) == "table" then
table.insert(
self.main_content,
KeyValueItem:new{
height = self.item_height,
width = self.item_width,
key = entry[1],
value = entry[2],
callback = entry.callback,
}
)
elseif type(entry) == "string" then
local c = string.sub(entry, 1, 1)
if c == "-" then
table.insert(self.main_content, LineWidget:new{
background = Blitbuffer.COLOR_LIGHT_GREY,
dimen = Geom:new{
w = self.item_width,
h = Screen:scaleBySize(2)
},
style = "solid",
})
end
end
table.insert(self.main_content,
VerticalSpan:new{ width = self.item_padding })
end
self.title_bar:setPageCount(self.show_page, self.pages)
UIManager:setDirty(self, function()
return "ui", self.dimen
end)
end
function KeyValuePage:onSwipe(arg, ges_ev)
if ges_ev.direction == "west" then
self:nextPage()
return true
elseif ges_ev.direction == "east" then
self:prevPage()
return true
end
end
function KeyValuePage:onClose()
UIManager:close(self)
return true
end
return KeyValuePage

@ -19,13 +19,13 @@ function LineWidget:paintTo(bb, x, y)
else else
if self.empty_segments then if self.empty_segments then
bb:paintRect(x, y, bb:paintRect(x, y,
self.empty_segments[1].s, self.empty_segments[1].s,
self.dimen.h, self.dimen.h,
self.background) self.background)
bb:paintRect(x + self.empty_segments[1].e, y, bb:paintRect(x + self.empty_segments[1].e, y,
self.dimen.w - x - self.empty_segments[1].e, self.dimen.w - x - self.empty_segments[1].e,
self.dimen.h, self.dimen.h,
self.background) self.background)
else else
bb:paintRect(x, y, self.dimen.w, self.dimen.h, self.background) bb:paintRect(x, y, self.dimen.w, self.dimen.h, self.background)
end end

@ -8,7 +8,6 @@ local BottomContainer = require("ui/widget/container/bottomcontainer")
local UnderlineContainer = require("ui/widget/container/underlinecontainer") local UnderlineContainer = require("ui/widget/container/underlinecontainer")
local FocusManager = require("ui/widget/focusmanager") local FocusManager = require("ui/widget/focusmanager")
local TextWidget = require("ui/widget/textwidget") local TextWidget = require("ui/widget/textwidget")
local LineWidget = require("ui/widget/linewidget")
local OverlapGroup = require("ui/widget/overlapgroup") local OverlapGroup = require("ui/widget/overlapgroup")
local VerticalSpan = require("ui/widget/verticalspan") local VerticalSpan = require("ui/widget/verticalspan")
local HorizontalSpan = require("ui/widget/horizontalspan") local HorizontalSpan = require("ui/widget/horizontalspan")
@ -82,7 +81,7 @@ NOTICE:
@menu entry must be provided in order to close the menu @menu entry must be provided in order to close the menu
--]] --]]
local MenuCloseButton = InputContainer:new{ local MenuCloseButton = InputContainer:new{
align = "right", overlap_align = "right",
menu = nil, menu = nil,
dimen = Geom:new{}, dimen = Geom:new{},
} }
@ -94,7 +93,10 @@ function MenuCloseButton:init()
} }
local text_size = self[1]:getSize() local text_size = self[1]:getSize()
self.dimen.w, self.dimen.h = text_size.w*2, text_size.h*2 self.dimen = Geom:new{
w = text_size.w*2,
h = text_size.h*2,
}
self.ges_events.Close = { self.ges_events.Close = {
GestureRange:new{ GestureRange:new{
@ -110,45 +112,6 @@ function MenuCloseButton:onClose()
return true return true
end end
--[[
Widget that displays a solid line in menu
--]]
local SeparatorMenuItem = InputContainer:new{
style = "solid",
dimen = nil,
_line_container = nil,
}
function SeparatorMenuItem:init()
self._line_container = CenterContainer:new{
vertical_align = "center",
dimen = Geom:new {
w = self.dimen.w,
h = self.dimen.h
},
HorizontalGroup:new {
align = "center",
OverlapGroup:new {
dimen = Geom:new { w = self.dimen.w, h = Screen:scaleBySize(2) },
LineWidget:new {
style = self.style,
dimen = Geom:new { w = self.dimen.w - 30, h = Screen:scaleBySize(2) },
}
},
}
}
self[1] = FrameContainer:new {
bordersize = 0,
padding = 0,
HorizontalGroup:new {
align = "center",
HorizontalSpan:new { width = 15 },
self._line_container
}
}
end
--[[ --[[
Widget that displays an item for menu Widget that displays an item for menu
--]] --]]
@ -430,7 +393,7 @@ function Menu:init()
-- start to set up widget layout -- -- start to set up widget layout --
----------------------------------- -----------------------------------
self.menu_title = TextWidget:new{ self.menu_title = TextWidget:new{
align = "center", overlap_align = "center",
text = self.title, text = self.title,
face = self.tface, face = self.tface,
} }
@ -578,9 +541,7 @@ function Menu:init()
if Device:isTouchDevice() then if Device:isTouchDevice() then
if self.has_close_button then if self.has_close_button then
table.insert(self.title_bar, table.insert(self.title_bar,
MenuCloseButton:new{ MenuCloseButton:new{ menu = self })
menu = self,
})
end end
-- watch for outer region if it's a self contained widget -- watch for outer region if it's a self contained widget
if self.is_popout then if self.is_popout then
@ -681,27 +642,20 @@ function Menu:updateItems(select_number)
item_shortcut = "Ent" item_shortcut = "Ent"
end end
end end
local item_tmp local item_tmp = MenuItem:new{
if self.item_table[i].text == "-" then show_parent = self.show_parent,
item_tmp = SeparatorMenuItem:new{ state = self.item_table[i].state,
dimen = self.item_dimen:new{ h = Screen:scaleBySize(10) }, state_size = self.state_size or {},
} text = self.item_table[i].text,
else mandatory = self.item_table[i].mandatory,
item_tmp = MenuItem:new{ bold = self.item_table.current == i,
show_parent = self.show_parent, face = self.cface,
state = self.item_table[i].state, dimen = self.item_dimen:new(),
state_size = self.state_size or {}, shortcut = item_shortcut,
text = self.item_table[i].text, shortcut_style = shortcut_style,
mandatory = self.item_table[i].mandatory, table = self.item_table[i],
bold = self.item_table.current == i, menu = self,
face = self.cface, }
dimen = self.item_dimen:new(),
shortcut = item_shortcut,
shortcut_style = shortcut_style,
table = self.item_table[i],
menu = self,
}
end
table.insert(self.item_group, item_tmp) table.insert(self.item_group, item_tmp)
-- this is for focus manager -- this is for focus manager
table.insert(self.layout, {item_tmp}) table.insert(self.layout, {item_tmp})
@ -923,17 +877,9 @@ end
function Menu:onSwipe(arg, ges_ev) function Menu:onSwipe(arg, ges_ev)
if ges_ev.direction == "west" then if ges_ev.direction == "west" then
if DCHANGE_WEST_SWIPE_TO_EAST then self:onNextPage()
self:onPrevPage()
else
self:onNextPage()
end
elseif ges_ev.direction == "east" then elseif ges_ev.direction == "east" then
if DCHANGE_WEST_SWIPE_TO_EAST then self:onPrevPage()
self:onNextPage()
else
self:onPrevPage()
end
end end
end end

@ -22,14 +22,22 @@ function OverlapGroup:getSize()
end end
end end
return self._size
end
function OverlapGroup:initDimen()
self:getSize() -- populate self._size
-- sync self._size with self.dimen, self.dimen has higher priority
if self.dimen.w then if self.dimen.w then
self._size.w = self.dimen.w self._size.w = self.dimen.w
else
self.dimen.w = self._size.w
end end
if self.dimen.h then if self.dimen.h then
self._size.h = self.dimen.h self._size.h = self.dimen.h
else
self.dimen.h = self._size.h
end end
return self._size
end end
function OverlapGroup:paintTo(bb, x, y) function OverlapGroup:paintTo(bb, x, y)
@ -37,10 +45,12 @@ function OverlapGroup:paintTo(bb, x, y)
for i, wget in ipairs(self) do for i, wget in ipairs(self) do
local wget_size = wget:getSize() local wget_size = wget:getSize()
if wget.align == "right" then if wget.overlap_align == "right" then
wget:paintTo(bb, x+size.w-wget_size.w, y) wget:paintTo(bb, x+size.w-wget_size.w, y)
elseif wget.align == "center" then elseif wget.overlap_align == "center" then
wget:paintTo(bb, x+math.floor((size.w-wget_size.w)/2), y) wget:paintTo(bb, x+math.floor((size.w-wget_size.w)/2), y)
elseif wget.overlap_offset then
wget:paintTo(bb, x+wget.overlap_offset[1], y+wget.overlap_offset[2])
else else
-- default to left -- default to left
wget:paintTo(bb, x, y) wget:paintTo(bb, x, y)

@ -25,23 +25,33 @@ local TextWidget = Widget:new{
--self._length = RenderText:renderUtf8Text(self._bb, 0, h*0.8, self.face, self.text, true, self.bold) --self._length = RenderText:renderUtf8Text(self._bb, 0, h*0.8, self.face, self.text, true, self.bold)
--end --end
function TextWidget:updateSize()
local tsize = RenderText:sizeUtf8Text(0, Screen:getWidth(), self.face, self.text, true, self.bold)
if not tsize then
self._length = 0
else
self._length = tsize.x
end
self._height = self.face.size * 1.5
end
function TextWidget:getSize() function TextWidget:getSize()
--if not self._bb then --if not self._bb then
--self:_render() --self:_render()
--end --end
--return { w = self._length, h = self._bb:getHeight() } --return { w = self._length, h = self._bb:getHeight() }
local tsize = RenderText:sizeUtf8Text(0, Screen:getWidth(), self.face, self.text, true, self.bold) self:updateSize()
if not tsize then
return Geom:new{}
end
self._length = tsize.x
self._height = self.face.size * 1.5
return Geom:new{ return Geom:new{
w = self._length, w = self._length,
h = self._height, h = self._height,
} }
end end
function TextWidget:setText(text)
self.text = text
self:updateSize()
end
function TextWidget:paintTo(bb, x, y) function TextWidget:paintTo(bb, x, y)
--if not self._bb then --if not self._bb then
--self:_render() --self:_render()

@ -94,16 +94,13 @@ function TouchMenuItem:onTapSelect(arg, ges)
end end
if enabled == false then return end if enabled == false then return end
UIManager:scheduleIn(0.0, function() self.item_frame.invert = true
self.item_frame.invert = true UIManager:setDirty(self.show_parent, function()
UIManager:setDirty(self.show_parent, function() return "ui", self.dimen
return "ui", self.dimen
end)
end) end)
-- yield to main UI loop to invert item
UIManager:scheduleIn(0.1, function() UIManager:scheduleIn(0.1, function()
self.menu:onMenuSelect(self.item) self.menu:onMenuSelect(self.item)
end)
UIManager:scheduleIn(0.5, function()
self.item_frame.invert = false self.item_frame.invert = false
UIManager:setDirty(self.show_parent, function() UIManager:setDirty(self.show_parent, function()
return "ui", self.dimen return "ui", self.dimen

@ -18,8 +18,8 @@ Use this method to define a class that's inherited from current class.
It only setup the metabale (or prototype chain) and will not initiatie It only setup the metabale (or prototype chain) and will not initiatie
a real instance, i.e. call self:init() a real instance, i.e. call self:init()
--]] --]]
function Widget:extend(o) function Widget:extend(from_o)
local o = o or {} local o = from_o or {}
setmetatable(o, self) setmetatable(o, self)
self.__index = self self.__index = self
return o return o

13
kodev

@ -265,7 +265,7 @@ usage: test [front|base] <TEST_NAME>
if [ ! -z $2 ]; then if [ ! -z $2 ]; then
test_path="${test_path}/$2" test_path="${test_path}/$2"
fi fi
busted --exclude-tags=notest ${test_path} busted -o verbose_print --exclude-tags=notest ${test_path}
popd popd
} }
@ -277,10 +277,13 @@ usage: $0 COMMAND <ARGS>
Supported commands: Supported commands:
build Build KOReader fetch-thirdparty Fetch thirdparty dependencies for build
clean Clean KOReader build build Build KOReader
run Run KOReader clean Clean KOReader build
wbuilder Run wbuilder.lua script (useful for building new UI widget) release Build KOReader release package
run Run KOReader
wbuilder Run wbuilder.lua script (useful for building new UI widget)
test Run tests
" "
if [ $# -lt 1 ]; then if [ $# -lt 1 ]; then

@ -1 +1 @@
Subproject commit 63f26f697fc2f1ce1ef2b0af61dd7875b2a8ce25 Subproject commit cc5c30286ade5151848a8dbb2be64e90fb2c8fcf

@ -1,21 +1,22 @@
local InputContainer = require("ui/widget/container/inputcontainer") local InputContainer = require("ui/widget/container/inputcontainer")
local MultiInputDialog = require("ui/widget/multiinputdialog") local MultiInputDialog = require("ui/widget/multiinputdialog")
local CenterContainer = require("ui/widget/container/centercontainer") local CenterContainer = require("ui/widget/container/centercontainer")
local KeyValuePage = require("ui/widget/keyvaluepage")
local UIManager = require("ui/uimanager") local UIManager = require("ui/uimanager")
local Screen = require("device").screen local Screen = require("device").screen
local Menu = require("ui/widget/menu")
local Font = require("ui/font") local Font = require("ui/font")
local TimeVal = require("ui/timeval") local TimeVal = require("ui/timeval")
local DataStorage = require("datastorage") local DataStorage = require("datastorage")
local lfs = require("libs/libkoreader-lfs") local lfs = require("libs/libkoreader-lfs")
local DEBUG = require("dbg") local DEBUG = require("dbg")
local T = require("ffi/util").template local T = require("ffi/util").template
local joinPath = require("ffi/util").joinPath
local _ = require("gettext") local _ = require("gettext")
local util = require("util") local util = require("util")
local tableutil = require("tableutil") local tableutil = require("tableutil")
local statistics_dir = DataStorage:getDataDir() .. "/statistics/" local statistics_dir = DataStorage:getDataDir() .. "/statistics/"
local history_dir = DataStorage:getDataDir() .. "/history/" local history_dir = DataStorage:getHistoryDir()
local ReaderStatistics = InputContainer:new { local ReaderStatistics = InputContainer:new {
last_time = nil, last_time = nil,
@ -51,37 +52,37 @@ function ReaderStatistics:init()
self.last_time = TimeVal:now() self.last_time = TimeVal:now()
end end
function ReaderStatistics:getBookProperties()
local props = self.view.document:getProps()
if props.title == "No document" or props.title == "" then
-- FIXME: sometimes crengine returns "No document", try one more time
props = self.view.document:getProps()
end
return props
end
function ReaderStatistics:initData(config) function ReaderStatistics:initData(config)
--first execution -- first execution
if self.is_enabled then if self.is_enabled then
local book_properties = self:getBookProperties()
self:savePropertiesInToData(book_properties)
if not self.data then if not self.data then
--first time merge data self.data = { performance_in_pages= {} }
self:inplaceMigration(); self:inplaceMigration(); -- first time merge data
end end
local book_properties = self:getBookProperties()
self.data.title = book_properties.title
self.data.authors = book_properties.authors
self.data.language = book_properties.language
self.data.series = book_properties.series
self.data.pages = self.view.document:getPageCount() self.data.pages = self.view.document:getPageCount()
return return
end end
end end
function ReaderStatistics:addToMainMenu(tab_item_table) function ReaderStatistics:getStatisticEnabledMenuItem()
table.insert(tab_item_table.plugins, {
text = _("Statistics"),
sub_item_table = {
self:getStatisticEnabledMenuTable(),
self:getStatisticSettingsMenuTable(),
self:getStatisticForCurrentBookMenuTable(),
self:getStatisticTotalStatisticMenuTable(),
}
})
end
function ReaderStatistics:getStatisticEnabledMenuTable()
return { return {
text_func = function() text = _("Enabled"),
return _("Enabled")
end,
checked_func = function() return self.is_enabled end, checked_func = function() return self.is_enabled end,
callback = function() callback = function()
-- if was enabled, have to save data to file -- if was enabled, have to save data to file
@ -99,18 +100,6 @@ function ReaderStatistics:getStatisticEnabledMenuTable()
} }
end end
function ReaderStatistics:getStatisticSettingsMenuTable()
return {
text_func = function()
return _("Settings")
end,
checked_func = function() return false end,
callback = function()
self:updateSettings()
end,
}
end
function ReaderStatistics:updateSettings() function ReaderStatistics:updateSettings()
self.settings_dialog = MultiInputDialog:new { self.settings_dialog = MultiInputDialog:new {
title = _("Statistics settings"), title = _("Statistics settings"),
@ -138,9 +127,9 @@ function ReaderStatistics:updateSettings()
{ {
text = _("Apply"), text = _("Apply"),
callback = function() callback = function()
self:saveSettings(MultiInputDialog:getFields())
self.settings_dialog:onClose() self.settings_dialog:onClose()
UIManager:close(self.settings_dialog) UIManager:close(self.settings_dialog)
self:saveSettings(MultiInputDialog:getFields())
end end
}, },
}, },
@ -153,101 +142,74 @@ function ReaderStatistics:updateSettings()
UIManager:show(self.settings_dialog) UIManager:show(self.settings_dialog)
end end
function ReaderStatistics:getStatisticForCurrentBookMenuTable() function ReaderStatistics:addToMainMenu(tab_item_table)
self.status_menu = {} table.insert(tab_item_table.plugins, {
text = _("Statistics"),
local book_status = Menu:new { sub_item_table = {
title = _("Status"), self:getStatisticEnabledMenuItem(),
item_table = self:updateCurrentStat(), {
is_borderless = true, text = _("Settings"),
is_popout = false, callback = function() self:updateSettings() end,
is_enable_shortcut = false, },
width = Screen:getWidth(), {
height = Screen:getHeight(), text = _("Current book"),
cface = Font:getFace("cfont", 20), callback = function()
} UIManager:show(KeyValuePage:new{
title = _("Statistics"),
self.status_menu = CenterContainer:new { kv_pairs = self:getCurrentStat(),
dimen = Screen:getSize(), })
book_status, end
} },
{
book_status.close_callback = function() text = _("All books"),
UIManager:close(self.status_menu) callback = function()
end total_msg, kv_pairs = self:getTotalStats()
UIManager:show(KeyValuePage:new{
book_status.show_parent = self.status_menu title = total_msg,
kv_pairs = kv_pairs,
return { })
text = _("Current"), end
enabled_func = function() return true end, },
checked_func = function() return false end, },
callback = function() })
book_status:swithItemTable(nil, self:updateCurrentStat())
UIManager:show(self.status_menu)
return true
end
}
end end
function ReaderStatistics:getStatisticTotalStatisticMenuTable() function ReaderStatistics:getCurrentStat()
self.total_status = Menu:new { local dates = {}
title = _("Total"), for k, v in pairs(self.data.performance_in_pages) do
item_table = self:updateTotalStat(), dates[os.date("%Y-%m-%d", k)] = ""
is_borderless = true,
is_popout = false,
is_enable_shortcut = false,
width = Screen:getWidth(),
height = Screen:getHeight(),
cface = Font:getFace("cfont", 20),
}
self.total_menu = CenterContainer:new {
dimen = Screen:getSize(),
self.total_status,
}
self.total_status.close_callback = function()
UIManager:close(self.total_menu)
end end
local total_days = util.tableSize(dates)
self.total_status.show_parent = self.total_menu local read_pages = util.tableSize(self.data.performance_in_pages)
local current_page = self.view.state.page -- get current page from the view
local avg_time_per_page = self.data.total_time_in_sec / read_pages
return { return {
text = _("Total"), { _("Current period"), util.secondsToClock(self.current_period, false) },
callback = function() { _("Time to read"), util.secondsToClock((self.data.pages - current_page) * avg_time_per_page, false) },
self.total_status:swithItemTable(nil, self:updateTotalStat()) { _("Total time"), util.secondsToClock(self.data.total_time_in_sec, false) },
UIManager:show(self.total_menu) { _("Total highlights"), self.data.highlights },
return true { _("Total notes"), self.data.notes },
end { _("Total days"), total_days },
{ _("Average time per page"), util.secondsToClock(avg_time_per_page, false) },
{ _("Read pages/Total pages"), read_pages .. "/" .. self.data.pages },
} }
end end
function ReaderStatistics:updateCurrentStat() function generateReadBooksTable(title, dates)
local stats = {} local result = {}
local dates = {} for k, v in tableutil.spairs(dates, function(t, a, b) return t[b].date < t[a].date end) do
table.insert(result, {
for k, v in pairs(self.data.performance_in_pages) do k,
dates[os.date("%Y-%m-%d", k)] = "" T(_("Pages (%1) Time: %2"), v.count, util.secondsToClock(v.read, false))
})
end end
return result
local read_pages = util.tableSize(self.data.performance_in_pages)
local current_page = self.view.state.page --get current page from the view
local average_time_per_page = self.data.total_time_in_sec / read_pages
table.insert(stats, { text = _("Current period"), mandatory = util.secondsToClock(self.current_period, false) })
table.insert(stats, { text = _("Time to read"), mandatory = util.secondsToClock((self.data.pages - current_page) * average_time_per_page, false) })
table.insert(stats, { text = _("Total time"), mandatory = util.secondsToClock(self.data.total_time_in_sec, false) })
table.insert(stats, { text = _("Total highlights"), mandatory = self.data.highlights })
table.insert(stats, { text = _("Total notes"), mandatory = self.data.notes })
table.insert(stats, { text = _("Total days"), mandatory = util.tableSize(dates) })
table.insert(stats, { text = _("Average time per page"), mandatory = util.secondsToClock(average_time_per_page, false) })
table.insert(stats, { text = _("Read pages/Total pages"), mandatory = read_pages .. "/" .. self.data.pages })
return stats
end end
-- For backward compatibility -- For backward compatibility
function ReaderStatistics:getDatesForBookOldFormat(book) function getDatesForBookOldFormat(book)
local dates = {} local dates = {}
for k, v in pairs(book.details) do for k, v in pairs(book.details) do
@ -267,11 +229,10 @@ function ReaderStatistics:getDatesForBookOldFormat(book)
end end
end end
return self:generateReadBooksTable(book.title, dates) return generateReadBooksTable(book.title, dates)
end end
function getDatesForBook(book)
function ReaderStatistics:getDatesForBook(book)
local dates = {} local dates = {}
for k, v in pairs(book.performance_in_pages) do for k, v in pairs(book.performance_in_pages) do
@ -283,107 +244,96 @@ function ReaderStatistics:getDatesForBook(book)
count = 1 count = 1
} }
else else
dates[date_text] = { -- TODO: test this
read = dates[date_text].read + v, local entry = dates[date_text]
count = dates[date_text].count + 1, entry.read = entry.read + v
date = dates[date_text].date entry.count = entry.count + 1
}
end end
end end
return self:generateReadBooksTable(book.title, dates) return generateReadBooksTable(book.title, dates)
end
function ReaderStatistics:generateReadBooksTable(title, dates)
local result = {}
table.insert(result, { text = title })
for k, v in tableutil.spairs(dates, function(t, a, b) return t[b].date < t[a].date end) do
table.insert(result, { text = k, mandatory = T(_("Pages (%1) Time: %2"), v.count, util.secondsToClock(v.read, false)) })
end
return result
end end
function ReaderStatistics:getTotalStats()
local total_stats = {
{
self.data.title,
util.secondsToClock(self.data.total_time_in_sec, false),
callback = function()
UIManager:show(KeyValuePage:new{
title = self.data.title,
kv_pairs = getDatesForBook(self.data),
})
end,
}
}
function ReaderStatistics:updateTotalStat() -- find stats for all other books in history
local total_stats = {} local proceded_titles, total_books_time = self:getStatisticsFromHistory(total_stats)
local total_books_time = 0 total_books_time = total_books_time + self:getOldStatisticsFromDirectory(proceded_titles, total_stats)
local proceded_titles = self:getStatisticsFromHistory(total_stats, total_books_time)
self:getOldStatisticsFromDirectory(proceded_titles, total_stats, total_books_time)
total_books_time = total_books_time + tonumber(self.data.total_time_in_sec) total_books_time = total_books_time + tonumber(self.data.total_time_in_sec)
table.insert(total_stats, 1, { text = _("Total hours read"), mandatory = util.secondsToClock(total_books_time, false) }) return T(_("Total hours read %1"),
table.insert(total_stats, 2, { text = "-" }) util.secondsToClock(total_books_time, false)),
table.insert(total_stats, 3, { total_stats
text = self.data.title,
mandatory = util.secondsToClock(self.data.total_time_in_sec, false),
callback = function()
self.total_status:swithItemTable(nil, self:getDatesForBook(self.data))
UIManager:show(self.total_menu)
return true
end,
})
return total_stats
end end
function ReaderStatistics:getStatisticsFromHistory(total_stats, total_books_time) function ReaderStatistics:getStatisticsFromHistory(total_stats)
local titles = {} local titles = {}
local total_books_time = 0
for curr_file in lfs.dir(history_dir) do for curr_file in lfs.dir(history_dir) do
local path = history_dir .. curr_file local path = joinPath(history_dir, curr_file)
if lfs.attributes(path, "mode") == "file" then if lfs.attributes(path, "mode") == "file" then
local book_result = self:importFromFile(history_dir, curr_file) local book_result = self:importFromFile(history_dir, curr_file)
local book_stats = book_result.stats local book_stats = book_result.stats
if book_stats and book_stats.title ~= self.data.title then if book_stats and book_stats.total_time_in_sec > 0
and book_stats.title ~= self.data.title then
titles[book_stats.title] = true titles[book_stats.title] = true
table.insert(total_stats, { table.insert(total_stats, {
text = book_stats.title, book_stats.title,
mandatory = util.secondsToClock(book_stats.total_time_in_sec, false), util.secondsToClock(book_stats.total_time_in_sec, false),
callback = function() callback = function()
self.total_status:swithItemTable(nil, self:getDatesForBook(book_stats)) UIManager:show(KeyValuePage:new{
UIManager:show(self.total_menu) title = book_stats.title,
return true kv_pairs = getDatesForBook(book_stats),
})
end, end,
}) })
total_books_time = total_books_time + tonumber(book_stats.total_time_in_sec) total_books_time = total_books_time + tonumber(book_stats.total_time_in_sec)
end end
end end
end end
return titles return titles, total_books_time
end end
-- For backward compatibility -- For backward compatibility
function ReaderStatistics:getOldStatisticsFromDirectory(exlude_titles, total_stats, total_books_time) function ReaderStatistics:getOldStatisticsFromDirectory(exlude_titles, total_stats)
if lfs.attributes(statistics_dir, "mode") ~= "directory" then if lfs.attributes(statistics_dir, "mode") ~= "directory" then
return return 0
end end
local total_books_time = 0
for curr_file in lfs.dir(statistics_dir) do for curr_file in lfs.dir(statistics_dir) do
local path = statistics_dir .. curr_file local path = statistics_dir .. curr_file
if lfs.attributes(path, "mode") == "file" then if lfs.attributes(path, "mode") == "file" then
local book_result = self:importFromFile(statistics_dir, curr_file) local book_result = self:importFromFile(statistics_dir, curr_file)
if book_result and book_result.title ~= self.data.title and not exlude_titles[book_result.title] then if book_result and book_stats.total_time_in_sec > 0
and book_result.title ~= self.data.title
and not exlude_titles[book_result.title] then
table.insert(total_stats, { table.insert(total_stats, {
text = book_result.title, book_result.title,
mandatory = util.secondsToClock(book_result.total_time, false), util.secondsToClock(book_result.total_time, false),
callback = function() callback = function()
self.total_status:swithItemTable(nil, self:getDatesForBookOldFormat(book_result)) UIManager:show(KeyValuePage:new{
UIManager:show(self.total_menu) title = book_result.title,
return true kv_pairs = getDatesForBookOldFormat(book_result),
})
end, end,
}) })
total_books_time = total_books_time + tonumber(book_result.total_time) total_books_time = total_books_time + tonumber(book_result.total_time)
end end
end end
end end
end return total_books_time
function ReaderStatistics:getBookProperties()
local props = self.view.document:getProps()
if props.title == "No document" or props.title == "" then --sometime crengine returns "No document" try to get one more time
props = self.view.document:getProps()
end
return props
end end
function ReaderStatistics:onPageUpdate(pageno) function ReaderStatistics:onPageUpdate(pageno)
@ -411,14 +361,6 @@ function ReaderStatistics:onPageUpdate(pageno)
end end
end end
function ReaderStatistics:savePropertiesInToData(item)
self.data.title = item.title
self.data.authors = item.authors
self.data.language = item.language
self.data.series = item.series
end
-- For backward compatibility -- For backward compatibility
function ReaderStatistics:inplaceMigration() function ReaderStatistics:inplaceMigration()
local oldData = self:importFromFile(statistics_dir, self.data.title .. ".stat") local oldData = self:importFromFile(statistics_dir, self.data.title .. ".stat")
@ -431,12 +373,12 @@ end
-- For backward compatibility -- For backward compatibility
function ReaderStatistics:importFromFile(base_path, item) function ReaderStatistics:importFromFile(base_path, item)
item = string.gsub(item, "^%s*(.-)%s*$", "%1") --trim item = string.gsub(item, "^%s*(.-)%s*$", "%1") -- trim
if lfs.attributes(base_path .. item, "mode") == "directory" then local statistic_file = joinPath(base_path, item)
if lfs.attributes(statistic_file, "mode") == "directory" then
return return
end end
local statisticFile = base_path .. item local ok, stored = pcall(dofile, statistic_file)
local ok, stored = pcall(dofile, statisticFile)
if ok then if ok then
return stored return stored
else else

@ -44,6 +44,7 @@ describe("ReaderBookmark module", function()
readerui = ReaderUI:new{ readerui = ReaderUI:new{
document = DocumentRegistry:openDocument(sample_epub), document = DocumentRegistry:openDocument(sample_epub),
} }
readerui.status.enabled = false
end) end)
before_each(function() before_each(function()
UIManager:quit() UIManager:quit()
@ -117,6 +118,7 @@ describe("ReaderBookmark module", function()
readerui = ReaderUI:new{ readerui = ReaderUI:new{
document = DocumentRegistry:openDocument(sample_pdf), document = DocumentRegistry:openDocument(sample_pdf),
} }
readerui.status.enabled = false
end) end)
before_each(function() before_each(function()
UIManager:quit() UIManager:quit()

@ -64,8 +64,7 @@ function TestGrid:paintTo(bb)
end end
function TestVisible:paintTo(bb) function TestVisible:paintTo(bb)
--Draw three lines at the borders to assess what the maximum visible coordinates are -- Draw three lines at the borders to assess what the maximum visible coordinates are
v_line = math.floor(bb:getWidth() / 50) v_line = math.floor(bb:getWidth() / 50)
h_line = math.floor(bb:getHeight() / 50) h_line = math.floor(bb:getHeight() / 50)
-- Paint white background for higher contrast -- Paint white background for higher contrast
@ -165,8 +164,6 @@ function Background:onQuitApplication()
UIManager:quit() UIManager:quit()
end end
----------------------------------------------------- -----------------------------------------------------
-- example widget: a clock -- example widget: a clock
----------------------------------------------------- -----------------------------------------------------
@ -185,7 +182,6 @@ function Clock:schedFunc()
self[1][1]:free() self[1][1]:free()
self[1][1] = self:getTextWidget() self[1][1] = self:getTextWidget()
UIManager:setDirty(self) UIManager:setDirty(self)
-- reschedule
-- TODO: wait until next real second shift -- TODO: wait until next real second shift
UIManager:scheduleIn(1, function() self:schedFunc() end) UIManager:scheduleIn(1, function() self:schedFunc() end)
end end
@ -250,7 +246,6 @@ M = Menu:new{
height = 600, height = 600,
} }
----------------------------------------------------- -----------------------------------------------------
-- a reader view widget -- a reader view widget
----------------------------------------------------- -----------------------------------------------------
@ -277,72 +272,62 @@ touch_menu = TouchMenu:new{
icon = "resources/icons/appbar.pokeball.png", icon = "resources/icons/appbar.pokeball.png",
{ {
text = "item1", text = "item1",
callback = function() callback = function() end,
end,
}, },
{ {
text = "item2", text = "item2",
callback = function() callback = function() end,
end,
}, },
{ {
text = "item3", text = "item3",
callback = function() callback = function() end,
end,
}, },
{ {
text = "item4", text = "item4",
callback = function() callback = function() end,
end,
}, },
{ {
text = "item5", text = "item5",
callback = function() callback = function() end,
end,
}, },
{ {
text = "item6", text = "item6",
callback = function() callback = function() end,
end,
}, },
{ {
text = "item7", text = "item7",
callback = function() callback = function() end,
end,
}, },
{ {
text = "item8", text = "item8",
callback = function() callback = function() end,
end,
}, },
{ {
text = "item9", text = "item9",
callback = function() callback = function() end,
end,
}, },
}, },
{ {
icon = "resources/icons/appbar.page.corner.bookmark.png", icon = "resources/icons/appbar.page.corner.bookmark.png",
{ {
text = "item10", text = "item10",
callback = function() callback = function() end,
end,
}, },
{ {
text = "item11", text = "item11",
callback = function() callback = function() end,
end,
}, },
}, },
{ {
icon = "resources/icons/appbar.home.png", icon = "resources/icons/appbar.home.png",
callback = function() callback = function() DEBUG("hello world!") end
DEBUG("hello world!")
end
} }
}, },
} }
-----------------------------------------------------
-- input box widget
-----------------------------------------------------
local TestInputText = InputText:new{ local TestInputText = InputText:new{
width = 400, width = 400,
enter_callback = function() print("Entered") end, enter_callback = function() print("Entered") end,
@ -353,11 +338,43 @@ local TestInputText = InputText:new{
}, },
} }
-----------------------------------------------------
-- key value page
-----------------------------------------------------
local KeyValuePage = require("ui/widget/keyvaluepage")
local kvp = KeyValuePage:new{
title = 'Statistics',
kv_pairs = {
{"1 Current period", "00:00:00"},
{"2 Time to read", "00:00:00"},
{"3 Time to read", "00:00:00"},
{"4 Time to read", "00:00:00"},
{"5 Time to read", "00:00:00"},
{"6 Time to read", "00:00:00"},
{"7 Time to read", "00:00:00"},
{"8 Time to read", "00:00:00"},
{"9 Time to read", "00:00:00"},
{"10 Time to read", "00:00:00"},
{"11 Time to read", "00:00:00"},
"----------------------------",
{"12 Time to read", "00:00:00"},
{"13 Time to read", "00:00:00"},
{"14 Time to read", "00:00:00"},
{"15 Time to read", "00:00:00"},
{"16 Time to read", "00:00:00"},
{"17 Time to read", "00:00:00"},
{"18 Time to read", "00:00:00"},
{"19 Time to read", "00:00:00"},
{"20 Time to read", "00:00:00"},
{"21 Time to read", "00:00:00"},
},
}
----------------------------------------------------------------------- -----------------------------------------------------------------------
-- you may want to uncomment following show calls to see the changes -- you may want to uncomment following show calls to see the changes
----------------------------------------------------------------------- -----------------------------------------------------------------------
--UIManager:show(Background:new()) --UIManager:show(Background:new())
-- UIManager:show(TestGrid) --UIManager:show(TestGrid)
UIManager:show(TestVisible) UIManager:show(TestVisible)
UIManager:show(Clock:new()) UIManager:show(Clock:new())
--UIManager:show(M) --UIManager:show(M)
@ -365,7 +382,9 @@ UIManager:show(Clock:new())
--UIManager:show(readerwindow) --UIManager:show(readerwindow)
--UIManager:show(touch_menu) --UIManager:show(touch_menu)
--UIManager:show(keyboard) --UIManager:show(keyboard)
UIManager:show(TestInputText) --UIManager:show(TestInputText)
TestInputText:onShowKeyboard() --TestInputText:onShowKeyboard()
UIManager:show(kvp)
UIManager:run() UIManager:run()

Loading…
Cancel
Save