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")
|
|
|
|
local ImageWidget = require("ui/widget/imagewidget")
|
|
|
|
local InfoMessage = require("ui/widget/infomessage")
|
|
|
|
local InputContainer = require("ui/widget/container/inputcontainer")
|
|
|
|
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")
|
2017-08-17 17:34:36 +00:00
|
|
|
local _ = require("gettext")
|
|
|
|
local Screen = Device.screen
|
|
|
|
local getMenuText = require("util").getMenuText
|
|
|
|
|
|
|
|
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")
|
|
|
|
local corner_mark = ImageWidget:new{
|
|
|
|
file = "resources/icons/dogear.png",
|
|
|
|
rotation_angle = 270
|
|
|
|
}
|
|
|
|
|
|
|
|
-- 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
|
|
|
|
|
|
|
|
|
|
|
|
-- 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.
|
|
|
|
|
|
|
|
local FakeCover = FrameContainer:new{
|
|
|
|
width = nil,
|
|
|
|
height = nil,
|
|
|
|
margin = 0,
|
|
|
|
padding = 0,
|
2017-09-11 08:32:39 +00:00
|
|
|
bordersize = Size.line.thin,
|
2017-09-22 16:24:38 +00:00
|
|
|
dim = nil,
|
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,
|
|
|
|
-- 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)
|
|
|
|
if not title then -- use filename as title (big and centered)
|
|
|
|
title = filename
|
|
|
|
filename = nil
|
|
|
|
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.
|
|
|
|
if not authors and title and self.filename:sub(1,title:len()) == title then
|
|
|
|
-- 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("_", " ")
|
|
|
|
end
|
|
|
|
|
|
|
|
-- 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
|
|
|
|
authors_wg:free()
|
|
|
|
authors_wg = nil
|
|
|
|
end
|
|
|
|
if title_wg then
|
|
|
|
title_wg:free()
|
|
|
|
title_wg = nil
|
|
|
|
end
|
|
|
|
if filename_wg then
|
|
|
|
filename_wg:free()
|
|
|
|
filename_wg = nil
|
|
|
|
end
|
|
|
|
-- Build new widgets
|
|
|
|
local texts_height = 0
|
|
|
|
if authors then
|
|
|
|
authors_wg = TextBoxWidget:new{
|
|
|
|
text = authors,
|
|
|
|
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,
|
|
|
|
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{
|
|
|
|
text = filename,
|
|
|
|
face = Font:getFace("cfont", math.max(self.filename_font_max - sizedec, self.filename_font_min)),
|
|
|
|
width = text_width,
|
|
|
|
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
|
|
|
|
-- 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
|
|
|
|
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
|
|
|
|
self.color = Blitbuffer.COLOR_GREY
|
|
|
|
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
|
|
|
|
local MosaicMenuItem = 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_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
|
|
|
|
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
|
|
|
|
if Device:hasKeys() then
|
|
|
|
self.active_key_events = {
|
|
|
|
Select = { {"Press"}, doc = "chose selected 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), don't waste space
|
|
|
|
self._underline_container = UnderlineContainer:new{
|
|
|
|
vertical_align = "center",
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
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)
|
|
|
|
local border_size = Screen:scaleBySize(2) -- make directories bolder
|
|
|
|
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
|
|
|
|
local directory = TextBoxWidget:new{
|
|
|
|
text = text,
|
|
|
|
face = Font:getFace("cfont", 20),
|
|
|
|
width = dimen_in.w,
|
|
|
|
alignment = "center",
|
|
|
|
bold = true,
|
|
|
|
}
|
|
|
|
local nbitems = TextBoxWidget:new{
|
|
|
|
text = self.mandatory,
|
|
|
|
face = Font:getFace("infont", 15),
|
|
|
|
width = dimen_in.w,
|
|
|
|
alignment = "center",
|
|
|
|
}
|
|
|
|
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
|
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 : various appearances
|
|
|
|
-- 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.w - 2*border_size
|
|
|
|
local max_img_h = dimen.h - 2*border_size
|
|
|
|
|
|
|
|
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
|
|
|
|
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,
|
|
|
|
color = self.file_deleted and Blitbuffer.COLOR_GREY or nil,
|
2017-08-17 17:34:36 +00:00
|
|
|
image,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
-- 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 bookinfo.title then
|
|
|
|
bookinfo.title = bookinfo.title .. " - " .. bookinfo.series
|
|
|
|
else
|
|
|
|
bookinfo.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 bookinfo.authors then
|
|
|
|
bookinfo.authors = bookinfo.authors .. " - " .. bookinfo.series
|
|
|
|
else
|
|
|
|
bookinfo.authors = bookinfo.series
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
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,
|
2017-09-22 16:24:38 +00:00
|
|
|
file_deleted = self.file_deleted,
|
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
|
|
|
|
self.cover_specs = {
|
|
|
|
sizetag = "M",
|
|
|
|
max_cover_w = max_img_w,
|
|
|
|
max_cover_h = max_img_h,
|
|
|
|
}
|
|
|
|
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,
|
2017-09-22 16:24:38 +00:00
|
|
|
filename = self.text .. "\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
|
|
|
|
-- align it on bottom left corner of widget
|
|
|
|
local target = self
|
|
|
|
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
|
|
|
|
if self.do_hint_opened and self.been_opened then
|
|
|
|
-- align it on bottom right corner of sub-widget
|
|
|
|
local target = self[1][1][1]
|
|
|
|
local ix = self.width - math.ceil((self.width - target.dimen.w)/2) - corner_mark:getSize().w
|
|
|
|
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()
|
|
|
|
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
|
|
|
|
-- 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
|
|
|
|
local ix = target.dimen.w - 1
|
|
|
|
local iy = 0
|
|
|
|
bb:paintBorder(target.dimen.x+ix, target.dimen.y+iy, d_w, d_h, 1)
|
|
|
|
local x_overflow = target.dimen.x+ix+d_w - x - self.dimen.w
|
|
|
|
if x_overflow > 0 then
|
|
|
|
-- Set alternate dimen to be marked as dirty to include this description in refresh
|
|
|
|
self.refresh_dimen = self[1].dimen:copy()
|
|
|
|
self.refresh_dimen.w = self.refresh_dimen.w + x_overflow
|
|
|
|
end
|
|
|
|
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- As done in MenuItem
|
|
|
|
function MosaicMenuItem:onFocus()
|
|
|
|
self._underline_container.color = Blitbuffer.COLOR_BLACK
|
|
|
|
self.key_events = self.active_key_events
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
|
|
|
function MosaicMenuItem:onUnfocus()
|
|
|
|
self._underline_container.color = Blitbuffer.COLOR_WHITE
|
|
|
|
self.key_events = {}
|
|
|
|
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()
|
|
|
|
self.dimen.w = self.width
|
|
|
|
self.dimen.h = self.height or Screen:getHeight()
|
|
|
|
|
|
|
|
local portrait_mode = true
|
|
|
|
if Screen:getWidth() > Screen:getHeight() then
|
|
|
|
portrait_mode = false
|
|
|
|
end
|
|
|
|
-- 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)
|
|
|
|
self.item_height = math.floor((self.dimen.h - self.others_height - (1+self.nb_rows)*self.item_margin) / self.nb_rows)
|
|
|
|
self.item_width = math.floor((self.dimen.w - (1+self.nb_cols)*self.item_margin) / self.nb_cols)
|
|
|
|
self.item_dimen = Geom:new{
|
|
|
|
w = self.item_width,
|
|
|
|
h = self.item_height
|
|
|
|
}
|
|
|
|
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{}
|
|
|
|
table.insert(self.item_group, cur_row)
|
|
|
|
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]
|
|
|
|
if item_shortcut == "Enter" then
|
|
|
|
item_shortcut = "Ent"
|
|
|
|
end
|
|
|
|
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
|