2019-12-06 21:55:39 +00:00
|
|
|
local BD = require("ui/bidi")
|
2017-08-17 17:34:36 +00:00
|
|
|
local Blitbuffer = require("ffi/blitbuffer")
|
|
|
|
local BottomContainer = require("ui/widget/container/bottomcontainer")
|
|
|
|
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")
|
2020-12-19 11:18:30 +00:00
|
|
|
local IconWidget = require("ui/widget/iconwidget")
|
2017-08-17 17:34:36 +00:00
|
|
|
local ImageWidget = require("ui/widget/imagewidget")
|
|
|
|
local InfoMessage = require("ui/widget/infomessage")
|
|
|
|
local InputContainer = require("ui/widget/container/inputcontainer")
|
2019-11-24 15:18:46 +00:00
|
|
|
local LeftContainer = require("ui/widget/container/leftcontainer")
|
2022-03-19 10:15:22 +00:00
|
|
|
local ProgressWidget = require("ui/widget/progresswidget")
|
2017-08-17 17:34:36 +00:00
|
|
|
local OverlapGroup = require("ui/widget/overlapgroup")
|
2017-09-11 08:32:39 +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")
|
2018-01-21 21:33:40 +00:00
|
|
|
local util = require("util")
|
2017-08-17 17:34:36 +00:00
|
|
|
local _ = require("gettext")
|
|
|
|
local Screen = Device.screen
|
2018-01-21 21:33:40 +00:00
|
|
|
local T = require("ffi/util").template
|
2019-12-06 21:55:39 +00:00
|
|
|
local getMenuText = require("ui/widget/menu").getMenuText
|
2017-08-17 17:34:36 +00:00
|
|
|
|
|
|
|
local BookInfoManager = require("bookinfomanager")
|
|
|
|
|
|
|
|
-- Here is the specific UI implementation for "mosaic" 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
|
2023-02-17 20:29:53 +00:00
|
|
|
local reading_mark
|
|
|
|
local abandoned_mark
|
|
|
|
local complete_mark
|
2022-03-19 10:15:22 +00:00
|
|
|
local progress_widget
|
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
|
Clarify our OOP semantics across the codebase (#9586)
Basically:
* Use `extend` for class definitions
* Use `new` for object instantiations
That includes some minor code cleanups along the way:
* Updated `Widget`'s docs to make the semantics clearer.
* Removed `should_restrict_JIT` (it's been dead code since https://github.com/koreader/android-luajit-launcher/pull/283)
* Minor refactoring of LuaSettings/LuaData/LuaDefaults/DocSettings to behave (mostly, they are instantiated via `open` instead of `new`) like everything else and handle inheritance properly (i.e., DocSettings is now a proper LuaSettings subclass).
* Default to `WidgetContainer` instead of `InputContainer` for stuff that doesn't actually setup key/gesture events.
* Ditto for explicit `*Listener` only classes, make sure they're based on `EventListener` instead of something uselessly fancier.
* Unless absolutely necessary, do not store references in class objects, ever; only values. Instead, always store references in instances, to avoid both sneaky inheritance issues, and sneaky GC pinning of stale references.
* ReaderUI: Fix one such issue with its `active_widgets` array, with critical implications, as it essentially pinned *all* of ReaderUI's modules, including their reference to the `Document` instance (i.e., that was a big-ass leak).
* Terminal: Make sure the shell is killed on plugin teardown.
* InputText: Fix Home/End/Del physical keys to behave sensibly.
* InputContainer/WidgetContainer: If necessary, compute self.dimen at paintTo time (previously, only InputContainers did, which might have had something to do with random widgets unconcerned about input using it as a baseclass instead of WidgetContainer...).
* OverlapGroup: Compute self.dimen at *init* time, because for some reason it needs to do that, but do it directly in OverlapGroup instead of going through a weird WidgetContainer method that it was the sole user of.
* ReaderCropping: Under no circumstances should a Document instance member (here, self.bbox) risk being `nil`ed!
* Kobo: Minor code cleanups.
2022-10-06 00:14:48 +00:00
|
|
|
local ItemShortCutIcon = WidgetContainer:extend{
|
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
|
2019-03-14 19:58:45 +00:00
|
|
|
background = Blitbuffer.COLOR_LIGHT_GRAY
|
2017-08-17 17:34:36 +00:00
|
|
|
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
|
|
|
|
|
|
|
|
|
|
|
|
-- We may find a better algorithm, or just a set of
|
|
|
|
-- nice looking combinations of 3 sizes to iterate thru
|
|
|
|
-- The rendering of the TextBoxWidget we're doing below
|
|
|
|
-- with decreasing font sizes till it fits is quite expensive.
|
|
|
|
|
Clarify our OOP semantics across the codebase (#9586)
Basically:
* Use `extend` for class definitions
* Use `new` for object instantiations
That includes some minor code cleanups along the way:
* Updated `Widget`'s docs to make the semantics clearer.
* Removed `should_restrict_JIT` (it's been dead code since https://github.com/koreader/android-luajit-launcher/pull/283)
* Minor refactoring of LuaSettings/LuaData/LuaDefaults/DocSettings to behave (mostly, they are instantiated via `open` instead of `new`) like everything else and handle inheritance properly (i.e., DocSettings is now a proper LuaSettings subclass).
* Default to `WidgetContainer` instead of `InputContainer` for stuff that doesn't actually setup key/gesture events.
* Ditto for explicit `*Listener` only classes, make sure they're based on `EventListener` instead of something uselessly fancier.
* Unless absolutely necessary, do not store references in class objects, ever; only values. Instead, always store references in instances, to avoid both sneaky inheritance issues, and sneaky GC pinning of stale references.
* ReaderUI: Fix one such issue with its `active_widgets` array, with critical implications, as it essentially pinned *all* of ReaderUI's modules, including their reference to the `Document` instance (i.e., that was a big-ass leak).
* Terminal: Make sure the shell is killed on plugin teardown.
* InputText: Fix Home/End/Del physical keys to behave sensibly.
* InputContainer/WidgetContainer: If necessary, compute self.dimen at paintTo time (previously, only InputContainers did, which might have had something to do with random widgets unconcerned about input using it as a baseclass instead of WidgetContainer...).
* OverlapGroup: Compute self.dimen at *init* time, because for some reason it needs to do that, but do it directly in OverlapGroup instead of going through a weird WidgetContainer method that it was the sole user of.
* ReaderCropping: Under no circumstances should a Document instance member (here, self.bbox) risk being `nil`ed!
* Kobo: Minor code cleanups.
2022-10-06 00:14:48 +00:00
|
|
|
local FakeCover = FrameContainer:extend{
|
2017-08-17 17:34:36 +00:00
|
|
|
width = nil,
|
|
|
|
height = nil,
|
|
|
|
margin = 0,
|
|
|
|
padding = 0,
|
2020-12-19 11:18:30 +00:00
|
|
|
bordersize = Size.border.thin,
|
2017-09-22 16:24:38 +00:00
|
|
|
dim = nil,
|
2023-02-17 20:29:53 +00:00
|
|
|
bottom_right_compensate = false,
|
2020-01-04 00:18:51 +00:00
|
|
|
-- Provided filename, title and authors should not be BD wrapped
|
2017-08-17 17:34:36 +00:00
|
|
|
filename = nil,
|
2017-09-22 16:24:38 +00:00
|
|
|
file_deleted = nil,
|
2017-08-17 17:34:36 +00:00
|
|
|
title = nil,
|
|
|
|
authors = nil,
|
2020-01-04 00:18:51 +00:00
|
|
|
-- The *_add should be provided BD wrapped if needed
|
|
|
|
filename_add = nil,
|
|
|
|
title_add = nil,
|
|
|
|
authors_add = nil,
|
2020-01-23 16:35:57 +00:00
|
|
|
book_lang = nil,
|
2017-08-17 17:34:36 +00:00
|
|
|
-- these font sizes will be scaleBySize'd by Font:getFace()
|
|
|
|
authors_font_max = 20,
|
|
|
|
authors_font_min = 6,
|
|
|
|
title_font_max = 24,
|
|
|
|
title_font_min = 10,
|
|
|
|
filename_font_max = 10,
|
|
|
|
filename_font_min = 8,
|
2017-09-13 14:56:20 +00:00
|
|
|
top_pad = Size.padding.default,
|
|
|
|
bottom_pad = Size.padding.default,
|
2017-08-17 17:34:36 +00:00
|
|
|
sizedec_step = Screen:scaleBySize(2), -- speeds up a bit if we don't do all font sizes
|
|
|
|
initial_sizedec = 0,
|
|
|
|
}
|
|
|
|
|
|
|
|
function FakeCover:init()
|
|
|
|
-- BookInfoManager:extractBookInfo() made sure
|
|
|
|
-- to save as nil (NULL) metadata that were an empty string
|
|
|
|
local authors = self.authors
|
|
|
|
local title = self.title
|
|
|
|
local filename = self.filename
|
|
|
|
-- (some engines may have already given filename (without extension) as title)
|
2020-01-04 00:18:51 +00:00
|
|
|
local bd_wrap_title_as_filename = false
|
2017-08-17 17:34:36 +00:00
|
|
|
if not title then -- use filename as title (big and centered)
|
|
|
|
title = filename
|
|
|
|
filename = nil
|
2020-01-04 00:18:51 +00:00
|
|
|
if not self.title_add and self.filename_add then
|
|
|
|
-- filename_add ("…" or "(deleted)") always comes without any title_add
|
|
|
|
self.title_add = self.filename_add
|
|
|
|
self.filename_add = nil
|
|
|
|
end
|
|
|
|
bd_wrap_title_as_filename = true
|
|
|
|
end
|
|
|
|
if filename then
|
|
|
|
filename = BD.filename(filename)
|
2017-08-17 17:34:36 +00:00
|
|
|
end
|
|
|
|
-- If no authors, and title is filename without extension, it was
|
|
|
|
-- probably made by an engine, and we can consider it a filename, and
|
|
|
|
-- act according to common usage in naming files.
|
2020-01-04 00:18:51 +00:00
|
|
|
if not authors and title and self.filename and self.filename:sub(1,title:len()) == title then
|
|
|
|
bd_wrap_title_as_filename = true
|
2017-08-17 17:34:36 +00:00
|
|
|
-- Replace a hyphen surrounded by spaces (which most probably was
|
|
|
|
-- used to separate Authors/Title/Serie/Year/Categorie in the
|
|
|
|
-- filename with a \n
|
|
|
|
title = title:gsub(" %- ", "\n")
|
|
|
|
-- Same with |
|
|
|
|
title = title:gsub("|", "\n")
|
|
|
|
-- Also replace underscores with spaces
|
|
|
|
title = title:gsub("_", " ")
|
2019-12-16 17:36:34 +00:00
|
|
|
-- Some filenames may also use dots as separators, but dots
|
|
|
|
-- can also have some meaning, so we can't just remove them.
|
|
|
|
-- But at least, make dots breakable (they wouldn't be if not
|
|
|
|
-- followed by a space), by adding to them a zero-width-space,
|
|
|
|
-- so the dots stay on the right of their preceeding word.
|
|
|
|
title = title:gsub("%.", ".\xE2\x80\x8B")
|
|
|
|
-- Except for a last dot near end of title that might preceed
|
|
|
|
-- a file extension: we'd rather want the dot and its suffix
|
|
|
|
-- together on a last line: so, move the zero-width-space
|
|
|
|
-- before it.
|
|
|
|
title = title:gsub("%.\xE2\x80\x8B(%w%w?%w?%w?%w?)$", "\xE2\x80\x8B.%1")
|
2020-01-04 00:18:51 +00:00
|
|
|
-- These substitutions will hopefully have no impact with the following BD wrapping
|
|
|
|
end
|
|
|
|
if title then
|
|
|
|
title = bd_wrap_title_as_filename and BD.filename(title) or BD.auto(title)
|
2017-08-17 17:34:36 +00:00
|
|
|
end
|
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 3, and
|
|
|
|
-- append "et al." on a 4th line if there are more
|
|
|
|
if authors and authors:find("\n") then
|
|
|
|
authors = util.splitToArray(authors, "\n")
|
2020-01-04 00:18:51 +00:00
|
|
|
for i=1, #authors do
|
|
|
|
authors[i] = BD.auto(authors[i])
|
|
|
|
end
|
2018-01-21 21:33:40 +00:00
|
|
|
if #authors > 3 then
|
|
|
|
authors = { authors[1], authors[2], T(_("%1 et al."), authors[3]) }
|
|
|
|
end
|
|
|
|
authors = table.concat(authors, "\n")
|
2020-01-04 00:18:51 +00:00
|
|
|
elseif authors then
|
|
|
|
authors = BD.auto(authors)
|
|
|
|
end
|
|
|
|
-- Add any _add, which must be already BD wrapped if needed
|
|
|
|
if self.filename_add then
|
|
|
|
filename = (filename and filename or "") .. self.filename_add
|
|
|
|
end
|
|
|
|
if self.title_add then
|
|
|
|
title = (title and title or "") .. self.title_add
|
|
|
|
end
|
|
|
|
if self.authors_add then
|
|
|
|
authors = (authors and authors or "") .. self.authors_add
|
2018-01-21 21:33:40 +00:00
|
|
|
end
|
2017-08-17 17:34:36 +00:00
|
|
|
|
|
|
|
-- We build the VerticalGroup widget with decreasing font sizes till
|
|
|
|
-- the widget fits into available height
|
|
|
|
local width = self.width - 2*(self.bordersize + self.margin + self.padding)
|
|
|
|
local height = self.height - 2*(self.bordersize + self.margin + self.padding)
|
|
|
|
local text_width = 7/8 * width -- make width of text smaller to have some padding
|
|
|
|
local inter_pad
|
|
|
|
local sizedec = self.initial_sizedec
|
|
|
|
local authors_wg, title_wg, filename_wg
|
|
|
|
local loop2 = false -- we may do a second pass with modifier title and authors strings
|
|
|
|
while true do
|
|
|
|
-- Free previously made widgets to avoid memory leaks
|
|
|
|
if authors_wg then
|
2022-03-14 18:56:18 +00:00
|
|
|
authors_wg:free(true)
|
2017-08-17 17:34:36 +00:00
|
|
|
authors_wg = nil
|
|
|
|
end
|
|
|
|
if title_wg then
|
2022-03-14 18:56:18 +00:00
|
|
|
title_wg:free(true)
|
2017-08-17 17:34:36 +00:00
|
|
|
title_wg = nil
|
|
|
|
end
|
|
|
|
if filename_wg then
|
2022-03-14 18:56:18 +00:00
|
|
|
filename_wg:free(true)
|
2017-08-17 17:34:36 +00:00
|
|
|
filename_wg = nil
|
|
|
|
end
|
|
|
|
-- Build new widgets
|
|
|
|
local texts_height = 0
|
|
|
|
if authors then
|
|
|
|
authors_wg = TextBoxWidget:new{
|
|
|
|
text = authors,
|
2020-01-23 16:35:57 +00:00
|
|
|
lang = self.book_lang,
|
2017-08-17 17:34:36 +00:00
|
|
|
face = Font:getFace("cfont", math.max(self.authors_font_max - sizedec, self.authors_font_min)),
|
|
|
|
width = text_width,
|
|
|
|
alignment = "center",
|
|
|
|
}
|
|
|
|
texts_height = texts_height + authors_wg:getSize().h
|
|
|
|
end
|
|
|
|
if title then
|
|
|
|
title_wg = TextBoxWidget:new{
|
|
|
|
text = title,
|
2020-01-23 16:35:57 +00:00
|
|
|
lang = self.book_lang,
|
2017-08-17 17:34:36 +00:00
|
|
|
face = Font:getFace("cfont", math.max(self.title_font_max - sizedec, self.title_font_min)),
|
|
|
|
width = text_width,
|
|
|
|
alignment = "center",
|
|
|
|
}
|
|
|
|
texts_height = texts_height + title_wg:getSize().h
|
|
|
|
end
|
|
|
|
if filename then
|
|
|
|
filename_wg = TextBoxWidget:new{
|
2020-01-04 00:18:51 +00:00
|
|
|
text = filename,
|
2020-01-23 16:35:57 +00:00
|
|
|
lang = self.book_lang, -- might as well use it for filename
|
2017-08-17 17:34:36 +00:00
|
|
|
face = Font:getFace("cfont", math.max(self.filename_font_max - sizedec, self.filename_font_min)),
|
2023-02-17 20:29:53 +00:00
|
|
|
width = self.bottom_right_compensate and width - 2 * corner_mark_size or text_width,
|
2017-08-17 17:34:36 +00:00
|
|
|
alignment = "center",
|
|
|
|
}
|
|
|
|
texts_height = texts_height + filename_wg:getSize().h
|
|
|
|
end
|
|
|
|
local free_height = height - texts_height
|
|
|
|
if authors then
|
|
|
|
free_height = free_height - self.top_pad
|
|
|
|
end
|
|
|
|
if filename then
|
|
|
|
free_height = free_height - self.bottom_pad
|
|
|
|
end
|
|
|
|
inter_pad = math.floor(free_height / 2)
|
|
|
|
|
|
|
|
local textboxes_ok = true
|
|
|
|
if (authors_wg and authors_wg.has_split_inside_word) or (title_wg and title_wg.has_split_inside_word) then
|
|
|
|
-- We may get a nicer cover at next lower font size
|
|
|
|
textboxes_ok = false
|
|
|
|
end
|
|
|
|
|
|
|
|
if textboxes_ok and free_height > 0.2 * height then -- enough free space to not look constrained
|
|
|
|
break
|
|
|
|
end
|
|
|
|
-- (We may store the first widgets matching free space requirements but
|
|
|
|
-- not textboxes_ok, so that if we never ever get textboxes_ok candidate,
|
|
|
|
-- we can use them instead of the super-small strings-modified we'll have
|
|
|
|
-- at the end that are worse than the firsts)
|
|
|
|
|
|
|
|
sizedec = sizedec + self.sizedec_step
|
|
|
|
if sizedec > 20 then -- break out of loop when too small
|
|
|
|
-- but try a 2nd loop with some cleanup to strings (for filenames
|
|
|
|
-- with no space but hyphen or underscore instead)
|
|
|
|
if not loop2 then
|
|
|
|
loop2 = true
|
|
|
|
sizedec = self.initial_sizedec -- restart from initial big size
|
2019-12-16 17:36:34 +00:00
|
|
|
if G_reader_settings:nilOrTrue("use_xtext") then
|
|
|
|
-- With Unicode/libunibreak, a break after a hyphen is allowed,
|
|
|
|
-- but not around underscores and dots without any space around.
|
|
|
|
-- So, append a zero-width-space to allow text wrap after them.
|
|
|
|
if title then
|
|
|
|
title = title:gsub("_", "_\xE2\x80\x8B"):gsub("%.", ".\xE2\x80\x8B")
|
|
|
|
end
|
|
|
|
if authors then
|
|
|
|
authors = authors:gsub("_", "_\xE2\x80\x8B"):gsub("%.", ".\xE2\x80\x8B")
|
|
|
|
end
|
|
|
|
else
|
|
|
|
-- Replace underscores and hyphens with spaces, to allow text wrap there.
|
|
|
|
if title then
|
|
|
|
title = title:gsub("-", " "):gsub("_", " ")
|
|
|
|
end
|
|
|
|
if authors then
|
|
|
|
authors = authors:gsub("-", " "):gsub("_", " ")
|
|
|
|
end
|
2017-08-17 17:34:36 +00:00
|
|
|
end
|
|
|
|
else -- 2nd loop done, no luck, give up
|
|
|
|
break
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
local vgroup = VerticalGroup:new{}
|
|
|
|
if authors then
|
|
|
|
table.insert(vgroup, VerticalSpan:new{ width = self.top_pad })
|
|
|
|
table.insert(vgroup, authors_wg)
|
|
|
|
end
|
|
|
|
table.insert(vgroup, VerticalSpan:new{ width = inter_pad })
|
|
|
|
if title then
|
|
|
|
table.insert(vgroup, title_wg)
|
|
|
|
end
|
|
|
|
table.insert(vgroup, VerticalSpan:new{ width = inter_pad })
|
|
|
|
if filename then
|
|
|
|
table.insert(vgroup, filename_wg)
|
|
|
|
table.insert(vgroup, VerticalSpan:new{ width = self.bottom_pad })
|
|
|
|
end
|
|
|
|
|
2017-09-22 16:24:38 +00:00
|
|
|
if self.file_deleted then
|
|
|
|
self.dim = true
|
2019-03-14 19:58:45 +00:00
|
|
|
self.color = Blitbuffer.COLOR_DARK_GRAY
|
2017-09-22 16:24:38 +00:00
|
|
|
end
|
|
|
|
|
2017-08-17 17:34:36 +00:00
|
|
|
-- As we are a FrameContainer, a border will be painted around self[1]
|
|
|
|
self[1] = CenterContainer:new{
|
|
|
|
dimen = Geom:new{
|
|
|
|
w = width,
|
|
|
|
h = height,
|
|
|
|
},
|
|
|
|
vgroup,
|
|
|
|
}
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
-- Based on menu.lua's MenuItem
|
Clarify our OOP semantics across the codebase (#9586)
Basically:
* Use `extend` for class definitions
* Use `new` for object instantiations
That includes some minor code cleanups along the way:
* Updated `Widget`'s docs to make the semantics clearer.
* Removed `should_restrict_JIT` (it's been dead code since https://github.com/koreader/android-luajit-launcher/pull/283)
* Minor refactoring of LuaSettings/LuaData/LuaDefaults/DocSettings to behave (mostly, they are instantiated via `open` instead of `new`) like everything else and handle inheritance properly (i.e., DocSettings is now a proper LuaSettings subclass).
* Default to `WidgetContainer` instead of `InputContainer` for stuff that doesn't actually setup key/gesture events.
* Ditto for explicit `*Listener` only classes, make sure they're based on `EventListener` instead of something uselessly fancier.
* Unless absolutely necessary, do not store references in class objects, ever; only values. Instead, always store references in instances, to avoid both sneaky inheritance issues, and sneaky GC pinning of stale references.
* ReaderUI: Fix one such issue with its `active_widgets` array, with critical implications, as it essentially pinned *all* of ReaderUI's modules, including their reference to the `Document` instance (i.e., that was a big-ass leak).
* Terminal: Make sure the shell is killed on plugin teardown.
* InputText: Fix Home/End/Del physical keys to behave sensibly.
* InputContainer/WidgetContainer: If necessary, compute self.dimen at paintTo time (previously, only InputContainers did, which might have had something to do with random widgets unconcerned about input using it as a baseclass instead of WidgetContainer...).
* OverlapGroup: Compute self.dimen at *init* time, because for some reason it needs to do that, but do it directly in OverlapGroup instead of going through a weird WidgetContainer method that it was the sole user of.
* ReaderCropping: Under no circumstances should a Document instance member (here, self.bbox) risk being `nil`ed!
* Kobo: Minor code cleanups.
2022-10-06 00:14:48 +00:00
|
|
|
local MosaicMenuItem = InputContainer:extend{
|
|
|
|
entry = nil, -- table, mandatory
|
2017-08-17 17:34:36 +00:00
|
|
|
text = nil,
|
|
|
|
show_parent = nil,
|
|
|
|
detail = nil,
|
|
|
|
dimen = nil,
|
|
|
|
shortcut = nil,
|
|
|
|
shortcut_style = "square",
|
|
|
|
_underline_container = nil,
|
|
|
|
do_cover_image = false,
|
|
|
|
do_hint_opened = false,
|
|
|
|
been_opened = false,
|
|
|
|
init_done = false,
|
|
|
|
bookinfo_found = false,
|
|
|
|
cover_specs = nil,
|
|
|
|
has_description = false,
|
|
|
|
}
|
|
|
|
|
|
|
|
function MosaicMenuItem: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*1/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
|
2022-03-19 10:15:22 +00:00
|
|
|
|
2017-08-17 17:34:36 +00:00
|
|
|
self.detail = self.text
|
2022-03-19 10:15:22 +00:00
|
|
|
self.percent_finished = nil
|
2023-02-17 20:29:53 +00:00
|
|
|
self.status = nil
|
2017-08-17 17:34:36 +00:00
|
|
|
|
|
|
|
-- we need this table per-instance, so we declare it here
|
2022-05-06 08:44:25 +00:00
|
|
|
self.ges_events = {
|
|
|
|
TapSelect = {
|
|
|
|
GestureRange:new{
|
|
|
|
ges = "tap",
|
|
|
|
range = self.dimen,
|
2017-08-17 17:34:36 +00:00
|
|
|
},
|
2022-05-06 08:44:25 +00:00
|
|
|
},
|
|
|
|
HoldSelect = {
|
|
|
|
GestureRange:new{
|
|
|
|
ges = "hold",
|
|
|
|
range = self.dimen,
|
2017-08-17 17:34:36 +00:00
|
|
|
},
|
2022-05-06 08:44:25 +00:00
|
|
|
},
|
|
|
|
}
|
2017-08-17 17:34:36 +00:00
|
|
|
|
|
|
|
-- 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), don't waste space
|
|
|
|
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 MosaicMenuItem: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
|
|
|
|
|
|
|
|
local dimen = Geom:new{
|
|
|
|
w = self.width,
|
|
|
|
h = self.height - 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)
|
2020-12-19 11:18:30 +00:00
|
|
|
local border_size = Size.border.thin
|
2018-03-14 17:14:52 +00:00
|
|
|
local max_img_w = dimen.w - 2*border_size
|
|
|
|
local max_img_h = dimen.h - 2*border_size
|
|
|
|
local cover_specs = {
|
|
|
|
sizetag = "M",
|
|
|
|
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
|
|
|
|
-- Directory : rounded corners
|
|
|
|
local margin = Screen:scaleBySize(5) -- make directories less wide
|
|
|
|
local padding = Screen:scaleBySize(5)
|
2020-12-19 11:18:30 +00:00
|
|
|
border_size = Size.border.thick -- make directories' borders larger
|
2017-08-17 17:34:36 +00:00
|
|
|
local dimen_in = Geom:new{
|
|
|
|
w = dimen.w - (margin + padding + border_size)*2,
|
|
|
|
h = dimen.h - (margin + padding + border_size)*2
|
|
|
|
}
|
|
|
|
local text = self.text
|
|
|
|
if text:match('/$') then -- remove /, more readable
|
|
|
|
text = text:sub(1, -2)
|
|
|
|
end
|
2020-01-04 00:18:51 +00:00
|
|
|
text = BD.directory(text)
|
2017-08-17 17:34:36 +00:00
|
|
|
local nbitems = TextBoxWidget:new{
|
|
|
|
text = self.mandatory,
|
|
|
|
face = Font:getFace("infont", 15),
|
|
|
|
width = dimen_in.w,
|
|
|
|
alignment = "center",
|
|
|
|
}
|
2019-12-16 17:36:34 +00:00
|
|
|
-- The directory name will be centered, with nbitems at bottom.
|
|
|
|
-- We could use 2*nbitems:getSize().h to keep that centering,
|
|
|
|
-- but using 3* will avoid getting the directory name stuck
|
|
|
|
-- to nbitems.
|
|
|
|
local available_height = dimen_in.h - 3 * nbitems:getSize().h
|
|
|
|
local dir_font_size = 20
|
|
|
|
local directory
|
|
|
|
while true do
|
|
|
|
if directory then
|
2022-03-14 18:56:18 +00:00
|
|
|
directory:free(true)
|
2019-12-16 17:36:34 +00:00
|
|
|
end
|
|
|
|
directory = TextBoxWidget:new{
|
|
|
|
text = text,
|
|
|
|
face = Font:getFace("cfont", dir_font_size),
|
|
|
|
width = dimen_in.w,
|
|
|
|
alignment = "center",
|
|
|
|
bold = true,
|
|
|
|
}
|
|
|
|
if directory:getSize().h <= available_height then
|
|
|
|
break
|
|
|
|
end
|
|
|
|
dir_font_size = dir_font_size - 1
|
|
|
|
if dir_font_size < 10 then -- don't go too low
|
|
|
|
directory:free()
|
|
|
|
directory.height = available_height
|
|
|
|
directory.height_adjust = true
|
|
|
|
directory.height_overflow_show_ellipsis = true
|
|
|
|
directory:init()
|
|
|
|
break
|
|
|
|
end
|
|
|
|
end
|
2017-08-17 17:34:36 +00:00
|
|
|
widget = FrameContainer:new{
|
|
|
|
width = dimen.w,
|
|
|
|
height = dimen.h,
|
|
|
|
margin = margin,
|
|
|
|
padding = padding,
|
|
|
|
bordersize = border_size,
|
|
|
|
radius = Screen:scaleBySize(10),
|
|
|
|
OverlapGroup:new{
|
|
|
|
dimen = dimen_in,
|
|
|
|
CenterContainer:new{ dimen=dimen_in, directory},
|
|
|
|
BottomContainer:new{ dimen=dimen_in, nbitems},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
else
|
2021-12-16 11:12:25 +00:00
|
|
|
local is_file_selected = self.menu.filemanager and self.menu.filemanager.selected_files
|
|
|
|
and self.menu.filemanager.selected_files[self.filepath]
|
|
|
|
if file_mode ~= "file" or is_file_selected then
|
|
|
|
self.file_deleted = true -- dim file
|
2017-09-22 16:24:38 +00:00
|
|
|
end
|
2017-08-17 17:34:36 +00:00
|
|
|
-- File : various appearances
|
|
|
|
|
|
|
|
if self.do_hint_opened and DocSettings:hasSidecarFile(self.filepath) then
|
|
|
|
self.been_opened = true
|
|
|
|
end
|
|
|
|
|
|
|
|
local bookinfo = BookInfoManager:getBookInfo(self.filepath, self.do_cover_image)
|
|
|
|
if bookinfo and self.do_cover_image and not bookinfo.ignore_cover then
|
|
|
|
if bookinfo.cover_fetched then
|
|
|
|
if bookinfo.has_cover and bookinfo.cover_sizetag ~= "M" then
|
|
|
|
-- there is a cover, but it's a small one (made by ListMenuItem),
|
|
|
|
-- and it would be ugly if scaled up to MosaicMenuItem size:
|
|
|
|
-- do as if not found to force a new extraction with our size
|
|
|
|
if bookinfo.cover_bb then
|
|
|
|
bookinfo.cover_bb:free()
|
|
|
|
end
|
|
|
|
bookinfo = nil
|
|
|
|
-- Note: with the current size differences between FileManager
|
|
|
|
-- and the History windows, we'll get lower max_img_* in History.
|
|
|
|
-- So, when one get Items first generated by the other, it will
|
|
|
|
-- have to do some scaling. Hopefully, people most probably
|
|
|
|
-- browse a lot more files than have them in history, so
|
|
|
|
-- it's most probably History that will have to do some scaling.
|
|
|
|
end
|
|
|
|
-- if not has_cover, book has no cover, no need to try again
|
|
|
|
else
|
|
|
|
-- cover was not fetched previously, do as if not found
|
|
|
|
-- to force a new extraction
|
|
|
|
bookinfo = nil
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
if bookinfo then -- This book is known
|
2023-02-17 20:29:53 +00:00
|
|
|
-- Current page / pages are available or more accurate in .sdr/metadata.lua
|
|
|
|
-- We use a cache (cleaned at end of this browsing session) to store
|
|
|
|
-- page, percent read and book status from sidecar files, to avoid
|
|
|
|
-- re-parsing them when re-rendering a visited page
|
|
|
|
-- This cache is shared with ListMenu, so we need to fill it with the same
|
|
|
|
-- info here than there, even if we don't need them all here.
|
|
|
|
if not self.menu.cover_info_cache then
|
|
|
|
self.menu.cover_info_cache = {}
|
|
|
|
end
|
|
|
|
local percent_finished, status
|
|
|
|
if DocSettings:hasSidecarFile(self.filepath) then
|
|
|
|
self.been_opened = true
|
2023-02-20 06:16:44 +00:00
|
|
|
self.menu:updateCache(self.filepath, nil, true, bookinfo.pages) -- create new cache entry if absent
|
2023-04-26 07:19:01 +00:00
|
|
|
dummy, percent_finished, status =
|
|
|
|
unpack(self.menu.cover_info_cache[self.filepath], 1, self.menu.cover_info_cache[self.filepath].n)
|
2023-02-17 20:29:53 +00:00
|
|
|
end
|
|
|
|
self.percent_finished = percent_finished
|
|
|
|
self.status = status
|
|
|
|
self.show_progress_bar = self.status ~= "complete" and BookInfoManager:getSetting("show_progress_in_mosaic") and self.percent_finished
|
|
|
|
|
2017-08-17 17:34:36 +00:00
|
|
|
local cover_bb_used = false
|
|
|
|
self.bookinfo_found = true
|
|
|
|
-- For wikipedia saved as epub, we made a cover from the 1st pic of the page,
|
|
|
|
-- which may not say much about the book. So, here, pretend we don't have
|
|
|
|
-- a cover
|
|
|
|
if bookinfo.authors and bookinfo.authors:match("^Wikipedia ") then
|
|
|
|
bookinfo.has_cover = nil
|
|
|
|
end
|
|
|
|
if self.do_cover_image and bookinfo.has_cover and not bookinfo.ignore_cover then
|
|
|
|
cover_bb_used = true
|
|
|
|
-- Let ImageWidget do the scaling and give us a bb that fit
|
|
|
|
local scale_factor = math.min(max_img_w / bookinfo.cover_w, max_img_h / bookinfo.cover_h)
|
|
|
|
local image= ImageWidget:new{
|
|
|
|
image = bookinfo.cover_bb,
|
|
|
|
scale_factor = scale_factor,
|
|
|
|
}
|
|
|
|
image:_render()
|
|
|
|
local image_size = image:getSize()
|
|
|
|
widget = CenterContainer:new{
|
|
|
|
dimen = dimen,
|
|
|
|
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,
|
2019-03-14 19:58:45 +00:00
|
|
|
color = self.file_deleted and Blitbuffer.COLOR_DARK_GRAY or nil,
|
2017-08-17 17:34:36 +00:00
|
|
|
image,
|
|
|
|
}
|
|
|
|
}
|
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
|
|
|
|
-- add Series metadata if requested
|
2019-10-22 21:49:04 +00:00
|
|
|
local series_mode = BookInfoManager:getSetting("series_mode")
|
2020-01-04 00:18:51 +00:00
|
|
|
local title_add, authors_add
|
2017-08-17 17:34:36 +00:00
|
|
|
if bookinfo.series then
|
2020-12-06 23:09:47 +00:00
|
|
|
if bookinfo.series_index then
|
|
|
|
bookinfo.series = BD.auto(bookinfo.series .. " #" .. bookinfo.series_index)
|
|
|
|
else
|
|
|
|
bookinfo.series = BD.auto(bookinfo.series)
|
|
|
|
end
|
2019-10-22 21:49:04 +00:00
|
|
|
if series_mode == "append_series_to_title" then
|
2017-08-17 17:34:36 +00:00
|
|
|
if bookinfo.title then
|
2020-01-04 00:18:51 +00:00
|
|
|
title_add = " - " .. bookinfo.series
|
2017-08-17 17:34:36 +00:00
|
|
|
else
|
2020-01-04 00:18:51 +00:00
|
|
|
title_add = bookinfo.series
|
2017-08-17 17:34:36 +00:00
|
|
|
end
|
|
|
|
end
|
2019-10-22 21:49:04 +00:00
|
|
|
if not bookinfo.authors then
|
|
|
|
if series_mode == "append_series_to_authors" or series_mode == "series_in_separate_line" then
|
2020-01-04 00:18:51 +00:00
|
|
|
authors_add = bookinfo.series
|
2017-08-17 17:34:36 +00:00
|
|
|
end
|
2019-10-22 21:49:04 +00:00
|
|
|
else
|
|
|
|
if series_mode == "append_series_to_authors" then
|
2020-01-04 00:18:51 +00:00
|
|
|
authors_add = " - " .. bookinfo.series
|
2019-10-22 21:49:04 +00:00
|
|
|
elseif series_mode == "series_in_separate_line" then
|
2020-01-04 00:18:51 +00:00
|
|
|
authors_add = "\n \n" .. bookinfo.series
|
2019-10-22 21:49:04 +00:00
|
|
|
end
|
2017-08-17 17:34:36 +00:00
|
|
|
end
|
|
|
|
end
|
2023-02-17 20:29:53 +00:00
|
|
|
local bottom_pad = Size.padding.default
|
|
|
|
if self.show_progress_bar and self.do_hint_opened then
|
|
|
|
bottom_pad = corner_mark_size + Screen:scaleBySize(2)
|
|
|
|
elseif self.show_progress_bar then
|
|
|
|
bottom_pad = corner_mark_size - Screen:scaleBySize(2)
|
|
|
|
end
|
2017-08-17 17:34:36 +00:00
|
|
|
widget = CenterContainer:new{
|
|
|
|
dimen = dimen,
|
|
|
|
FakeCover:new{
|
|
|
|
-- reduced width to make it look less squared, more like a book
|
|
|
|
width = math.floor(dimen.w * 7/8),
|
|
|
|
height = dimen.h,
|
|
|
|
bordersize = border_size,
|
|
|
|
filename = self.text,
|
|
|
|
title = not bookinfo.ignore_meta and bookinfo.title,
|
|
|
|
authors = not bookinfo.ignore_meta and bookinfo.authors,
|
2020-01-04 00:18:51 +00:00
|
|
|
title_add = not bookinfo.ignore_meta and title_add,
|
|
|
|
authors_add = not bookinfo.ignore_meta and authors_add,
|
2020-01-23 16:35:57 +00:00
|
|
|
book_lang = not bookinfo.ignore_meta and bookinfo.language,
|
2017-09-22 16:24:38 +00:00
|
|
|
file_deleted = self.file_deleted,
|
2023-02-17 20:29:53 +00:00
|
|
|
bottom_pad = bottom_pad,
|
|
|
|
bottom_right_compensate = not self.show_progress_bar and self.do_hint_opened,
|
2017-08-17 17:34:36 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
end
|
|
|
|
-- In case we got a blitbuffer and didnt use it (ignore_cover, wikipedia), 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
|
|
|
|
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 making the same FakeCover
|
|
|
|
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
|
|
|
|
-- Same as real FakeCover, but let it be squared (like a file)
|
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 = CenterContainer:new{
|
|
|
|
dimen = dimen,
|
|
|
|
FakeCover:new{
|
|
|
|
width = dimen.w,
|
|
|
|
height = dimen.h,
|
|
|
|
bordersize = border_size,
|
2020-01-04 00:18:51 +00:00
|
|
|
filename = self.text,
|
|
|
|
filename_add = "\n" .. hint,
|
2017-08-17 17:34:36 +00:00
|
|
|
initial_sizedec = 4, -- start with a smaller font when filenames only
|
2017-09-22 16:24:38 +00:00
|
|
|
file_deleted = self.file_deleted,
|
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
|
|
|
|
self._underline_container[1] = widget
|
|
|
|
end
|
|
|
|
|
|
|
|
function MosaicMenuItem: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
|
2017-08-17 17:34:36 +00:00
|
|
|
-- inside FrameContainer were image would be drawn on top of the top border...
|
2017-08-18 15:21:36 +00:00
|
|
|
-- 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("MosaicMenuItem: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
|
2023-02-17 20:29:53 +00:00
|
|
|
-- align it on top left corner of widget
|
2017-08-17 17:34:36 +00:00
|
|
|
local target = self
|
2019-12-06 21:55:39 +00:00
|
|
|
local ix
|
|
|
|
if BD.mirroredUILayout() then
|
|
|
|
ix = target.dimen.w - self.shortcut_icon.dimen.w
|
|
|
|
else
|
|
|
|
ix = 0
|
|
|
|
end
|
2023-02-17 20:29:53 +00:00
|
|
|
local iy = 0
|
2017-08-17 17:34:36 +00:00
|
|
|
self.shortcut_icon:paintTo(bb, x+ix, y+iy)
|
|
|
|
end
|
|
|
|
|
2023-02-17 20:29:53 +00:00
|
|
|
if self.do_hint_opened and self.been_opened then
|
2017-08-17 17:34:36 +00:00
|
|
|
-- align it on bottom right corner of sub-widget
|
|
|
|
local target = self[1][1][1]
|
2019-12-06 21:55:39 +00:00
|
|
|
local ix
|
|
|
|
if BD.mirroredUILayout() then
|
|
|
|
ix = math.floor((self.width - target.dimen.w)/2)
|
|
|
|
else
|
|
|
|
ix = self.width - math.ceil((self.width - target.dimen.w)/2) - corner_mark:getSize().w
|
|
|
|
end
|
2017-08-17 17:34:36 +00:00
|
|
|
local iy = self.height - math.ceil((self.height - target.dimen.h)/2) - corner_mark:getSize().h
|
|
|
|
-- math.ceil() makes it looks better than math.floor()
|
2023-02-17 20:29:53 +00:00
|
|
|
if self.status == "abandoned" then
|
|
|
|
corner_mark = abandoned_mark
|
|
|
|
elseif self.status == "complete" then
|
|
|
|
corner_mark = complete_mark
|
|
|
|
else
|
|
|
|
corner_mark = reading_mark
|
|
|
|
end
|
2017-08-17 17:34:36 +00:00
|
|
|
corner_mark:paintTo(bb, x+ix, y+iy)
|
|
|
|
end
|
|
|
|
|
2023-02-17 20:29:53 +00:00
|
|
|
if self.show_progress_bar then
|
2022-03-19 10:15:22 +00:00
|
|
|
local cover_item = self[1][1][1]
|
2023-02-17 20:29:53 +00:00
|
|
|
local progress_widget_margin = math.floor((corner_mark_size - progress_widget.height) / 2)
|
|
|
|
progress_widget.width = cover_item.width - 2*progress_widget_margin
|
|
|
|
local pos_x = x + math.ceil((self.width - progress_widget.width) / 2)
|
|
|
|
if self.do_hint_opened then
|
|
|
|
progress_widget.width = progress_widget.width - corner_mark_size
|
|
|
|
if BD.mirroredUILayout() then
|
|
|
|
pos_x = pos_x + corner_mark_size
|
|
|
|
end
|
|
|
|
end
|
|
|
|
local pos_y = y + self.height - math.ceil((self.height - cover_item.height) / 2) - corner_mark_size + progress_widget_margin
|
|
|
|
if self.status == "abandoned" then
|
|
|
|
progress_widget.fillcolor = Blitbuffer.COLOR_GRAY_6
|
|
|
|
else
|
|
|
|
progress_widget.fillcolor = Blitbuffer.COLOR_BLACK
|
|
|
|
end
|
2022-03-19 10:15:22 +00:00
|
|
|
progress_widget:setPercentage(self.percent_finished)
|
|
|
|
progress_widget:paintTo(bb, pos_x, pos_y)
|
|
|
|
end
|
|
|
|
|
2017-08-17 17:34:36 +00:00
|
|
|
-- 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
|
|
|
|
-- On book's right (for similarity to ListMenuItem)
|
|
|
|
local target = self[1][1][1]
|
|
|
|
local d_w = Screen:scaleBySize(3)
|
|
|
|
local d_h = math.ceil(target.dimen.h / 8)
|
|
|
|
-- Paint it directly relative to target.dimen.x/y which has been computed at this point
|
2019-12-06 21:55:39 +00:00
|
|
|
local ix
|
|
|
|
if BD.mirroredUILayout() then
|
|
|
|
ix = - d_w + 1
|
2017-08-17 17:34:36 +00:00
|
|
|
-- Set alternate dimen to be marked as dirty to include this description in refresh
|
2019-12-06 21:55:39 +00:00
|
|
|
local x_overflow_left = x - target.dimen.x+ix -- positive if overflow
|
|
|
|
if x_overflow_left > 0 then
|
|
|
|
self.refresh_dimen = self[1].dimen:copy()
|
|
|
|
self.refresh_dimen.x = self.refresh_dimen.x - x_overflow_left
|
|
|
|
self.refresh_dimen.w = self.refresh_dimen.w + x_overflow_left
|
|
|
|
end
|
|
|
|
else
|
|
|
|
ix = target.dimen.w - 1
|
|
|
|
-- Set alternate dimen to be marked as dirty to include this description in refresh
|
|
|
|
local x_overflow_right = target.dimen.x+ix+d_w - x - self.dimen.w
|
|
|
|
if x_overflow_right > 0 then
|
|
|
|
self.refresh_dimen = self[1].dimen:copy()
|
|
|
|
self.refresh_dimen.w = self.refresh_dimen.w + x_overflow_right
|
|
|
|
end
|
2017-08-17 17:34:36 +00:00
|
|
|
end
|
2019-12-06 21:55:39 +00:00
|
|
|
local iy = 0
|
|
|
|
bb:paintBorder(target.dimen.x+ix, target.dimen.y+iy, d_w, d_h, 1)
|
2017-08-17 17:34:36 +00:00
|
|
|
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- As done in MenuItem
|
|
|
|
function MosaicMenuItem:onFocus()
|
|
|
|
self._underline_container.color = Blitbuffer.COLOR_BLACK
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
|
|
|
function MosaicMenuItem:onUnfocus()
|
|
|
|
self._underline_container.color = Blitbuffer.COLOR_WHITE
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
|
|
|
function MosaicMenuItem: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 MosaicMenuItem:onTapSelect(arg)
|
|
|
|
self.menu:onMenuSelect(self.entry)
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
|
|
|
function MosaicMenuItem: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 MosaicMenu = {}
|
|
|
|
|
|
|
|
function MosaicMenu:_recalculateDimen()
|
2021-02-25 23:48:05 +00:00
|
|
|
local portrait_mode = Screen:getWidth() <= Screen:getHeight()
|
2017-08-17 17:34:36 +00:00
|
|
|
-- 3 x 3 grid by default if not initially provided (4 x 2 in landscape mode)
|
|
|
|
if portrait_mode then
|
|
|
|
self.nb_cols = self.nb_cols_portrait or 3
|
|
|
|
self.nb_rows = self.nb_rows_portrait or 3
|
|
|
|
else
|
|
|
|
self.nb_cols = self.nb_cols_landscape or 4
|
|
|
|
self.nb_rows = self.nb_rows_landscape or 2
|
|
|
|
end
|
|
|
|
self.perpage = self.nb_rows * self.nb_cols
|
|
|
|
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
|
|
|
|
|
|
|
-- Find out available height from other UI elements made in Menu
|
|
|
|
self.others_height = 0
|
|
|
|
if self.title_bar then -- 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
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Set our items target size
|
|
|
|
self.item_margin = Screen:scaleBySize(10)
|
[RFC] Pagination UI shenanigans (#7335)
* Menu/KeyValuePage/ReaderGoTo: Unify the dialogs. (Generally, "Enter page number" as title, and "Go to page" as OK button).
* Allow *tapping* on pagination buttons, too. Added spacers around the text to accommodate for that.
* Disable input handlers when <= 1 pages, while still printing the label in black.
* Always display both the label and the chevrons, even on single page content. (Menu being an exception, because it can handle showing no content at all, in which case we hide the chevrons).
* KVP: Tweak the pagination buttons layout in order to have consistent centering, regardless of whether the return arrow is enabled or not. (Also, match Menu's layout, more or less).
* Menu: Minor layout tweaks to follow the KVP tweaks above. Fixes, among possibly other things, buttons in (non-FM) "List" menus overlapping the final entry (e.g., OPDS), and popout menus with a border being misaligned (e.g., Calibre, Find a file).
* CalendarView: Minor layout tweaks to follow the KVP tweaks. Ensures the pagination buttons are laid out in the same way as everywhere else (they used to be a wee bit higher).
2021-02-25 04:15:23 +00:00
|
|
|
self.item_height = math.floor((self.inner_dimen.h - self.others_height - (1+self.nb_rows)*self.item_margin) / self.nb_rows)
|
|
|
|
self.item_width = math.floor((self.inner_dimen.w - (1+self.nb_cols)*self.item_margin) / self.nb_cols)
|
2017-08-17 17:34:36 +00:00
|
|
|
self.item_dimen = Geom:new{
|
|
|
|
w = self.item_width,
|
|
|
|
h = self.item_height
|
|
|
|
}
|
2018-07-19 06:18:55 +00:00
|
|
|
|
|
|
|
-- Create or replace corner_mark if needed
|
|
|
|
-- 1/12 (larger) or 1/16 (smaller) of cover looks allright
|
2023-02-17 20:29:53 +00:00
|
|
|
local mark_size = math.floor(math.min(self.item_width, self.item_height) / 8)
|
2018-07-19 06:18:55 +00:00
|
|
|
if mark_size ~= corner_mark_size then
|
|
|
|
corner_mark_size = mark_size
|
|
|
|
if corner_mark then
|
2023-02-17 20:29:53 +00:00
|
|
|
reading_mark:free()
|
|
|
|
abandoned_mark:free()
|
|
|
|
complete_mark:free()
|
2018-07-19 06:18:55 +00:00
|
|
|
end
|
2023-02-17 20:29:53 +00:00
|
|
|
reading_mark = IconWidget:new{
|
|
|
|
icon = "dogear.reading",
|
|
|
|
rotation_angle = BD.mirroredUILayout() and 270 or 0,
|
|
|
|
width = corner_mark_size,
|
|
|
|
height = corner_mark_size,
|
|
|
|
}
|
|
|
|
abandoned_mark = IconWidget:new{
|
|
|
|
icon = BD.mirroredUILayout() and "dogear.abandoned.rtl" or "dogear.abandoned",
|
|
|
|
width = corner_mark_size,
|
|
|
|
height = corner_mark_size,
|
|
|
|
}
|
|
|
|
complete_mark = IconWidget:new{
|
|
|
|
icon = BD.mirroredUILayout() and "dogear.complete.rtl" or "dogear.complete",
|
|
|
|
alpha = true,
|
2018-07-19 06:18:55 +00:00
|
|
|
width = corner_mark_size,
|
|
|
|
height = corner_mark_size,
|
|
|
|
}
|
2023-02-17 20:29:53 +00:00
|
|
|
corner_mark = reading_mark
|
2018-07-19 06:18:55 +00:00
|
|
|
end
|
2022-03-19 10:15:22 +00:00
|
|
|
|
|
|
|
-- Create or replace progress_widget if needed
|
|
|
|
local progress_bar_width = self.item_width * 0.60;
|
|
|
|
if not progress_widget or progress_widget.width ~= progress_bar_width then
|
|
|
|
progress_widget = ProgressWidget:new{
|
|
|
|
bgcolor = Blitbuffer.COLOR_WHITE,
|
|
|
|
fillcolor = Blitbuffer.COLOR_BLACK,
|
|
|
|
bordercolor = Blitbuffer.COLOR_BLACK,
|
2023-02-17 20:29:53 +00:00
|
|
|
height = Screen:scaleBySize(8),
|
2022-03-19 10:15:22 +00:00
|
|
|
margin_h = Screen:scaleBySize(1),
|
|
|
|
width = progress_bar_width,
|
|
|
|
radius = Size.border.thin,
|
2023-02-17 20:29:53 +00:00
|
|
|
bordersize = Size.border.default,
|
2022-03-19 10:15:22 +00:00
|
|
|
}
|
|
|
|
end
|
2017-08-17 17:34:36 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
function MosaicMenu:_updateItemsBuildUI()
|
|
|
|
-- Build our grid
|
|
|
|
local idx_offset = (self.page - 1) * self.perpage
|
|
|
|
local cur_row = nil
|
|
|
|
for idx = 1, self.perpage do
|
|
|
|
local entry = self.item_table[idx_offset + idx]
|
|
|
|
if entry == nil then break end
|
|
|
|
|
|
|
|
if idx % self.nb_cols == 1 then -- new row
|
|
|
|
table.insert(self.item_group, VerticalSpan:new{ width = self.item_margin })
|
|
|
|
cur_row = HorizontalGroup:new{}
|
2019-11-24 15:18:46 +00:00
|
|
|
-- Have items on the possibly non-fully filled last row aligned to the left
|
2019-11-25 13:31:50 +00:00
|
|
|
local container = self._do_center_partial_rows and CenterContainer or LeftContainer
|
|
|
|
table.insert(self.item_group, container:new{
|
2019-11-24 15:18:46 +00:00
|
|
|
dimen = Geom:new{
|
[RFC] Pagination UI shenanigans (#7335)
* Menu/KeyValuePage/ReaderGoTo: Unify the dialogs. (Generally, "Enter page number" as title, and "Go to page" as OK button).
* Allow *tapping* on pagination buttons, too. Added spacers around the text to accommodate for that.
* Disable input handlers when <= 1 pages, while still printing the label in black.
* Always display both the label and the chevrons, even on single page content. (Menu being an exception, because it can handle showing no content at all, in which case we hide the chevrons).
* KVP: Tweak the pagination buttons layout in order to have consistent centering, regardless of whether the return arrow is enabled or not. (Also, match Menu's layout, more or less).
* Menu: Minor layout tweaks to follow the KVP tweaks above. Fixes, among possibly other things, buttons in (non-FM) "List" menus overlapping the final entry (e.g., OPDS), and popout menus with a border being misaligned (e.g., Calibre, Find a file).
* CalendarView: Minor layout tweaks to follow the KVP tweaks. Ensures the pagination buttons are laid out in the same way as everywhere else (they used to be a wee bit higher).
2021-02-25 04:15:23 +00:00
|
|
|
w = self.inner_dimen.w,
|
2019-11-24 15:18:46 +00:00
|
|
|
h = self.item_height
|
|
|
|
},
|
|
|
|
cur_row
|
|
|
|
})
|
2017-08-17 17:34:36 +00:00
|
|
|
table.insert(cur_row, HorizontalSpan:new({ width = self.item_margin }))
|
|
|
|
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 = MosaicMenuItem:new{
|
|
|
|
height = self.item_height,
|
|
|
|
width = self.item_width,
|
|
|
|
entry = entry,
|
|
|
|
text = 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,
|
|
|
|
}
|
|
|
|
table.insert(cur_row, item_tmp)
|
|
|
|
table.insert(cur_row, HorizontalSpan:new({ width = self.item_margin }))
|
|
|
|
|
|
|
|
-- 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
|
|
|
|
table.insert(self.item_group, VerticalSpan:new{ width = self.item_margin }) -- bottom padding
|
|
|
|
end
|
|
|
|
|
|
|
|
return MosaicMenu
|