Merge remote-tracking branch 'upstream/master' into mtk-kaleido

reviewable/pr11737/r5
NiLuJe 4 weeks ago
commit 7ce62bafae

@ -24,20 +24,41 @@ jobs:
- name: Check out Git repository
uses: actions/checkout@v4
with:
clean: false
fetch-depth: 0
filter: tree:0
show-progress: false
- name: Homebrew install dependencies
# Compared to the README, adds ccache for faster compilation times
# Compared to the emulator, adds p7zip.
run: >
brew install -q nasm ragel binutils coreutils libtool autoconf automake cmake makedepend
sdl2 lua@5.1 luarocks gettext pkg-config wget gnu-getopt grep bison
ccache p7zip
# Compared to the README, adds p7zip.
run: |
packages=(
nasm binutils coreutils libtool autoconf automake cmake makedepend
sdl2 lua@5.1 luarocks gettext pkg-config wget gnu-getopt grep bison
p7zip
)
# Lua 5.1 is disabled, so we need to work around that:
# - fetch all packages
brew fetch "${packages[@]}"
# - disable auto-updates
export HOMEBREW_NO_AUTO_UPDATE=1
# - install lua@5.1 from cache
brew install "$(brew --cache lua@5.1)"
# - and install the rest
brew install "${packages[@]}"
- name: Update PATH
run: |
printf '%s\n' \
"$(brew --prefix)/opt/bison/bin" \
"$(brew --prefix)/opt/gettext/bin" \
"$(brew --prefix)/opt/gnu-getopt/bin" \
"$(brew --prefix)/opt/grep/libexec/gnubin" \
>>"${GITHUB_PATH}"
- name: Building in progress…
run: |
export MACOSX_DEPLOYMENT_TARGET=10.15;
export PATH="$(brew --prefix)/opt/gettext/bin:$(brew --prefix)/opt/gnu-getopt/bin:$(brew --prefix)/opt/bison/bin:$(brew --prefix)/opt/grep/libexec/gnubin:${PATH}";
./kodev release macos
- name: Uploading artifacts

@ -1 +1 @@
Subproject commit c440af0a57ca5127d12f366fff3fc59ad885d809
Subproject commit 9a90ea881201ae64f188150b4e3c02daeb3901bc

