mirror of
https://github.com/koreader/koreader.git
synced 2025-08-10 00:52:38 +00:00
* FocusManager: Fix `focus_flags` check in `moveFocusTo` (0 is truthy in Lua, can't do AND checks like in C ;).) * FileManager+FileChooser: Pass our custom title bar directly to FileChooser (which also means we can now use FC's FocusManager layout directly). * FileChooser/Menu: Get rid of the weird `outer_title_bar` hack, and simply take a `custom_title_bar` pointer to an actual TitleBar instance instead. * FileManager/Menu/ListMenu/CoverMenu: Fix content height computations in `_recalculateDimen` (all the non-FM cases were including an old and now unused padding value, `self.header_padding`, leading to more blank space at the bottom than necessary, and, worse, leading to different item heights between FM views, possibly leading to unnecessary thumbnail scaling !) * ButtonDialog: Proper focus management when the ButtonTable is wrapped in a ScrollableContainer. * ConfigDialog: Implement a stupid workaround for a weird FocusManager issue when going back from `[⋮]` buttons. * ConfigDialog: Don't move the visual focus in `update` (i.e., we use `NOT_FOCUS` now that it works as intended). * DictQuickLookup: Ensures the `Menu` key bind does the exact same thing as the hamburger icon. * DictQuickLookup: Ensure we refocus after having mangled the FocusManager layout (prevents an old focus highlight from lingering on the wrong button). * FileChooser: Stop flagging it as no_title, because it is *never* without a title. (This behavior was a remnant of the previous FM-specific title bar hacks, which are no longer a thing). * FileChooser: Stop calling `mergeTitleBarIntoLayout` twice in `updateItems`. We already call Menu's, which handles it. (Prevents the title bar from being added twice to the FocusManager layout). * FocusManager: Relax the `Unfocus` checks in `moveFocusTo` to ensure we *always* unfocus something (if unfocusing was requested), even if we have to blast the whole widget tree to do so. This ensures callers that mangle self.layout can expect things to work after calling it regardless of how borked the current focus is. * FocusManager: Allow passing `focus_flags` to `refocusWidget`, so that it can be forwarded to the internal `moveFocusTo` call. * FocusManager: The above also allows us to enforce a default that ensures we do *not* send a Focus event on Touch devices, even if they have the hasDPad devcap. This essentially restores the previous/current behavior of not showing the visual feedback from such focus "events" sent programmatically, given the `focus_flags` check fix at the root of this PR ;). * InputDialog: Fix numerous issues relating to double/ghost instances of both InputText and VirtualKeyboard, ensuring we only ever have a single InputText & VK instance live. * InputDialog: Make sure every way we have of hiding the VK play nice together, especially when the `toggleKeyboard` button (shown w/ `add_nav_bar`) is at play. And doubly so when we're `fullscreen`, as hiding the VK implies resizing the widget. * InputText: Make sure we're flagged as in-focus when tapping inside the text field. * InputText: Make sure we don't attempt to show an already-visible VK in the custom `hasDPad` `onFocus` handler. * Menu: Get rid of an old and no longer used (nor meaningful) hack in `onFocus` about the initial/programmatically-sent Focus event. * Menu: Get rid of the unused `header_padding` field mentioned earlier in the FM/FC fixes. * Menu: Use `FOCUS_ONLY_ON_NT` in the explicit `moveFocusTo` call in `updatePageInfo`, so as to keep the current behavior of not showing the visual feedback of this focus on Touch devices. * Menu: Make sure *all* the `moveFocusTo` calls are gated behind the `hasDPad` devcap (previously, that was only the case for `updatePageInfo`, but not `mergeTitleBarIntoLayout` (which is called by `updateItems`). * MultiInputDialog: Actively get rid of the InputText & VK instances from the base class's constructor that we do not use. * MultiInputDialog: Ensure the FocusManager layout is *slightly* less broken (password fields can still be a bit weird, though). * TextViewer: Get rid of the unfocus -> layout mangling -> refocus hack now that `refocusWidget` handles this case sanely. * VirtualKeyboard: Notify our parent InputDialog when we get closed, so it can act accordingly (e.g., resize itself when `fullscreen`). * ScrollableContainer: Implement the necessary machinery for focus handling inside ButtonDialog (specifically, when scrolling via PgUp/PgDwn). * TextEditor: Given the above fixes, the plugin is no longer disabled on non-touch devices. * ReaderBookMark: Make sure we request a full refresh when closing the "Edit note" dialog, as CRe highlights may extend past its dimensions, and if it's closed separately from VK, the refresh would have been limited to its own dimensions, leaving a neat InputDialog-sized hole in the highlights ;).
1486 lines
56 KiB
Lua
1486 lines
56 KiB
Lua
local BD = require("ui/bidi")
|
|
local Blitbuffer = require("ffi/blitbuffer")
|
|
local ButtonDialog = require("ui/widget/buttondialog")
|
|
local CenterContainer = require("ui/widget/container/centercontainer")
|
|
local CheckButton = require("ui/widget/checkbutton")
|
|
local ConfirmBox = require("ui/widget/confirmbox")
|
|
local Device = require("device")
|
|
local Event = require("ui/event")
|
|
local Geom = require("ui/geometry")
|
|
local GestureRange = require("ui/gesturerange")
|
|
local InputContainer = require("ui/widget/container/inputcontainer")
|
|
local InputDialog = require("ui/widget/inputdialog")
|
|
local LineWidget = require("ui/widget/linewidget")
|
|
local Menu = require("ui/widget/menu")
|
|
local Size = require("ui/size")
|
|
local SpinWidget = require("ui/widget/spinwidget")
|
|
local TextViewer = require("ui/widget/textviewer")
|
|
local UIManager = require("ui/uimanager")
|
|
local Utf8Proc = require("ffi/utf8proc")
|
|
local util = require("util")
|
|
local _ = require("gettext")
|
|
local N_ = _.ngettext
|
|
local Screen = require("device").screen
|
|
local T = require("ffi/util").template
|
|
|
|
local ReaderBookmark = InputContainer:extend{
|
|
-- mark the type of a bookmark with a symbol + non-expandable space
|
|
display_prefix = {
|
|
highlight = "\u{2592}\u{2002}", -- "medium shade"
|
|
note = "\u{F040}\u{2002}", -- "pencil"
|
|
bookmark = "\u{F097}\u{2002}", -- "empty bookmark"
|
|
},
|
|
display_type = {
|
|
highlight = _("highlights"),
|
|
note = _("notes"),
|
|
bookmark = _("page bookmarks"),
|
|
},
|
|
}
|
|
|
|
function ReaderBookmark:init()
|
|
self:registerKeyEvents()
|
|
|
|
if G_reader_settings:hasNot("bookmarks_items_per_page") then
|
|
-- The Bookmarks items per page and items' font size can now be
|
|
-- configured. Previously, the ones set for the file browser
|
|
-- were used. Initialize them from these ones.
|
|
local items_per_page = G_reader_settings:readSetting("items_per_page") or Menu.items_per_page_default
|
|
G_reader_settings:saveSetting("bookmarks_items_per_page", items_per_page)
|
|
local items_font_size = G_reader_settings:readSetting("items_font_size")
|
|
if items_font_size and items_font_size ~= Menu.getItemFontSize(items_per_page) then
|
|
-- Keep the user items font size if it's not the default for items_per_page
|
|
G_reader_settings:saveSetting("bookmarks_items_font_size", items_font_size)
|
|
end
|
|
end
|
|
self.items_text = G_reader_settings:readSetting("bookmarks_items_text_type", "note")
|
|
self.items_max_lines = G_reader_settings:readSetting("bookmarks_items_max_lines")
|
|
|
|
self.ui.menu:registerToMainMenu(self)
|
|
-- NOP our own gesture handling
|
|
self.ges_events = nil
|
|
end
|
|
|
|
function ReaderBookmark:onGesture() end
|
|
|
|
function ReaderBookmark:registerKeyEvents()
|
|
if Device:hasKeyboard() then
|
|
self.key_events.ShowBookmark = { { "B" }, { "Shift", "Left" } }
|
|
self.key_events.ToggleBookmark = { { "Shift", "Right" } }
|
|
elseif Device:hasScreenKB() then
|
|
self.key_events.ShowBookmark = { { "ScreenKB", "Left" } }
|
|
self.key_events.ToggleBookmark = { { "ScreenKB", "Right" } }
|
|
end
|
|
end
|
|
|
|
ReaderBookmark.onPhysicalKeyboardConnected = ReaderBookmark.registerKeyEvents
|
|
|
|
function ReaderBookmark:addToMainMenu(menu_items)
|
|
menu_items.bookmarks = {
|
|
text = _("Bookmarks"),
|
|
callback = function()
|
|
self:onShowBookmark()
|
|
end,
|
|
}
|
|
if not Device:isTouchDevice() and not (Device:hasScreenKB() or Device:hasSymKey()) then
|
|
menu_items.toggle_bookmark = {
|
|
text_func = function()
|
|
return self:isPageBookmarked() and _("Remove bookmark for current page") or _("Bookmark current page")
|
|
end,
|
|
callback = function()
|
|
self:onToggleBookmark()
|
|
end,
|
|
}
|
|
end
|
|
if self.ui.paging then
|
|
menu_items.bookmark_browsing_mode = {
|
|
text = _("Bookmark browsing mode"),
|
|
checked_func = function()
|
|
return self.ui.paging.bookmark_flipping_mode
|
|
end,
|
|
callback = function(touchmenu_instance)
|
|
self.ui.paging:onToggleBookmarkFlipping()
|
|
touchmenu_instance:closeMenu()
|
|
end,
|
|
}
|
|
end
|
|
menu_items.bookmarks_settings = {
|
|
text = _("Bookmarks"),
|
|
sub_item_table = {
|
|
{
|
|
text_func = function()
|
|
return T(_("Max lines per bookmark: %1"), self.items_max_lines or _("disabled"))
|
|
end,
|
|
keep_menu_open = true,
|
|
callback = function(touchmenu_instance)
|
|
local default_value = 4
|
|
local spin_wodget = SpinWidget:new{
|
|
title_text = _("Max lines per bookmark"),
|
|
info_text = _("Set maximum number of lines to enable flexible item heights."),
|
|
value = self.items_max_lines or default_value,
|
|
value_min = 1,
|
|
value_max = 10,
|
|
default_value = default_value,
|
|
ok_always_enabled = true,
|
|
callback = function(spin)
|
|
G_reader_settings:saveSetting("bookmarks_items_max_lines", spin.value)
|
|
self.items_max_lines = spin.value
|
|
touchmenu_instance:updateItems()
|
|
end,
|
|
extra_text = _("Disable"),
|
|
extra_callback = function()
|
|
G_reader_settings:delSetting("bookmarks_items_max_lines")
|
|
self.items_max_lines = nil
|
|
touchmenu_instance:updateItems()
|
|
end,
|
|
}
|
|
UIManager:show(spin_wodget)
|
|
end,
|
|
},
|
|
{
|
|
text_func = function()
|
|
local curr_perpage = self.items_max_lines and _("flexible")
|
|
or G_reader_settings:readSetting("bookmarks_items_per_page")
|
|
return T(_("Bookmarks per page: %1"), curr_perpage)
|
|
end,
|
|
enabled_func = function()
|
|
return not self.items_max_lines
|
|
end,
|
|
keep_menu_open = true,
|
|
callback = function(touchmenu_instance)
|
|
local curr_perpage = G_reader_settings:readSetting("bookmarks_items_per_page")
|
|
local items = SpinWidget:new{
|
|
title_text = _("Bookmarks per page"),
|
|
value = curr_perpage,
|
|
value_min = 6,
|
|
value_max = 24,
|
|
default_value = Menu.items_per_page_default,
|
|
callback = function(spin)
|
|
G_reader_settings:saveSetting("bookmarks_items_per_page", spin.value)
|
|
touchmenu_instance:updateItems()
|
|
end,
|
|
}
|
|
UIManager:show(items)
|
|
end,
|
|
},
|
|
{
|
|
text_func = function()
|
|
local curr_perpage = G_reader_settings:readSetting("bookmarks_items_per_page")
|
|
local default_font_size = Menu.getItemFontSize(curr_perpage)
|
|
local curr_font_size = G_reader_settings:readSetting("bookmarks_items_font_size", default_font_size)
|
|
return T(_("Bookmark font size: %1"), curr_font_size)
|
|
end,
|
|
keep_menu_open = true,
|
|
callback = function(touchmenu_instance)
|
|
local curr_perpage = G_reader_settings:readSetting("bookmarks_items_per_page")
|
|
local default_font_size = Menu.getItemFontSize(curr_perpage)
|
|
local curr_font_size = G_reader_settings:readSetting("bookmarks_items_font_size", default_font_size)
|
|
local items_font = SpinWidget:new{
|
|
title_text = _("Bookmark font size"),
|
|
value = curr_font_size,
|
|
value_min = 10,
|
|
value_max = 72,
|
|
default_value = default_font_size,
|
|
callback = function(spin)
|
|
G_reader_settings:saveSetting("bookmarks_items_font_size", spin.value)
|
|
touchmenu_instance:updateItems()
|
|
end,
|
|
}
|
|
UIManager:show(items_font)
|
|
end,
|
|
},
|
|
{
|
|
text = _("Shrink bookmark font size to fit more text"),
|
|
enabled_func = function()
|
|
return not self.items_max_lines
|
|
end,
|
|
checked_func = function()
|
|
return not self.items_max_lines and G_reader_settings:isTrue("bookmarks_items_multilines_show_more_text")
|
|
end,
|
|
callback = function()
|
|
G_reader_settings:flipNilOrFalse("bookmarks_items_multilines_show_more_text")
|
|
end,
|
|
separator = true,
|
|
},
|
|
{
|
|
text_func = function()
|
|
return T(_("Show in items: %1"), self:genShowInItemsMenuItems())
|
|
end,
|
|
sub_item_table = {
|
|
self:genShowInItemsMenuItems("text"),
|
|
self:genShowInItemsMenuItems("all"),
|
|
self:genShowInItemsMenuItems("note"),
|
|
},
|
|
},
|
|
{
|
|
text = _("Show separator between items"),
|
|
checked_func = function()
|
|
return G_reader_settings:isTrue("bookmarks_items_show_separator")
|
|
end,
|
|
callback = function()
|
|
G_reader_settings:flipNilOrFalse("bookmarks_items_show_separator")
|
|
end,
|
|
separator = true,
|
|
},
|
|
{
|
|
text_func = function()
|
|
return T(_("Sort by: %1"), self:genSortByMenuItems())
|
|
end,
|
|
sub_item_table = {
|
|
self:genSortByMenuItems("page"),
|
|
self:genSortByMenuItems("date", true),
|
|
-- separator
|
|
{
|
|
text = _("Reverse sorting"),
|
|
checked_func = function()
|
|
return G_reader_settings:isTrue("bookmarks_items_reverse_sorting")
|
|
end,
|
|
callback = function()
|
|
G_reader_settings:flipNilOrFalse("bookmarks_items_reverse_sorting")
|
|
end,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
menu_items.bookmark_search = {
|
|
text = _("Bookmark search"),
|
|
enabled_func = function()
|
|
return self.ui.annotation:hasAnnotations()
|
|
end,
|
|
callback = function()
|
|
self:onSearchBookmark()
|
|
end,
|
|
}
|
|
end
|
|
|
|
function ReaderBookmark:genShowInItemsMenuItems(value)
|
|
local strings = {
|
|
text = _("highlighted text"),
|
|
all = _("highlighted text and note"),
|
|
note = _("note if set, otherwise highlighted text"),
|
|
}
|
|
if value == nil then
|
|
value = G_reader_settings:readSetting("bookmarks_items_text_type", "note")
|
|
return strings[value]
|
|
end
|
|
return {
|
|
text = strings[value],
|
|
checked_func = function()
|
|
return self.items_text == value
|
|
end,
|
|
radio = true,
|
|
callback = function()
|
|
self.items_text = value
|
|
G_reader_settings:saveSetting("bookmarks_items_text_type", value)
|
|
end,
|
|
}
|
|
end
|
|
|
|
function ReaderBookmark:genSortByMenuItems(value, separator)
|
|
local strings = {
|
|
page = _("page number"),
|
|
date = _("date"),
|
|
}
|
|
local strings_reverse = {
|
|
page = _("page number, reverse"),
|
|
date = _("date, reverse"),
|
|
}
|
|
if value == nil then
|
|
local curr_value = G_reader_settings:readSetting("bookmarks_items_sorting") or "page"
|
|
if G_reader_settings:isTrue("bookmarks_items_reverse_sorting") then
|
|
return strings_reverse[curr_value]
|
|
else
|
|
return strings[curr_value]
|
|
end
|
|
end
|
|
return {
|
|
text = strings[value],
|
|
checked_func = function()
|
|
return value == (G_reader_settings:readSetting("bookmarks_items_sorting") or "page")
|
|
end,
|
|
radio = true,
|
|
callback = function()
|
|
G_reader_settings:saveSetting("bookmarks_items_sorting", value ~= "page" and value or nil)
|
|
end,
|
|
separator = separator,
|
|
}
|
|
end
|
|
|
|
-- page bookmarks, dogear
|
|
|
|
function ReaderBookmark:onToggleBookmark()
|
|
self:toggleBookmark()
|
|
self.view.dogear:onSetDogearVisibility(not self.view.dogear_visible)
|
|
-- Refresh the dogear first, because it might inherit ReaderUI refresh hints.
|
|
UIManager:setDirty(self.view.dialog, function()
|
|
return "ui",
|
|
self.view.dogear:getRefreshRegion()
|
|
end)
|
|
-- And ask for a footer refresh, in case we have bookmark_count enabled.
|
|
-- Assuming the footer is visible, it'll request a refresh regardless, but the EPDC should optimize it out if no content actually changed.
|
|
self.view.footer:onUpdateFooter(self.view.footer_visible)
|
|
return true
|
|
end
|
|
|
|
function ReaderBookmark:toggleBookmark(pageno)
|
|
local pn_or_xp, item
|
|
if pageno then
|
|
if self.ui.rolling then
|
|
pn_or_xp = self.ui.document:getPageXPointer(pageno)
|
|
else
|
|
pn_or_xp = pageno
|
|
end
|
|
else
|
|
pn_or_xp = self:getCurrentPageNumber()
|
|
end
|
|
local index = self:getDogearBookmarkIndex(pn_or_xp)
|
|
if index then
|
|
item = table.remove(self.ui.annotation.annotations, index)
|
|
else
|
|
local text
|
|
local chapter = self.ui.toc:getTocTitleByPage(pn_or_xp)
|
|
if chapter == "" then
|
|
chapter = nil
|
|
else
|
|
-- @translators In which chapter title (%1) a note is found.
|
|
text = T(_("in %1"), chapter)
|
|
end
|
|
item = {
|
|
page = pn_or_xp,
|
|
text = text,
|
|
chapter = chapter,
|
|
}
|
|
self.ui.annotation:addItem(item)
|
|
end
|
|
self.ui:handleEvent(Event:new("AnnotationsModified", { item }))
|
|
end
|
|
|
|
function ReaderBookmark:setDogearVisibility(pn_or_xp)
|
|
local visible = self:isPageBookmarked(pn_or_xp)
|
|
self.view.dogear:onSetDogearVisibility(visible)
|
|
end
|
|
|
|
function ReaderBookmark:isPageBookmarked(pn_or_xp)
|
|
local page = pn_or_xp or self:getCurrentPageNumber()
|
|
return self:getDogearBookmarkIndex(page) and true or false
|
|
end
|
|
|
|
function ReaderBookmark:isBookmarkInPageOrder(a, b)
|
|
local a_page = self:getBookmarkPageNumber(a)
|
|
local b_page = self:getBookmarkPageNumber(b)
|
|
if a_page == b_page then -- have page bookmarks before highlights
|
|
return not a.drawer
|
|
end
|
|
return a_page < b_page
|
|
end
|
|
|
|
function ReaderBookmark:getDogearBookmarkIndex(pn_or_xp)
|
|
local doesMatch
|
|
if self.ui.paging then
|
|
doesMatch = function(p1, p2)
|
|
return p1 == p2
|
|
end
|
|
else
|
|
doesMatch = function(p1, p2)
|
|
return self.ui.document:getPageFromXPointer(p1) == self.ui.document:getPageFromXPointer(p2)
|
|
end
|
|
end
|
|
local _middle
|
|
local _start, _end = 1, #self.ui.annotation.annotations
|
|
while _start <= _end do
|
|
_middle = bit.rshift(_start + _end, 1)
|
|
local v = self.ui.annotation.annotations[_middle]
|
|
if not v.drawer and doesMatch(v.page, pn_or_xp) then
|
|
return _middle
|
|
elseif self:isBookmarkInPageOrder({page = pn_or_xp}, v) then
|
|
_end = _middle - 1
|
|
else
|
|
_start = _middle + 1
|
|
end
|
|
end
|
|
end
|
|
|
|
-- remove, update bookmark
|
|
|
|
function ReaderBookmark:removeItem(item)
|
|
local index = self.ui.annotation:getItemIndex(item)
|
|
if item.pos0 then
|
|
self.ui.highlight:deleteHighlight(index) -- will call ReaderBookmark:removeItemByIndex()
|
|
else -- dogear bookmark, update it in case we removed a bookmark for current page
|
|
self:removeItemByIndex(index)
|
|
self:setDogearVisibility(self:getCurrentPageNumber())
|
|
end
|
|
end
|
|
|
|
function ReaderBookmark:removeItemByIndex(index)
|
|
local item = self.ui.annotation.annotations[index]
|
|
local item_type = self.getBookmarkType(item)
|
|
if item_type == "highlight" then
|
|
self.ui:handleEvent(Event:new("AnnotationsModified", { item, nb_highlights_added = -1 }))
|
|
elseif item_type == "note" then
|
|
self.ui:handleEvent(Event:new("AnnotationsModified", { item, nb_notes_added = -1 }))
|
|
end
|
|
table.remove(self.ui.annotation.annotations, index)
|
|
self.view.footer:onUpdateFooter(self.view.footer_visible)
|
|
end
|
|
|
|
function ReaderBookmark:deleteItemNote(item)
|
|
local index = self.ui.annotation:getItemIndex(item)
|
|
self.ui.annotation.annotations[index].note = nil
|
|
self.ui:handleEvent(Event:new("AnnotationsModified", { item, nb_highlights_added = 1, nb_notes_added = -1 }))
|
|
end
|
|
|
|
-- navigation
|
|
|
|
function ReaderBookmark:onPageUpdate(pageno)
|
|
local pn_or_xp = self.ui.paging and pageno or self.ui.document:getXPointer()
|
|
self:setDogearVisibility(pn_or_xp)
|
|
end
|
|
|
|
function ReaderBookmark:onPosUpdate(pos)
|
|
local pn_or_xp = self.ui.document:getXPointer()
|
|
self:setDogearVisibility(pn_or_xp)
|
|
end
|
|
|
|
function ReaderBookmark:gotoBookmark(pn_or_xp, marker_xp)
|
|
if pn_or_xp then
|
|
local event = self.ui.paging and "GotoPage" or "GotoXPointer"
|
|
self.ui:handleEvent(Event:new(event, pn_or_xp, marker_xp))
|
|
end
|
|
end
|
|
|
|
function ReaderBookmark:getNextBookmarkedPage(pn_or_xp, page_bookmark_only)
|
|
local pageno = self:getBookmarkPageNumber({page = pn_or_xp})
|
|
for i = 1, #self.ui.annotation.annotations do
|
|
local item = self.ui.annotation.annotations[i]
|
|
if (not page_bookmark_only or not item.drawer) and pageno < self:getBookmarkPageNumber(item) then
|
|
return item.page
|
|
end
|
|
end
|
|
end
|
|
|
|
function ReaderBookmark:getPreviousBookmarkedPage(pn_or_xp, page_bookmark_only)
|
|
local pageno = self:getBookmarkPageNumber({page = pn_or_xp})
|
|
for i = #self.ui.annotation.annotations, 1, -1 do
|
|
local item = self.ui.annotation.annotations[i]
|
|
if (not page_bookmark_only or not item.drawer) and pageno > self:getBookmarkPageNumber(item) then
|
|
return item.page
|
|
end
|
|
end
|
|
end
|
|
|
|
function ReaderBookmark:getFirstBookmarkedPage(pn_or_xp)
|
|
if #self.ui.annotation.annotations > 0 then
|
|
local pageno = self:getBookmarkPageNumber({page = pn_or_xp})
|
|
local item = self.ui.annotation.annotations[1]
|
|
if pageno > self:getBookmarkPageNumber(item) then
|
|
return item.page
|
|
end
|
|
end
|
|
end
|
|
|
|
function ReaderBookmark:getLastBookmarkedPage(pn_or_xp)
|
|
if #self.ui.annotation.annotations > 0 then
|
|
local pageno = self:getBookmarkPageNumber({page = pn_or_xp})
|
|
local item = self.ui.annotation.annotations[#self.ui.annotation.annotations]
|
|
if pageno < self:getBookmarkPageNumber(item) then
|
|
return item.page
|
|
end
|
|
end
|
|
end
|
|
|
|
function ReaderBookmark:onGotoPreviousBookmark(pn_or_xp)
|
|
self:gotoBookmark(self:getPreviousBookmarkedPage(pn_or_xp))
|
|
return true
|
|
end
|
|
|
|
function ReaderBookmark:onGotoNextBookmark(pn_or_xp)
|
|
self:gotoBookmark(self:getNextBookmarkedPage(pn_or_xp))
|
|
return true
|
|
end
|
|
|
|
function ReaderBookmark:onGotoPreviousBookmarkFromPage(add_current_location_to_stack)
|
|
if add_current_location_to_stack ~= false then -- nil or true
|
|
self.ui.link:addCurrentLocationToStack()
|
|
end
|
|
local pn_or_xp = self:getCurrentPageNumber()
|
|
self:gotoBookmark(self:getPreviousBookmarkedPage(pn_or_xp))
|
|
return true
|
|
end
|
|
|
|
function ReaderBookmark:onGotoNextBookmarkFromPage(add_current_location_to_stack)
|
|
if add_current_location_to_stack ~= false then -- nil or true
|
|
self.ui.link:addCurrentLocationToStack()
|
|
end
|
|
local pn_or_xp = self:getCurrentPageNumber()
|
|
self:gotoBookmark(self:getNextBookmarkedPage(pn_or_xp))
|
|
return true
|
|
end
|
|
|
|
function ReaderBookmark:onGotoFirstBookmark(add_current_location_to_stack)
|
|
if add_current_location_to_stack ~= false then -- nil or true
|
|
self.ui.link:addCurrentLocationToStack()
|
|
end
|
|
local pn_or_xp = self:getCurrentPageNumber()
|
|
self:gotoBookmark(self:getFirstBookmarkedPage(pn_or_xp))
|
|
return true
|
|
end
|
|
|
|
function ReaderBookmark:onGotoLastBookmark(add_current_location_to_stack)
|
|
if add_current_location_to_stack ~= false then -- nil or true
|
|
self.ui.link:addCurrentLocationToStack()
|
|
end
|
|
local pn_or_xp = self:getCurrentPageNumber()
|
|
self:gotoBookmark(self:getLastBookmarkedPage(pn_or_xp))
|
|
return true
|
|
end
|
|
|
|
-- bookmarks misc info, helpers
|
|
|
|
function ReaderBookmark:getCurrentPageNumber()
|
|
return self.ui.paging and self.view.state.page or self.ui.document:getXPointer()
|
|
end
|
|
|
|
function ReaderBookmark:getBookmarkPageNumber(bookmark)
|
|
return self.ui.paging and bookmark.page or self.ui.document:getPageFromXPointer(bookmark.page)
|
|
end
|
|
|
|
function ReaderBookmark.getBookmarkType(bookmark)
|
|
if bookmark.drawer then
|
|
if bookmark.note then
|
|
return "note"
|
|
end
|
|
return "highlight"
|
|
end
|
|
return "bookmark"
|
|
end
|
|
|
|
function ReaderBookmark:getLatestBookmark()
|
|
local latest_bookmark, latest_bookmark_idx
|
|
local latest_bookmark_datetime = "0"
|
|
for i, v in ipairs(self.ui.annotation.annotations) do
|
|
if v.datetime > latest_bookmark_datetime then
|
|
latest_bookmark_datetime = v.datetime
|
|
latest_bookmark = v
|
|
latest_bookmark_idx = i
|
|
end
|
|
end
|
|
return latest_bookmark, latest_bookmark_idx
|
|
end
|
|
|
|
function ReaderBookmark:getBookmarkedPages()
|
|
local pages = {}
|
|
for _, bm in ipairs(self.ui.annotation.annotations) do
|
|
local page = self:getBookmarkPageNumber(bm)
|
|
local btype = self.getBookmarkType(bm)
|
|
if not pages[page] then
|
|
pages[page] = {}
|
|
end
|
|
if not pages[page][btype] then
|
|
pages[page][btype] = true
|
|
end
|
|
end
|
|
return pages
|
|
end
|
|
|
|
function ReaderBookmark:getBookmarkPageString(page)
|
|
if self.ui.rolling then
|
|
if self.ui.pagemap and self.ui.pagemap:wantsPageLabels() then
|
|
return self.ui.pagemap:getXPointerPageLabel(page, true)
|
|
end
|
|
page = self.ui.document:getPageFromXPointer(page)
|
|
end
|
|
if self.ui.document:hasHiddenFlows() then
|
|
local flow = self.ui.document:getPageFlow(page)
|
|
page = self.ui.document:getPageNumberInFlow(page)
|
|
if flow > 0 then
|
|
page = T("[%1]%2", page, flow)
|
|
end
|
|
end
|
|
return tostring(page)
|
|
end
|
|
|
|
function ReaderBookmark:isBookmarkAutoText(bookmark)
|
|
-- old bookmarks only
|
|
if bookmark.text == "" or bookmark.text == bookmark.notes then
|
|
return true
|
|
end
|
|
local page = self:getBookmarkPageString(bookmark.page)
|
|
local auto_text = T(_("Page %1 %2 @ %3"), page, bookmark.notes, bookmark.datetime)
|
|
return bookmark.text == auto_text
|
|
end
|
|
|
|
-- bookmark list, dialogs
|
|
|
|
function ReaderBookmark:onShowBookmark()
|
|
self.sorting_mode = G_reader_settings:readSetting("bookmarks_items_sorting") or "page"
|
|
self.is_reverse_sorting = G_reader_settings:isTrue("bookmarks_items_reverse_sorting")
|
|
|
|
-- build up item_table
|
|
local item_table = {}
|
|
local curr_page_num = self:getCurrentPageNumber()
|
|
local curr_page_string = self:getBookmarkPageString(curr_page_num)
|
|
local curr_page_index = self.ui.annotation:getInsertionIndex({page = curr_page_num})
|
|
local num = #self.ui.annotation.annotations + 1
|
|
curr_page_index = self.is_reverse_sorting and num - curr_page_index or curr_page_index
|
|
local curr_page_index_filtered = curr_page_index
|
|
for i = 1, #self.ui.annotation.annotations do
|
|
local v = self.ui.annotation.annotations[self.is_reverse_sorting and num - i or i]
|
|
local item = util.tableDeepCopy(v)
|
|
item.text_orig = item.text or ""
|
|
item.type = self.getBookmarkType(item)
|
|
if not self.match_table or self:doesBookmarkMatchTable(item) then
|
|
item.text = self:getBookmarkItemText(item)
|
|
item.mandatory = self:getBookmarkPageString(item.page)
|
|
if (not self.is_reverse_sorting and i >= curr_page_index) or (self.is_reverse_sorting and i <= curr_page_index) then
|
|
item.after_curr_page = true
|
|
item.mandatory_dim = true
|
|
end
|
|
if item.mandatory == curr_page_string then
|
|
item.bold = true
|
|
item.after_curr_page = nil
|
|
item.mandatory_dim = nil
|
|
end
|
|
table.insert(item_table, item)
|
|
else
|
|
curr_page_index_filtered = curr_page_index_filtered - 1
|
|
end
|
|
end
|
|
local curr_page_datetime
|
|
if self.sorting_mode == "date" and #item_table > 0 then
|
|
local idx = math.max(1, math.min(curr_page_index_filtered, #item_table))
|
|
curr_page_datetime = item_table[idx].datetime
|
|
local sort_func = self.is_reverse_sorting and function(a, b) return a.datetime > b.datetime end
|
|
or function(a, b) return a.datetime < b.datetime end
|
|
table.sort(item_table, sort_func)
|
|
end
|
|
|
|
local items_per_page = G_reader_settings:readSetting("bookmarks_items_per_page")
|
|
local items_font_size = G_reader_settings:readSetting("bookmarks_items_font_size", Menu.getItemFontSize(items_per_page))
|
|
local multilines_show_more_text = G_reader_settings:isTrue("bookmarks_items_multilines_show_more_text")
|
|
local show_separator = G_reader_settings:isTrue("bookmarks_items_show_separator")
|
|
|
|
self.bookmark_menu = CenterContainer:new{
|
|
dimen = Screen:getSize(),
|
|
covers_fullscreen = true, -- hint for UIManager:_repaint()
|
|
}
|
|
local bm_menu = Menu:new{
|
|
title = T(_("Bookmarks (%1)"), #item_table),
|
|
item_table = item_table,
|
|
is_borderless = true,
|
|
is_popout = false,
|
|
title_bar_fm_style = true,
|
|
items_per_page = items_per_page,
|
|
items_font_size = items_font_size,
|
|
items_max_lines = self.items_max_lines,
|
|
multilines_show_more_text = multilines_show_more_text,
|
|
line_color = show_separator and Blitbuffer.COLOR_DARK_GRAY or Blitbuffer.COLOR_WHITE,
|
|
title_bar_left_icon = "appbar.menu",
|
|
on_close_ges = {
|
|
GestureRange:new{
|
|
ges = "two_finger_swipe",
|
|
range = Geom:new{
|
|
x = 0, y = 0,
|
|
w = Screen:getWidth(),
|
|
h = Screen:getHeight(),
|
|
},
|
|
direction = BD.flipDirectionIfMirroredUILayout("east")
|
|
}
|
|
},
|
|
show_parent = self.bookmark_menu,
|
|
}
|
|
table.insert(self.bookmark_menu, bm_menu)
|
|
|
|
local bookmark = self
|
|
|
|
function bm_menu:onMenuSelect(item)
|
|
if self.select_count then
|
|
if item.dim then
|
|
item.dim = nil
|
|
if item.after_curr_page then
|
|
item.mandatory_dim = true
|
|
end
|
|
self.select_count = self.select_count - 1
|
|
else
|
|
item.dim = true
|
|
self.select_count = self.select_count + 1
|
|
end
|
|
bookmark:updateBookmarkList(nil, -1)
|
|
else
|
|
bookmark.ui.link:addCurrentLocationToStack()
|
|
bookmark:gotoBookmark(item.page, item.pos0)
|
|
self.close_callback()
|
|
end
|
|
end
|
|
|
|
function bm_menu:onMenuHold(item)
|
|
bookmark:showBookmarkDetails(item)
|
|
return true
|
|
end
|
|
|
|
function bm_menu:toggleSelectMode()
|
|
if self.select_count then
|
|
self.select_count = nil
|
|
for _, v in ipairs(item_table) do
|
|
v.dim = nil
|
|
if v.after_curr_page then
|
|
v.mandatory_dim = true
|
|
end
|
|
end
|
|
self:setTitleBarLeftIcon("appbar.menu")
|
|
else
|
|
self.select_count = 0
|
|
self:setTitleBarLeftIcon("check")
|
|
end
|
|
bookmark:updateBookmarkList(nil, -1)
|
|
end
|
|
|
|
function bm_menu:onLeftButtonTap()
|
|
local bm_dialog, dialog_title
|
|
local buttons = {}
|
|
if self.select_count then
|
|
local actions_enabled = self.select_count > 0
|
|
local more_selections_enabled = self.select_count < #item_table
|
|
if actions_enabled then
|
|
dialog_title = T(N_("1 bookmark selected", "%1 bookmarks selected", self.select_count), self.select_count)
|
|
else
|
|
dialog_title = _("No bookmarks selected")
|
|
end
|
|
table.insert(buttons, {
|
|
{
|
|
text = _("Select all"),
|
|
enabled = more_selections_enabled,
|
|
callback = function()
|
|
UIManager:close(bm_dialog)
|
|
for _, v in ipairs(item_table) do
|
|
v.dim = true
|
|
end
|
|
self.select_count = #item_table
|
|
bookmark:updateBookmarkList(nil, -1)
|
|
end,
|
|
},
|
|
{
|
|
text = _("Select page"),
|
|
enabled = more_selections_enabled,
|
|
callback = function()
|
|
UIManager:close(bm_dialog)
|
|
local item_first = (bm_menu.page - 1) * bm_menu.perpage + 1
|
|
local item_last = math.min(item_first + bm_menu.perpage - 1, #item_table)
|
|
for i = item_first, item_last do
|
|
local v = item_table[i]
|
|
if v.dim == nil then
|
|
v.dim = true
|
|
self.select_count = self.select_count + 1
|
|
end
|
|
end
|
|
bookmark:updateBookmarkList(nil, -1)
|
|
end,
|
|
},
|
|
})
|
|
table.insert(buttons, {
|
|
{
|
|
text = _("Deselect all"),
|
|
enabled = actions_enabled,
|
|
callback = function()
|
|
UIManager:close(bm_dialog)
|
|
for _, v in ipairs(item_table) do
|
|
v.dim = nil
|
|
if v.after_curr_page then
|
|
v.mandatory_dim = true
|
|
end
|
|
end
|
|
self.select_count = 0
|
|
bookmark:updateBookmarkList(nil, -1)
|
|
end,
|
|
},
|
|
{
|
|
text = _("Delete note"),
|
|
enabled = actions_enabled,
|
|
callback = function()
|
|
UIManager:show(ConfirmBox:new{
|
|
text = _("Delete bookmark notes?"),
|
|
ok_text = _("Delete"),
|
|
ok_callback = function()
|
|
UIManager:close(bm_dialog)
|
|
for _, v in ipairs(item_table) do
|
|
if v.dim then
|
|
bookmark:deleteItemNote(v)
|
|
end
|
|
end
|
|
self:onClose()
|
|
bookmark:onShowBookmark()
|
|
end,
|
|
})
|
|
end,
|
|
},
|
|
})
|
|
table.insert(buttons, {
|
|
{
|
|
text = _("Exit select mode"),
|
|
callback = function()
|
|
UIManager:close(bm_dialog)
|
|
self:toggleSelectMode()
|
|
end,
|
|
},
|
|
{
|
|
text = _("Remove"),
|
|
enabled = actions_enabled and not bookmark.ui.highlight.select_mode,
|
|
callback = function()
|
|
UIManager:show(ConfirmBox:new{
|
|
text = _("Remove selected bookmarks?"),
|
|
ok_text = _("Remove"),
|
|
ok_callback = function()
|
|
UIManager:close(bm_dialog)
|
|
for i = #item_table, 1, -1 do
|
|
if item_table[i].dim then
|
|
bookmark:removeItem(item_table[i])
|
|
table.remove(item_table, i)
|
|
end
|
|
end
|
|
self.select_count = nil
|
|
self:setTitleBarLeftIcon("appbar.menu")
|
|
bookmark:updateBookmarkList(item_table, -1)
|
|
end,
|
|
})
|
|
end,
|
|
},
|
|
})
|
|
else -- select mode off
|
|
dialog_title = _("Filter by bookmark type")
|
|
local actions_enabled = #item_table > 0
|
|
local type_count = { highlight = 0, note = 0, bookmark = 0 }
|
|
for _, item in ipairs(bookmark.ui.annotation.annotations) do
|
|
local item_type = bookmark.getBookmarkType(item)
|
|
type_count[item_type] = type_count[item_type] + 1
|
|
end
|
|
local genBookmarkTypeButton = function(item_type)
|
|
return {
|
|
text = bookmark.display_prefix[item_type] ..
|
|
T(_("%1 (%2)"), bookmark.display_type[item_type], type_count[item_type]),
|
|
callback = function()
|
|
UIManager:close(bm_dialog)
|
|
self:onClose()
|
|
bookmark.match_table = { [item_type] = true }
|
|
bookmark:onShowBookmark()
|
|
end,
|
|
}
|
|
end
|
|
table.insert(buttons, {
|
|
{
|
|
text = _("All (reset filters)"),
|
|
callback = function()
|
|
UIManager:close(bm_dialog)
|
|
self:onClose()
|
|
bookmark:onShowBookmark()
|
|
end,
|
|
},
|
|
genBookmarkTypeButton("highlight"),
|
|
})
|
|
table.insert(buttons, {
|
|
genBookmarkTypeButton("bookmark"),
|
|
genBookmarkTypeButton("note"),
|
|
})
|
|
table.insert(buttons, {}) -- separator
|
|
table.insert(buttons, {
|
|
{
|
|
text = _("Filter by edited highlighted text"),
|
|
callback = function()
|
|
UIManager:close(bm_dialog)
|
|
bookmark:filterByEditedText()
|
|
end,
|
|
},
|
|
})
|
|
table.insert(buttons, {
|
|
{
|
|
text = _("Filter by highlight style"),
|
|
callback = function()
|
|
UIManager:close(bm_dialog)
|
|
bookmark:filterByHighlightStyle()
|
|
end,
|
|
},
|
|
})
|
|
table.insert(buttons, {}) -- separator
|
|
table.insert(buttons, {
|
|
{
|
|
text = _("Current page"),
|
|
callback = function()
|
|
UIManager:close(bm_dialog)
|
|
local idx
|
|
if bookmark.sorting_mode == "date" then
|
|
for i, v in ipairs(item_table) do
|
|
if v.datetime == curr_page_datetime then
|
|
idx = i
|
|
break
|
|
end
|
|
end
|
|
else -- "page"
|
|
idx = curr_page_index_filtered
|
|
end
|
|
bookmark:updateBookmarkList(nil, idx)
|
|
end,
|
|
},
|
|
{
|
|
text = _("Latest bookmark"),
|
|
enabled = actions_enabled
|
|
and not (bookmark.match_table or bookmark.show_edited_only or bookmark.show_drawer_only),
|
|
callback = function()
|
|
UIManager:close(bm_dialog)
|
|
local idx
|
|
if bookmark.sorting_mode == "date" then
|
|
idx = bookmark.is_reverse_sorting and 1 or #item_table
|
|
else -- "page"
|
|
idx = select(2, bookmark:getLatestBookmark())
|
|
idx = bookmark.is_reverse_sorting and #item_table - idx + 1 or idx
|
|
end
|
|
bookmark:updateBookmarkList(nil, idx)
|
|
bookmark:showBookmarkDetails(item_table[idx])
|
|
end,
|
|
},
|
|
})
|
|
table.insert(buttons, {
|
|
{
|
|
text = _("Select bookmarks"),
|
|
enabled = actions_enabled,
|
|
callback = function()
|
|
UIManager:close(bm_dialog)
|
|
self:toggleSelectMode()
|
|
end,
|
|
},
|
|
{
|
|
text = _("Search bookmarks"),
|
|
enabled = actions_enabled,
|
|
callback = function()
|
|
UIManager:close(bm_dialog)
|
|
bookmark:onSearchBookmark()
|
|
end,
|
|
},
|
|
})
|
|
end
|
|
bm_dialog = ButtonDialog:new{
|
|
title = dialog_title,
|
|
title_align = "center",
|
|
buttons = buttons,
|
|
}
|
|
UIManager:show(bm_dialog)
|
|
end
|
|
|
|
function bm_menu:onLeftButtonHold()
|
|
self:toggleSelectMode()
|
|
return true
|
|
end
|
|
|
|
bm_menu.close_callback = function()
|
|
UIManager:close(self.bookmark_menu)
|
|
self.bookmark_menu = nil
|
|
self.match_table = nil
|
|
self.show_edited_only = nil
|
|
self.show_drawer_only = nil
|
|
end
|
|
|
|
local idx
|
|
if bookmark.sorting_mode == "date" then -- show the most recent bookmark
|
|
idx = bookmark.is_reverse_sorting and 1 or #item_table
|
|
else -- "page", show bookmark in the current book page
|
|
idx = curr_page_index_filtered
|
|
end
|
|
self:updateBookmarkList(nil, idx)
|
|
UIManager:show(self.bookmark_menu)
|
|
return true
|
|
end
|
|
|
|
function ReaderBookmark:updateBookmarkList(item_table, item_number)
|
|
local bm_menu = self.bookmark_menu[1]
|
|
|
|
local title
|
|
if item_table then
|
|
title = T(_("Bookmarks (%1)"), #item_table)
|
|
end
|
|
|
|
local subtitle
|
|
if bm_menu.select_count then
|
|
subtitle = T(_("Selected: %1"), bm_menu.select_count)
|
|
else
|
|
if self.show_edited_only then
|
|
subtitle = _("Filter: edited highlighted text")
|
|
elseif self.show_drawer_only then
|
|
subtitle = _("Highlight style:") .. " " .. self.ui.highlight:getHighlightStyleString(self.show_drawer_only):lower()
|
|
elseif self.match_table then
|
|
if self.match_table.search_str then
|
|
subtitle = T(_("Query: %1"), self.match_table.search_str)
|
|
else
|
|
local types = {}
|
|
for type, type_string in pairs(self.display_type) do
|
|
if self.match_table[type] then
|
|
table.insert(types, type_string)
|
|
end
|
|
end
|
|
table.sort(types)
|
|
subtitle = #types > 0 and _("Bookmark type:") .. " " .. table.concat(types, ", ")
|
|
end
|
|
else
|
|
subtitle = ""
|
|
end
|
|
end
|
|
|
|
bm_menu:switchItemTable(title, item_table, item_number, nil, subtitle)
|
|
end
|
|
|
|
function ReaderBookmark:getBookmarkItemIndex(item)
|
|
if self.match_table or self.show_edited_only or self.show_drawer_only -- filtered
|
|
or self.sorting_mode ~= "page" then -- or item_table order does not match with annotations
|
|
return self.ui.annotation:getItemIndex(item)
|
|
end
|
|
if self.is_reverse_sorting then
|
|
return #self.ui.annotation.annotations - item.idx + 1
|
|
end
|
|
return item.idx
|
|
end
|
|
|
|
function ReaderBookmark:getBookmarkItemText(item)
|
|
local text
|
|
if item.type == "highlight" or self.items_text == "text" then
|
|
text = self.display_prefix[item.type] .. item.text_orig
|
|
else
|
|
if item.type == "note" and self.items_text == "note" then
|
|
text = self.display_prefix["note"] .. item.note
|
|
else
|
|
if item.type == "bookmark" then
|
|
text = self.display_prefix["bookmark"]
|
|
else -- it is a note, but we show the "highlight" prefix before the highlighted text
|
|
text = self.display_prefix["highlight"]
|
|
end
|
|
if self.items_text == "all" or self.items_text == "note" then
|
|
text = text .. item.text_orig
|
|
end
|
|
if item.note then
|
|
text = text .. "\u{2002}" .. self.display_prefix["note"] .. item.note
|
|
end
|
|
end
|
|
end
|
|
if self.sorting_mode == "date" then
|
|
text = item.datetime .. "\u{2002}" .. text
|
|
end
|
|
return text
|
|
end
|
|
|
|
function ReaderBookmark:_getDialogHeader(bookmark)
|
|
local page_str = bookmark.mandatory or self:getBookmarkPageString(bookmark.page)
|
|
return T(_("Page: %1"), page_str) .. " " .. T(_("Time: %1"), bookmark.datetime)
|
|
end
|
|
|
|
function ReaderBookmark:showBookmarkDetails(item)
|
|
local bm_menu = self.bookmark_menu[1]
|
|
local item_table = bm_menu.item_table
|
|
local text = self:_getDialogHeader(item) .. "\n\n"
|
|
local prefix = item.type == "bookmark" and self.display_prefix["bookmark"] or self.display_prefix["highlight"]
|
|
text = text .. prefix .. item.text_orig
|
|
if item.note then
|
|
text = text .. "\n\n" .. self.display_prefix["note"] .. item.note
|
|
end
|
|
local not_select_mode = not bm_menu.select_count and not self.ui.highlight.select_mode
|
|
|
|
local textviewer
|
|
local edit_details_callback = function()
|
|
self.details_updated = true
|
|
UIManager:close(textviewer)
|
|
self:showBookmarkDetails(item_table[item.idx])
|
|
end
|
|
local _showBookmarkDetails = function(idx)
|
|
UIManager:close(textviewer)
|
|
self:updateBookmarkList(nil, idx)
|
|
self:showBookmarkDetails(item_table[idx])
|
|
end
|
|
-- Refresh the bookmark list whenever details may have been edited
|
|
local _updateBookmarkList = function()
|
|
if self.details_updated then
|
|
self.details_updated = nil
|
|
if self.show_edited_only then
|
|
for i = #item_table, 1, -1 do
|
|
if not item_table[i].text_edited then
|
|
table.remove(item_table, i)
|
|
end
|
|
end
|
|
end
|
|
self:updateBookmarkList(item_table, -1)
|
|
end
|
|
end
|
|
|
|
textviewer = TextViewer:new{
|
|
title = T(_("Bookmark details (%1/%2)"), item.idx, #item_table),
|
|
text = text,
|
|
text_type = "bookmark",
|
|
close_callback = function()
|
|
_updateBookmarkList()
|
|
end,
|
|
buttons_table = {
|
|
{
|
|
{
|
|
text = _("Reset text"),
|
|
enabled = item.drawer and not_select_mode and item.text_edited or false,
|
|
callback = function()
|
|
self:setHighlightedText(item, nil, edit_details_callback)
|
|
end,
|
|
},
|
|
{
|
|
text = _("Edit text"),
|
|
enabled = item.drawer and not_select_mode or false,
|
|
callback = function()
|
|
self:editHighlightedText(item, edit_details_callback)
|
|
end,
|
|
},
|
|
},
|
|
{
|
|
{
|
|
text = _("Remove bookmark"),
|
|
enabled = not_select_mode,
|
|
callback = function()
|
|
UIManager:show(ConfirmBox:new{
|
|
text = _("Remove this bookmark?"),
|
|
ok_text = _("Remove"),
|
|
ok_callback = function()
|
|
self:removeItem(item)
|
|
table.remove(item_table, item.idx)
|
|
self:updateBookmarkList(item_table, -1)
|
|
UIManager:close(textviewer)
|
|
end,
|
|
})
|
|
end,
|
|
},
|
|
{
|
|
text = item.note and _("Edit note") or _("Add note"),
|
|
enabled = not bm_menu.select_count,
|
|
callback = function()
|
|
self:setBookmarkNote(item, nil, nil, edit_details_callback)
|
|
end,
|
|
},
|
|
},
|
|
{
|
|
{
|
|
text = _("Close"),
|
|
callback = function()
|
|
_updateBookmarkList()
|
|
UIManager:close(textviewer)
|
|
end,
|
|
},
|
|
{
|
|
text = _("Go to bookmark"),
|
|
enabled = not bm_menu.select_count,
|
|
callback = function()
|
|
UIManager:close(textviewer)
|
|
self.ui.link:addCurrentLocationToStack()
|
|
self:gotoBookmark(item.page, item.pos0)
|
|
bm_menu.close_callback()
|
|
end,
|
|
},
|
|
},
|
|
{
|
|
{
|
|
text = "▕◁",
|
|
enabled = item.idx > 1,
|
|
callback = function()
|
|
_showBookmarkDetails(1)
|
|
end,
|
|
},
|
|
{
|
|
text = "◁",
|
|
enabled = item.idx > 1,
|
|
callback = function()
|
|
_showBookmarkDetails(item.idx - 1)
|
|
end,
|
|
},
|
|
{
|
|
text = "▷",
|
|
enabled = item.idx < #item_table,
|
|
callback = function()
|
|
_showBookmarkDetails(item.idx + 1)
|
|
end,
|
|
},
|
|
{
|
|
text = "▷▏",
|
|
enabled = item.idx < #item_table,
|
|
callback = function()
|
|
_showBookmarkDetails(#item_table)
|
|
end,
|
|
},
|
|
},
|
|
}
|
|
}
|
|
UIManager:show(textviewer)
|
|
return true
|
|
end
|
|
|
|
function ReaderBookmark:setBookmarkNote(item_or_index, is_new_note, new_note, caller_callback)
|
|
local item, index
|
|
if self.bookmark_menu then
|
|
item = item_or_index -- in item_table
|
|
index = self:getBookmarkItemIndex(item)
|
|
else -- from Highlight
|
|
index = item_or_index
|
|
end
|
|
local annotation = self.ui.annotation.annotations[index]
|
|
local type_before = item and item.type or self.getBookmarkType(annotation)
|
|
local input_text = annotation.note
|
|
if new_note then
|
|
if input_text then
|
|
input_text = input_text .. "\n\n" .. new_note
|
|
else
|
|
input_text = new_note
|
|
end
|
|
end
|
|
local input_dialog
|
|
input_dialog = InputDialog:new{
|
|
title = _("Edit note"),
|
|
description = " " .. self:_getDialogHeader(annotation),
|
|
input = input_text,
|
|
allow_newline = true,
|
|
add_scroll_buttons = true,
|
|
use_available_height = true,
|
|
buttons = {
|
|
{
|
|
{
|
|
text = _("Cancel"),
|
|
id = "close",
|
|
callback = function()
|
|
-- NOTE: We'll want a full refresh on close, as the CRe highlight may extend past our own dimensions,
|
|
-- especially if we're closed separately from our VirtualKeyboard.
|
|
UIManager:close(input_dialog, "flashui")
|
|
if is_new_note then -- "Add note" called from highlight dialog and cancelled, remove saved highlight
|
|
self:removeItemByIndex(index)
|
|
end
|
|
end,
|
|
},
|
|
{
|
|
text = _("Paste"), -- insert highlighted text
|
|
callback = function()
|
|
input_dialog:addTextToInput(annotation.text)
|
|
end,
|
|
},
|
|
{
|
|
text = _("Save"),
|
|
is_enter_default = true,
|
|
callback = function()
|
|
local value = input_dialog:getInputText()
|
|
if value == "" then -- blank input deletes note
|
|
value = nil
|
|
end
|
|
annotation.note = value
|
|
local type_after = self.getBookmarkType(annotation)
|
|
if type_before ~= type_after then
|
|
if type_before == "highlight" then
|
|
self.ui:handleEvent(Event:new("AnnotationsModified",
|
|
{ annotation, nb_highlights_added = -1, nb_notes_added = 1 }))
|
|
else
|
|
self.ui:handleEvent(Event:new("AnnotationsModified",
|
|
{ annotation, nb_highlights_added = 1, nb_notes_added = -1 }))
|
|
end
|
|
end
|
|
UIManager:close(input_dialog)
|
|
if item then
|
|
item.note = value
|
|
item.type = type_after
|
|
item.text = self:getBookmarkItemText(item)
|
|
end
|
|
caller_callback()
|
|
end,
|
|
},
|
|
}
|
|
},
|
|
}
|
|
UIManager:show(input_dialog)
|
|
input_dialog:onShowKeyboard()
|
|
end
|
|
|
|
function ReaderBookmark:editHighlightedText(item, caller_callback)
|
|
local input_dialog
|
|
input_dialog = InputDialog:new{
|
|
title = _("Edit highlighted text"),
|
|
description = " " .. self:_getDialogHeader(item),
|
|
input = item.text_orig,
|
|
allow_newline = true,
|
|
add_scroll_buttons = true,
|
|
use_available_height = true,
|
|
buttons = {
|
|
{
|
|
{
|
|
text = _("Cancel"),
|
|
id = "close",
|
|
callback = function()
|
|
UIManager:close(input_dialog)
|
|
end,
|
|
},
|
|
{
|
|
text = _("Save"),
|
|
is_enter_default = true,
|
|
callback = function()
|
|
self:setHighlightedText(item, input_dialog:getInputText(), caller_callback)
|
|
UIManager:close(input_dialog)
|
|
end,
|
|
},
|
|
}
|
|
},
|
|
}
|
|
UIManager:show(input_dialog)
|
|
input_dialog:onShowKeyboard()
|
|
end
|
|
|
|
function ReaderBookmark:setHighlightedText(item, text, caller_callback)
|
|
local edited
|
|
if text then
|
|
edited = true
|
|
else -- reset to selected text
|
|
if self.ui.rolling then
|
|
text = self.ui.document:getTextFromXPointers(item.pos0, item.pos1)
|
|
else
|
|
text = self.ui.document:getTextFromPositions(item.pos0, item.pos1).text
|
|
end
|
|
end
|
|
local index = self:getBookmarkItemIndex(item)
|
|
self.ui.annotation.annotations[index].text = text
|
|
self.ui.annotation.annotations[index].text_edited = edited
|
|
item.text_orig = text
|
|
item.text = self:getBookmarkItemText(item)
|
|
item.text_edited = edited
|
|
caller_callback()
|
|
end
|
|
|
|
function ReaderBookmark:onSearchBookmark()
|
|
local input_dialog
|
|
local check_button_case, separator, check_button_bookmark, check_button_highlight, check_button_note
|
|
input_dialog = InputDialog:new{
|
|
title = _("Search bookmarks"),
|
|
buttons = {
|
|
{
|
|
{
|
|
text = _("Cancel"),
|
|
id = "close",
|
|
callback = function()
|
|
UIManager:close(input_dialog)
|
|
end,
|
|
},
|
|
{
|
|
text = _("Search"),
|
|
is_enter_default = true,
|
|
callback = function()
|
|
local search_str = input_dialog:getInputText()
|
|
if search_str == "" then
|
|
search_str = nil
|
|
else
|
|
if not check_button_case.checked then
|
|
search_str = Utf8Proc.lowercase(util.fixUtf8(search_str, "?"))
|
|
end
|
|
end
|
|
self.match_table = {
|
|
search_str = search_str,
|
|
bookmark = check_button_bookmark.checked,
|
|
highlight = check_button_highlight.checked,
|
|
note = check_button_note.checked,
|
|
case_sensitive = check_button_case.checked,
|
|
}
|
|
UIManager:close(input_dialog)
|
|
if self.bookmark_menu then -- from bookmark list
|
|
local bm_menu = self.bookmark_menu[1]
|
|
local item_table = bm_menu.item_table
|
|
for i = #item_table, 1, -1 do
|
|
if not self:doesBookmarkMatchTable(item_table[i]) then
|
|
table.remove(item_table, i)
|
|
end
|
|
end
|
|
self:updateBookmarkList(item_table)
|
|
else -- from main menu
|
|
self:onShowBookmark()
|
|
end
|
|
end,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
check_button_case = CheckButton:new{
|
|
text = " " .. _("Case sensitive"),
|
|
checked = false,
|
|
parent = input_dialog,
|
|
}
|
|
input_dialog:addWidget(check_button_case)
|
|
local separator_width = input_dialog:getAddedWidgetAvailableWidth()
|
|
separator = CenterContainer:new{
|
|
dimen = Geom:new{
|
|
w = separator_width,
|
|
h = 2 * Size.span.vertical_large,
|
|
},
|
|
LineWidget:new{
|
|
background = Blitbuffer.COLOR_DARK_GRAY,
|
|
dimen = Geom:new{
|
|
w = separator_width,
|
|
h = Size.line.medium,
|
|
}
|
|
},
|
|
}
|
|
input_dialog:addWidget(separator)
|
|
check_button_highlight = CheckButton:new{
|
|
text = " " .. self.display_prefix["highlight"] .. self.display_type["highlight"],
|
|
checked = true,
|
|
parent = input_dialog,
|
|
}
|
|
input_dialog:addWidget(check_button_highlight)
|
|
check_button_note = CheckButton:new{
|
|
text = " " .. self.display_prefix["note"] .. self.display_type["note"],
|
|
checked = true,
|
|
parent = input_dialog,
|
|
}
|
|
input_dialog:addWidget(check_button_note)
|
|
check_button_bookmark = CheckButton:new{
|
|
text = " " .. self.display_prefix["bookmark"] .. self.display_type["bookmark"],
|
|
checked = true,
|
|
parent = input_dialog,
|
|
}
|
|
input_dialog:addWidget(check_button_bookmark)
|
|
|
|
UIManager:show(input_dialog)
|
|
input_dialog:onShowKeyboard()
|
|
return true
|
|
end
|
|
|
|
function ReaderBookmark:filterByEditedText()
|
|
local bm_menu = self.bookmark_menu[1]
|
|
local item_table = bm_menu.item_table
|
|
for i = #item_table, 1, -1 do
|
|
if not item_table[i].text_edited then
|
|
table.remove(item_table, i)
|
|
end
|
|
end
|
|
self.show_edited_only = true
|
|
self:updateBookmarkList(item_table)
|
|
end
|
|
|
|
function ReaderBookmark:filterByHighlightStyle()
|
|
local filter_by_drawer_callback = function(drawer)
|
|
local bm_menu = self.bookmark_menu[1]
|
|
local item_table = bm_menu.item_table
|
|
for i = #item_table, 1, -1 do
|
|
if item_table[i].drawer ~= drawer then
|
|
table.remove(item_table, i)
|
|
end
|
|
end
|
|
self.show_drawer_only = drawer
|
|
self:updateBookmarkList(item_table)
|
|
end
|
|
self.ui.highlight:showHighlightStyleDialog(filter_by_drawer_callback)
|
|
end
|
|
|
|
function ReaderBookmark:doesBookmarkMatchTable(item)
|
|
if self.match_table[item.type] then
|
|
if self.match_table.search_str then
|
|
local text = item.text_orig
|
|
if item.note then -- search in the highlighted text and in the note
|
|
text = text .. "\u{FFFF}" .. item.note
|
|
end
|
|
if not self.match_table.case_sensitive then
|
|
text = Utf8Proc.lowercase(util.fixUtf8(text, "?"))
|
|
end
|
|
return text:find(self.match_table.search_str)
|
|
end
|
|
return true
|
|
end
|
|
end
|
|
|
|
return ReaderBookmark
|