2017-08-17 17:34:36 +00:00
|
|
|
local Blitbuffer = require("ffi/blitbuffer")
|
|
|
|
local CenterContainer = require("ui/widget/container/centercontainer")
|
|
|
|
local Device = require("device")
|
|
|
|
local DocSettings = require("docsettings")
|
|
|
|
local Font = require("ui/font")
|
|
|
|
local FrameContainer = require("ui/widget/container/framecontainer")
|
|
|
|
local Geom = require("ui/geometry")
|
|
|
|
local GestureRange = require("ui/gesturerange")
|
|
|
|
local HorizontalGroup = require("ui/widget/horizontalgroup")
|
|
|
|
local HorizontalSpan = require("ui/widget/horizontalspan")
|
|
|
|
local ImageWidget = require("ui/widget/imagewidget")
|
|
|
|
local InfoMessage = require("ui/widget/infomessage")
|
|
|
|
local InputContainer = require("ui/widget/container/inputcontainer")
|
|
|
|
local LeftContainer = require("ui/widget/container/leftcontainer")
|
|
|
|
local LineWidget = require("ui/widget/linewidget")
|
2019-01-01 08:45:44 +00:00
|
|
|
local Math = require("optmath")
|
2017-08-17 17:34:36 +00:00
|
|
|
local OverlapGroup = require("ui/widget/overlapgroup")
|
|
|
|
local RightContainer = require("ui/widget/container/rightcontainer")
|
2017-09-13 14:56:20 +00:00
|
|
|
local Size = require("ui/size")
|
2017-08-17 17:34:36 +00:00
|
|
|
local TextBoxWidget = require("ui/widget/textboxwidget")
|
|
|
|
local TextWidget = require("ui/widget/textwidget")
|
|
|
|
local UIManager = require("ui/uimanager")
|
|
|
|
local UnderlineContainer = require("ui/widget/container/underlinecontainer")
|
|
|
|
local VerticalGroup = require("ui/widget/verticalgroup")
|
|
|
|
local VerticalSpan = require("ui/widget/verticalspan")
|
|
|
|
local WidgetContainer = require("ui/widget/container/widgetcontainer")
|
|
|
|
local lfs = require("libs/libkoreader-lfs")
|
2017-08-18 15:21:36 +00:00
|
|
|
local logger = require("logger")
|
2017-08-17 17:34:36 +00:00
|
|
|
local util = require("util")
|
|
|
|
local _ = require("gettext")
|
|
|
|
local Screen = Device.screen
|
|
|
|
local T = require("ffi/util").template
|
|
|
|
|
|
|
|
local BookInfoManager = require("bookinfomanager")
|
|
|
|
|
|
|
|
-- Here is the specific UI implementation for "list" display modes
|
|
|
|
-- (see covermenu.lua for the generic code)
|
|
|
|
|
|
|
|
-- We will show a rotated dogear at bottom right corner of cover widget for
|
|
|
|
-- opened files (the dogear will make it look like a "used book")
|
2018-07-19 06:18:55 +00:00
|
|
|
-- The ImageWidget Will be created when we know the available height (and
|
|
|
|
-- recreated if height changes)
|
|
|
|
local corner_mark_size = -1
|
|
|
|
local corner_mark
|
2017-08-17 17:34:36 +00:00
|
|
|
|
|
|
|
-- ItemShortCutIcon (for keyboard navigation) is private to menu.lua and can't be accessed,
|
|
|
|
-- so we need to redefine it
|
|
|
|
local ItemShortCutIcon = WidgetContainer:new{
|
2017-09-11 08:32:39 +00:00
|
|
|
dimen = Geom:new{ w = Screen:scaleBySize(22), h = Screen:scaleBySize(22) },
|
2017-08-17 17:34:36 +00:00
|
|
|
key = nil,
|
2017-09-13 14:56:20 +00:00
|
|
|
bordersize = Size.border.default,
|
2017-08-17 17:34:36 +00:00
|
|
|
radius = 0,
|
|
|
|
style = "square",
|
|
|
|
}
|
|
|
|
|
|
|
|
function ItemShortCutIcon:init()
|
|
|
|
if not self.key then
|
|
|
|
return
|
|
|
|
end
|
|
|
|
local radius = 0
|
|
|
|
local background = Blitbuffer.COLOR_WHITE
|
|
|
|
if self.style == "rounded_corner" then
|
|
|
|
radius = math.floor(self.width/2)
|
|
|
|
elseif self.style == "grey_square" then
|
|
|
|
background = Blitbuffer.gray(0.2)
|
|
|
|
end
|
|
|
|
local sc_face
|
|
|
|
if self.key:len() > 1 then
|
|
|
|
sc_face = Font:getFace("ffont", 14)
|
|
|
|
else
|
|
|
|
sc_face = Font:getFace("scfont", 22)
|
|
|
|
end
|
|
|
|
self[1] = FrameContainer:new{
|
|
|
|
padding = 0,
|
|
|
|
bordersize = self.bordersize,
|
|
|
|
radius = radius,
|
|
|
|
background = background,
|
|
|
|
dimen = self.dimen,
|
|
|
|
CenterContainer:new{
|
|
|
|
dimen = self.dimen,
|
|
|
|
TextWidget:new{
|
|
|
|
text = self.key,
|
|
|
|
face = sc_face,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
-- Based on menu.lua's MenuItem
|
|
|
|
local ListMenuItem = InputContainer:new{
|
|
|
|
entry = {},
|
|
|
|
text = nil,
|
|
|
|
show_parent = nil,
|
|
|
|
detail = nil,
|
|
|
|
dimen = nil,
|
|
|
|
shortcut = nil,
|
|
|
|
shortcut_style = "square",
|
|
|
|
_underline_container = nil,
|
|
|
|
do_cover_image = false,
|
|
|
|
do_filename_only = false,
|
|
|
|
do_hint_opened = false,
|
|
|
|
been_opened = false,
|
|
|
|
init_done = false,
|
|
|
|
bookinfo_found = false,
|
|
|
|
cover_specs = nil,
|
|
|
|
has_description = false,
|
|
|
|
}
|
|
|
|
|
|
|
|
function ListMenuItem:init()
|
|
|
|
-- filepath may be provided as 'file' (history) or 'path' (filechooser)
|
|
|
|
-- store it as attribute so we can use it elsewhere
|
|
|
|
self.filepath = self.entry.file or self.entry.path
|
|
|
|
|
|
|
|
-- As done in MenuItem
|
|
|
|
-- Squared letter for keyboard navigation
|
|
|
|
if self.shortcut then
|
|
|
|
local shortcut_icon_dimen = Geom:new()
|
|
|
|
shortcut_icon_dimen.w = math.floor(self.dimen.h*2/5)
|
|
|
|
shortcut_icon_dimen.h = shortcut_icon_dimen.w
|
|
|
|
-- To keep a simpler widget structure, this shortcut icon will not
|
|
|
|
-- be part of it, but will be painted over the widget in our paintTo
|
|
|
|
self.shortcut_icon = ItemShortCutIcon:new{
|
|
|
|
dimen = shortcut_icon_dimen,
|
|
|
|
key = self.shortcut,
|
|
|
|
style = self.shortcut_style,
|
|
|
|
}
|
|
|
|
end
|
|
|
|
self.detail = self.text
|
|
|
|
|
|
|
|
-- we need this table per-instance, so we declare it here
|
|
|
|
if Device:isTouchDevice() then
|
|
|
|
self.ges_events = {
|
|
|
|
TapSelect = {
|
|
|
|
GestureRange:new{
|
|
|
|
ges = "tap",
|
|
|
|
range = self.dimen,
|
|
|
|
},
|
|
|
|
doc = "Select Menu Item",
|
|
|
|
},
|
|
|
|
HoldSelect = {
|
|
|
|
GestureRange:new{
|
|
|
|
ges = "hold",
|
|
|
|
range = self.dimen,
|
|
|
|
},
|
|
|
|
doc = "Hold Menu Item",
|
|
|
|
},
|
|
|
|
}
|
|
|
|
end
|
|
|
|
|
|
|
|
-- We now build the minimal widget container that won't change after udpate()
|
|
|
|
|
|
|
|
-- As done in MenuItem
|
|
|
|
-- for compatibility with keyboard navigation
|
|
|
|
-- (which does not seem to work well when multiple pages,
|
|
|
|
-- even with classic menu)
|
|
|
|
self.underline_h = 1 -- smaller than default (3) to not shift our vertical aligment
|
|
|
|
self._underline_container = UnderlineContainer:new{
|
2018-03-20 19:34:54 +00:00
|
|
|
vertical_align = "top",
|
|
|
|
padding = 0,
|
2017-08-17 17:34:36 +00:00
|
|
|
dimen = Geom:new{
|
|
|
|
w = self.width,
|
|
|
|
h = self.height
|
|
|
|
},
|
|
|
|
linesize = self.underline_h,
|
|
|
|
-- widget : will be filled in self:update()
|
|
|
|
}
|
|
|
|
self[1] = self._underline_container
|
|
|
|
|
|
|
|
-- Remaining part of initialization is done in update(), because we may
|
|
|
|
-- have to do it more than once if item not found in db
|
|
|
|
self:update()
|
|
|
|
self.init_done = true
|
|
|
|
end
|
|
|
|
|
|
|
|
function ListMenuItem:update()
|
|
|
|
-- We will be a disctinctive widget whether we are a directory,
|
|
|
|
-- a known file with image / without image, or a not yet known file
|
|
|
|
local widget
|
|
|
|
|
|
|
|
-- we'll add a VerticalSpan of same size as underline container for balance
|
|
|
|
local dimen = Geom:new{
|
|
|
|
w = self.width,
|
|
|
|
h = self.height - 2 * self.underline_h
|
|
|
|
}
|
|
|
|
|
2018-03-14 17:14:52 +00:00
|
|
|
-- We'll draw a border around cover images, it may not be
|
|
|
|
-- needed with some covers, but it's nicer when cover is
|
|
|
|
-- a pure white background (like rendered text page)
|
|
|
|
local border_size = 1
|
|
|
|
local max_img_w = dimen.h - 2*border_size -- width = height, squared
|
|
|
|
local max_img_h = dimen.h - 2*border_size
|
|
|
|
local cover_specs = {
|
|
|
|
sizetag = "s",
|
|
|
|
max_cover_w = max_img_w,
|
|
|
|
max_cover_h = max_img_h,
|
|
|
|
}
|
|
|
|
-- Make it available to our menu, for batch extraction
|
|
|
|
-- to know what size is needed for current view
|
|
|
|
if self.do_cover_image then
|
|
|
|
self.menu.cover_specs = cover_specs
|
|
|
|
else
|
|
|
|
self.menu.cover_specs = false
|
|
|
|
end
|
|
|
|
|
2017-09-22 16:24:38 +00:00
|
|
|
local file_mode = lfs.attributes(self.filepath, "mode")
|
|
|
|
if file_mode == "directory" then
|
2017-08-17 17:34:36 +00:00
|
|
|
self.is_directory = true
|
|
|
|
-- nb items on the right, directory name on the left
|
|
|
|
local wright = TextWidget:new{
|
|
|
|
text = self.mandatory,
|
|
|
|
face = Font:getFace("infont", 15),
|
|
|
|
}
|
|
|
|
local wleft_width = dimen.w - wright:getSize().w
|
|
|
|
local wleft = TextBoxWidget:new{
|
|
|
|
text = self.text,
|
|
|
|
face = Font:getFace("cfont", 20),
|
|
|
|
width = wleft_width,
|
|
|
|
alignment = "left",
|
|
|
|
bold = true,
|
|
|
|
}
|
|
|
|
widget = OverlapGroup:new{
|
|
|
|
dimen = dimen,
|
|
|
|
LeftContainer:new{
|
|
|
|
dimen = dimen,
|
|
|
|
HorizontalGroup:new{
|
|
|
|
HorizontalSpan:new{ width = Screen:scaleBySize(10) },
|
|
|
|
wleft,
|
|
|
|
}
|
|
|
|
},
|
|
|
|
RightContainer:new{
|
|
|
|
dimen = dimen,
|
|
|
|
HorizontalGroup:new{
|
|
|
|
wright,
|
|
|
|
HorizontalSpan:new{ width = Screen:scaleBySize(10) },
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
else
|
2017-09-22 16:24:38 +00:00
|
|
|
if file_mode ~= "file" then
|
|
|
|
self.file_deleted = true
|
|
|
|
end
|
2017-08-17 17:34:36 +00:00
|
|
|
-- File
|
|
|
|
|
|
|
|
local bookinfo = BookInfoManager:getBookInfo(self.filepath, self.do_cover_image)
|
|
|
|
if bookinfo and self.do_cover_image and not bookinfo.ignore_cover then
|
|
|
|
if not bookinfo.cover_fetched then
|
|
|
|
-- cover was not fetched previously, do as if not found
|
|
|
|
-- to force a new extraction
|
|
|
|
bookinfo = nil
|
|
|
|
end
|
|
|
|
-- If there's already a cover and it's a "M" size (MosaicMenuItem),
|
|
|
|
-- we'll use it and scale it down (it may slow a bit rendering,
|
|
|
|
-- but "M" size may be useful in another view (FileBrowser/History),
|
|
|
|
-- so we don't replace it).
|
|
|
|
end
|
|
|
|
|
|
|
|
if bookinfo then -- This book is known
|
|
|
|
self.bookinfo_found = true
|
|
|
|
local cover_bb_used = false
|
|
|
|
|
|
|
|
-- Build the left widget : image if wanted
|
|
|
|
local wleft = nil
|
|
|
|
local wleft_width = 0 -- if not do_cover_image
|
|
|
|
local wleft_height
|
|
|
|
if self.do_cover_image then
|
|
|
|
wleft_height = dimen.h
|
|
|
|
wleft_width = wleft_height -- make it squared
|
|
|
|
if bookinfo.has_cover and not bookinfo.ignore_cover then
|
|
|
|
cover_bb_used = true
|
|
|
|
-- Let ImageWidget do the scaling and give us the final size
|
|
|
|
local scale_factor = math.min(max_img_w / bookinfo.cover_w, max_img_h / bookinfo.cover_h)
|
|
|
|
local wimage = ImageWidget:new{
|
|
|
|
image = bookinfo.cover_bb,
|
|
|
|
scale_factor = scale_factor,
|
|
|
|
}
|
|
|
|
wimage:_render()
|
|
|
|
local image_size = wimage:getSize() -- get final widget size
|
|
|
|
wleft = CenterContainer:new{
|
|
|
|
dimen = Geom:new{ w = wleft_width, h = wleft_height },
|
|
|
|
FrameContainer:new{
|
|
|
|
width = image_size.w + 2*border_size,
|
|
|
|
height = image_size.h + 2*border_size,
|
|
|
|
margin = 0,
|
|
|
|
padding = 0,
|
|
|
|
bordersize = border_size,
|
2017-09-22 16:24:38 +00:00
|
|
|
dim = self.file_deleted,
|
2017-08-17 17:34:36 +00:00
|
|
|
wimage,
|
|
|
|
}
|
|
|
|
}
|
Enable HW dithering in a few key places (#4541)
* Enable HW dithering on supported devices (Clara HD, Forma; Oasis 2, PW4)
* FileManager and co. (where appropriate, i.e., when covers are shown)
* Book Status
* Reader, where appropriate:
* CRe: on pages whith image content (for over 7.5% of the screen area, should hopefully leave stuff like bullet points or small scene breaks alone).
* Other engines: on user-request (in the gear tab of the bottom menu), via the new "Dithering" knob (will only appear on supported devices).
* ScreenSaver
* ImageViewer
* Minimize repaints when flash_ui is enabled (by, almost everywhere, only repainting the flashing element, and not the toplevel window which hosts it).
(The first pass of this involved fixing a few Button instances whose show_parent was wrong, in particular, chevrons in the FM & TopMenu).
* Hunted down a few redundant repaints (unneeded setDirty("all") calls),
either by switching the widget to nil when only a refresh was needed, and not a repaint,
or by passing the appropritate widget to setDirty.
(Note to self: Enable *verbose* debugging to catch broken setDirty calls via its post guard).
There were also a few instances of 'em right behind a widget close.
* Don't repaint the underlying widget when initially showing TopMenu & ConfigDialog.
We unfortunately do need to do it when switching tabs, because of their variable heights.
* On Kobo, disabled the extra and completely useless full refresh before suspend/reboot/poweroff, as well as on resume. No more double refreshes!
* Fix another debug guard in Kobo sysfs_light
* Switch ImageWidget & ImageViewer mostly to "ui" updates, which will be better suited to image content pretty much everywhere, REAGL or not.
PS: (Almost :100: commits! :D)
2019-02-07 00:14:37 +00:00
|
|
|
-- Let menu know it has some item with images
|
|
|
|
self.menu._has_cover_images = true
|
|
|
|
self._has_cover_image = true
|
2017-08-17 17:34:36 +00:00
|
|
|
else
|
|
|
|
-- empty element the size of an image
|
|
|
|
wleft = CenterContainer:new{
|
|
|
|
dimen = Geom:new{ w = wleft_width, h = wleft_height },
|
|
|
|
HorizontalSpan:new{ width = wleft_width },
|
|
|
|
}
|
|
|
|
end
|
|
|
|
end
|
|
|
|
-- In case we got a blitbuffer and didnt use it (ignore_cover), free it
|
|
|
|
if bookinfo.cover_bb and not cover_bb_used then
|
|
|
|
bookinfo.cover_bb:free()
|
|
|
|
end
|
|
|
|
-- So we can draw an indicator if this book has a description
|
|
|
|
if bookinfo.description then
|
|
|
|
self.has_description = true
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Gather some info, mostly for right widget:
|
|
|
|
-- file size (self.mandatory) (not available with History)
|
|
|
|
-- file type
|
|
|
|
-- pages read / nb of pages (not available for crengine doc not opened)
|
|
|
|
local directory, filename = util.splitFilePathName(self.filepath) -- luacheck: no unused
|
|
|
|
local filename_without_suffix, filetype = util.splitFileNameSuffix(filename)
|
|
|
|
local fileinfo_str = filetype
|
|
|
|
if self.mandatory then
|
|
|
|
fileinfo_str = self.mandatory .. " " .. fileinfo_str
|
|
|
|
end
|
2018-07-19 06:18:55 +00:00
|
|
|
if bookinfo._no_provider then
|
|
|
|
-- for unspported files: don't show extension on the right,
|
|
|
|
-- keep it in filename
|
|
|
|
filename_without_suffix = filename
|
|
|
|
fileinfo_str = self.mandatory
|
|
|
|
end
|
2017-08-17 17:34:36 +00:00
|
|
|
-- Current page / pages are available or more accurate in .sdr/metadata.lua
|
2017-10-21 17:55:29 +00:00
|
|
|
-- We use a cache (cleaned at end of this browsing session) to store
|
2018-03-02 16:22:41 +00:00
|
|
|
-- page, percent read and book status from sidecar files, to avoid
|
|
|
|
-- re-parsing them when re-rendering a visited page
|
2017-10-21 17:55:29 +00:00
|
|
|
if not self.menu.cover_info_cache then
|
|
|
|
self.menu.cover_info_cache = {}
|
|
|
|
end
|
2017-08-17 17:34:36 +00:00
|
|
|
local pages_str = ""
|
2018-03-02 16:22:41 +00:00
|
|
|
local percent_finished, status
|
2017-08-17 17:34:36 +00:00
|
|
|
local pages = bookinfo.pages -- default to those in bookinfo db
|
|
|
|
if DocSettings:hasSidecarFile(self.filepath) then
|
|
|
|
self.been_opened = true
|
2017-10-21 17:55:29 +00:00
|
|
|
if self.menu.cover_info_cache[self.filepath] then
|
2018-03-02 16:22:41 +00:00
|
|
|
pages, percent_finished, status = unpack(self.menu.cover_info_cache[self.filepath])
|
2017-10-21 17:55:29 +00:00
|
|
|
else
|
|
|
|
local docinfo = DocSettings:open(self.filepath)
|
|
|
|
-- We can get nb of page in the new 'doc_pages' setting, or from the old 'stats.page'
|
|
|
|
if docinfo.data.doc_pages then
|
|
|
|
pages = docinfo.data.doc_pages
|
|
|
|
elseif docinfo.data.stats and docinfo.data.stats.pages then
|
|
|
|
if docinfo.data.stats.pages ~= 0 then -- crengine with statistics disabled stores 0
|
|
|
|
pages = docinfo.data.stats.pages
|
|
|
|
end
|
2017-08-17 17:34:36 +00:00
|
|
|
end
|
2018-03-02 16:22:41 +00:00
|
|
|
if docinfo.data.summary and docinfo.data.summary.status then
|
|
|
|
status = docinfo.data.summary.status
|
|
|
|
end
|
2017-10-21 17:55:29 +00:00
|
|
|
percent_finished = docinfo.data.percent_finished
|
2018-03-02 16:22:41 +00:00
|
|
|
self.menu.cover_info_cache[self.filepath] = {pages, percent_finished, status}
|
2017-08-17 17:34:36 +00:00
|
|
|
end
|
|
|
|
end
|
2018-03-02 16:22:41 +00:00
|
|
|
if status == "complete" or status == "abandoned" then
|
|
|
|
-- Display these instead of the read %
|
|
|
|
if pages then
|
|
|
|
if status == "complete" then
|
|
|
|
pages_str = T(_("Finished - %1 pages"), pages)
|
|
|
|
else
|
|
|
|
pages_str = T(_("On hold - %1 pages"), pages)
|
|
|
|
end
|
|
|
|
else
|
|
|
|
pages_str = status == "complete" and _("Finished") or _("On hold")
|
|
|
|
end
|
|
|
|
elseif percent_finished then
|
2017-08-17 17:34:36 +00:00
|
|
|
if pages then
|
2019-01-01 08:45:44 +00:00
|
|
|
if BookInfoManager:getSetting("show_pages_read_as_progress") then
|
|
|
|
pages_str = T(_("page %1 of %2"), Math.round(percent_finished*pages), pages)
|
|
|
|
else
|
|
|
|
pages_str = T(_("%1 % of %2 pages"), math.floor(100*percent_finished), pages)
|
|
|
|
end
|
|
|
|
if BookInfoManager:getSetting("show_pages_left_in_progress") then
|
|
|
|
pages_str = T(_("%1, %2 to read"), pages_str, Math.round(pages-percent_finished*pages), pages)
|
|
|
|
end
|
2017-08-17 17:34:36 +00:00
|
|
|
else
|
|
|
|
pages_str = string.format("%d %%", math.floor(100*percent_finished))
|
|
|
|
end
|
|
|
|
else
|
|
|
|
if pages then
|
|
|
|
pages_str = T(_("%1 pages"), pages)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Build the right widget
|
|
|
|
|
|
|
|
local wfileinfo = TextWidget:new{
|
|
|
|
text = fileinfo_str,
|
|
|
|
face = Font:getFace("cfont", 14),
|
2017-09-22 16:24:38 +00:00
|
|
|
fgcolor = self.file_deleted and Blitbuffer.COLOR_GREY or nil,
|
2017-08-17 17:34:36 +00:00
|
|
|
}
|
|
|
|
local wpageinfo = TextWidget:new{
|
|
|
|
text = pages_str,
|
|
|
|
face = Font:getFace("cfont", 14),
|
2017-09-22 16:24:38 +00:00
|
|
|
fgcolor = self.file_deleted and Blitbuffer.COLOR_GREY or nil,
|
2017-08-17 17:34:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
local wright_width = math.max(wfileinfo:getSize().w, wpageinfo:getSize().w)
|
|
|
|
local wright_right_padding = Screen:scaleBySize(10)
|
|
|
|
|
|
|
|
-- We just built two string to be put one on top of the other, and we want
|
|
|
|
-- the combo centered. Given the nature of our strings (numbers,
|
|
|
|
-- uppercase MB/KB on top text, number and lowercase "page" on bottom text),
|
|
|
|
-- we get the annoying feeling it's not centered but shifted towards top.
|
|
|
|
-- Let's add a small VerticalSpan at top to give a better visual
|
|
|
|
-- feeling of centering.
|
|
|
|
local wright = CenterContainer:new{
|
|
|
|
dimen = Geom:new{ w = wright_width, h = dimen.h },
|
|
|
|
VerticalGroup:new{
|
|
|
|
align = "right",
|
|
|
|
VerticalSpan:new{ width = Screen:scaleBySize(2) },
|
|
|
|
wfileinfo,
|
|
|
|
wpageinfo,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-19 06:18:55 +00:00
|
|
|
-- Create or replace corner_mark if needed
|
|
|
|
local wright_bottom_pad_available = math.ceil( (dimen.h - wright[1]:getSize().h) *2/3 )
|
|
|
|
-- We should normally use 1/2 because of CenterContainer, but there's
|
|
|
|
-- some space inside the text widget that we can use for a larger marker
|
|
|
|
if wright_bottom_pad_available ~= corner_mark_size then
|
|
|
|
corner_mark_size = wright_bottom_pad_available
|
|
|
|
if corner_mark then
|
|
|
|
corner_mark:free()
|
|
|
|
end
|
|
|
|
corner_mark = ImageWidget:new{
|
|
|
|
file = "resources/icons/dogear.png",
|
|
|
|
rotation_angle = 270,
|
|
|
|
width = corner_mark_size,
|
|
|
|
height = corner_mark_size,
|
|
|
|
}
|
|
|
|
end
|
2017-08-17 17:34:36 +00:00
|
|
|
|
|
|
|
-- Build the middle main widget, in the space available
|
|
|
|
local wmain_left_padding = Screen:scaleBySize(10)
|
|
|
|
if self.do_cover_image then
|
|
|
|
-- we need less padding, as cover image, most often in
|
|
|
|
-- portrait mode, will provide some padding
|
|
|
|
wmain_left_padding = Screen:scaleBySize(5)
|
|
|
|
end
|
|
|
|
local wmain_right_padding = Screen:scaleBySize(10) -- used only for next calculation
|
|
|
|
local wmain_width = dimen.w - wleft_width - wmain_left_padding - wmain_right_padding - wright_width - wright_right_padding
|
|
|
|
|
|
|
|
local fontname_title = "cfont"
|
|
|
|
local fontname_authors = "cfont"
|
|
|
|
local fontsize_title = 20
|
|
|
|
local fontsize_authors = 18
|
|
|
|
local wtitle, wauthors
|
|
|
|
local title, authors
|
|
|
|
-- whether to use or not title and authors
|
|
|
|
if self.do_filename_only or bookinfo.ignore_meta then
|
|
|
|
title = filename_without_suffix -- made out above
|
|
|
|
authors = nil
|
|
|
|
else
|
|
|
|
title = bookinfo.title and bookinfo.title or filename_without_suffix
|
|
|
|
authors = bookinfo.authors
|
2018-01-21 21:33:40 +00:00
|
|
|
-- If multiple authors (crengine separates them with \n), we
|
|
|
|
-- can display them on multiple lines, but limit to 2, and
|
|
|
|
-- append "et al." to the 2nd if there are more
|
|
|
|
if authors and authors:find("\n") then
|
|
|
|
authors = util.splitToArray(authors, "\n")
|
|
|
|
if #authors > 2 then
|
|
|
|
authors = { authors[1], T(_("%1 et al."), authors[2]) }
|
|
|
|
end
|
|
|
|
authors = table.concat(authors, "\n")
|
|
|
|
end
|
2017-08-17 17:34:36 +00:00
|
|
|
end
|
|
|
|
-- add Series metadata if requested
|
|
|
|
if bookinfo.series then
|
|
|
|
if BookInfoManager:getSetting("append_series_to_title") then
|
2017-10-12 22:31:40 +00:00
|
|
|
-- Shorten calibre series decimal number (#4.0 => #4)
|
|
|
|
bookinfo.series = bookinfo.series:gsub("(#%d+)%.0$", "%1")
|
2017-08-17 17:34:36 +00:00
|
|
|
if title then
|
|
|
|
title = title .. " - " .. bookinfo.series
|
|
|
|
else
|
|
|
|
title = bookinfo.series
|
|
|
|
end
|
|
|
|
end
|
|
|
|
if BookInfoManager:getSetting("append_series_to_authors") then
|
2017-10-12 22:31:40 +00:00
|
|
|
bookinfo.series = bookinfo.series:gsub("(#%d+)%.0$", "%1")
|
2017-08-17 17:34:36 +00:00
|
|
|
if authors then
|
|
|
|
authors = authors .. " - " .. bookinfo.series
|
|
|
|
else
|
|
|
|
authors = bookinfo.series
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
if bookinfo.unsupported then
|
|
|
|
-- Let's show this fact in place of the anyway empty authors slot
|
|
|
|
authors = T(_("(no book information: %1)"), bookinfo.unsupported)
|
|
|
|
end
|
|
|
|
-- Build title and authors texts with decreasing font size
|
|
|
|
-- till it fits in the space available
|
|
|
|
while true do
|
|
|
|
-- Free previously made widgets to avoid memory leaks
|
|
|
|
if wtitle then
|
|
|
|
wtitle:free()
|
|
|
|
end
|
|
|
|
if wauthors then
|
|
|
|
wauthors:free()
|
|
|
|
wauthors = nil
|
|
|
|
end
|
|
|
|
-- BookInfoManager:extractBookInfo() made sure
|
|
|
|
-- to save as nil (NULL) metadata that were an empty string
|
|
|
|
wtitle = TextBoxWidget:new{
|
|
|
|
text = title,
|
|
|
|
face = Font:getFace(fontname_title, fontsize_title),
|
|
|
|
width = wmain_width,
|
|
|
|
alignment = "left",
|
|
|
|
bold = true,
|
2017-09-22 16:24:38 +00:00
|
|
|
fgcolor = self.file_deleted and Blitbuffer.COLOR_GREY or nil,
|
2017-08-17 17:34:36 +00:00
|
|
|
}
|
|
|
|
local height = wtitle:getSize().h
|
|
|
|
if authors then
|
|
|
|
wauthors = TextBoxWidget:new{
|
|
|
|
text = authors,
|
|
|
|
face = Font:getFace(fontname_authors, fontsize_authors),
|
|
|
|
width = wmain_width,
|
|
|
|
alignment = "left",
|
2017-09-22 16:24:38 +00:00
|
|
|
fgcolor = self.file_deleted and Blitbuffer.COLOR_GREY or nil,
|
2017-08-17 17:34:36 +00:00
|
|
|
}
|
|
|
|
height = height + wauthors:getSize().h
|
|
|
|
end
|
|
|
|
if height < dimen.h then -- we fit !
|
|
|
|
break
|
|
|
|
end
|
|
|
|
-- If we don't fit, decrease both font sizes
|
|
|
|
fontsize_title = fontsize_title - 1
|
|
|
|
fontsize_authors = fontsize_authors - 1
|
|
|
|
-- Don't go too low, and get out of this loop
|
|
|
|
if fontsize_title < 3 or fontsize_authors < 3 then
|
|
|
|
break
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
local wmain = LeftContainer:new{
|
|
|
|
dimen = dimen,
|
|
|
|
VerticalGroup:new{
|
|
|
|
wtitle,
|
|
|
|
wauthors,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
-- Build the final widget
|
|
|
|
widget = OverlapGroup:new{
|
|
|
|
dimen = dimen,
|
|
|
|
}
|
|
|
|
if self.do_cover_image then
|
|
|
|
-- add left widget
|
|
|
|
if wleft then
|
|
|
|
-- no need for left padding, as cover image, most often in
|
|
|
|
-- portrait mode, will have some padding - the rare landscape
|
|
|
|
-- mode cover image will be stuck to screen side thus
|
|
|
|
table.insert(widget, wleft)
|
|
|
|
end
|
|
|
|
-- pad main widget on the left with size of left widget
|
|
|
|
wmain = HorizontalGroup:new{
|
|
|
|
HorizontalSpan:new{ width = wleft_width },
|
|
|
|
HorizontalSpan:new{ width = wmain_left_padding },
|
|
|
|
wmain
|
|
|
|
}
|
|
|
|
else
|
|
|
|
-- pad main widget on the left
|
|
|
|
wmain = HorizontalGroup:new{
|
|
|
|
HorizontalSpan:new{ width = wmain_left_padding },
|
|
|
|
wmain
|
|
|
|
}
|
|
|
|
end
|
|
|
|
-- add padded main widget
|
|
|
|
table.insert(widget, LeftContainer:new{
|
|
|
|
dimen = dimen,
|
|
|
|
wmain
|
|
|
|
})
|
|
|
|
-- add right widget
|
|
|
|
table.insert(widget, RightContainer:new{
|
|
|
|
dimen = dimen,
|
|
|
|
HorizontalGroup:new{
|
|
|
|
wright,
|
|
|
|
HorizontalSpan:new{ width = wright_right_padding },
|
|
|
|
},
|
|
|
|
})
|
|
|
|
|
|
|
|
else -- bookinfo not found
|
|
|
|
if self.init_done then
|
|
|
|
-- Non-initial update(), but our widget is still not found:
|
|
|
|
-- it does not need to change, so avoid remaking the same widget
|
|
|
|
return
|
|
|
|
end
|
|
|
|
-- If we're in no image mode, don't save images in DB : people
|
|
|
|
-- who don't care about images will have a smaller DB, but
|
|
|
|
-- a new extraction will have to be made when one switch to image mode
|
|
|
|
if self.do_cover_image then
|
|
|
|
-- Not in db, we're going to fetch some cover
|
2018-03-14 17:14:52 +00:00
|
|
|
self.cover_specs = cover_specs
|
2017-08-17 17:34:36 +00:00
|
|
|
end
|
|
|
|
--
|
|
|
|
if self.do_hint_opened and DocSettings:hasSidecarFile(self.filepath) then
|
|
|
|
self.been_opened = true
|
|
|
|
end
|
|
|
|
-- A real simple widget, nothing fancy
|
2017-09-22 16:24:38 +00:00
|
|
|
local hint = "…" -- display hint it's being loaded
|
|
|
|
if self.file_deleted then -- unless file was deleted (can happen with History)
|
|
|
|
hint = " " .. _("(deleted)")
|
|
|
|
end
|
2017-08-17 17:34:36 +00:00
|
|
|
widget = LeftContainer:new{
|
|
|
|
dimen = dimen,
|
|
|
|
HorizontalGroup:new{
|
|
|
|
HorizontalSpan:new{ width = Screen:scaleBySize(10) },
|
|
|
|
TextBoxWidget:new{
|
2017-09-22 16:24:38 +00:00
|
|
|
text = self.text .. hint,
|
2017-08-17 17:34:36 +00:00
|
|
|
face = Font:getFace("cfont", 18),
|
|
|
|
width = dimen.w - 2 * Screen:scaleBySize(10),
|
|
|
|
alignment = "left",
|
2017-09-22 16:24:38 +00:00
|
|
|
fgcolor = self.file_deleted and Blitbuffer.COLOR_GREY or nil,
|
2017-08-17 17:34:36 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
}
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Fill container with our widget
|
|
|
|
if self._underline_container[1] then
|
|
|
|
-- There is a previous one, that we need to free()
|
|
|
|
local previous_widget = self._underline_container[1]
|
|
|
|
previous_widget:free()
|
|
|
|
end
|
|
|
|
-- Add some pad at top to balance with hidden underline line at bottom
|
|
|
|
self._underline_container[1] = VerticalGroup:new{
|
|
|
|
VerticalSpan:new{ width = self.underline_h },
|
|
|
|
widget
|
|
|
|
}
|
|
|
|
end
|
|
|
|
|
|
|
|
function ListMenuItem:paintTo(bb, x, y)
|
2017-08-18 15:21:36 +00:00
|
|
|
-- We used to get non-integer x or y that would cause some mess with image
|
|
|
|
-- inside FrameContainer were image would be drawn on top of the top border...
|
|
|
|
-- Fixed by having TextWidget:updateSize() math.ceil()'ing its length and height
|
|
|
|
-- But let us know if that happens again
|
|
|
|
if x ~= math.floor(x) or y ~= math.floor(y) then
|
|
|
|
logger.err("ListMenuItem:paintTo() got non-integer x/y :", x, y)
|
|
|
|
end
|
2017-08-17 17:34:36 +00:00
|
|
|
|
|
|
|
-- Original painting
|
|
|
|
InputContainer.paintTo(self, bb, x, y)
|
|
|
|
|
|
|
|
-- to which we paint over the shortcut icon
|
|
|
|
if self.shortcut_icon then
|
|
|
|
-- align it on bottom left corner of sub-widget
|
|
|
|
local target = self[1][1][2]
|
|
|
|
local ix = 0
|
|
|
|
local iy = target.dimen.h - self.shortcut_icon.dimen.h
|
|
|
|
self.shortcut_icon:paintTo(bb, x+ix, y+iy)
|
|
|
|
end
|
|
|
|
|
|
|
|
-- to which we paint over a dogear if needed
|
2018-07-19 06:18:55 +00:00
|
|
|
if corner_mark and self.do_hint_opened and self.been_opened then
|
2017-08-17 17:34:36 +00:00
|
|
|
-- align it on bottom right corner of widget
|
|
|
|
local ix = self.width - corner_mark:getSize().w
|
|
|
|
local iy = self.height - corner_mark:getSize().h
|
|
|
|
corner_mark:paintTo(bb, x+ix, y+iy)
|
|
|
|
end
|
|
|
|
|
|
|
|
-- to which we paint a small indicator if this book has a description
|
|
|
|
if self.has_description and not BookInfoManager:getSetting("no_hint_description") then
|
|
|
|
local target = self[1][1][2]
|
|
|
|
local d_w = Screen:scaleBySize(3)
|
|
|
|
local d_h = math.ceil(target.dimen.h / 4)
|
|
|
|
if self.do_cover_image and target[1][1][1] then
|
|
|
|
-- it has an image, align it on image's framecontainer's right border
|
|
|
|
target = target[1][1]
|
|
|
|
bb:paintBorder(target.dimen.x + target.dimen.w - 1, target.dimen.y, d_w, d_h, 1)
|
|
|
|
else
|
|
|
|
-- no image, align it to the left border
|
|
|
|
bb:paintBorder(x, y, d_w, d_h, 1)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- As done in MenuItem
|
|
|
|
function ListMenuItem:onFocus()
|
|
|
|
self._underline_container.color = Blitbuffer.COLOR_BLACK
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
|
|
|
function ListMenuItem:onUnfocus()
|
|
|
|
self._underline_container.color = Blitbuffer.COLOR_WHITE
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
|
|
|
function ListMenuItem:onShowItemDetail()
|
|
|
|
UIManager:show(InfoMessage:new{ text = self.detail, })
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
|
|
|
-- The transient color inversions done in MenuItem:onTapSelect
|
|
|
|
-- and MenuItem:onHoldSelect are ugly when done on an image,
|
|
|
|
-- so let's not do it
|
|
|
|
-- Also, no need for 2nd arg 'pos' (only used in readertoc.lua)
|
|
|
|
function ListMenuItem:onTapSelect(arg)
|
|
|
|
self.menu:onMenuSelect(self.entry)
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
|
|
|
function ListMenuItem:onHoldSelect(arg, ges)
|
|
|
|
self.menu:onMenuHold(self.entry)
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
-- Simple holder of methods that will replace those
|
|
|
|
-- in the real Menu class or instance
|
|
|
|
local ListMenu = {}
|
|
|
|
|
|
|
|
function ListMenu:_recalculateDimen()
|
|
|
|
self.dimen.w = self.width
|
|
|
|
self.dimen.h = self.height or Screen:getHeight()
|
|
|
|
|
|
|
|
-- Find out available height from other UI elements made in Menu
|
|
|
|
self.others_height = 0
|
|
|
|
if self.title_bar then -- Menu:init() has been done
|
|
|
|
if not self.is_borderless then
|
|
|
|
self.others_height = self.others_height + 2
|
|
|
|
end
|
|
|
|
if not self.no_title then
|
|
|
|
self.others_height = self.others_height + self.header_padding
|
|
|
|
self.others_height = self.others_height + self.title_bar.dimen.h
|
|
|
|
end
|
|
|
|
if self.page_info then
|
|
|
|
self.others_height = self.others_height + self.page_info:getSize().h
|
|
|
|
end
|
|
|
|
else
|
|
|
|
-- Menu:init() not yet done: other elements used to calculate self.others_heights
|
|
|
|
-- are not yet defined, so next calculations will be wrong, and we may get
|
|
|
|
-- a self.perpage higher than it should be: Menu:init() will set a wrong self.page.
|
|
|
|
-- We'll have to update it, if we want FileManager to get back to the original page.
|
|
|
|
self.page_recalc_needed_next_time = true
|
2017-10-21 17:55:29 +00:00
|
|
|
-- Also remember original position (and focused_path), which will be changed by
|
|
|
|
-- Menu/FileChooser to a probably wrong value
|
2017-08-17 17:34:36 +00:00
|
|
|
self.itemnum_orig = self.path_items[self.path]
|
2017-10-21 17:55:29 +00:00
|
|
|
self.focused_path_orig = self.focused_path
|
2017-08-17 17:34:36 +00:00
|
|
|
end
|
|
|
|
local available_height = self.dimen.h - self.others_height
|
|
|
|
|
|
|
|
-- 64 hardcoded for now, gives 10 items both in filemanager
|
|
|
|
-- and history on kobo glo hd
|
|
|
|
local item_height_min = Screen:scaleBySize(64)
|
|
|
|
self.perpage = math.floor(available_height / item_height_min)
|
|
|
|
self.page_num = math.ceil(#self.item_table / self.perpage)
|
2017-10-21 17:55:29 +00:00
|
|
|
-- fix current page if out of range
|
|
|
|
if self.page_num > 0 and self.page > self.page_num then self.page = self.page_num end
|
2017-08-17 17:34:36 +00:00
|
|
|
|
|
|
|
local height_remaining = available_height - self.perpage * item_height_min
|
|
|
|
height_remaining = height_remaining - (self.perpage+1) -- N+1 LineWidget separators
|
|
|
|
self.item_height = item_height_min + math.floor(height_remaining / self.perpage)
|
|
|
|
self.item_width = self.dimen.w
|
|
|
|
self.item_dimen = Geom:new{
|
|
|
|
w = self.item_width,
|
|
|
|
h = self.item_height
|
|
|
|
}
|
|
|
|
|
|
|
|
if self.page_recalc_needed then
|
2017-10-21 17:55:29 +00:00
|
|
|
-- self.page has probably been set to a wrong value, we recalculate
|
|
|
|
-- it here as done in Menu:init() or Menu:switchItemTable()
|
2017-08-17 17:34:36 +00:00
|
|
|
if #self.item_table > 0 then
|
|
|
|
self.page = math.ceil((self.itemnum_orig or 1) / self.perpage)
|
|
|
|
end
|
2017-10-21 17:55:29 +00:00
|
|
|
if self.focused_path_orig then
|
|
|
|
for num, item in ipairs(self.item_table) do
|
|
|
|
if item.path == self.focused_path_orig then
|
|
|
|
self.page = math.floor((num-1) / self.perpage) + 1
|
|
|
|
break
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
if self.page_num > 0 and self.page > self.page_num then self.page = self.page_num end
|
2017-08-17 17:34:36 +00:00
|
|
|
self.page_recalc_needed = nil
|
|
|
|
self.itemnum_orig = nil
|
2017-10-21 17:55:29 +00:00
|
|
|
self.focused_path_orig = nil
|
2017-08-17 17:34:36 +00:00
|
|
|
end
|
|
|
|
if self.page_recalc_needed_next_time then
|
|
|
|
self.page_recalc_needed = true
|
|
|
|
self.page_recalc_needed_next_time = nil
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
function ListMenu:_updateItemsBuildUI()
|
|
|
|
-- Build our list
|
|
|
|
table.insert(self.item_group, LineWidget:new{
|
2017-09-13 14:56:20 +00:00
|
|
|
dimen = Geom:new{ w = self.width, h = Size.line.thin },
|
2017-08-17 17:34:36 +00:00
|
|
|
background = Blitbuffer.COLOR_GREY,
|
|
|
|
style = "solid",
|
|
|
|
})
|
|
|
|
local idx_offset = (self.page - 1) * self.perpage
|
|
|
|
for idx = 1, self.perpage do
|
|
|
|
local entry = self.item_table[idx_offset + idx]
|
|
|
|
if entry == nil then break end
|
|
|
|
|
|
|
|
-- Keyboard shortcuts, as done in Menu
|
|
|
|
local item_shortcut = nil
|
|
|
|
local shortcut_style = "square"
|
|
|
|
if self.is_enable_shortcut then
|
|
|
|
-- give different shortcut_style to keys in different
|
|
|
|
-- lines of keyboard
|
|
|
|
if idx >= 11 and idx <= 20 then
|
|
|
|
shortcut_style = "grey_square"
|
|
|
|
end
|
|
|
|
item_shortcut = self.item_shortcuts[idx]
|
|
|
|
end
|
|
|
|
|
|
|
|
local item_tmp = ListMenuItem:new{
|
|
|
|
height = self.item_height,
|
|
|
|
width = self.item_width,
|
|
|
|
entry = entry,
|
|
|
|
text = util.getMenuText(entry),
|
|
|
|
show_parent = self.show_parent,
|
|
|
|
mandatory = entry.mandatory,
|
|
|
|
dimen = self.item_dimen:new(),
|
|
|
|
shortcut = item_shortcut,
|
|
|
|
shortcut_style = shortcut_style,
|
|
|
|
menu = self,
|
|
|
|
do_cover_image = self._do_cover_images,
|
|
|
|
do_hint_opened = self._do_hint_opened,
|
|
|
|
do_filename_only = self._do_filename_only,
|
|
|
|
}
|
|
|
|
table.insert(self.item_group, item_tmp)
|
|
|
|
table.insert(self.item_group, LineWidget:new{
|
2017-09-13 14:56:20 +00:00
|
|
|
dimen = Geom:new{ w = self.width, h = Size.line.thin },
|
2017-08-17 17:34:36 +00:00
|
|
|
background = Blitbuffer.COLOR_GREY,
|
|
|
|
style = "solid",
|
|
|
|
})
|
|
|
|
|
|
|
|
-- this is for focus manager
|
|
|
|
table.insert(self.layout, {item_tmp})
|
|
|
|
|
2017-09-22 16:24:38 +00:00
|
|
|
if not item_tmp.bookinfo_found and not item_tmp.is_directory and not item_tmp.file_deleted then
|
2017-08-17 17:34:36 +00:00
|
|
|
-- Register this item for update
|
|
|
|
table.insert(self.items_to_update, item_tmp)
|
|
|
|
end
|
|
|
|
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
return ListMenu
|