@ -292,7 +292,7 @@ function FileManager:setupLayout()
table.insert(buttons, {}) -- separator
table.insert(buttons, {
filemanagerutil.genResetSettingsButton(doc_settings_or_file, close_dialog_refresh_callback),
filemanagerutil.genAddRemoveFavoritesButton(file, close_dialog_callback),
file_manager.collections:genAddToCollectionButton(file, close_dialog_callback),
})
end
table.insert(buttons, {
@ -515,11 +515,17 @@ function FileManager:tapPlus()
local title, buttons
if self.select_mode then
local function toggle_select_mode_callback()
self:onToggleSelectMode()
end
local select_count = util.tableSize(self.selected_files)
local actions_enabled = select_count > 0
title = actions_enabled and T(N_("1 file selected", "%1 files selected", select_count), select_count)
or _("No files selected")
buttons = {
{
self.collections:genAddToCollectionButton(self.selected_files, close_dialog_callback, toggle_select_mode_callback),
},
{
{
text = _("Show selected files list"),

@ -1,19 +1,26 @@
local BD = require("ui/bidi")
local ButtonDialog = require("ui/widget/buttondialog")
local ConfirmBox = require("ui/widget/confirmbox")
local Device = require("device")
local DocSettings = require("docsettings")
local DocumentRegistry = require("document/documentregistry")
local FileManagerBookInfo = require("apps/filemanager/filemanagerbookinfo")
local InfoMessage = require("ui/widget/infomessage")
local InputDialog = require("ui/widget/inputdialog")
local Menu = require("ui/widget/menu")
local ReadCollection = require("readcollection")
local SortWidget = require("ui/widget/sortwidget")
local UIManager = require("ui/uimanager")
local WidgetContainer = require("ui/widget/container/widgetcontainer")
local Screen = require("device").screen
local filemanagerutil = require("apps/filemanager/filemanagerutil")
local _ = require("gettext")
local T = require("ffi/util").template
local util = require("util")
local FileManagerCollection = WidgetContainer:extend{
title = _("Favorites"),
title = _("Collections"),
default_collection_title = _("Favorites"),
checkmark = "\u{2713}",
}
function FileManagerCollection:init()
@ -21,21 +28,73 @@ function FileManagerCollection:init()
end
function FileManagerCollection:addToMainMenu(menu_items)
menu_items.favorites = {
text = self.default_collection_title,
callback = function()
self:onShowColl()
end,
}
menu_items.collections = {
text = self.title,
callback = function()
self:onShowColl()
self:onShowCollList()
end,
}
end
function FileManagerCollection:updateItemTable()
-- collection
function FileManagerCollection:getCollectionTitle(collection_name)
return collection_name == ReadCollection.default_collection_name
and self.default_collection_title -- favorites
or collection_name
end
function FileManagerCollection:onShowColl(collection_name)
collection_name = collection_name or ReadCollection.default_collection_name
self.coll_menu = Menu:new{
ui = self.ui,
covers_fullscreen = true, -- hint for UIManager:_repaint()
is_borderless = true,
is_popout = false,
-- item and book cover thumbnail dimensions in Mosaic and Detailed list display modes
-- must be equal in File manager, History and Collection windows to avoid image scaling
title_bar_fm_style = true,
title_bar_left_icon = "appbar.menu",
onLeftButtonTap = function() self:showCollDialog() end,
onMenuChoice = self.onMenuChoice,
onMenuHold = self.onMenuHold,
onSetRotationMode = self.MenuSetRotationModeHandler,
_manager = self,
collection_name = collection_name,
}
self.coll_menu.close_callback = function()
if self.files_updated then
if self.ui.file_chooser then
self.ui.file_chooser:refreshPath()
end
self.files_updated = nil
end
UIManager:close(self.coll_menu)
self.coll_menu = nil
end
self:updateItemTable()
UIManager:show(self.coll_menu)
return true
end
function FileManagerCollection:updateItemTable(show_last_item)
local item_table = {}
for _, item in pairs(ReadCollection.coll[self.coll_menu.collection_name]) do
table.insert(item_table, item)
end
table.sort(item_table, function(v1, v2) return v1.order < v2.order end)
self.coll_menu:switchItemTable(self.title, item_table, -1)
if #item_table > 1 then
table.sort(item_table, function(v1, v2) return v1.order < v2.order end)
end
local title = self:getCollectionTitle(self.coll_menu.collection_name)
title = T("%1 (%2)", title, #item_table)
local item_number = show_last_item and #item_table or -1
self.coll_menu:switchItemTable(title, item_table, item_number)
end
function FileManagerCollection:onMenuChoice(item)
@ -92,7 +151,7 @@ function FileManagerCollection:onMenuHold(item)
table.insert(buttons, {
filemanagerutil.genResetSettingsButton(doc_settings_or_file, close_dialog_update_callback, is_currently_opened),
{
text = _("Remove from favorites"),
text = _("Remove from collection"),
callback = function()
UIManager:close(self.collfile_dialog)
ReadCollection:removeItem(file, self.collection_name)
@ -124,79 +183,37 @@ function FileManagerCollection:onMenuHold(item)
return true
end
function FileManagerCollection:MenuSetRotationModeHandler(rotation)
if rotation ~= nil and rotation ~= Screen:getRotationMode() then
UIManager:close(self._manager.coll_menu)
if self._manager.ui.view and self._manager.ui.view.onSetRotationMode then
self._manager.ui.view:onSetRotationMode(rotation)
elseif self._manager.ui.onSetRotationMode then
self._manager.ui:onSetRotationMode(rotation)
else
Screen:setRotationMode(rotation)
end
self._manager:onShowColl()
end
return true
end
function FileManagerCollection:onShowColl(collection_name)
collection_name = collection_name or ReadCollection.default_collection_name
self.coll_menu = Menu:new{
ui = self.ui,
covers_fullscreen = true, -- hint for UIManager:_repaint()
is_borderless = true,
is_popout = false,
-- item and book cover thumbnail dimensions in Mosaic and Detailed list display modes
-- must be equal in File manager, History and Collection windows to avoid image scaling
title_bar_fm_style = true,
title_bar_left_icon = "appbar.menu",
onLeftButtonTap = function() self:showCollDialog() end,
onMenuChoice = self.onMenuChoice,
onMenuHold = self.onMenuHold,
onSetRotationMode = self.MenuSetRotationModeHandler,
_manager = self,
collection_name = collection_name,
}
self.coll_menu.close_callback = function()
if self.files_updated then
if self.ui.file_chooser then
self.ui.file_chooser:refreshPath()
end
self.files_updated = nil
end
UIManager:close(self.coll_menu)
self.coll_menu = nil
end
self:updateItemTable()
UIManager:show(self.coll_menu)
return true
end
function FileManagerCollection:showCollDialog()
local coll_dialog
local buttons = {
{{
text = _("Sort favorites"),
text = _("Collections"),
callback = function()
UIManager:close(coll_dialog)
self.coll_menu.close_callback()
self:onShowCollList()
end,
}},
{}, -- separator
{{
text = _("Arrange books in collection"),
callback = function()
UIManager:close(coll_dialog)
self:sortCollection()
end,
}},
{{
text = _("Add a book to favorites"),
text = _("Add a book to collection"),
callback = function()
UIManager:close(coll_dialog)
local PathChooser = require("ui/widget/pathchooser")
local path_chooser = PathChooser:new{
path = G_reader_settings:readSetting("home_dir"),
select_directory = false,
file_filter = function(file)
return DocumentRegistry:hasProvider(file)
end,
onConfirm = function(file)
if not ReadCollection:hasFile(file) then
if not ReadCollection:isFileInCollection(file, self.coll_menu.collection_name) then
ReadCollection:addItem(file, self.coll_menu.collection_name)
self:updateItemTable()
self:updateItemTable(true) -- show added item
end
end,
}
@ -205,19 +222,20 @@ function FileManagerCollection:showCollDialog()
}},
}
if self.ui.document then
local has_file = ReadCollection:hasFile(self.ui.document.file)
local file = self.ui.document.file
local is_in_collection = ReadCollection:isFileInCollection(file, self.coll_menu.collection_name)
table.insert(buttons, {{
text_func = function()
return has_file and _("Remove current book from favorites") or _("Add current book to favorites")
return is_in_collection and _("Remove current book from collection") or _("Add current book to collection")
end,
callback = function()
UIManager:close(coll_dialog)
if has_file then
ReadCollection:removeItem(self.ui.document.file)
if is_in_collection then
ReadCollection:removeItem(file, self.coll_menu.collection_name)
else
ReadCollection:addItem(self.ui.document.file, self.coll_menu.collection_name)
ReadCollection:addItem(file, self.coll_menu.collection_name)
end
self:updateItemTable()
self:updateItemTable(not is_in_collection)
end,
}})
end
@ -228,12 +246,10 @@ function FileManagerCollection:showCollDialog()
end
function FileManagerCollection:sortCollection()
local item_table = ReadCollection:getOrderedCollection(self.coll_menu.collection_name)
local SortWidget = require("ui/widget/sortwidget")
local sort_widget
sort_widget = SortWidget:new{
title = _("Sort favorites"),
item_table = item_table,
title = _("Arrange books in collection"),
item_table = ReadCollection:getOrderedCollection(self.coll_menu.collection_name),
callback = function()
ReadCollection:updateCollectionOrder(self.coll_menu.collection_name, sort_widget.item_table)
self:updateItemTable()
@ -242,10 +258,341 @@ function FileManagerCollection:sortCollection()
UIManager:show(sort_widget)
end
function FileManagerCollection:MenuSetRotationModeHandler(rotation)
if rotation ~= nil and rotation ~= Screen:getRotationMode() then
UIManager:close(self._manager.coll_menu)
if self._manager.ui.view and self._manager.ui.view.onSetRotationMode then
self._manager.ui.view:onSetRotationMode(rotation)
elseif self._manager.ui.onSetRotationMode then
self._manager.ui:onSetRotationMode(rotation)
else
Screen:setRotationMode(rotation)
end
self._manager:onShowColl()
end
return true
end
function FileManagerCollection:onBookMetadataChanged()
if self.coll_menu then
self.coll_menu:updateItems()
end
end
-- collection list
function FileManagerCollection:onShowCollList(file_or_files, caller_callback, no_dialog)
self.selected_colections = nil
if file_or_files then -- select mode
if type(file_or_files) == "string" then -- checkmark collections containing the file
self.selected_colections = ReadCollection:getCollectionsWithFile(file_or_files)
else -- do not checkmark any
self.selected_colections = {}
end
end
self.coll_list = Menu:new{
subtitle = "",
covers_fullscreen = true,
is_borderless = true,
is_popout = false,
title_bar_fm_style = true,
title_bar_left_icon = file_or_files and "check" or "appbar.menu",
onLeftButtonTap = function() self:showCollListDialog(caller_callback, no_dialog) end,
onMenuChoice = self.onCollListChoice,
onMenuHold = self.onCollListHold,
onSetRotationMode = self.MenuSetRotationModeHandler,
_manager = self,
}
self.coll_list.close_callback = function(force_close)
if force_close or self.selected_colections == nil then
UIManager:close(self.coll_list)
self.coll_list = nil
end
end
self:updateCollListItemTable(true) -- init
UIManager:show(self.coll_list)
return true
end
function FileManagerCollection:updateCollListItemTable(do_init, item_number)
local item_table
if do_init then
item_table = {}
for name, coll in pairs(ReadCollection.coll) do
local mandatory
if self.selected_colections then
mandatory = self.selected_colections[name] and self.checkmark or " "
self.coll_list.items_mandatory_font_size = self.coll_list.font_size
else
mandatory = util.tableSize(coll)
end
table.insert(item_table, {
text = self:getCollectionTitle(name),
mandatory = mandatory,
name = name,
order = ReadCollection.coll_order[name],
})
end
if #item_table > 1 then
table.sort(item_table, function(v1, v2) return v1.order < v2.order end)
end
else
item_table = self.coll_list.item_table
end
local title = T(_("Collections (%1)"), #item_table)
local subtitle
if self.selected_colections then
local selected_nb = util.tableSize(self.selected_colections)
subtitle = self.selected_colections and T(_("Selected collections: %1"), selected_nb)
if do_init and selected_nb > 0 then -- show first collection containing the long-pressed book
for i, item in ipairs(item_table) do
if self.selected_colections[item.name] then
item_number = i
break
end
end
end
end
self.coll_list:switchItemTable(title, item_table, item_number or -1, nil, subtitle)
end
function FileManagerCollection:onCollListChoice(item)
if self._manager.selected_colections then
if item.mandatory == self._manager.checkmark then
self.item_table[item.idx].mandatory = " "
self._manager.selected_colections[item.name] = nil
else
self.item_table[item.idx].mandatory = self._manager.checkmark
self._manager.selected_colections[item.name] = true
end
self._manager:updateCollListItemTable()
else
self._manager:onShowColl(item.name)
end
end
function FileManagerCollection:onCollListHold(item)
if item.name == ReadCollection.default_collection_name -- Favorites non-editable
or self._manager.selected_colections then -- select mode
return
end
local button_dialog
local buttons = {
{
{
text = _("Remove collection"),
callback = function()
UIManager:close(button_dialog)
self._manager:removeCollection(item)
end
},
{
text = _("Rename collection"),
callback = function()
UIManager:close(button_dialog)
self._manager:renameCollection(item)
end
},
},
}
button_dialog = ButtonDialog:new{
title = item.text,
title_align = "center",
buttons = buttons,
}
UIManager:show(button_dialog)
return true
end
function FileManagerCollection:showCollListDialog(caller_callback, no_dialog)
if no_dialog then
caller_callback()
self.coll_list.close_callback(true)
return
end
local button_dialog, buttons
local new_collection_button = {
{
text = _("New collection"),
callback = function()
UIManager:close(button_dialog)
self:addCollection()
end,
},
}
if self.selected_colections then -- select mode
buttons = {
new_collection_button,
{}, -- separator
{
{
text = _("Deselect all"),
callback = function()
UIManager:close(button_dialog)
for name in pairs(self.selected_colections) do
self.selected_colections[name] = nil
end
self:updateCollListItemTable(true)
end,
},
{
text = _("Select all"),
callback = function()
UIManager:close(button_dialog)
for name in pairs(ReadCollection.coll) do
self.selected_colections[name] = true
end
self:updateCollListItemTable(true)
end,
},
},
{
{
text = _("Apply selection"),
callback = function()
UIManager:close(button_dialog)
caller_callback()
self.coll_list.close_callback(true)
end,
},
},
}
else
buttons = {
new_collection_button,
{
{
text = _("Arrange collections"),
callback = function()
UIManager:close(button_dialog)
self:sortCollections()
end,
},
},
}
end
button_dialog = ButtonDialog:new{
buttons = buttons,
}
UIManager:show(button_dialog)
end
function FileManagerCollection:editCollectionName(editCallback, old_name)
local input_dialog
input_dialog = InputDialog:new{
title = _("Enter collection name"),
input = old_name,
input_hint = old_name,
buttons = {{
{
text = _("Cancel"),
id = "close",
callback = function()
UIManager:close(input_dialog)
end,
},
{
text = _("Save"),
callback = function()
local new_name = input_dialog:getInputText()
if new_name == "" or new_name == old_name then return end
if ReadCollection.coll[new_name] then
UIManager:show(InfoMessage:new{
text = T(_("Collection already exists: %1"), new_name),
})
else
UIManager:close(input_dialog)
editCallback(new_name)
end
end,
},
}},
}
UIManager:show(input_dialog)
input_dialog:onShowKeyboard()
end
function FileManagerCollection:addCollection()
local editCallback = function(name)
ReadCollection:addCollection(name)
local mandatory
if self.selected_colections then
self.selected_colections[name] = true
mandatory = self.checkmark
else
mandatory = 0
end
table.insert(self.coll_list.item_table, {
text = name,
mandatory = mandatory,
name = name,
order = ReadCollection.coll_order[name],
})
self:updateCollListItemTable(false, #self.coll_list.item_table) -- show added item
end
self:editCollectionName(editCallback)
end
function FileManagerCollection:renameCollection(item)
local editCallback = function(name)
ReadCollection:renameCollection(item.name, name)
self.coll_list.item_table[item.idx].text = name
self.coll_list.item_table[item.idx].name = name
self:updateCollListItemTable()
end
self:editCollectionName(editCallback, item.name)
end
function FileManagerCollection:removeCollection(item)
UIManager:show(ConfirmBox:new{
text = _("Remove collection?") .. "\n\n" .. item.text,
ok_text = _("Remove"),
ok_callback = function()
ReadCollection:removeCollection(item.name)
table.remove(self.coll_list.item_table, item.idx)
self:updateCollListItemTable()
end,
})
end
function FileManagerCollection:sortCollections()
local sort_widget
sort_widget = SortWidget:new{
title = _("Arrange collections"),
item_table = util.tableDeepCopy(self.coll_list.item_table),
callback = function()
ReadCollection:updateCollectionListOrder(sort_widget.item_table)
self:updateCollListItemTable(true) -- init
end,
}
UIManager:show(sort_widget)
end
-- external
function FileManagerCollection:genAddToCollectionButton(file_or_files, caller_pre_callback, caller_post_callback, button_disabled)
return {
text = _("Add to collection"),
enabled = not button_disabled,
callback = function()
if caller_pre_callback then
caller_pre_callback()
end
local caller_callback = function()
if type(file_or_files) == "string" then
ReadCollection:addRemoveItemMultiple(file_or_files, self.selected_colections)
else -- selected files
ReadCollection:addItemsMultiple(file_or_files, self.selected_colections)
end
if caller_post_callback then
caller_post_callback()
end
end
self:onShowCollList(file_or_files, caller_callback)
end,
}
end
return FileManagerCollection

@ -265,7 +265,7 @@ function FileSearcher:onMenuSelect(item)
table.insert(buttons, {}) -- separator
table.insert(buttons, {
filemanagerutil.genResetSettingsButton(file, close_dialog_callback, is_currently_opened),
filemanagerutil.genAddRemoveFavoritesButton(file, close_dialog_callback),
self.ui.collections:genAddToCollectionButton(file, close_dialog_callback),
})
end
table.insert(buttons, {

@ -6,6 +6,7 @@ local DocSettings = require("docsettings")
local FileManagerBookInfo = require("apps/filemanager/filemanagerbookinfo")
local InputDialog = require("ui/widget/inputdialog")
local Menu = require("ui/widget/menu")
local ReadCollection = require("readcollection")
local UIManager = require("ui/uimanager")
local WidgetContainer = require("ui/widget/container/widgetcontainer")
local Screen = require("device").screen
@ -79,6 +80,8 @@ function FileManagerHistory:updateItemTable()
local subtitle = ""
if self.search_string then
subtitle = T(_("Search results (%1)"), #item_table)
elseif self.selected_colections then
subtitle = T(_("Filtered by collections (%1)"), #item_table)
elseif self.filter ~= "all" then
subtitle = T(_("Status: %1 (%2)"), filter_text[self.filter]:lower(), #item_table)
end
@ -101,6 +104,13 @@ function FileManagerHistory:isItemMatch(item)
end
end
end
if self.selected_colections then
for name in pairs(self.selected_colections) do
if not ReadCollection:isFileInCollection(item.file, name) then
return false
end
end
end
return self.filter == "all" or item.status == self.filter
end
@ -168,7 +178,7 @@ function FileManagerHistory:onMenuHold(item)
end
table.insert(buttons, {
filemanagerutil.genResetSettingsButton(doc_settings_or_file, close_dialog_update_callback, is_currently_opened),
filemanagerutil.genAddRemoveFavoritesButton(file, close_dialog_callback, item.dim),
self._manager.ui.collections:genAddToCollectionButton(file, close_dialog_callback, nil, item.dim),
})
table.insert(buttons, {
{
@ -251,6 +261,7 @@ function FileManagerHistory:onShowHist(search_info)
self.case_sensitive = search_info.case_sensitive
else
self.search_string = nil
self.selected_colections = nil
end
self.filter = G_reader_settings:readSetting("history_filter", "all")
self.is_frozen = G_reader_settings:isTrue("history_freeze_finished_books")
@ -289,6 +300,7 @@ function FileManagerHistory:showHistDialog()
self.filter = filter
if filter == "all" then -- reset all filters
self.search_string = nil
self.selected_colections = nil
end
self:updateItemTable()
end,
@ -304,6 +316,19 @@ function FileManagerHistory:showHistDialog()
genFilterButton("abandoned"),
genFilterButton("complete"),
})
table.insert(buttons, {
{
text = _("Filter by collections"),
callback = function()
UIManager:close(hist_dialog)
local caller_callback = function()
self.selected_colections = self.ui.collections.selected_colections
self:updateItemTable()
end
self.ui.collections:onShowCollList({}, caller_callback, true) -- do not select any, no dialog to apply
end,
},
})
table.insert(buttons, {
{
text = _("Search in filename and book metadata"),

@ -73,11 +73,18 @@ end
-- Purge doc settings except kept
function filemanagerutil.resetDocumentSettings(file)
local settings_to_keep = {
annotations = true,
annotations_paging = true,
annotations_rolling = true,
bookmarks = true,
bookmarks_paging = true,
bookmarks_rolling = true,
bookmarks_sorted_20220106 = true,
bookmarks_version = true,
cre_dom_version = true,
highlight = true,
highlight_paging = true,
highlight_rolling = true,
highlights_imported = true,
last_page = true,
last_xpointer = true,
@ -234,25 +241,6 @@ function filemanagerutil.genResetSettingsButton(doc_settings_or_file, caller_cal
}
end
function filemanagerutil.genAddRemoveFavoritesButton(file, caller_callback, button_disabled)
local ReadCollection = require("readcollection")
local has_file = ReadCollection:hasFile(file)
return {
text_func = function()
return has_file and _("Remove from favorites") or _("Add to favorites")
end,
enabled = not button_disabled,
callback = function()
caller_callback()
if has_file then
ReadCollection:removeItem(file)
else
ReadCollection:addItem(file)
end
end,
}
end
function filemanagerutil.genShowFolderButton(file, caller_callback, button_disabled)
return {
text = _("Show folder"),

@ -0,0 +1,425 @@
local WidgetContainer = require("ui/widget/container/widgetcontainer")
local logger = require("logger")
local _ = require("gettext")
local T = require("ffi/util").template
local ReaderAnnotation = WidgetContainer:extend{
annotations = nil, -- array sorted by annotation position order, ascending
}
-- build, read, save
function ReaderAnnotation:buildAnnotation(bm, highlights, init)
-- bm: associated single bookmark ; highlights: tables with all highlights
local note = bm.text
if note == "" then
note = nil
end
local chapter = bm.chapter
local hl, pageno = self:getHighlightByDatetime(highlights, bm.datetime)
if init then
if note and self.ui.bookmark:isBookmarkAutoText(bm) then
note = nil
end
if chapter == nil then
chapter = self.ui.toc:getTocTitleByPage(bm.page)
end
pageno = self.ui.paging and bm.page or self.document:getPageFromXPointer(bm.page)
end
if not hl then -- page bookmark or orphaned bookmark
hl = {}
if bm.highlighted then -- orphaned bookmark
hl.drawer = self.view.highlight.saved_drawer
hl.color = self.view.highlight.saved_color
if self.ui.paging then
if bm.pos0.page == bm.pos1.page then
hl.pboxes = self.document:getPageBoxesFromPositions(bm.page, bm.pos0, bm.pos1)
else -- multi-page highlight, restore the first box only
hl.pboxes = self.document:getPageBoxesFromPositions(bm.page, bm.pos0, bm.pos0)
end
end
end
end
if self.ui.paging then
-- old single-page reflow highlights do not have page in position
if not bm.pos0.page then
bm.pos0.page = bm.page
bm.pos1.page = bm.page
end
end
return { -- annotation
datetime = bm.datetime, -- creation time, not changeable
drawer = hl.drawer, -- highlight drawer
color = hl.color, -- highlight color
text = bm.notes, -- highlighted text, editable
text_edited = hl.edited, -- true if highlighted text has been edited
note = note, -- user's note, editable
chapter = chapter, -- book chapter title
pageno = pageno, -- book page number
page = bm.page, -- highlight location, xPointer or number (pdf)
pos0 = bm.pos0, -- highlight start position, xPointer (== page) or table (pdf)
pos1 = bm.pos1, -- highlight end position, xPointer or table (pdf)
pboxes = hl.pboxes, -- pdf pboxes, used only and changeable by addMarkupAnnotation
ext = hl.ext, -- pdf multi-page highlight
}
end
function ReaderAnnotation:getHighlightByDatetime(highlights, datetime)
for pageno, page_highlights in pairs(highlights) do
for _, highlight in ipairs(page_highlights) do
if highlight.datetime == datetime then
return highlight, pageno
end
end
end
end
function ReaderAnnotation:getAnnotationsFromBookmarksHighlights(bookmarks, highlights, init)
local annotations = {}
for i = #bookmarks, 1, -1 do
table.insert(annotations, self:buildAnnotation(bookmarks[i], highlights, init))
end
if init then
self:sortItems(annotations)
end
return annotations
end
function ReaderAnnotation:onReadSettings(config)
local annotations = config:readSetting("annotations")
if annotations then
-- KOHighlights may set this key when it has merged annotations from different sources:
-- we want to make sure they are updated and sorted
local needs_update = config:isTrue("annotations_externally_modified")
local needs_sort -- if incompatible annotations were built of old highlights/bookmarks
-- Annotation formats in crengine and mupdf are incompatible.
local has_annotations = #annotations > 0
local annotations_type = has_annotations and type(annotations[1].page)
if self.ui.rolling and annotations_type ~= "string" then -- incompatible format loaded, or empty
if has_annotations then -- backup incompatible format if not empty
config:saveSetting("annotations_paging", annotations)
end
-- load compatible format
annotations = config:readSetting("annotations_rolling") or {}
config:delSetting("annotations_rolling")
needs_sort = true
elseif self.ui.paging and annotations_type ~= "number" then
if has_annotations then
config:saveSetting("annotations_rolling", annotations)
end
annotations = config:readSetting("annotations_paging") or {}
config:delSetting("annotations_paging")
needs_sort = true
end
self.annotations = annotations
if needs_update or needs_sort then
if self.ui.rolling then
self.ui:registerPostInitCallback(function()
self:updatedAnnotations(needs_update, needs_sort)
end)
else
self:updatedAnnotations(needs_update, needs_sort)
end
config:delSetting("annotations_externally_modified")
end
else -- first run
if self.ui.rolling then
self.ui:registerPostInitCallback(function()
self:migrateToAnnotations(config)
end)
else
self:migrateToAnnotations(config)
end
end
end
function ReaderAnnotation:migrateToAnnotations(config)
local bookmarks = config:readSetting("bookmarks") or {}
local highlights = config:readSetting("highlight") or {}
if config:hasNot("highlights_imported") then
-- before 2014, saved highlights were not added to bookmarks when they were created.
for page, hls in pairs(highlights) do
for _, hl in ipairs(hls) do
local hl_page = self.ui.paging and page or hl.pos0
-- highlights saved by some old versions don't have pos0 field
-- we just ignore those highlights
if hl_page then
local item = {
datetime = hl.datetime,
highlighted = true,
notes = hl.text,
page = hl_page,
pos0 = hl.pos0,
pos1 = hl.pos1,
}
if self.ui.paging then
item.pos0.page = page
item.pos1.page = page
end
table.insert(bookmarks, item)
end
end
end
end
-- Bookmarks/highlights formats in crengine and mupdf are incompatible.
local has_bookmarks = #bookmarks > 0
local bookmarks_type = has_bookmarks and type(bookmarks[1].page)
if self.ui.rolling then
if bookmarks_type == "string" then -- compatible format loaded, check for incompatible old backup
if config:has("bookmarks_paging") then -- save incompatible old backup
local bookmarks_paging = config:readSetting("bookmarks_paging")
local highlights_paging = config:readSetting("highlight_paging")
local annotations = self:getAnnotationsFromBookmarksHighlights(bookmarks_paging, highlights_paging)
config:saveSetting("annotations_paging", annotations)
config:delSetting("bookmarks_paging")
config:delSetting("highlight_paging")
end
else -- incompatible format loaded, or empty
if has_bookmarks then -- save incompatible format if not empty
local annotations = self:getAnnotationsFromBookmarksHighlights(bookmarks, highlights)
config:saveSetting("annotations_paging", annotations)
end
-- load compatible format
bookmarks = config:readSetting("bookmarks_rolling") or {}
highlights = config:readSetting("highlight_rolling") or {}
config:delSetting("bookmarks_rolling")
config:delSetting("highlight_rolling")
end
else -- self.ui.paging
if bookmarks_type == "number" then
if config:has("bookmarks_rolling") then
local bookmarks_rolling = config:readSetting("bookmarks_rolling")
local highlights_rolling = config:readSetting("highlight_rolling")
local annotations = self:getAnnotationsFromBookmarksHighlights(bookmarks_rolling, highlights_rolling)
config:saveSetting("annotations_rolling", annotations)
config:delSetting("bookmarks_rolling")
config:delSetting("highlight_rolling")
end
else
if has_bookmarks then
local annotations = self:getAnnotationsFromBookmarksHighlights(bookmarks, highlights)
config:saveSetting("annotations_rolling", annotations)
end
bookmarks = config:readSetting("bookmarks_paging") or {}
highlights = config:readSetting("highlight_paging") or {}
config:delSetting("bookmarks_paging")
config:delSetting("highlight_paging")
end
end
self.annotations = self:getAnnotationsFromBookmarksHighlights(bookmarks, highlights, true)
end
function ReaderAnnotation:onDocumentRerendered()
self.needs_update = true
end
function ReaderAnnotation:onCloseDocument()
self:updatePageNumbers()
end
function ReaderAnnotation:onSaveSettings()
self:updatePageNumbers()
self.ui.doc_settings:saveSetting("annotations", self.annotations)
end
-- items handling
function ReaderAnnotation:updatePageNumbers()
if self.needs_update and self.ui.rolling then -- triggered by ReaderRolling on document layout change
for _, item in ipairs(self.annotations) do
item.pageno = self.document:getPageFromXPointer(item.page)
end
end
self.needs_update = nil
end
function ReaderAnnotation:sortItems(items)
if #items > 1 then
local sort_func = self.ui.rolling and function(a, b) return self:isItemInPositionOrderRolling(a, b) end
or function(a, b) return self:isItemInPositionOrderPaging(a, b) end
table.sort(items, sort_func)
end
end
function ReaderAnnotation:updatedAnnotations(needs_update, needs_sort)
if needs_update then
self.needs_update = true
self:updatePageNumbers()
needs_sort = true
end
if needs_sort then
self:sortItems(self.annotations)
end
end
function ReaderAnnotation:updateItemByXPointer(item)
-- called by ReaderRolling:checkXPointersAndProposeDOMVersionUpgrade()
local chapter = self.ui.toc:getTocTitleByPage(item.page)
if chapter == "" then
chapter = nil
end
if not item.drawer then -- page bookmark
item.text = chapter and T(_("in %1"), chapter) or nil
end
item.chapter = chapter
item.pageno = self.document:getPageFromXPointer(item.page)
end
function ReaderAnnotation:isItemInPositionOrderRolling(a, b)
local a_page = self.document:getPageFromXPointer(a.page)
local b_page = self.document:getPageFromXPointer(b.page)
if a_page == b_page then -- both items in the same page
if a.drawer and b.drawer then -- both items are highlights, compare positions
local compare_xp = self.document:compareXPointers(a.page, b.page)
if compare_xp then
if compare_xp == 0 then -- both highlights with the same start, compare ends
compare_xp = self.document:compareXPointers(a.pos1, b.pos1)
if compare_xp then
return compare_xp > 0
end
logger.warn("Invalid xpointer in highlight:", a.pos1, b.pos1)
return true
end
return compare_xp > 0
end
-- if compare_xp is nil, some xpointer is invalid and "a" will be sorted first to page 1
logger.warn("Invalid xpointer in highlight:", a.page, b.page)
return true
end
return not a.drawer -- have page bookmarks before highlights
end
return a_page < b_page
end
function ReaderAnnotation:isItemInPositionOrderPaging(a, b)
if a.page == b.page then -- both items in the same page
if a.drawer and b.drawer then -- both items are highlights, compare positions
local is_reflow = self.document.configurable.text_wrap -- save reflow mode
self.document.configurable.text_wrap = 0 -- native positions
-- sort start and end positions of each highlight
local a_start, a_end, b_start, b_end, result
if self.document:comparePositions(a.pos0, a.pos1) > 0 then
a_start, a_end = a.pos0, a.pos1
else
a_start, a_end = a.pos1, a.pos0
end
if self.document:comparePositions(b.pos0, b.pos1) > 0 then
b_start, b_end = b.pos0, b.pos1
else
b_start, b_end = b.pos1, b.pos0
end
-- compare start positions
local compare_pos = self.document:comparePositions(a_start, b_start)
if compare_pos == 0 then -- both highlights with the same start, compare ends
result = self.document:comparePositions(a_end, b_end) > 0
else
result = compare_pos > 0
end
self.document.configurable.text_wrap = is_reflow -- restore reflow mode
return result
end
return not a.drawer -- have page bookmarks before highlights
end
return a.page < b.page
end
function ReaderAnnotation:getItemIndex(item, no_binary)
local doesMatch
if item.datetime then
doesMatch = function(a, b)
return a.datetime == b.datetime
end
else
if self.ui.rolling then
doesMatch = function(a, b)
if a.text ~= b.text or a.pos0 ~= b.pos0 or a.pos1 ~= b.pos1 then
return false
end
return true
end
else
doesMatch = function(a, b)
if a.text ~= b.text or a.pos0.page ~= b.pos0.page
or a.pos0.x ~= b.pos0.x or a.pos1.x ~= b.pos1.x
or a.pos0.y ~= b.pos0.y or a.pos1.y ~= b.pos1.y then
return false
end
return true
end
end
end
if not no_binary then
local isInOrder = self.ui.rolling and self.isItemInPositionOrderRolling or self.isItemInPositionOrderPaging
local _start, _end, _middle = 1, #self.annotations
while _start <= _end do
_middle = bit.rshift(_start + _end, 1)
local v = self.annotations[_middle]
if doesMatch(item, v) then
return _middle
elseif isInOrder(self, item, v) then
_end = _middle - 1
else
_start = _middle + 1
end
end
end
for i, v in ipairs(self.annotations) do
if doesMatch(item, v) then
return i
end
end
end
function ReaderAnnotation:getInsertionIndex(item)
local isInOrder = self.ui.rolling and self.isItemInPositionOrderRolling or self.isItemInPositionOrderPaging
local _start, _end, _middle, direction = 1, #self.annotations, 1, 0
while _start <= _end do
_middle = bit.rshift(_start + _end, 1)
if isInOrder(self, item, self.annotations[_middle]) then
_end, direction = _middle - 1, 0
else
_start, direction = _middle + 1, 1
end
end
return _middle + direction
end
function ReaderAnnotation:addItem(item)
item.datetime = os.date("%Y-%m-%d %H:%M:%S")
item.pageno = self.ui.paging and item.page or self.document:getPageFromXPointer(item.page)
local index = self:getInsertionIndex(item)
table.insert(self.annotations, index, item)
return index
end
-- info
function ReaderAnnotation:hasAnnotations()
return #self.annotations > 0
end
function ReaderAnnotation:getNumberOfAnnotations()
return #self.annotations
end
function ReaderAnnotation:getNumberOfHighlightsAndNotes() -- for Statistics plugin
local highlights = 0
local notes = 0
for _, item in ipairs(self.annotations) do
if item.drawer then
if item.note then
notes = notes + 1
else
highlights = highlights + 1
end
end
end
return highlights, notes
end
return ReaderAnnotation

File diff suppressed because it is too large Load Diff

@ -219,7 +219,7 @@ local footerTextGeneratorMap = {
bookmark_count = function(footer)
local symbol_type = footer.settings.item_prefix
local prefix = symbol_prefix[symbol_type].bookmark_count
local bookmark_count = footer.ui.bookmark:getNumberOfBookmarks()
local bookmark_count = footer.ui.annotation:getNumberOfAnnotations()
if footer.settings.all_at_once and footer.settings.hide_empty_generators and bookmark_count == 0 then
return ""
end

@ -7,6 +7,7 @@ local Geom = require("ui/geometry")
local InfoMessage = require("ui/widget/infomessage")
local InputContainer = require("ui/widget/container/inputcontainer")
local Notification = require("ui/widget/notification")
local RadioButtonWidget = require("ui/widget/radiobuttonwidget")
local TextViewer = require("ui/widget/textviewer")
local Translator = require("ui/translator")
local UIManager = require("ui/uimanager")
@ -73,11 +74,11 @@ function ReaderHighlight:init()
["02_highlight"] = function(this)
return {
text = _("Highlight"),
enabled = this.hold_pos ~= nil,
callback = function()
this:saveHighlight(true)
this:onClose()
end,
enabled = this.hold_pos ~= nil,
}
end,
["03_copy"] = function(this)
@ -96,11 +97,11 @@ function ReaderHighlight:init()
["04_add_note"] = function(this)
return {
text = _("Add note"),
enabled = this.hold_pos ~= nil,
callback = function()
this:addNote()
this:onClose()
end,
enabled = this.hold_pos ~= nil,
}
end,
-- then information lookup functions, putting on the left those that
@ -127,11 +128,11 @@ function ReaderHighlight:init()
end,
}
end,
["07_translate"] = function(this, page, index)
["07_translate"] = function(this, index)
return {
text = _("Translate"),
callback = function()
this:translate(this.selected_text, page, index)
this:translate(index)
-- We don't call this:onClose(), so one can still see
-- the highlighted text when moving the translated
-- text window, and also if NetworkMgr:promptWifiOn()
@ -429,7 +430,7 @@ function ReaderHighlight:addToMainMenu(menu_items)
self.view.highlight.lighten_factor = spin.value
UIManager:setDirty(self.dialog, "ui")
if touchmenu_instance then touchmenu_instance:updateItems() end
end
end,
}
UIManager:show(spin_widget)
end,
@ -455,7 +456,7 @@ function ReaderHighlight:addToMainMenu(menu_items)
},
})
end
UIManager:show(require("ui/widget/radiobuttonwidget"):new{
UIManager:show(RadioButtonWidget:new{
title_text = _("Note marker"),
width_factor = 0.5,
keep_shown_on_apply = true,
@ -475,7 +476,7 @@ function ReaderHighlight:addToMainMenu(menu_items)
})
end,
})
if self.document.info.has_pages then
if self.ui.paging then
menu_items.panel_zoom_options = {
text = _("Panel zoom (manga/comic)"),
sub_item_table = self:genPanelZoomMenu(),
@ -563,7 +564,7 @@ function ReaderHighlight:addToMainMenu(menu_items)
callback = function(spin)
G_reader_settings:saveSetting("highlight_long_hold_threshold_s", spin.value)
if touchmenu_instance then touchmenu_instance:updateItems() end
end
end,
}
UIManager:show(items)
end,
@ -671,6 +672,7 @@ function ReaderHighlight:clear(clear_id)
end
self.is_word_selection = false
self.selected_text_start_xpointer = nil
self.gest_pos = nil
if self.hold_pos then
self.hold_pos = nil
self.selected_text = nil
@ -693,8 +695,8 @@ function ReaderHighlight:onTapSelectModeIcon()
cancel_text = _("Close"),
ok_callback = function()
self.select_mode = false
self:deleteHighlight(self.highlight_page, self.highlight_idx)
end
self:deleteHighlight(self.highlight_idx)
end,
})
return true
end
@ -706,7 +708,7 @@ function ReaderHighlight:onTap(_, ges)
-- ReaderHighlight:clear can only return true if self.hold_pos was set anyway.
local cleared = self.hold_pos and self:clear()
-- We only care about potential taps on existing highlights, not on taps that closed a highlight menu.
if not cleared and ges then
if not cleared and ges and self.ui.annotation:hasAnnotations() then
if self.ui.paging then
return self:onTapPageSavedHighlight(ges)
else
@ -720,31 +722,24 @@ function ReaderHighlight:onTapPageSavedHighlight(ges)
local pos = self.view:screenToPageTransform(ges.pos)
local highlights_tapped = {}
for _, page in ipairs(pages) do
local items = self.view:getPageSavedHighlights(page)
if items then
for i, item in ipairs(items) do
local boxes = self.ui.document:getPageBoxesFromPositions(page, item.pos0, item.pos1)
if boxes then
for _, box in ipairs(boxes) do
if inside_box(pos, box) then
logger.dbg("Tap on highlight")
local hl_page, hl_i
if item.parent then -- multi-page highlight
hl_page, hl_i = unpack(item.parent)
else
hl_page, hl_i = page, i
end
if self.select_mode then
if hl_page == self.highlight_page and hl_i == self.highlight_idx then
-- tap on the first fragment: abort select mode, clear highlight
self.select_mode = false
self:deleteHighlight(hl_page, hl_i)
return true
end
else
table.insert(highlights_tapped, {hl_page, hl_i})
break
local items = self:getPageSavedHighlights(page)
for i, item in ipairs(items) do
local boxes = self.ui.document:getPageBoxesFromPositions(page, item.pos0, item.pos1)
if boxes then
for __, box in ipairs(boxes) do
if inside_box(pos, box) then
logger.dbg("Tap on highlight")
local hl_i = item.parent or i -- parent exists in multi-page highlight only
if self.select_mode then
if hl_i == self.highlight_idx then
-- tap on the first fragment: abort select mode, clear highlight
self.select_mode = false
self:deleteHighlight(hl_i)
return true
end
else
table.insert(highlights_tapped, hl_i)
break
end
end
end
@ -761,7 +756,6 @@ function ReaderHighlight:onTapXPointerSavedHighlight(ges)
-- showing menu...). We might want to cache these boxes per page (and
-- clear that cache when page layout change or highlights are added
-- or removed).
local cur_view_top, cur_view_bottom
local pos = self.view:screenToPageTransform(ges.pos)
-- NOTE: By now, pos.page is set, but if a highlight spans across multiple pages,
-- it's stored under the hash of its *starting* point,
@ -770,46 +764,39 @@ function ReaderHighlight:onTapXPointerSavedHighlight(ges)
-- because pos.page isn't super accurate in continuous mode
-- (it's the page number for what's it the topleft corner of the screen,
-- i.e., often a bit earlier)...
-- Even in page mode, it's safer to use pos and ui.dimen.h
-- than pages' xpointers pos, even if ui.dimen.h is a bit
-- larger than pages' heights
local cur_view_top = self.document:getCurrentPos()
local cur_view_bottom
if self.view.view_mode == "page" and self.document:getVisiblePageCount() > 1 then
cur_view_bottom = cur_view_top + 2 * self.ui.dimen.h
else
cur_view_bottom = cur_view_top + self.ui.dimen.h
end
local highlights_tapped = {}
for page, items in pairs(self.view.highlight.saved) do
if items then
for i = 1, #items do
local item = items[i]
local pos0, pos1 = item.pos0, item.pos1
-- document:getScreenBoxesFromPositions() is expensive, so we
-- first check this item is on current page
if not cur_view_top then
-- Even in page mode, it's safer to use pos and ui.dimen.h
-- than pages' xpointers pos, even if ui.dimen.h is a bit
-- larger than pages' heights
cur_view_top = self.ui.document:getCurrentPos()
if self.view.view_mode == "page" and self.ui.document:getVisiblePageCount() > 1 then
cur_view_bottom = cur_view_top + 2 * self.ui.dimen.h
else
cur_view_bottom = cur_view_top + self.ui.dimen.h
end
end
local spos0 = self.ui.document:getPosFromXPointer(pos0)
local spos1 = self.ui.document:getPosFromXPointer(pos1)
local start_pos = math.min(spos0, spos1)
local end_pos = math.max(spos0, spos1)
if start_pos <= cur_view_bottom and end_pos >= cur_view_top then
local boxes = self.ui.document:getScreenBoxesFromPositions(pos0, pos1, true) -- get_segments=true
if boxes then
for index, box in pairs(boxes) do
if inside_box(pos, box) then
logger.dbg("Tap on highlight")
if self.select_mode then
if page == self.highlight_page and i == self.highlight_idx then
-- tap on the first fragment: abort select mode, clear highlight
self.select_mode = false
self:deleteHighlight(page, i)
return true
end
else
table.insert(highlights_tapped, {page, i})
break
for hl_i, item in ipairs(self.ui.annotation.annotations) do
if item.drawer then
-- document:getScreenBoxesFromPositions() is expensive, so we
-- first check this item is on current page
local start_pos = self.document:getPosFromXPointer(item.pos0)
local end_pos = self.document:getPosFromXPointer(item.pos1)
if start_pos <= cur_view_bottom and end_pos >= cur_view_top then
local boxes = self.ui.document:getScreenBoxesFromPositions(item.pos0, item.pos1, true) -- get_segments=true
if boxes then
for _, box in ipairs(boxes) do
if inside_box(pos, box) then
logger.dbg("Tap on highlight")
if self.select_mode then
if hl_i == self.highlight_idx then
-- tap on the first fragment: abort select mode, clear highlight
self.select_mode = false
self:deleteHighlight(hl_i)
return true
end
else
table.insert(highlights_tapped, hl_i)
break
end
end
end
@ -822,10 +809,9 @@ function ReaderHighlight:onTapXPointerSavedHighlight(ges)
end
end
function ReaderHighlight:updateHighlight(page, index, side, direction, move_by_char)
if self.ui.paging then return end
local highlight = self.view.highlight.saved[page][index]
local highlight_time = highlight.datetime
function ReaderHighlight:updateHighlight(index, side, direction, move_by_char)
local highlight = self.ui.annotation.annotations[index]
local highlight_before = util.tableDeepCopy(highlight)
local highlight_beginning = highlight.pos0
local highlight_end = highlight.pos1
if side == 0 then -- we move pos0
@ -846,7 +832,10 @@ function ReaderHighlight:updateHighlight(page, index, side, direction, move_by_c
if updated_highlight_beginning then
local order = self.ui.document:compareXPointers(updated_highlight_beginning, highlight_end)
if order and order > 0 then -- only if beginning did not go past end
self.view.highlight.saved[page][index].pos0 = updated_highlight_beginning
highlight.pos0 = updated_highlight_beginning
highlight.page = updated_highlight_beginning
highlight.chapter = self.ui.toc:getTocTitleByPage(updated_highlight_beginning)
highlight.pageno = self.document:getPageFromXPointer(updated_highlight_beginning)
end
end
else -- we move pos1
@ -867,23 +856,16 @@ function ReaderHighlight:updateHighlight(page, index, side, direction, move_by_c
if updated_highlight_end then
local order = self.ui.document:compareXPointers(highlight_beginning, updated_highlight_end)
if order and order > 0 then -- only if end did not go back past beginning
self.view.highlight.saved[page][index].pos1 = updated_highlight_end
highlight.pos1 = updated_highlight_end
end
end
end
local new_beginning = self.view.highlight.saved[page][index].pos0
local new_end = self.view.highlight.saved[page][index].pos1
local new_beginning = highlight.pos0
local new_end = highlight.pos1
local new_text = self.ui.document:getTextFromXPointers(new_beginning, new_end)
local new_chapter = self.ui.toc:getTocTitleByPage(new_beginning)
self.view.highlight.saved[page][index].text = cleanupSelectedText(new_text)
self.view.highlight.saved[page][index].chapter = new_chapter
local new_highlight = self.view.highlight.saved[page][index]
self.ui.bookmark:updateBookmark({
page = highlight_beginning,
datetime = highlight_time,
updated_highlight = new_highlight
})
highlight.text = cleanupSelectedText(new_text)
self.ui:handleEvent(Event:new("AnnotationsModified", { highlight, highlight_before }))
if side == 0 then
-- Ensure we show the page with the new beginning of highlight
if not self.ui.document:isXPointerInCurrentPage(new_beginning) then
@ -909,20 +891,15 @@ end
function ReaderHighlight:showChooseHighlightDialog(highlights)
if #highlights == 1 then
local page, index = unpack(highlights[1])
local item = self.view.highlight.saved[page][index]
local bookmark_note = self.ui.bookmark:getBookmarkNote({datetime = item.datetime})
self:showHighlightNoteOrDialog(page, index, bookmark_note)
self:showHighlightNoteOrDialog(highlights[1])
else -- overlapped highlights
local dialog
local buttons = {}
for i, v in ipairs(highlights) do
local page, index = unpack(v)
local item = self.view.highlight.saved[page][index]
local bookmark_note = self.ui.bookmark:getBookmarkNote({datetime = item.datetime})
for i, index in ipairs(highlights) do
local item = self.ui.annotation.annotations[index]
buttons[i] = {{
text = (bookmark_note and self.ui.bookmark.display_prefix["note"]
or self.ui.bookmark.display_prefix["highlight"]) .. item.text,
text = (item.note and self.ui.bookmark.display_prefix["note"]
or self.ui.bookmark.display_prefix["highlight"]) .. item.text,
align = "left",
avoid_text_truncation = false,
font_face = "smallinfofont",
@ -930,7 +907,7 @@ function ReaderHighlight:showChooseHighlightDialog(highlights)
font_bold = false,
callback = function()
UIManager:close(dialog)
self:showHighlightNoteOrDialog(page, index, bookmark_note)
self:showHighlightNoteOrDialog(index)
end,
}}
end
@ -942,7 +919,8 @@ function ReaderHighlight:showChooseHighlightDialog(highlights)
return true
end
function ReaderHighlight:showHighlightNoteOrDialog(page, index, bookmark_note)
function ReaderHighlight:showHighlightNoteOrDialog(index)
local bookmark_note = self.ui.annotation.annotations[index].note
if bookmark_note then
local textviewer
textviewer = TextViewer:new{
@ -957,14 +935,14 @@ function ReaderHighlight:showHighlightNoteOrDialog(page, index, bookmark_note)
text = _("Delete highlight"),
callback = function()
UIManager:close(textviewer)
self:deleteHighlight(page, index)
self:deleteHighlight(index)
end,
},
{
text = _("Edit highlight"),
callback = function()
UIManager:close(textviewer)
self:onShowHighlightDialog(page, index, false)
self:onShowHighlightDialog(index)
end,
},
},
@ -972,17 +950,18 @@ function ReaderHighlight:showHighlightNoteOrDialog(page, index, bookmark_note)
}
UIManager:show(textviewer)
else
self:onShowHighlightDialog(page, index, true)
self:onShowHighlightDialog(index)
end
end
function ReaderHighlight:onShowHighlightDialog(page, index, is_auto_text)
function ReaderHighlight:onShowHighlightDialog(index)
local item = self.ui.annotation.annotations[index]
local buttons = {
{
{
text = _("Delete"),
callback = function()
self:deleteHighlight(page, index)
self:deleteHighlight(index)
UIManager:close(self.edit_highlight_dialog)
self.edit_highlight_dialog = nil
end,
@ -990,15 +969,15 @@ function ReaderHighlight:onShowHighlightDialog(page, index, is_auto_text)
{
text = C_("Highlight", "Style"),
callback = function()
self:editHighlightStyle(page, index)
self:editHighlightStyle(index)
UIManager:close(self.edit_highlight_dialog)
self.edit_highlight_dialog = nil
end,
},
{
text = is_auto_text and _("Add note") or _("Edit note"),
text = item.note and _("Edit note") or _("Add note"),
callback = function()
self:editHighlight(page, index)
self:editHighlight(index)
UIManager:close(self.edit_highlight_dialog)
self.edit_highlight_dialog = nil
end,
@ -1006,8 +985,8 @@ function ReaderHighlight:onShowHighlightDialog(page, index, is_auto_text)
{
text = "",
callback = function()
self.selected_text = self.view.highlight.saved[page][index]
self:onShowHighlightMenu(page, index)
self.selected_text = util.tableDeepCopy(item)
self:onShowHighlightMenu(index)
UIManager:close(self.edit_highlight_dialog)
self.edit_highlight_dialog = nil
end,
@ -1016,7 +995,7 @@ function ReaderHighlight:onShowHighlightDialog(page, index, is_auto_text)
}
if self.ui.rolling then
local enabled = not self.view.highlight.saved[page][index].edited
local enabled = not item.text_edited
local start_prev = "◁▒▒"
local start_next = "▷▒▒"
local end_prev = "▒▒◁"
@ -1031,10 +1010,10 @@ function ReaderHighlight:onShowHighlightDialog(page, index, is_auto_text)
text = start_prev,
enabled = enabled,
callback = function()
self:updateHighlight(page, index, 0, -1, false)
self:updateHighlight(index, 0, -1, false)
end,
hold_callback = function()
self:updateHighlight(page, index, 0, -1, true)
self:updateHighlight(index, 0, -1, true)
return true
end
},
@ -1042,10 +1021,10 @@ function ReaderHighlight:onShowHighlightDialog(page, index, is_auto_text)
text = start_next,
enabled = enabled,
callback = function()
self:updateHighlight(page, index, 0, 1, false)
self:updateHighlight(index, 0, 1, false)
end,
hold_callback = function()
self:updateHighlight(page, index, 0, 1, true)
self:updateHighlight(index, 0, 1, true)
return true
end
},
@ -1053,26 +1032,26 @@ function ReaderHighlight:onShowHighlightDialog(page, index, is_auto_text)
text = end_prev,
enabled = enabled,
callback = function()
self:updateHighlight(page, index, 1, -1, false)
self:updateHighlight(index, 1, -1, false)
end,
hold_callback = function()
self:updateHighlight(page, index, 1, -1, true)
self:updateHighlight(index, 1, -1, true)
end
},
{
text = end_next,
enabled = enabled,
callback = function()
self:updateHighlight(page, index, 1, 1, false)
self:updateHighlight(index, 1, 1, false)
end,
hold_callback = function()
self:updateHighlight(page, index, 1, 1, true)
self:updateHighlight(index, 1, 1, true)
end
}
})
end
self.edit_highlight_dialog = ButtonDialog:new{
buttons = buttons
buttons = buttons,
}
UIManager:show(self.edit_highlight_dialog)
return true
@ -1090,7 +1069,7 @@ function ReaderHighlight:removeFromHighlightDialog(idx)
return button
end
function ReaderHighlight:onShowHighlightMenu(page, index)
function ReaderHighlight:onShowHighlightMenu(index)
if not self.selected_text then
return
end
@ -1099,7 +1078,7 @@ function ReaderHighlight:onShowHighlightMenu(page, index)
local columns = 2
for idx, fn_button in ffiUtil.orderedPairs(self._highlight_buttons) do
local button = fn_button(self, page, index)
local button = fn_button(self, index)
if not button.show_in_highlight_dialog_func or button.show_in_highlight_dialog_func() then
if #highlight_buttons[#highlight_buttons] >= columns then
table.insert(highlight_buttons, {})
@ -1198,12 +1177,12 @@ function ReaderHighlight:_resetHoldTimer(clear)
end
function ReaderHighlight:onTogglePanelZoomSetting(arg, ges)
if not self.document.info.has_pages then return end
if self.ui.rolling then return end
self.panel_zoom_enabled = not self.panel_zoom_enabled
end
function ReaderHighlight:onToggleFallbackTextSelection(arg, ges)
if not self.document.info.has_pages then return end
if self.ui.rolling then return end
self.panel_zoom_fallback_to_text_selection = not self.panel_zoom_fallback_to_text_selection
end
@ -1229,7 +1208,7 @@ function ReaderHighlight:onPanelZoom(arg, ges)
end
function ReaderHighlight:onHold(arg, ges)
if self.document.info.has_pages and self.panel_zoom_enabled then
if self.ui.paging and self.panel_zoom_enabled then
local res = self:onPanelZoom(arg, ges)
if res or not self.panel_zoom_fallback_to_text_selection then
return res
@ -1548,20 +1527,20 @@ function ReaderHighlight:viewSelectionHTML(debug_view, no_css_files_buttons)
end
end
function ReaderHighlight:translate(selected_text, page, index)
function ReaderHighlight:translate(index)
if self.ui.rolling then
-- Extend the selected text to include any punctuation at start or end,
-- which may give a better translation with the added context.
local extended_text = self.ui.document:extendXPointersToSentenceSegment(selected_text.pos0, selected_text.pos1)
local extended_text = self.ui.document:extendXPointersToSentenceSegment(self.selected_text.pos0, self.selected_text.pos1)
if extended_text then
selected_text = extended_text
self.selected_text = extended_text
end
end
if #selected_text.text > 0 then
self:onTranslateText(selected_text.text, page, index)
if #self.selected_text.text > 0 then
self:onTranslateText(self.selected_text.text, index)
-- or we will do OCR
elseif self.hold_pos then
local text = self.ui.document:getOCRText(self.hold_pos.page, selected_text)
local text = self.ui.document:getOCRText(self.hold_pos.page, self.selected_text)
logger.dbg("OCRed text:", text)
if text and text ~= "" then
self:onTranslateText(text)
@ -1572,14 +1551,9 @@ function ReaderHighlight:translate(selected_text, page, index)
end
end
end
dbg:guard(ReaderHighlight, "translate",
function(self, selected_text)
assert(selected_text ~= nil,
"translate must not be called with nil selected_text!")
end)
function ReaderHighlight:onTranslateText(text, page, index)
Translator:showTranslation(text, true, nil, nil, true, page, index)
function ReaderHighlight:onTranslateText(text, index)
Translator:showTranslation(text, true, nil, nil, true, index)
end
function ReaderHighlight:onTranslateCurrentPage()
@ -1661,7 +1635,7 @@ function ReaderHighlight:onHoldRelease()
self:addNote()
self:onClose()
elseif default_highlight_action == "translate" then
self:translate(self.selected_text)
self:translate()
elseif default_highlight_action == "wikipedia" then
self:lookupWikipedia()
self:onClose()
@ -1751,137 +1725,45 @@ function ReaderHighlight:highlightFromHoldPos()
end
end
function ReaderHighlight:onHighlight()
self:saveHighlight()
end
function ReaderHighlight:onUnhighlight(bookmark_item)
local page
local sel_text
local sel_pos0
local datetime
local idx
if bookmark_item then -- called from Bookmarks menu onHold
page = bookmark_item.page
sel_text = bookmark_item.notes
sel_pos0 = bookmark_item.pos0
datetime = bookmark_item.datetime
else -- called from DictQuickLookup Unhighlight button
--- @fixme: is this self.hold_pos access safe?
page = self.hold_pos.page
sel_text = cleanupSelectedText(self.selected_text.text)
sel_pos0 = self.selected_text.pos0
end
if self.ui.paging then -- We can safely use page
-- As we may have changed spaces and hyphens handling in the extracted
-- text over the years, check text identities with them removed
local sel_text_cleaned = sel_text:gsub("[ -]", ""):gsub("\u{00AD}", "")
for index = 1, #self.view.highlight.saved[page] do
local highlight = self.view.highlight.saved[page][index]
-- pos0 are tables and can't be compared directly, except when from
-- DictQuickLookup where these are the same object.
-- If bookmark_item provided, just check datetime
if ( (datetime == nil and highlight.pos0 == sel_pos0) or
(datetime ~= nil and highlight.datetime == datetime) ) then
if highlight.text:gsub("[ -]", ""):gsub("\u{00AD}", "") == sel_text_cleaned then
idx = index
break
end
end
end
else -- page is a xpointer
-- The original page could be found in bookmark_item.text, but
-- no more if it has been renamed: we need to loop through all
-- highlights on all page slots
for p, highlights in pairs(self.view.highlight.saved) do
for index = 1, #highlights do
local highlight = highlights[index]
-- pos0 are strings and can be compared directly
if highlight.text == sel_text and (
(datetime == nil and highlight.pos0 == sel_pos0) or
(datetime ~= nil and highlight.datetime == datetime)) then
page = p -- this is the original page slot
idx = index
break
end
end
if idx then
break
end
end
end
if idx then
logger.dbg("found highlight to delete on page", page, idx)
self:deleteHighlight(page, idx, bookmark_item)
return true
end
end
function ReaderHighlight:getHighlightByDatetime(datetime)
for page, highlights in pairs(self.view.highlight.saved) do
for _, highlight in ipairs(highlights) do
if highlight.datetime == datetime then
return highlight
end
end
end
end
function ReaderHighlight:getHighlightBookmarkItem()
function ReaderHighlight:saveHighlight(extend_to_sentence)
logger.dbg("save highlight")
if self.hold_pos and not self.selected_text then
self:highlightFromHoldPos()
end
if self.selected_text and self.selected_text.pos0 and self.selected_text.pos1 then
return {
page = self.ui.paging and self.selected_text.pos0.page or self.selected_text.pos0,
pos0 = self.selected_text.pos0,
pos1 = self.selected_text.pos1,
notes = cleanupSelectedText(self.selected_text.text),
highlighted = true,
}
end
end
function ReaderHighlight:saveHighlight(extend_to_sentence)
self.ui:handleEvent(Event:new("AddHighlight"))
logger.dbg("save highlight")
if self.selected_text and self.selected_text.pos0 and self.selected_text.pos1 then
if extend_to_sentence and self.ui.rolling then
local extended_text = self.ui.document:extendXPointersToSentenceSegment(self.selected_text.pos0, self.selected_text.pos1)
if extended_text then
self.selected_text = extended_text
local pg_or_xp
if self.ui.rolling then
if extend_to_sentence then
local extended_text = self.ui.document:extendXPointersToSentenceSegment(self.selected_text.pos0, self.selected_text.pos1)
if extended_text then
self.selected_text = extended_text
end
end
pg_or_xp = self.selected_text.pos0
else
pg_or_xp = self.selected_text.pos0.page
end
local page = self.ui.paging and self.selected_text.pos0.page or self.ui.document:getPageFromXPointer(self.selected_text.pos0)
if not self.view.highlight.saved[page] then
self.view.highlight.saved[page] = {}
end
local datetime = os.date("%Y-%m-%d %H:%M:%S")
local pg_or_xp = self.ui.paging and page or self.selected_text.pos0
local chapter_name = self.ui.toc:getTocTitleByPage(pg_or_xp)
local hl_item = {
datetime = datetime,
text = cleanupSelectedText(self.selected_text.text),
local item = {
page = self.ui.paging and self.selected_text.pos0.page or self.selected_text.pos0,
pos0 = self.selected_text.pos0,
pos1 = self.selected_text.pos1,
pboxes = self.selected_text.pboxes,
ext = self.selected_text.ext,
text = cleanupSelectedText(self.selected_text.text),
drawer = self.view.highlight.saved_drawer,
chapter = chapter_name,
chapter = self.ui.toc:getTocTitleByPage(pg_or_xp),
}
table.insert(self.view.highlight.saved[page], hl_item)
local bookmark_item = self:getHighlightBookmarkItem()
if bookmark_item then
bookmark_item.datetime = datetime
bookmark_item.chapter = chapter_name
self.ui.bookmark:addBookmark(bookmark_item)
if self.ui.paging then
item.pboxes = self.selected_text.pboxes
item.ext = self.selected_text.ext
self:writePdfAnnotation("save", item)
end
self:writePdfAnnotation("save", page, hl_item)
return page, #self.view.highlight.saved[page]
local index = self.ui.annotation:addItem(item)
self.view.footer:onUpdateFooter(self.view.footer_visible)
self.ui:handleEvent(Event:new("AnnotationsModified", { item, nb_highlights_added = 1 }))
return index
end
end
function ReaderHighlight:writePdfAnnotation(action, page, item, content)
function ReaderHighlight:writePdfAnnotation(action, item, content)
if self.ui.rolling or G_reader_settings:readSetting("save_document") == "disable" then
return
end
@ -1897,24 +1779,10 @@ function ReaderHighlight:writePdfAnnotation(action, page, item, content)
end
local can_write
if item.pos0.page == item.pos1.page then -- single-page highlight
local item_
if item.pboxes then
item_ = item
else -- called from bookmarks to write bookmark note to annotation
for _, hl in ipairs(self.view.highlight.saved[page]) do
if hl.datetime == item.datetime then
item_ = {pboxes = hl.pboxes}
break
end
end
end
can_write = doAction(action, page, item_, content)
can_write = doAction(action, item.pos0.page, item, content)
else -- multi-page highlight
local is_reflow = self.ui.document.configurable.text_wrap
for hl_page = item.pos0.page, item.pos1.page do
self.ui.document.configurable.text_wrap = 0
local hl_part = self:getSavedExtendedHighlightPage(item, hl_page)
self.ui.document.configurable.text_wrap = is_reflow
can_write = doAction(action, hl_page, hl_part, content)
if can_write == false then break end
if action == "save" then -- update pboxes from quadpoints
@ -1934,14 +1802,6 @@ If you wish your highlights to be saved in the document, just move it to a writa
end
end
function ReaderHighlight:addNote(text)
local page, index = self:saveHighlight(true)
if text then self:clear() end
self:editHighlight(page, index, true, text)
UIManager:close(self.edit_highlight_dialog)
self.edit_highlight_dialog = nil
end
function ReaderHighlight:lookupWikipedia()
if self.selected_text then
self.ui:handleEvent(Event:new("LookupWikipedia", cleanupSelectedText(self.selected_text.text)))
@ -1970,59 +1830,48 @@ function ReaderHighlight:onHighlightDictLookup()
end
end
function ReaderHighlight:deleteHighlight(page, i, bookmark_item)
logger.dbg("delete highlight", page, i)
-- The per-page table is a pure array
local removed = table.remove(self.view.highlight.saved[page], i)
-- But the main, outer table is a hash, so clear the table for this page if there are no longer any highlights on it
if #self.view.highlight.saved[page] == 0 then
self.view.highlight.saved[page] = nil
end
if bookmark_item then
self.ui.bookmark:removeBookmark(bookmark_item)
else
self.ui.bookmark:removeBookmark({
page = self.ui.paging and page or removed.pos0,
datetime = removed.datetime,
})
end
self:writePdfAnnotation("delete", page, removed)
function ReaderHighlight:deleteHighlight(index)
logger.dbg("delete highlight", index)
local item = self.ui.annotation.annotations[index]
self:writePdfAnnotation("delete", item)
self.ui.bookmark:removeItemByIndex(index)
UIManager:setDirty(self.dialog, "ui")
end
function ReaderHighlight:editHighlight(page, i, is_new_note, text)
local item = self.view.highlight.saved[page][i]
self.ui.bookmark:setBookmarkNote({
page = self.ui.paging and page or item.pos0,
datetime = item.datetime,
}, true, is_new_note, text)
function ReaderHighlight:addNote(text)
local index = self:saveHighlight(true)
if text then -- called from Translator to save translation to note
self:clear()
end
self:editHighlight(index, true, text)
UIManager:close(self.edit_highlight_dialog)
self.edit_highlight_dialog = nil
end
function ReaderHighlight:editHighlight(index, is_new_note, text)
self.ui.bookmark:setBookmarkNote(index, is_new_note, text)
end
function ReaderHighlight:editHighlightStyle(page, i)
local item = self.view.highlight.saved[page][i]
function ReaderHighlight:editHighlightStyle(index)
local item = self.ui.annotation.annotations[index]
local apply_drawer = function(drawer)
self:writePdfAnnotation("delete", page, item)
self:writePdfAnnotation("delete", item)
item.drawer = drawer
if self.ui.paging then
self:writePdfAnnotation("save", page, item)
local bm_note = self.ui.bookmark:getBookmarkNote(item)
if bm_note then
self:writePdfAnnotation("content", page, item, bm_note)
self:writePdfAnnotation("save", item)
if item.note then
self:writePdfAnnotation("content", item, item.note)
end
end
UIManager:setDirty(self.dialog, "ui")
self.ui:handleEvent(Event:new("BookmarkUpdated",
self.ui.bookmark:getBookmarkForHighlight({
page = self.ui.paging and page or item.pos0,
datetime = item.datetime,
})))
self.ui:handleEvent(Event:new("AnnotationsModified", { item }))
end
self:showHighlightStyleDialog(apply_drawer, item.drawer)
end
function ReaderHighlight:showHighlightStyleDialog(caller_callback, item_drawer)
local default_drawer, keep_shown_on_apply
if item_drawer then -- called from editHighlightStyle
if item_drawer then -- called from ReaderHighlight:editHighlightStyle()
default_drawer = self.view.highlight.saved_drawer or
G_reader_settings:readSetting("highlight_drawing_style", "lighten")
keep_shown_on_apply = true
@ -2037,7 +1886,6 @@ function ReaderHighlight:showHighlightStyleDialog(caller_callback, item_drawer)
},
})
end
local RadioButtonWidget = require("ui/widget/radiobuttonwidget")
UIManager:show(RadioButtonWidget:new{
title_text = _("Highlight style"),
width_factor = 0.5,
@ -2051,14 +1899,14 @@ function ReaderHighlight:showHighlightStyleDialog(caller_callback, item_drawer)
end
function ReaderHighlight:startSelection()
self.highlight_page, self.highlight_idx = self:saveHighlight()
self.highlight_idx = self:saveHighlight()
self.select_mode = true
end
function ReaderHighlight:extendSelection()
-- item1 - starting fragment (saved), item2 - ending fragment (currently selected)
-- new extended highlight includes item1, item2 and the text between them
local item1 = self.view.highlight.saved[self.highlight_page][self.highlight_idx]
local item1 = self.ui.annotation.annotations[self.highlight_idx]
local item2_pos0, item2_pos1 = self.selected_text.pos0, self.selected_text.pos1
-- getting starting and ending positions, text and pboxes of extended highlight
local new_pos0, new_pos1, new_text, new_pboxes, ext
@ -2106,7 +1954,7 @@ function ReaderHighlight:extendSelection()
-- true to draw
new_text = self.ui.document:getTextFromXPointers(new_pos0, new_pos1, true)
end
self:deleteHighlight(self.highlight_page, self.highlight_idx) -- starting fragment
self:deleteHighlight(self.highlight_idx) -- starting fragment
self.selected_text = {
text = new_text,
pos0 = new_pos0,
@ -2126,7 +1974,10 @@ function ReaderHighlight:getExtendedHighlightPage(pos0, pos1, cur_page)
local page_boxes = self.ui.document:getTextBoxes(page)
if page == pos0.page then
-- first page (from the start of highlight to the end of the page)
item.pos0 = pos0
item.pos0 = {
x = pos0.x,
y = pos0.y,
}
item.pos1 = {
x = page_boxes[#page_boxes][#page_boxes[#page_boxes]].x1,
y = page_boxes[#page_boxes][#page_boxes[#page_boxes]].y1,
@ -2147,7 +1998,10 @@ function ReaderHighlight:getExtendedHighlightPage(pos0, pos1, cur_page)
x = page_boxes[1][1].x0,
y = page_boxes[1][1].y0,
}
item.pos1 = pos1
item.pos1 = {
x = pos1.x,
y = pos1.y,
}
end
item.pos0.page = page
item.pos1.page = page
@ -2159,44 +2013,53 @@ function ReaderHighlight:getExtendedHighlightPage(pos0, pos1, cur_page)
return item
end
-- Returns one page of saved multi-page highlight
-- Returns the list of highlights in page.
-- The list includes full single-page highlights and parts of multi-page highlights.
-- (For pdf documents only)
function ReaderHighlight:getSavedExtendedHighlightPage(hl_or_bm, page, index)
local highlight
if hl_or_bm.ext then
highlight = hl_or_bm
else -- called from bookmark, need to find the corresponding highlight
for _, hl in ipairs(self.view.highlight.saved[hl_or_bm.page]) do
if hl.datetime == hl_or_bm.datetime then
highlight = hl
break
function ReaderHighlight:getPageSavedHighlights(page)
local highlights = {}
for index, highlight in ipairs(self.ui.annotation.annotations) do
if highlight.drawer and highlight.pos0.page <= page and page <= highlight.pos1.page then
if highlight.ext then -- multi-page highlight
local item = self:getSavedExtendedHighlightPage(highlight, page, index)
table.insert(highlights, item)
else
table.insert(highlights, highlight)
end
end
end
local item = {}
item.datetime = highlight.datetime
item.drawer = highlight.drawer
item.pos0 = highlight.ext[page].pos0
item.pos0.zoom = highlight.pos0.zoom
return highlights
end
-- Returns one page of saved multi-page highlight
-- (For pdf documents only)
function ReaderHighlight:getSavedExtendedHighlightPage(highlight, page, index)
local item = {
datetime = highlight.datetime,
drawer = highlight.drawer,
text = highlight.text,
page = highlight.page,
pos0 = highlight.ext[page].pos0,
pos1 = highlight.ext[page].pos1,
pboxes = highlight.ext[page].pboxes,
parent = index,
}
item.pos0.zoom = highlight.pos0.zoom
item.pos0.rotation = highlight.pos0.rotation
item.pos1 = highlight.ext[page].pos1
item.pos1.zoom = highlight.pos0.zoom
item.pos1.zoom = highlight.pos0.zoom
item.pos1.rotation = highlight.pos0.rotation
item.pboxes = highlight.ext[page].pboxes
item.parent = {highlight.pos0.page, index}
return item
end
function ReaderHighlight:onReadSettings(config)
self.view.highlight.saved_drawer = config:readSetting("highlight_drawer")
or G_reader_settings:readSetting("highlight_drawing_style") or self.view.highlight.saved_drawer
self.view.highlight.disabled = G_reader_settings:has("default_highlight_action")
and G_reader_settings:readSetting("default_highlight_action") == "nothing"
self.view.highlight.disabled = G_reader_settings:readSetting("default_highlight_action") == "nothing"
self.allow_corner_scroll = G_reader_settings:nilOrTrue("highlight_corner_scroll")
-- panel zoom settings isn't supported in EPUB
if self.document.info.has_pages then
if self.ui.paging then
local ext = util.getFileNameSuffix(self.ui.document.file)
G_reader_settings:initializeExtSettings("panel_zoom_enabled", {cbz = true, cbt = true})
G_reader_settings:initializeExtSettings("panel_zoom_fallback_to_text_selection", {pdf = true})
@ -2231,6 +2094,8 @@ function ReaderHighlight:onClose()
self:clear()
end
-- dpad/keys support
function ReaderHighlight:onHighlightPress()
if self._current_indicator_pos then
if not self._start_indicator_highlight then

@ -3,7 +3,7 @@ ReaderLink is an abstraction for document-specific link interfaces.
]]
local BD = require("ui/bidi")
local ButtonDialogTitle = require("ui/widget/buttondialogtitle")
local ButtonDialog = require("ui/widget/buttondialog")
local ConfirmBox = require("ui/widget/confirmbox")
local Device = require("device")
local DocumentRegistry = require("document/documentregistry")
@ -118,7 +118,7 @@ function ReaderLink:init()
end)
if G_reader_settings:isTrue("opening_page_location_stack") then
-- Add location at book opening to stack
self.ui:registerPostReadyCallback(function()
self.ui:registerPostReaderReadyCallback(function()
self:addCurrentLocationToStack()
end)
end
@ -712,6 +712,10 @@ function ReaderLink:addCurrentLocationToStack(loc)
table.insert(self.location_stack, location)
end
function ReaderLink:popFromLocationStack()
return table.remove(self.location_stack)
end
function ReaderLink:onClearLocationStack(show_notification)
self.location_stack = {}
self:onClearForwardLocationStack()
@ -858,7 +862,7 @@ end
function ReaderLink:onGoToExternalLink(link_url)
local buttons, title = self:getButtonsForExternalLinkDialog(link_url)
self.external_link_dialog = ButtonDialogTitle:new{
self.external_link_dialog = ButtonDialog:new{
title = title,
buttons = buttons,
}

@ -100,7 +100,7 @@ function ReaderRolling:init()
self.valid_cache_rendering_hash = self.ui.document:getDocumentRenderingHash(false)
end
end)
table.insert(self.ui.postReaderCallback, function()
table.insert(self.ui.postReaderReadyCallback, function()
self:updatePos()
-- Disable crengine internal history, with required redraw
self.ui.document:enableInternalHistory(false)
@ -234,7 +234,7 @@ function ReaderRolling:onReadSettings(config)
-- And check if we can migrate to a newest DOM version after
-- the book is loaded (unless the user told us not to).
if config:nilOrFalse("cre_keep_old_dom_version") then
self.ui:registerPostReadyCallback(function()
self.ui:registerPostReaderReadyCallback(function()
self:checkXPointersAndProposeDOMVersionUpgrade()
end)
end
@ -1029,9 +1029,9 @@ function ReaderRolling:onUpdatePos(force)
if self.batched_update_count > 0 then
return
end
if self.ui.postReaderCallback ~= nil then -- ReaderUI:init() not yet done
if self.ui.postReaderReadyCallback ~= nil then -- ReaderUI:init() not yet done
-- Don't schedule any updatePos as long as ReaderUI:init() is
-- not finished (one will be called in the ui.postReaderCallback
-- not finished (one will be called in the ui.postReaderReadyCallback
-- we have set above) to avoid multiple refreshes.
return true
end
@ -1129,7 +1129,7 @@ function ReaderRolling:onRedrawCurrentView()
end
function ReaderRolling:onSetDimensions(dimen)
if self.ui.postReaderCallback ~= nil then
if self.ui.postReaderReadyCallback ~= nil then
-- ReaderUI:init() not yet done: just set document dimensions
self.ui.document:setViewDimen(Screen:getSize())
-- (what's done in the following else is done elsewhere by
@ -1445,25 +1445,12 @@ function ReaderRolling:checkXPointersAndProposeDOMVersionUpgrade()
local applyFuncToXPointersSlots = function(func)
-- Last position
func(self, "xpointer", "last position in book")
-- Bookmarks
if self.ui.bookmark and self.ui.bookmark.bookmarks and #self.ui.bookmark.bookmarks > 0 then
-- Annotations
if self.ui.annotation and self.ui.annotation.annotations and #self.ui.annotation.annotations > 0 then
local slots = { "page", "pos0", "pos1" }
for _, bookmark in ipairs(self.ui.bookmark.bookmarks) do
for _, item in ipairs(self.ui.annotation.annotations) do
for _, slot in ipairs(slots) do
func(bookmark, slot, bookmark.notes or "bookmark")
end
end
end
-- Highlights
if self.view.highlight and self.view.highlight.saved then
local slots = { "pos0", "pos1" }
for page, items in pairs(self.view.highlight.saved) do
if items and #items > 0 then
for _, highlight in ipairs(items) do
for _, slot in ipairs(slots) do
func(highlight, slot, highlight.text or "highlight")
end
end
func(item, slot, item.text or "annotation")
end
end
end
@ -1509,6 +1496,9 @@ function ReaderRolling:checkXPointersAndProposeDOMVersionUpgrade()
local new_xp = normalized_xpointers[xp]
if new_xp then
obj[slot] = new_xp
if slot == "page" then
self.ui.annotation:updateItemByXPointer(obj)
end
else
-- Let lost/not-found XPointer be. There is a small chance that
-- it will be found (it it was made before the boxing code moved

@ -289,6 +289,9 @@ function ReaderSearch:onShowSearchDialog(text, direction, regex, case_insensitiv
local res = search_func(self, search_term, param, regex, case_insensitive)
if res then
if self.ui.paging then
if not current_page then -- initial search
current_page = self.ui.paging.current_page
end
no_results = false
self.ui.link:onGotoLink({page = res.page - 1}, neglect_current_location)
self.view.highlight.temp[res.page] = res
@ -357,8 +360,24 @@ function ReaderSearch:onShowSearchDialog(text, direction, regex, case_insensitiv
self.ui.link:onGotoLink({xpointer=valid_link}, neglect_current_location)
end
end
-- Don't add result pages to location ("Go back") stack
neglect_current_location = true
if not neglect_current_location then
-- Initial search: onGotoLink() has added the current page to the location stack,
-- and we don't want this to be done when showing further pages with results.
-- But if this initial search is showing results on the current page, we don't want
-- the original page added: we will do it when we jump to a different page.
-- For now, only do this with CreDocument. With PDF, whether in single page mode or
-- in scroll mode, the view can scroll a bit when showing results, and we want to
-- allow "go back" to restore the original viewport.
if self.ui.rolling and self.view.view_mode == "page" then
if current_page == (self.ui.rolling and self.ui.document:getCurrentPage() or self.ui.paging.current_page) then
self.ui.link:popFromLocationStack()
neglect_current_location = false
else
-- We won't add further result pages to the location stack ("Go back").
neglect_current_location = true
end
end
end
end
if no_results then
local notification_text

@ -785,11 +785,9 @@ local BOOK_TWEAK_INPUT_HINT = T([[
local CSS_SUGGESTIONS = {
{ _("Long-press for info ⓘ"), _([[
This popup menu provides a quick CSS cheat sheet, showing common selector syntax and classic CSS properties.
It also shows some KOReader-specific non-standard CSS features, that can be useful with e-books.
This menu provides a non-exhaustive CSS syntax and properties list. It also shows some KOReader-specific, non-standard CSS features that can be useful with e-books.
Most of these CSS bits are used by our categorized Style tweaks in the top menu: look there and long-press on a style tweak to see its CSS code and a description of what it does.
If these are not enough to trigger what you want, you may need to adapt them: tap on their CSS code to copy it to the clipboard. You can then paste it here and edit it.
Most of these bits are already used by our categorized 'Style tweaks' (found in the top menu). Long-press on any style-tweak option to see its code and its expected results. Should these not be enough to achieve your desired look, you may need to adjust them slightly: tap once on the CSS code-box to copy the code to the clipboard, paste it here and edit it.
Long-press on any item in this popup to get more information on what it does and what it can help solving.

@ -186,11 +186,11 @@ function ReaderThumbnail:removeFromCache(hash_subs, remove_only_non_matching)
return nb_removed, size_removed
end
function ReaderThumbnail:resetCachedPagesForBookmarks(...)
function ReaderThumbnail:resetCachedPagesForBookmarks(annotations)
-- Multiple bookmarks may be provided
local start_page, end_page
for i = 1, select("#", ...) do
local bm = select(i, ...)
for i = 1, #annotations do
local bm = annotations[i]
if self.ui.rolling then
-- Look at all properties that may be xpointers
for _, k in ipairs({"page", "pos0", "pos1"}) do
@ -537,9 +537,6 @@ end
ReaderThumbnail.onDocumentRerendered = ReaderThumbnail.resetCache
ReaderThumbnail.onDocumentPartiallyRerendered = ReaderThumbnail.resetCache
-- Emitted When adding/removing/updating bookmarks and highlights
ReaderThumbnail.onBookmarkAdded = ReaderThumbnail.resetCachedPagesForBookmarks
ReaderThumbnail.onBookmarkRemoved = ReaderThumbnail.resetCachedPagesForBookmarks
ReaderThumbnail.onBookmarkUpdated = ReaderThumbnail.resetCachedPagesForBookmarks
ReaderThumbnail.onBookmarkEdited = ReaderThumbnail.resetCachedPagesForBookmarks
ReaderThumbnail.onAnnotationsModified = ReaderThumbnail.resetCachedPagesForBookmarks
return ReaderThumbnail

@ -353,7 +353,7 @@ function ReaderToc:getTocIndexByPage(pn_or_xp, skip_ignored_ticks)
if type(pn_or_xp) == "string" then
return self:getAccurateTocIndexByXPointer(pn_or_xp, skip_ignored_ticks)
end
local prev_index = 1
local prev_index = 0
for _k,_v in ipairs(self.toc) do
if not skip_ignored_ticks or not self.toc_ticks_ignored_levels[_v.depth] then
if _v.page == pageno then
@ -368,7 +368,7 @@ function ReaderToc:getTocIndexByPage(pn_or_xp, skip_ignored_ticks)
prev_index = _k
end
end
return prev_index
return prev_index > 0 and prev_index or nil
end
function ReaderToc:getAccurateTocIndexByXPointer(xptr, skip_ignored_ticks)

@ -95,7 +95,6 @@ function ReaderView:init()
temp_drawer = "invert",
temp = {},
saved_drawer = "lighten",
saved = {},
indicator = nil, -- geom: non-touch highlight position indicator: {x = 50, y=50}
}
self.page_states = {}
@ -522,6 +521,7 @@ function ReaderView:drawTempHighlight(bb, x, y)
end
function ReaderView:drawSavedHighlight(bb, x, y)
if #self.ui.annotation.annotations == 0 then return end
if self.ui.paging then
self:drawPageSavedHighlight(bb, x, y)
else
@ -529,45 +529,18 @@ function ReaderView:drawSavedHighlight(bb, x, y)
end
end
-- Returns the list of highlights in page.
-- The list includes full single-page highlights and parts of multi-page highlights.
function ReaderView:getPageSavedHighlights(page)
local highlights = {}
local is_reflow = self.document.configurable.text_wrap
self.document.configurable.text_wrap = 0
for page_num, page_highlights in pairs(self.highlight.saved) do
for i, highlight in ipairs(page_highlights) do
-- old single-page reflow highlights do not have page in position
local pos0_page = highlight.pos0.page or page_num
local pos1_page = highlight.pos1.page or page_num
if pos0_page <= page and page <= pos1_page then
if pos0_page == pos1_page then -- single-page highlight
table.insert(highlights, highlight)
else -- multi-page highlight
local item = self.ui.highlight:getSavedExtendedHighlightPage(highlight, page, i)
table.insert(highlights, item)
end
end
end
end
self.document.configurable.text_wrap = is_reflow
return highlights
end
function ReaderView:drawPageSavedHighlight(bb, x, y)
local pages = self:getCurrentPageList()
for _, page in ipairs(pages) do
local items = self:getPageSavedHighlights(page)
local items = self.ui.highlight:getPageSavedHighlights(page)
for _, item in ipairs(items) do
local boxes = self.document:getPageBoxesFromPositions(page, item.pos0, item.pos1)
if boxes then
local drawer = item.drawer or self.highlight.saved_drawer
local draw_note_mark = self.highlight.note_mark and
self.ui.bookmark:getBookmarkNote({datetime = item.datetime})
local draw_note_mark = item.note and self.highlight.note_mark
for _, box in ipairs(boxes) do
local rect = self:pageToScreenTransform(page, box)
if rect then
self:drawHighlightRect(bb, x, y, rect, drawer, draw_note_mark)
self:drawHighlightRect(bb, x, y, rect, item.drawer, draw_note_mark)
if draw_note_mark and self.highlight.note_mark == "sidemark" then
draw_note_mark = false -- side mark in the first line only
end
@ -583,48 +556,38 @@ function ReaderView:drawXPointerSavedHighlight(bb, x, y)
-- showing menu...). We might want to cache these boxes per page (and
-- clear that cache when page layout change or highlights are added
-- or removed).
local cur_view_top, cur_view_bottom
for _, items in pairs(self.highlight.saved) do
if items then
for j = 1, #items do
local item = items[j]
local pos0, pos1 = item.pos0, item.pos1
-- document:getScreenBoxesFromPositions() is expensive, so we
-- first check this item is on current page
if not cur_view_top then
-- Even in page mode, it's safer to use pos and ui.dimen.h
-- than pages' xpointers pos, even if ui.dimen.h is a bit
-- larger than pages' heights
cur_view_top = self.document:getCurrentPos()
if self.view_mode == "page" and self.document:getVisiblePageCount() > 1 then
cur_view_bottom = cur_view_top + 2 * self.ui.dimen.h
else
cur_view_bottom = cur_view_top + self.ui.dimen.h
end
end
local spos0 = self.document:getPosFromXPointer(pos0)
local spos1 = self.document:getPosFromXPointer(pos1)
local start_pos = math.min(spos0, spos1)
local end_pos = math.max(spos0, spos1)
if start_pos <= cur_view_bottom and end_pos >= cur_view_top then
local boxes = self.document:getScreenBoxesFromPositions(pos0, pos1, true) -- get_segments=true
if boxes then
local drawer = item.drawer or self.highlight.saved_drawer
local draw_note_mark = self.highlight.note_mark and
self.ui.bookmark:getBookmarkNote({datetime = item.datetime})
for _, box in ipairs(boxes) do
if box.h ~= 0 then
self:drawHighlightRect(bb, x, y, box, drawer, draw_note_mark)
if draw_note_mark and self.highlight.note_mark == "sidemark" then
draw_note_mark = false -- side mark in the first line only
end
-- Even in page mode, it's safer to use pos and ui.dimen.h
-- than pages' xpointers pos, even if ui.dimen.h is a bit
-- larger than pages' heights
local cur_view_top = self.document:getCurrentPos()
local cur_view_bottom
if self.view_mode == "page" and self.document:getVisiblePageCount() > 1 then
cur_view_bottom = cur_view_top + 2 * self.ui.dimen.h
else
cur_view_bottom = cur_view_top + self.ui.dimen.h
end
for _, item in ipairs(self.ui.annotation.annotations) do
if item.drawer then
-- document:getScreenBoxesFromPositions() is expensive, so we
-- first check if this item is on current page
local start_pos = self.document:getPosFromXPointer(item.pos0)
local end_pos = self.document:getPosFromXPointer(item.pos1)
if start_pos <= cur_view_bottom and end_pos >= cur_view_top then
local boxes = self.document:getScreenBoxesFromPositions(item.pos0, item.pos1, true) -- get_segments=true
if boxes then
local draw_note_mark = item.note and self.highlight.note_mark
for _, box in ipairs(boxes) do
if box.h ~= 0 then
self:drawHighlightRect(bb, x, y, box, item.drawer, draw_note_mark)
if draw_note_mark and self.highlight.note_mark == "sidemark" then
draw_note_mark = false -- side mark in the first line only
end
end -- end for each box
end -- end if boxes
end
end
end
end -- end for each highlight
end
end
end -- end for all saved highlight
end
end
function ReaderView:drawHighlightRect(bb, _x, _y, rect, drawer, draw_note_mark)
@ -914,40 +877,6 @@ function ReaderView:onReadSettings(config)
self:resetLayout()
local page_scroll = config:readSetting("kopt_page_scroll") or self.document.configurable.page_scroll
self.page_scroll = page_scroll == 1 and true or false
self.highlight.saved = config:readSetting("highlight", {})
-- Highlight formats in crengine and mupdf are incompatible.
-- Backup highlights when the document is opened with incompatible engine.
local page, page_highlights
while true do -- remove empty tables for pages without highlights and get the first page with highlights
page, page_highlights = next(self.highlight.saved)
if not page or #page_highlights > 0 then
break -- we're done (there is none, or there is some usable)
else
self.highlight.saved[page] = nil -- clean it up while we're at it, and find another one
end
end
if page_highlights then
local highlight_type = type(page_highlights[1].pos0)
if self.ui.rolling and highlight_type == "table" then
config:saveSetting("highlight_paging", self.highlight.saved)
self.highlight.saved = config:readSetting("highlight_rolling", {})
config:saveSetting("highlight", self.highlight.saved)
config:delSetting("highlight_rolling")
elseif self.ui.paging and highlight_type == "string" then
config:saveSetting("highlight_rolling", self.highlight.saved)
self.highlight.saved = config:readSetting("highlight_paging", {})
config:saveSetting("highlight", self.highlight.saved)
config:delSetting("highlight_paging")
end
else
if self.ui.rolling and config:has("highlight_rolling") then
self.highlight.saved = config:readSetting("highlight_rolling")
config:delSetting("highlight_rolling")
elseif self.ui.paging and config:has("highlight_paging") then
self.highlight.saved = config:readSetting("highlight_paging")
config:delSetting("highlight_paging")
end
end
self.inverse_reading_order = config:isTrue("inverse_reading_order") or G_reader_settings:isTrue("inverse_reading_order")
self.page_overlap_enable = config:isTrue("show_overlap_enable") or G_reader_settings:isTrue("page_overlap_enable") or G_defaults:readSetting("DSHOWOVERLAP")
self.page_overlap_style = config:readSetting("page_overlap_style") or G_reader_settings:readSetting("page_overlap_style") or "dim"
@ -1107,7 +1036,6 @@ function ReaderView:onSaveSettings()
if G_reader_settings:nilOrFalse("lock_rotation") then
self.document.configurable.rotation_mode = Screen:getRotationMode() -- will be saved by ReaderConfig
end
self.ui.doc_settings:saveSetting("highlight", self.highlight.saved)
self.ui.doc_settings:saveSetting("inverse_reading_order", self.inverse_reading_order)
self.ui.doc_settings:saveSetting("show_overlap_enable", self.page_overlap_enable)
self.ui.doc_settings:saveSetting("page_overlap_style", self.page_overlap_style)

@ -24,6 +24,7 @@ local LanguageSupport = require("languagesupport")
local Notification = require("ui/widget/notification")
local PluginLoader = require("pluginloader")
local ReaderActivityIndicator = require("apps/reader/modules/readeractivityindicator")
local ReaderAnnotation = require("apps/reader/modules/readerannotation")
local ReaderBack = require("apps/reader/modules/readerback")
local ReaderBookmark = require("apps/reader/modules/readerbookmark")
local ReaderConfig = require("apps/reader/modules/readerconfig")
@ -83,7 +84,7 @@ local ReaderUI = InputContainer:extend{
password = nil,
postInitCallback = nil,
postReaderCallback = nil,
postReaderReadyCallback = nil,
}
function ReaderUI:registerModule(name, ui_module, always_active)
@ -102,8 +103,8 @@ function ReaderUI:registerPostInitCallback(callback)
table.insert(self.postInitCallback, callback)
end
function ReaderUI:registerPostReadyCallback(callback)
table.insert(self.postReaderCallback, callback)
function ReaderUI:registerPostReaderReadyCallback(callback)
table.insert(self.postReaderReadyCallback, callback)
end
function ReaderUI:init()
@ -116,7 +117,7 @@ function ReaderUI:init()
Device:setIgnoreInput(true) -- Avoid ANRs on Android with unprocessed events.
self.postInitCallback = {}
self.postReaderCallback = {}
self.postReaderReadyCallback = {}
-- if we are not the top level dialog ourselves, it must be given in the table
if not self.dialog then
self.dialog = self
@ -182,6 +183,12 @@ function ReaderUI:init()
view = self.view,
ui = self
})
self:registerModule("annotation", ReaderAnnotation:new{
dialog = self.dialog,
view = self.view,
ui = self,
document = self.document,
})
-- reader goto controller
-- "goto" being a dirty keyword in Lua?
self:registerModule("gotopage", ReaderGoto:new{
@ -491,10 +498,10 @@ function ReaderUI:init()
-- Need the same event for PDF document
self:handleEvent(Event:new("ReaderReady", self.doc_settings))
for _,v in ipairs(self.postReaderCallback) do
for _,v in ipairs(self.postReaderReadyCallback) do
v()
end
self.postReaderCallback = nil
self.postReaderReadyCallback = nil
Device:setIgnoreInput(false) -- Allow processing of events (on Android).
Input:inhibitInputUntil(0.2)
@ -584,6 +591,7 @@ end
function ReaderUI:showReader(file, provider, seamless)
logger.dbg("show reader ui")
file = ffiUtil.realpath(file)
if lfs.attributes(file, "mode") ~= "file" then
UIManager:show(InfoMessage:new{
text = T(_("File '%1' does not exist."), BD.filepath(file))

@ -53,7 +53,8 @@ local settingsList = {
open_previous_document = {category="none", event="OpenLastDoc", title=_("Open previous document"), general=true},
history = {category="none", event="ShowHist", title=_("History"), general=true},
history_search = {category="none", event="SearchHistory", title=_("History search"), general=true},
favorites = {category="none", event="ShowColl", arg="favorites", title=_("Favorites"), general=true},
favorites = {category="none", event="ShowColl", title=_("Favorites"), general=true},
collections = {category="none", event="ShowCollList", title=_("Collections"), general=true},
filemanager = {category="none", event="Home", title=_("File browser"), general=true, separator=true},
----
dictionary_lookup = {category="none", event="ShowDictionaryLookup", title=_("Dictionary lookup"), general=true},
@ -273,6 +274,7 @@ local dispatcher_menu_order = {
"history",
"history_search",
"favorites",
"collections",
"filemanager",
----
"dictionary_lookup",
@ -714,7 +716,7 @@ function Dispatcher:_sortActions(caller, location, settings, touchmenu_instance)
local SortWidget = require("ui/widget/sortwidget")
local sort_widget
sort_widget = SortWidget:new{
title = _("Sort"),
title = _("Arrange actions"),
item_table = display_list,
callback = function()
if location[settings] and next(location[settings]) ~= nil then
@ -983,7 +985,7 @@ function Dispatcher:addSubMenu(caller, menu, location, settings)
end
menu[#menu].separator = true
table.insert(menu, {
text = _("Sort"),
text = _("Arrange actions"),
checked_func = function()
return location[settings] ~= nil
and location[settings].settings ~= nil

@ -8,11 +8,14 @@ local util = require("util")
local collection_file = DataStorage:getSettingsDir() .. "/collection.lua"
local ReadCollection = {
coll = {},
coll = nil, -- hash table
coll_order = nil, -- hash table
last_read_time = 0,
default_collection_name = "favorites",
}
-- read, write
local function buildEntry(file, order, mandatory)
file = FFIUtil.realpath(file)
if not file then return end
@ -41,6 +44,7 @@ function ReadCollection:_read()
end
logger.dbg("ReadCollection: reading from collection file")
self.coll = {}
self.coll_order = {}
for coll_name, collection in pairs(collections.data) do
local coll = {}
for _, v in ipairs(collection) do
@ -50,14 +54,23 @@ function ReadCollection:_read()
end
end
self.coll[coll_name] = coll
if not collection.settings then -- favorites, first run
collection.settings = { order = 1 }
end
self.coll_order[coll_name] = collection.settings.order
end
end
function ReadCollection:write(collection_name)
local collections = LuaSettings:open(collection_file)
for coll_name in pairs(collections.data) do
if not self.coll[coll_name] then
collections:delSetting(coll_name)
end
end
for coll_name, coll in pairs(self.coll) do
if not collection_name or coll_name == collection_name then
local data = {}
local data = { settings = { order = self.coll_order[coll_name] } }
for _, item in pairs(coll) do
table.insert(data, { file = item.file, order = item.order })
end
@ -68,20 +81,32 @@ function ReadCollection:write(collection_name)
collections:flush()
end
function ReadCollection:getFileCollectionName(file, collection_name)
-- info
function ReadCollection:isFileInCollection(file, collection_name)
file = FFIUtil.realpath(file) or file
for coll_name, coll in pairs(self.coll) do
if not collection_name or coll_name == collection_name then
if coll[file] then
return coll_name, file
end
return self.coll[collection_name][file] and true or false
end
function ReadCollection:isFileInCollections(file)
file = FFIUtil.realpath(file) or file
for _, coll in pairs(self.coll) do
if coll[file] then
return true
end
end
return false
end
function ReadCollection:hasFile(file, collection_name)
local coll_name = self:getFileCollectionName(file, collection_name)
return coll_name and true or false
function ReadCollection:getCollectionsWithFile(file)
file = FFIUtil.realpath(file) or file
local collections = {}
for coll_name, coll in pairs(self.coll) do
if coll[file] then
collections[coll_name] = true
end
end
return collections
end
function ReadCollection:getCollectionMaxOrder(collection_name)
@ -94,61 +119,74 @@ function ReadCollection:getCollectionMaxOrder(collection_name)
return max_order
end
function ReadCollection:getOrderedCollection(collection_name)
local ordered_coll = {}
for _, item in pairs(self.coll[collection_name]) do
table.insert(ordered_coll, item)
end
table.sort(ordered_coll, function(v1, v2) return v1.order < v2.order end)
return ordered_coll
end
function ReadCollection:updateCollectionOrder(collection_name, ordered_coll)
local coll = self.coll[collection_name]
for i, item in ipairs(ordered_coll) do
coll[item.file].order = i
end
self:write(collection_name)
end
-- manage items
function ReadCollection:addItem(file, collection_name)
collection_name = collection_name or self.default_collection_name
local max_order = self:getCollectionMaxOrder(collection_name)
local item = buildEntry(file, max_order + 1)
self.coll[collection_name][item.file] = item
self:write(collection_name)
end
function ReadCollection:addItems(files, collection_name) -- files = { filepath = true, }
collection_name = collection_name or self.default_collection_name
local coll = self.coll[collection_name]
local max_order = self:getCollectionMaxOrder(collection_name)
local do_write
for file in pairs(files) do
if not self:hasFile(file) then
max_order = max_order + 1
local item = buildEntry(file, max_order)
coll[item.file] = item
do_write = true
function ReadCollection:addRemoveItemMultiple(file, collections_to_add)
file = FFIUtil.realpath(file) or file
for coll_name, coll in pairs(self.coll) do
if collections_to_add[coll_name] then
if not coll[file] then
local max_order = self:getCollectionMaxOrder(coll_name)
coll[file] = buildEntry(file, max_order + 1)
end
else
if coll[file] then
coll[file] = nil
end
end
end
if do_write then
self:write(collection_name)
self:write()
end
function ReadCollection:addItemsMultiple(files, collections_to_add)
for file in pairs(files) do
file = FFIUtil.realpath(file) or file
for coll_name in pairs(collections_to_add) do
local coll = self.coll[coll_name]
if not coll[file] then
local max_order = self:getCollectionMaxOrder(coll_name)
coll[file] = buildEntry(file, max_order + 1)
end
end
end
self:write()
end
function ReadCollection:removeItem(file, collection_name, no_write)
local coll_name, file_name = self:getFileCollectionName(file, collection_name)
if coll_name then
self.coll[coll_name][file_name] = nil
if not no_write then
self:write(coll_name)
function ReadCollection:removeItem(file, collection_name, no_write) -- FM: delete file; FMColl: remove file
file = FFIUtil.realpath(file) or file
if collection_name then
if self.coll[collection_name][file] then
self.coll[collection_name][file] = nil
if not no_write then
self:write(collection_name)
end
return true
end
else
local do_write
for _, coll in pairs(self.coll) do
if coll[file] then
coll[file] = nil
do_write = true
end
end
if do_write then
if not no_write then
self:write()
end
return true
end
return true
end
end
function ReadCollection:removeItems(files) -- files = { filepath = true, }
function ReadCollection:removeItems(files) -- FM: delete files
local do_write
for file in pairs(files) do
if self:removeItem(file, nil, true) then
@ -160,12 +198,12 @@ function ReadCollection:removeItems(files) -- files = { filepath = true, }
end
end
function ReadCollection:removeItemsByPath(path)
function ReadCollection:removeItemsByPath(path) -- FM: delete folder
local do_write
for coll_name, coll in pairs(self.coll) do
for file_name in pairs(coll) do
if util.stringStartsWith(file_name, path) then
self.coll[coll_name][file_name] = nil
coll[file_name] = nil
do_write = true
end
end
@ -185,21 +223,29 @@ function ReadCollection:_updateItem(coll_name, file_name, new_filepath, new_path
coll[item.file] = item
end
function ReadCollection:updateItem(file, new_filepath)
local coll_name, file_name = self:getFileCollectionName(file)
if coll_name then
self:_updateItem(coll_name, file_name, new_filepath)
self:write(coll_name)
function ReadCollection:updateItem(file, new_filepath) -- FM: rename file, move file
file = FFIUtil.realpath(file) or file
local do_write
for coll_name, coll in pairs(self.coll) do
if coll[file] then
self:_updateItem(coll_name, file, new_filepath)
do_write = true
end
end
if do_write then
self:write()
end
end
function ReadCollection:updateItems(files, new_path) -- files = { filepath = true, }
function ReadCollection:updateItems(files, new_path) -- FM: move files
local do_write
for file in pairs(files) do
local coll_name, file_name = self:getFileCollectionName(file)
if coll_name then
self:_updateItem(coll_name, file_name, nil, new_path)
do_write = true
file = FFIUtil.realpath(file) or file
for coll_name, coll in pairs(self.coll) do
if coll[file] then
self:_updateItem(coll_name, file, nil, new_path)
do_write = true
end
end
end
if do_write then
@ -207,7 +253,7 @@ function ReadCollection:updateItems(files, new_path) -- files = { filepath = tru
end
end
function ReadCollection:updateItemsByPath(path, new_path)
function ReadCollection:updateItemsByPath(path, new_path) -- FM: rename folder, move folder
local len = #path
local do_write
for coll_name, coll in pairs(self.coll) do
@ -223,6 +269,58 @@ function ReadCollection:updateItemsByPath(path, new_path)
end
end
function ReadCollection:getOrderedCollection(collection_name)
local ordered_coll = {}
for _, item in pairs(self.coll[collection_name]) do
table.insert(ordered_coll, item)
end
table.sort(ordered_coll, function(v1, v2) return v1.order < v2.order end)
return ordered_coll
end
function ReadCollection:updateCollectionOrder(collection_name, ordered_coll)
local coll = self.coll[collection_name]
for i, item in ipairs(ordered_coll) do
coll[item.file].order = i
end
self:write(collection_name)
end
-- manage collections
function ReadCollection:addCollection(coll_name)
local max_order = 0
for _, order in pairs(self.coll_order) do
if max_order < order then
max_order = order
end
end
self.coll_order[coll_name] = max_order + 1
self.coll[coll_name] = {}
self:write(coll_name)
end
function ReadCollection:renameCollection(coll_name, new_name)
self.coll_order[new_name] = self.coll_order[coll_name]
self.coll[new_name] = self.coll[coll_name]
self.coll_order[coll_name] = nil
self.coll[coll_name] = nil
self:write(new_name)
end
function ReadCollection:removeCollection(coll_name)
self.coll_order[coll_name] = nil
self.coll[coll_name] = nil
self:write()
end
function ReadCollection:updateCollectionListOrder(ordered_coll)
for i, item in ipairs(ordered_coll) do
self.coll_order[item.name] = i
end
self:write()
end
ReadCollection:_read()
return ReadCollection

@ -168,6 +168,7 @@ local order = {
"history",
"open_last_document",
"----------------------------",
"favorites",
"collections",
"----------------------------",
"mass_storage_actions", -- if Device:canToggleMassStorage()

@ -227,6 +227,7 @@ local order = {
"history",
"open_previous_document",
"----------------------------",
"favorites",
"collections",
"----------------------------",
"book_status",

@ -503,14 +503,14 @@ Show translated text in TextViewer, with alternate translations
@string source_lang[opt="auto"] (`"en"`, `"fr"`, ``) or `"auto"` to auto-detect source language
@string target_lang[opt] (`"en"`, `"fr"`, ``)
--]]
function Translator:showTranslation(text, detailed_view, source_lang, target_lang, from_highlight, page, index)
function Translator:showTranslation(text, detailed_view, source_lang, target_lang, from_highlight, index)
if Device:hasClipboard() then
Device.input.setClipboardText(text)
end
local NetworkMgr = require("ui/network/manager")
if NetworkMgr:willRerunWhenOnline(function()
self:showTranslation(text, detailed_view, source_lang, target_lang, from_highlight, page, index)
self:showTranslation(text, detailed_view, source_lang, target_lang, from_highlight, index)
end) then
return
end
@ -519,11 +519,11 @@ function Translator:showTranslation(text, detailed_view, source_lang, target_lan
-- translation service query.
local Trapper = require("ui/trapper")
Trapper:wrap(function()
self:_showTranslation(text, detailed_view, source_lang, target_lang, from_highlight, page, index)
self:_showTranslation(text, detailed_view, source_lang, target_lang, from_highlight, index)
end)
end
function Translator:_showTranslation(text, detailed_view, source_lang, target_lang, from_highlight, page, index)
function Translator:_showTranslation(text, detailed_view, source_lang, target_lang, from_highlight, index)
if not target_lang then
target_lang = self:getTargetLanguage()
end
@ -632,8 +632,8 @@ function Translator:_showTranslation(text, detailed_view, source_lang, target_la
UIManager:close(textviewer)
UIManager:close(ui.highlight.highlight_dialog)
ui.highlight.highlight_dialog = nil
if page then
ui.highlight:editHighlight(page, index, false, text_main)
if index then
ui.highlight:editHighlight(index, false, text_main)
else
ui.highlight:addNote(text_main)
end
@ -645,8 +645,8 @@ function Translator:_showTranslation(text, detailed_view, source_lang, target_la
UIManager:close(textviewer)
UIManager:close(ui.highlight.highlight_dialog)
ui.highlight.highlight_dialog = nil
if page then
ui.highlight:editHighlight(page, index, false, text_all)
if index then
ui.highlight:editHighlight(index, false, text_all)
else
ui.highlight:addNote(text_all)
end

@ -65,11 +65,6 @@ local DictQuickLookup = InputContainer:extend{
rotated_update_wiki_languages_on_close = nil,
}
local highlight_strings = {
highlight =_("Highlight"),
unhighlight = _("Unhighlight"),
}
function DictQuickLookup.getWikiSaveEpubDefaultDir()
local dir = G_reader_settings:readSetting("home_dir") or filemanagerutil.getDefaultDir()
if dir:sub(-1) ~= "/" then
@ -438,19 +433,13 @@ function DictQuickLookup:init()
},
{
id = "highlight",
text = self:getHighlightText(),
text = _("Highlight"),
enabled = not self:isDocless() and self.highlight ~= nil,
callback = function()
if self:getHighlightText() == highlight_strings.highlight then
self.ui:handleEvent(Event:new("Highlight"))
else
self.ui:handleEvent(Event:new("Unhighlight"))
end
self.save_highlight = not self.save_highlight
-- Just update, repaint and refresh *this* button
local this = self.button_table:getButtonById("highlight")
if not this then return end
this:enableDisable(self.highlight ~= nil)
this:setText(self:getHighlightText(), this.width)
this:setText(self.save_highlight and _("Unhighlight") or _("Highlight"), this.width)
this:refresh()
end,
},
@ -953,22 +942,6 @@ function DictQuickLookup:onShow()
return true
end
function DictQuickLookup:getHighlightedItem()
if self:isDocless() then return end
return self.ui.highlight:getHighlightBookmarkItem()
end
function DictQuickLookup:getHighlightText()
local item = self:getHighlightedItem()
if not item then
return highlight_strings.highlight, false
elseif self.ui.bookmark:isBookmarkAdded(item) then
return highlight_strings.unhighlight, false
else
return highlight_strings.highlight, true
end
end
function DictQuickLookup:isPrevDictAvaiable()
return self.dict_index > 1
end
@ -1150,13 +1123,19 @@ function DictQuickLookup:onClose(no_clear)
self.ui:handleEvent(Event:new("UpdateWikiLanguages", self.wiki_languages))
end
end
if self.highlight and not no_clear then
-- delay unhighlight of selection, so we can see where we stopped when
-- back from our journey into dictionary or wikipedia
local clear_id = self.highlight:getClearId()
UIManager:scheduleIn(0.5, function()
self.highlight:clear(clear_id)
end)
if self.save_highlight then
self.highlight:saveHighlight()
self.highlight:clear()
else
if self.highlight and not no_clear then
-- delay unhighlight of selection, so we can see where we stopped when
-- back from our journey into dictionary or wikipedia
local clear_id = self.highlight:getClearId()
UIManager:scheduleIn(0.5, function()
self.highlight:clear(clear_id)
end)
end
end
return true
end

@ -57,8 +57,14 @@ function CoverMenu:updateCache(file, status, do_create, pages)
local percent_finished = doc_settings:readSetting("percent_finished")
local summary = doc_settings:readSetting("summary")
status = summary and summary.status
local highlight = doc_settings:readSetting("highlight")
local has_highlight = highlight and next(highlight) and true
local has_highlight
local annotations = doc_settings:readSetting("annotations")
if annotations then
has_highlight = #annotations > 0
else
local highlight = doc_settings:readSetting("highlight")
has_highlight = highlight and next(highlight) and true
end
self.cover_info_cache[file] = table.pack(pages, percent_finished, status, has_highlight) -- may be a sparse array
else
if self.cover_info_cache and self.cover_info_cache[file] then

@ -145,7 +145,7 @@ function CoverBrowser:addToMainMenu(menu_items)
sub_item_table = history_sub_item_table,
})
table.insert(sub_item_table, {
text = _("Favorites display mode"),
text = _("Collections display mode"),
enabled_func = function()
return not BookInfoManager:getSetting("unified_display_mode")
end,
@ -364,7 +364,7 @@ function CoverBrowser:addToMainMenu(menu_items)
end,
},
{
text = _("Show hint for book status in favorites"),
text = _("Show hint for book status in collections"),
checked_func = function() return BookInfoManager:getSetting("collections_hint_opened") end,
callback = function()
BookInfoManager:toggleSetting("collections_hint_opened")

@ -235,6 +235,23 @@ function MyClipping:getImage(image)
end
end
function MyClipping:parseAnnotations(annotations, book)
for _, item in ipairs(annotations) do
if item.drawer then
local clipping = {
sort = "highlight",
page = item.pageno,
time = self:getTime(item.datetime),
text = self:getText(item.text),
note = self:getText(item.note),
chapter = item.chapter,
drawer = item.drawer,
}
table.insert(book, { clipping })
end
end
end
function MyClipping:parseHighlight(highlights, bookmarks, book)
--DEBUG("book", book.file)
@ -249,13 +266,14 @@ function MyClipping:parseHighlight(highlights, bookmarks, book)
local orphan_highlights = {}
for page, items in pairs(highlights) do
for _, item in ipairs(items) do
local clipping = {}
clipping.page = page
clipping.sort = "highlight"
clipping.time = self:getTime(item.datetime or "")
clipping.text = self:getText(item.text)
clipping.chapter = item.chapter
clipping.drawer = item.drawer
local clipping = {
sort = "highlight",
page = page,
time = self:getTime(item.datetime or ""),
text = self:getText(item.text),
chapter = item.chapter,
drawer = item.drawer,
}
local bookmark_found = false
for _, bookmark in pairs(bookmarks) do
if bookmark.datetime == item.datetime then
@ -316,9 +334,13 @@ end
function MyClipping:getClippingsFromBook(clippings, doc_path)
local doc_settings = DocSettings:open(doc_path)
local highlights = doc_settings:readSetting("highlight")
if not highlights then return end
local bookmarks = doc_settings:readSetting("bookmarks")
local highlights, bookmarks
local annotations = doc_settings:readSetting("annotations")
if annotations == nil then
highlights = doc_settings:readSetting("highlight")
if highlights == nil then return end
bookmarks = doc_settings:readSetting("bookmarks")
end
local props = doc_settings:readSetting("doc_props")
props = FileManagerBookInfo.extendProps(props, doc_path)
local title, author = self:getTitleAuthor(doc_path, props)
@ -326,8 +348,13 @@ function MyClipping:getClippingsFromBook(clippings, doc_path)
file = doc_path,
title = title,
author = author,
number_of_pages = doc_settings:readSetting("doc_pages"),
}
self:parseHighlight(highlights, bookmarks, clippings[title])
if annotations then
self:parseAnnotations(annotations, clippings[title])
else
self:parseHighlight(highlights, bookmarks, clippings[title])
end
end
function MyClipping:parseHistory()
@ -361,7 +388,7 @@ function MyClipping:parseCurrentDoc(view)
output_filename = util.getSafeFilename(title),
number_of_pages = view.document.info.number_of_pages,
}
self:parseHighlight(view.highlight.saved, view.ui.bookmark.bookmarks, clippings[title])
self:parseAnnotations(view.ui.annotation.annotations, clippings[title])
return clippings
end

@ -156,6 +156,7 @@ end
--- Parse and export highlights from the currently opened document.
function Exporter:exportCurrentNotes()
self.ui.annotation:updatePageNumbers()
local clippings = self:getDocumentClippings()
self:exportClippings(clippings)
end

@ -402,6 +402,11 @@ local _function_info_cache = {}
local getFunctionInfo = function(func, full_code)
local info = debug.getinfo( func, "S" )
local src, firstline, lastline = info.source, info.linedefined, info.lastlinedefined
if firstline < 0 then
-- With builtin or C functions, we get: [C] -1 -1
-- Get something like "function: builtin" or "function: 0x7f5931f03828" instead
src = string.format("%s", func)
end
local hash = src.."#"..firstline.."#"..lastline
if _function_info_cache[hash] and not full_code then
return _function_info_cache[hash]
@ -455,8 +460,19 @@ local getFunctionInfo = function(func, full_code)
dummy, cnt = signature:gsub("^[^(]*:","")
info.is_method = cnt > 0
info.classname = signature:gsub(".-(%w+):.*","%1")
else
-- possibly some Lua builtin function or from some C-module
lines = {}
info.source = "builtin or C module"
info.no_source = true
info.signature = src
info.nb_args = -1
info.is_method = false
info.classname = ""
end
if hash then
_function_info_cache[hash] = info
end
_function_info_cache[hash] = info
if not full_code then
return info
end
@ -520,6 +536,7 @@ This service is aimed at developers, <mark>use at your own risk</mark>.
<li><a href="device/">device</a> the Device object (get a screenshot: <a href="device/screen/bb">device/screen/bb</a>).
<li><a href="UIManager/">UIManager</a> and its <a href="UIManager/_window_stack/">window stack</a>.
<li><a href="g_settings/">g_settings</a> your global settings saved as settings.reader.lua.
<li><a href="globals/">globals</a> the global namespace
<big>Send an event:</big>
<li><a href="event/">list of dispatcher/gestures actions</a>.
@ -597,6 +614,8 @@ function HttpInspector:onRequest(data, request_id)
return self:exposeObject(Device, uri, reqinfo)
elseif fragment == "g_settings" then
return self:exposeObject(G_reader_settings, uri, reqinfo)
elseif fragment == "globals" then
return self:exposeObject(_G, uri, reqinfo)
elseif fragment == "UIManager" then
return self:exposeObject(UIManager, uri, reqinfo)
elseif fragment == "event" then
@ -878,14 +897,18 @@ function HttpInspector:showFunctionDetails(obj, reqinfo)
add_html("It may be called, to get results <b>as JSON</b>, with:")
output_sample_uris("/")
add_html("")
local dummy, git_commit = require("version"):getNormalizedCurrentVersion()
local github_uri = T("https://github.com/koreader/koreader/blob/%1/%2#L%3", git_commit, func_info.source, func_info.firstline)
add_html(T("Here's a snippet of the function code (it can be viewed with syntax coloring and line numbers <a href='%1'>on Github</a>):", github_uri))
add_html("<div style='background-color: lightgray'>")
for _, line in ipairs(func_info.lines) do
add_html(line)
if func_info.no_source then
add_html(T("Builtin function or from a C module: no source code available."))
else
local dummy, git_commit = require("version"):getNormalizedCurrentVersion()
local github_uri = T("https://github.com/koreader/koreader/blob/%1/%2#L%3", git_commit, func_info.source, func_info.firstline)
add_html(T("Here's a snippet of the function code (it can be viewed with syntax coloring and line numbers <a href='%1'>on Github</a>):", github_uri))
add_html("<div style='background-color: lightgray'>")
for _, line in ipairs(func_info.lines) do
add_html(line)
end
add_html("\n</div>")
end
add_html("\n</div>")
add_html("</pre>")
html = table.concat(html, "\n")
return self:sendResponse(reqinfo, 200, CTYPE.HTML, html)
@ -942,7 +965,9 @@ function HttpInspector:callFunction(func, instance, args_as_uri, output_html, re
http_code = 500
-- On error, instead of the array on success, let's return an object,
-- with keys 'error' and "stacktrace"
err, trace = res[2]:match("^(.-)\n(.*)$")
if res[2] then
err, trace = res[2]:match("^(.-)\n(.*)$")
end
json = getAsJsonString({["error"] = err, ["stacktrace"] = trace})
end
if output_html then

@ -187,7 +187,7 @@ function ReaderStatistics:initData()
self.data.pages = self.document:getPageCount()
-- Update these numbers to what's actually stored in the settings
self.data.highlights, self.data.notes = self.ui.bookmark:getNumberOfHighlightsAndNotes()
self.data.highlights, self.data.notes = self.ui.annotation:getNumberOfHighlightsAndNotes()
self.id_curr_book = self:getIdBookDB()
self.book_read_pages, self.book_read_time = self:getPageTimeTotalStats(self.id_curr_book)
if self.book_read_pages > 0 then
@ -2622,6 +2622,13 @@ function ReaderStatistics:onPageUpdate(pageno)
return
end
if self._reading_paused_ts then
-- Reading paused: don't update stats, but remember the current
-- page for when reading resumed.
self._reading_paused_curr_page = pageno
return
end
-- We only care about *actual* page turns ;)
if self.curr_page == pageno then
return
@ -2727,27 +2734,14 @@ function ReaderStatistics:onCloseDocument()
self:insertDB()
end
function ReaderStatistics:onAddHighlight()
if self.settings.is_enabled then
self.data.highlights = self.data.highlights + 1
end
end
function ReaderStatistics:onDelHighlight()
if self.settings.is_enabled then
self.data.highlights = self.data.highlights - 1
end
end
function ReaderStatistics:onAddNote()
function ReaderStatistics:onAnnotationsModified(annotations)
if self.settings.is_enabled then
self.data.notes = self.data.notes + 1
end
end
function ReaderStatistics:onDelNote()
if self.settings.is_enabled then
self.data.notes = self.data.notes - 1
if annotations.nb_highlights_added then
self.data.highlights = self.data.highlights + annotations.nb_highlights_added
end
if annotations.nb_notes_added then
self.data.notes = self.data.notes + annotations.nb_notes_added
end
end
end
@ -2786,6 +2780,11 @@ function ReaderStatistics:onReadingResumed()
if data_tuple then
data_tuple[1] = data_tuple[1] + pause_duration
end
if self._reading_paused_curr_page and self._reading_paused_curr_page ~= self.curr_page then
self._reading_paused_ts = nil
self:onPageUpdate(self._reading_paused_curr_page)
self._reading_paused_curr_page = nil
end
end
end
self._reading_paused_ts = nil

@ -20,7 +20,7 @@ describe("ReaderBookmark module", function()
readerui.highlight:onHoldPan(nil, { pos = pos1 })
readerui.highlight:onHoldRelease()
assert.truthy(readerui.highlight.highlight_dialog)
readerui.highlight:onHighlight()
readerui.highlight:saveHighlight()
UIManager:nextTick(function()
UIManager:close(readerui.highlight.highlight_dialog)
UIManager:close(readerui)
@ -67,17 +67,6 @@ describe("ReaderBookmark module", function()
UIManager:show(readerui)
readerui.rolling:onGotoPage(10)
end)
it("should compare bookmarks properly", function()
assert.truthy(readerui.bookmark:isBookmarkSame(
{ notes = 'foo', page = 1, pos0 = 0, pos1 = 2, },
{ notes = 'foo', page = 1, pos0 = 0, pos1 = 2, }))
assert.falsy(readerui.bookmark:isBookmarkSame(
{ notes = 'foo', page = 1, pos0 = 0, pos1 = 2, },
{ notes = 'bar', page = 1, pos0 = 0, pos1 = 2, }))
assert.falsy(readerui.bookmark:isBookmarkSame(
{ notes = 'foo0', page = 1, pos0 = 0, pos1 = 0, },
{ notes = 'foo', page = 1, pos0 = 0, pos1 = 2, }))
end)
it("should show dogear after toggling non-bookmarked page", function()
assert.falsy(readerui.view.dogear_visible)
toggler_dogear(readerui)
@ -90,7 +79,7 @@ describe("ReaderBookmark module", function()
Screen:shot("screenshots/reader_bookmark_nodogear_epub.png")
assert.falsy(readerui.view.dogear_visible)
end)
it("should sort bookmarks with descending page numbers", function()
it("should sort bookmarks with ascending page numbers", function()
local pages = {1, 20, 5, 30, 10, 40, 15, 25, 35, 45}
for _, page in ipairs(pages) do
readerui.rolling:onGotoPage(page)
@ -99,11 +88,11 @@ describe("ReaderBookmark module", function()
readerui.bookmark:onShowBookmark()
show_bookmark_menu(readerui)
Screen:shot("screenshots/reader_bookmark_10marks_epub.png")
assert.are.same(10, #readerui.bookmark.bookmarks)
assert.are.same(10, #readerui.annotation.annotations)
assert.are.same(15, readerui.document:getPageFromXPointer(readerui.annotation.annotations[4].page))
end)
it("should keep descending page numbers after removing bookmarks", function()
local pages = {1, 30, 10, 40, 20}
readerui.bookmark.bookmarks = {}
for _, page in ipairs(pages) do
readerui.rolling:onGotoPage(page)
toggler_dogear(readerui)
@ -111,7 +100,7 @@ describe("ReaderBookmark module", function()
readerui.bookmark:onShowBookmark()
show_bookmark_menu(readerui)
Screen:shot("screenshots/reader_bookmark_5marks_epub.png")
assert.are.same(5, #readerui.bookmark.bookmarks)
assert.are.same(5, #readerui.annotation.annotations)
end)
it("should add bookmark by highlighting", function()
highlight_text(readerui,
@ -120,18 +109,18 @@ describe("ReaderBookmark module", function()
readerui.bookmark:onShowBookmark()
show_bookmark_menu(readerui)
Screen:shot("screenshots/reader_bookmark_6marks_epub.png")
assert.are.same(6, #readerui.bookmark.bookmarks)
assert.are.same(6, #readerui.annotation.annotations)
end)
it("should get previous bookmark for certain page", function()
local xpointer = readerui.document:getXPointer()
local bm_xpointer = readerui.bookmark:getPreviousBookmarkedPage(xpointer)
assert.are.same(6, #readerui.bookmark.bookmarks)
assert.are.same(1, readerui.document:getPageFromXPointer(bm_xpointer))
assert.are.same(6, #readerui.annotation.annotations)
assert.are.same(5, readerui.document:getPageFromXPointer(bm_xpointer))
end)
it("should get next bookmark for certain page", function()
local xpointer = readerui.document:getXPointer()
local bm_xpointer = readerui.bookmark:getNextBookmarkedPage(xpointer)
assert.are.same(20, readerui.document:getPageFromXPointer(bm_xpointer))
assert.are.same(15, readerui.document:getPageFromXPointer(bm_xpointer))
end)
end)
@ -154,34 +143,6 @@ describe("ReaderBookmark module", function()
UIManager:show(readerui)
readerui.paging:onGotoPage(10)
end)
it("should does bookmark comparison properly", function()
assert.truthy(readerui.bookmark:isBookmarkSame(
{ notes = 'foo', pos0 = { page = 1 , x = 2, y = 3},
pos1 = { page = 1, x = 20, y = 3 }, },
{ notes = 'foo', pos0 = { page = 1 , x = 2, y = 3},
pos1 = { page = 1, x = 20, y = 3 }, }))
assert.falsy(readerui.bookmark:isBookmarkSame(
{ notes = 'foo', page = 1, pos0 = 0, pos1 = 2, },
{ notes = 'foo', page = 1, pos1 = 2, }))
assert.falsy(readerui.bookmark:isBookmarkSame(
{ notes = 'foo', page = 1, pos0 = 0, pos1 = 2, },
{ notes = 'foo', page = 1, pos0 = 2, }))
assert.falsy(readerui.bookmark:isBookmarkSame(
{ notes = 'foo', pos0 = { page = 1 , x = 2, y = 3},
pos1 = { page = 1, x = 20, y = 3 }, },
{ notes = 'foo', pos0 = { page = 2 , x = 2, y = 3},
pos1 = { page = 1, x = 20, y = 3 }, }))
assert.falsy(readerui.bookmark:isBookmarkSame(
{ notes = 'foo', pos0 = { page = 1 , x = 1, y = 3},
pos1 = { page = 1, x = 20, y = 3 }, },
{ notes = 'foo', pos0 = { page = 1 , x = 2, y = 3},
pos1 = { page = 1, x = 20, y = 3 }, }))
assert.falsy(readerui.bookmark:isBookmarkSame(
{ notes = 'foo', pos0 = { page = 1 , x = 1, y = 3},
pos1 = { page = 1, x = 20, y = 3 }, },
{ notes = 'foo', pos0 = { page = 1 , x = 1, y = 3},
pos1 = { page = 1, x = 20, y = 2 }, }))
end)
it("should show dogear after toggling non-bookmarked page", function()
toggler_dogear(readerui)
Screen:shot("screenshots/reader_bookmark_dogear_pdf.png")
@ -192,7 +153,7 @@ describe("ReaderBookmark module", function()
Screen:shot("screenshots/reader_bookmark_nodogear_pdf.png")
assert.truthy(not readerui.view.dogear_visible)
end)
it("should sort bookmarks with descending page numbers", function()
it("should sort bookmarks with ascending page numbers", function()
local pages = {1, 20, 5, 30, 10, 40, 15, 25, 35, 45}
for _, page in ipairs(pages) do
readerui.paging:onGotoPage(page)
@ -201,7 +162,8 @@ describe("ReaderBookmark module", function()
readerui.bookmark:onShowBookmark()
show_bookmark_menu(readerui)
Screen:shot("screenshots/reader_bookmark_10marks_pdf.png")
assert.are.same(10, #readerui.bookmark.bookmarks)
assert.are.same(10, #readerui.annotation.annotations)
assert.are.same(15, readerui.annotation.annotations[4].page)
end)
it("should keep descending page numbers after removing bookmarks", function()
local pages = {1, 30, 10, 40, 20}
@ -212,14 +174,14 @@ describe("ReaderBookmark module", function()
readerui.bookmark:onShowBookmark()
show_bookmark_menu(readerui)
Screen:shot("screenshots/reader_bookmark_5marks_pdf.png")
assert.are.same(5, #readerui.bookmark.bookmarks)
assert.are.same(5, #readerui.annotation.annotations)
end)
it("should add bookmark by highlighting", function()
highlight_text(readerui, Geom:new{ x = 260, y = 70 }, Geom:new{ x = 260, y = 150 })
readerui.bookmark:onShowBookmark()
show_bookmark_menu(readerui)
Screen:shot("screenshots/reader_bookmark_6marks_pdf.png")
assert.are.same(6, #readerui.bookmark.bookmarks)
assert.are.same(6, #readerui.annotation.annotations)
end)
it("should get previous bookmark for certain page", function()
assert.are.same(5, readerui.bookmark:getPreviousBookmarkedPage(10))
@ -227,31 +189,5 @@ describe("ReaderBookmark module", function()
it("should get next bookmark for certain page", function()
assert.are.same(15, readerui.bookmark:getNextBookmarkedPage(10))
end)
it("should search/add bookmarks properly", function()
-- clear bookmarks created by previous tests
readerui.bookmark.bookmarks = {}
local p1 = { x = 0, y = 0, page = 100 }
local bm1 = { notes = 'foo', page = 10,
pos0 = { x = 0, y = 0, page = 100 }, pos1 = p1, }
assert.falsy(readerui.bookmark:isBookmarkAdded(bm1))
readerui.bookmark:addBookmark(bm1)
assert.are.same(readerui.bookmark.bookmarks, {bm1})
local bm2 = { notes = 'foo', page = 1,
pos0 = { x = 0, y = 0, page = 1 }, pos1 = p1, }
assert.falsy(readerui.bookmark:isBookmarkAdded(bm2))
readerui.bookmark:addBookmark(bm2)
assert.are.same({bm1, bm2}, readerui.bookmark.bookmarks)
local bm3 = { notes = 'foo', page = 5,
pos0 = { x = 0, y = 0, page = 5 }, pos1 = p1, }
assert.falsy(readerui.bookmark:isBookmarkAdded(bm3))
readerui.bookmark:addBookmark(bm3)
assert.are.same({bm1, bm3, bm2}, readerui.bookmark.bookmarks)
assert.truthy(readerui.bookmark:isBookmarkAdded(bm1))
assert.truthy(readerui.bookmark:isBookmarkAdded(bm2))
assert.truthy(readerui.bookmark:isBookmarkAdded(bm3))
end)
end)
end)

@ -17,7 +17,7 @@ describe("Readerhighlight module", function()
readerui.highlight:onHold(nil, { pos = pos0 })
readerui.highlight:onHoldRelease()
readerui.highlight:onHighlight()
readerui.highlight:saveHighlight()
assert.spy(s).was_called()
assert.spy(s).was_called_with(match.is_ref(readerui.languagesupport),
@ -50,7 +50,7 @@ describe("Readerhighlight module", function()
assert.truthy(readerui.highlight.highlight_dialog)
assert.truthy(UIManager._window_stack[next_slot].widget
== readerui.highlight.highlight_dialog)
readerui.highlight:onHighlight()
readerui.highlight:saveHighlight()
UIManager:scheduleIn(1, function()
UIManager:close(readerui.highlight.highlight_dialog)
UIManager:close(readerui)
@ -64,7 +64,7 @@ describe("Readerhighlight module", function()
readerui.highlight:onHold(nil, { pos = pos0 })
readerui.highlight:onHoldPan(nil, { pos = pos1 })
readerui.highlight:onHoldRelease()
readerui.highlight:onHighlight()
readerui.highlight:saveHighlight()
readerui.highlight:clear()
UIManager:close(readerui.highlight.highlight_dialog)
readerui.highlight:onTap(nil, { pos = pos2 })
@ -106,12 +106,13 @@ describe("Readerhighlight module", function()
end)
after_each(function()
readerui.highlight:clear()
readerui.annotation.annotations = {}
end)
it("should highlight single word", function()
highlight_single_word(readerui, Geom:new{ x = 400, y = 70 })
Screen:shot("screenshots/reader_highlight_single_word_epub.png")
assert.spy(selection_spy).was_called()
assert.truthy(readerui.view.highlight.saved[page])
assert.truthy(#readerui.annotation.annotations == 1)
end)
it("should highlight text", function()
highlight_text(readerui,
@ -119,7 +120,7 @@ describe("Readerhighlight module", function()
Geom:new{ x = 400, y = 170 })
Screen:shot("screenshots/reader_highlight_text_epub.png")
assert.spy(selection_spy).was_called()
assert.truthy(readerui.view.highlight.saved[page])
assert.truthy(#readerui.annotation.annotations == 1)
end)
it("should response on tap gesture", function()
tap_highlight_text(readerui,
@ -154,6 +155,7 @@ describe("Readerhighlight module", function()
end)
after_each(function()
readerui.highlight:clear()
readerui.annotation.annotations = {}
end)
it("should response on tap gesture #nocov", function()
tap_highlight_text(readerui,
@ -165,10 +167,12 @@ describe("Readerhighlight module", function()
it("should highlight single word", function()
highlight_single_word(readerui, Geom:new{ x = 260, y = 70 })
Screen:shot("screenshots/reader_highlight_single_word_pdf.png")
assert.truthy(#readerui.annotation.annotations == 1)
end)
it("should highlight text", function()
highlight_text(readerui, Geom:new{ x = 260, y = 170 }, Geom:new{ x = 260, y = 250 })
Screen:shot("screenshots/reader_highlight_text_pdf.png")
assert.truthy(#readerui.annotation.annotations == 1)
end)
end)
describe("for scanned page without text layer", function()
@ -179,6 +183,7 @@ describe("Readerhighlight module", function()
end)
after_each(function()
readerui.highlight:clear()
readerui.annotation.annotations = {}
end)
it("should respond to tap gesture #nocov", function()
tap_highlight_text(readerui, Geom:new{ x = 260, y = 70 }, Geom:new{ x = 260, y = 150 }, Geom:new{ x = 280, y = 110 })
@ -187,10 +192,12 @@ describe("Readerhighlight module", function()
it("should highlight single word", function()
highlight_single_word(readerui, Geom:new{ x = 260, y = 70 })
Screen:shot("screenshots/reader_highlight_single_word_pdf_scanned.png")
assert.truthy(#readerui.annotation.annotations == 1)
end)
it("should highlight text", function()
highlight_text(readerui, Geom:new{ x = 260, y = 70 }, Geom:new{ x = 260, y = 150 })
Screen:shot("screenshots/reader_highlight_text_pdf_scanned.png")
assert.truthy(#readerui.annotation.annotations == 1)
end)
end)
describe("for reflowed page", function()
@ -202,6 +209,7 @@ describe("Readerhighlight module", function()
end)
after_each(function()
readerui.highlight:clear()
readerui.annotation.annotations = {}
readerui.document.configurable.text_wrap = 0
UIManager:close(readerui) -- close to flush settings
-- We haven't torn it down yet
@ -214,10 +222,12 @@ describe("Readerhighlight module", function()
it("should highlight single word", function()
highlight_single_word(readerui, Geom:new{ x = 260, y = 70 })
Screen:shot("screenshots/reader_highlight_single_word_pdf_reflowed.png")
assert.truthy(#readerui.annotation.annotations == 1)
end)
it("should highlight text", function()
highlight_text(readerui, Geom:new{ x = 260, y = 70 }, Geom:new{ x = 260, y = 150 })
Screen:shot("screenshots/reader_highlight_text_pdf_reflowed.png")
assert.truthy(#readerui.annotation.annotations == 1)
end)
end)
end)
@ -247,6 +257,7 @@ describe("Readerhighlight module", function()
end)
after_each(function()
readerui.highlight:clear()
readerui.annotation.annotations = {}
end)
it("should highlight single word", function()
highlight_single_word(readerui, Geom:new{ x = 260, y = 70 })
@ -255,6 +266,7 @@ describe("Readerhighlight module", function()
it("should highlight text", function()
highlight_text(readerui, Geom:new{ x = 260, y = 170 }, Geom:new{ x = 260, y = 250 })
Screen:shot("screenshots/reader_highlight_text_pdf_scroll.png")
assert.truthy(#readerui.annotation.annotations == 1)
end)
it("should response on tap gesture", function()
tap_highlight_text(readerui,
@ -262,6 +274,7 @@ describe("Readerhighlight module", function()
Geom:new{ x = 260, y = 150 },
Geom:new{ x = 280, y = 110 })
Screen:shot("screenshots/reader_tap_highlight_text_pdf_scroll.png")
assert.truthy(#readerui.annotation.annotations == 1)
end)
end)
describe("for scanned page without text layer", function()
@ -273,14 +286,17 @@ describe("Readerhighlight module", function()
end)
after_each(function()
readerui.highlight:clear()
readerui.annotation.annotations = {}
end)
it("should highlight single word", function()
highlight_single_word(readerui, Geom:new{ x = 260, y = 70 })
Screen:shot("screenshots/reader_highlight_single_word_pdf_scanned_scroll.png")
assert.truthy(#readerui.annotation.annotations == 1)
end)
it("should highlight text", function()
highlight_text(readerui, Geom:new{x = 192, y = 186}, Geom:new{x = 280, y = 186})
Screen:shot("screenshots/reader_highlight_text_pdf_scanned_scroll.png")
assert.truthy(#readerui.annotation.annotations == 1)
end)
it("should response on tap gesture", function()
tap_highlight_text(readerui, Geom:new{ x = 260, y = 70 }, Geom:new{ x = 260, y = 150 }, Geom:new{ x = 280, y = 110 })
@ -296,6 +312,7 @@ describe("Readerhighlight module", function()
end)
after_each(function()
readerui.highlight:clear()
readerui.annotation.annotations = {}
readerui.document.configurable.text_wrap = 0
UIManager:close(readerui) -- close to flush settings
-- We haven't torn it down yet
@ -304,10 +321,12 @@ describe("Readerhighlight module", function()
it("should highlight single word", function()
highlight_single_word(readerui, Geom:new{ x = 260, y = 70 })
Screen:shot("screenshots/reader_highlight_single_word_pdf_reflowed_scroll.png")
assert.truthy(#readerui.annotation.annotations == 1)
end)
it("should highlight text", function()
highlight_text(readerui, Geom:new{ x = 260, y = 70 }, Geom:new{ x = 260, y = 150 })
Screen:shot("screenshots/reader_highlight_text_pdf_reflowed_scroll.png")
assert.truthy(#readerui.annotation.annotations == 1)
end)
it("should response on tap gesture", function()
tap_highlight_text(readerui, Geom:new{ x = 260, y = 70 }, Geom:new{ x = 260, y = 150 }, Geom:new{ x = 280, y = 110 })

Loading…
Cancel
Save