diff --git a/frontend/apps/reader/modules/readerbookmark.lua b/frontend/apps/reader/modules/readerbookmark.lua index 58f441fc2..6c9179bc7 100644 --- a/frontend/apps/reader/modules/readerbookmark.lua +++ b/frontend/apps/reader/modules/readerbookmark.lua @@ -26,12 +26,11 @@ local T = require("ffi/util").template local ReaderBookmark = InputContainer:extend{ bookmarks_items_per_page_default = 14, - bookmarks = nil, -- 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" + note = "\u{F040}\u{2002}", -- "pencil" + bookmark = "\u{F097}\u{2002}", -- "empty bookmark" }, } @@ -91,7 +90,7 @@ function ReaderBookmark:addToMainMenu(menu_items) return self.ui.paging.bookmark_flipping_mode end, callback = function(touchmenu_instance) - self:toggleBookmarkBrowsingMode() + self.ui.paging:onToggleBookmarkFlipping() touchmenu_instance:closeMenu() end, } @@ -174,15 +173,6 @@ function ReaderBookmark:addToMainMenu(menu_items) G_reader_settings:flipNilOrTrue("bookmarks_items_reverse_sorting") end, }, - { - text = _("Add page number / timestamp to bookmark"), - checked_func = function() - return G_reader_settings:nilOrTrue("bookmarks_items_auto_text") - end, - callback = function() - G_reader_settings:flipNilOrTrue("bookmarks_items_auto_text") - end, - }, }, } menu_items.bookmark_search = { @@ -196,222 +186,361 @@ function ReaderBookmark:addToMainMenu(menu_items) } end -function ReaderBookmark:toggleBookmarkBrowsingMode() - self.ui:handleEvent(Event:new("ToggleBookmarkFlipping")) +-- page bookmarks, dogear + +function ReaderBookmark:onToggleBookmark() + self:toggleBookmark() + self.view.footer:onUpdateFooter(self.view.footer_visible) + self.view.dogear:onSetDogearVisibility(not self.view.dogear_visible) + UIManager:setDirty(self.view.dialog, "ui") + return true end -function ReaderBookmark:isBookmarkInPositionOrder(a, b) - if self.ui.paging then - if a.page == b.page then -- both bookmarks in the same page - if a.highlighted and b.highlighted then -- both are highlights, compare positions - local is_reflow = self.ui.document.configurable.text_wrap -- save reflow mode - -- reflow mode didn't set page in positions (in older bookmarks) - if not a.pos0.page then - a.pos0.page = a.page - a.pos1.page = a.page - end - if not b.pos0.page then - b.pos0.page = b.page - b.pos1.page = b.page - end - self.ui.document.configurable.text_wrap = 0 -- native positions - -- sort start and end positions of each highlight - local compare_pos, a_start, a_end, b_start, b_end, result - compare_pos = self.ui.document:comparePositions(a.pos0, a.pos1) > 0 - a_start = compare_pos and a.pos0 or a.pos1 - a_end = compare_pos and a.pos1 or a.pos0 - compare_pos = self.ui.document:comparePositions(b.pos0, b.pos1) > 0 - b_start = compare_pos and b.pos0 or b.pos1 - b_end = compare_pos and b.pos1 or b.pos0 - -- compare start positions - compare_pos = self.ui.document:comparePositions(a_start, b_start) - if compare_pos == 0 then -- both highlights with the same start, compare ends - result = self.ui.document:comparePositions(a_end, b_end) < 0 - else - result = compare_pos < 0 - end - self.ui.document.configurable.text_wrap = is_reflow -- restore reflow mode - return result - end - return a.highlighted -- have page bookmarks before highlights +function ReaderBookmark:toggleBookmark(pageno) + local pn_or_xp + if pageno then + if self.ui.rolling then + pn_or_xp = self.ui.document:getPageXPointer(pageno) + else + pn_or_xp = pageno end - return a.page > b.page else - local a_page = self.ui.document:getPageFromXPointer(a.page) - local b_page = self.ui.document:getPageFromXPointer(b.page) - if a_page == b_page then -- both bookmarks in the same page - local compare_xp = self.ui.document:compareXPointers(a.page, b.page) - if compare_xp then - if compare_xp == 0 then -- both bookmarks with the same start - if a.highlighted and b.highlighted then -- both are highlights, compare ends - compare_xp = self.ui.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 - end - return a.highlighted -- have page bookmarks before highlights - end - return compare_xp < 0 - end - -- if compare_xp is nil, some xpointer is invalid and will be sorted first to page 1 - logger.warn("Invalid xpointer in highlight:", a.page, b.page) + pn_or_xp = self:getCurrentPageNumber() + end + local index = self:getDogearBookmarkIndex(pn_or_xp) + if index then + local item = table.remove(self.ui.annotation.annotations, index) + self.ui:handleEvent(Event:new("BookmarkRemoved", item)) + 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 - return a_page > b_page + self:addItem({ + page = pn_or_xp, + datetime = os.date("%Y-%m-%d %H:%M:%S"), + text = text, + chapter = chapter, + }) end 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 bookmarks before highlights - return a.highlighted + 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 - return a_page > b_page end -function ReaderBookmark:isBookmarkInReversePageOrder(a, b) - -- The way this is used (by getNextBookmarkedPage(), iterating bookmarks - -- in reverse order), we want to skip highlights, but also the current - -- page: so we do not do any "a.page == b.page" check (not even with - -- a reverse logic than the one from above function). - return self:getBookmarkPageNumber(a) < self:getBookmarkPageNumber(b) +-- add, remove, update bookmark + +function ReaderBookmark:addItem(item) + local index = self.ui.annotation:addItem(item) + self.ui:handleEvent(Event:new("BookmarkAdded", item)) + self.view.footer:onUpdateFooter(self.view.footer_visible) + return index +end + +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:isBookmarkPageInPageOrder(a, b) - return a > self:getBookmarkPageNumber(b) +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("DelHighlight")) + elseif item_type == "note" then + self.ui:handleEvent(Event:new("DelNote")) + end + self.ui:handleEvent(Event:new("BookmarkRemoved", item)) + table.remove(self.ui.annotation.annotations, index) + self.view.footer:onUpdateFooter(self.view.footer_visible) end -function ReaderBookmark:isBookmarkPageInReversePageOrder(a, b) - return a < self:getBookmarkPageNumber(b) +function ReaderBookmark:deleteItemNote(item) + local index = self.ui.annotation:getItemIndex(item) + self.ui.annotation.annotations[index].note = nil + self.ui:handleEvent(Event:new("DelNote")) + self.ui:handleEvent(Event:new("AddHighlight")) end -function ReaderBookmark:fixBookmarkSort(config) - -- for backward compatibility, since previously bookmarks for credocuments - -- are not well sorted. We need to do a whole sorting for at least once. - -- 20220106: accurate sorting with isBookmarkInPositionOrder - if config:hasNot("bookmarks_sorted_20220106") then - table.sort(self.bookmarks, function(a, b) - return self:isBookmarkInPositionOrder(a, b) - 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:importSavedHighlight(config) - local textmarks = config:readSetting("highlight") or {} - -- import saved highlight once, because from now on highlight are added to - -- bookmarks when they are created. - if config:hasNot("highlights_imported") then - for page, marks in pairs(textmarks) do - for _, mark in ipairs(marks) do - local mark_page = self.ui.paging and page or mark.pos0 - -- highlights saved by some old versions don't have pos0 field - -- we just ignore those highlights - if mark_page then - self:addBookmark({ - page = mark_page, - datetime = mark.datetime, - notes = mark.text, - highlighted = true, - }) - 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:updateHighlightsIfNeeded(config) - -- adds "chapter" property to highlights and bookmarks already saved in the document - local version = config:readSetting("bookmarks_version") or 0 - if version >= 20200615 then - return +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 - for page, highlights in pairs(self.view.highlight.saved) do - for _, highlight in ipairs(highlights) do - local pn_or_xp = self.ui.paging and page or highlight.pos0 - highlight.chapter = self.ui.toc:getTocTitleByPage(pn_or_xp) +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 - for _, bookmark in ipairs(self.bookmarks) do - local pn_or_xp = (self.ui.rolling and bookmark.pos0) and bookmark.pos0 or bookmark.page - bookmark.chapter = self.ui.toc:getTocTitleByPage(pn_or_xp) +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:onReadSettings(config) - -- need to do this after initialization because checking xpointer - -- may cause segfaults before credocuments are inited. - self.ui:registerPostInitCallback(function() - self:fixBookmarkSort(config) - self:importSavedHighlight(config) - self:updateHighlightsIfNeeded(config) - end) +function ReaderBookmark:onGotoPreviousBookmark(pn_or_xp) + self:gotoBookmark(self:getPreviousBookmarkedPage(pn_or_xp)) + return true end -function ReaderBookmark:onSaveSettings() - self.ui.doc_settings:saveSetting("bookmarks_version", 20200615) - self.ui.doc_settings:makeTrue("bookmarks_sorted_20220106") - self.ui.doc_settings:makeTrue("highlights_imported") +function ReaderBookmark:onGotoNextBookmark(pn_or_xp) + self:gotoBookmark(self:getNextBookmarkedPage(pn_or_xp)) + return true end -function ReaderBookmark:onToggleBookmark() - self:toggleBookmark() - self.view.footer:onUpdateFooter(self.view.footer_visible) - self.view.dogear:onSetDogearVisibility(not self.view.dogear_visible) - UIManager:setDirty(self.view.dialog, "ui") +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.ui:getCurrentPage() + self:gotoBookmark(self:getPreviousBookmarkedPage(pn_or_xp)) return true end -function ReaderBookmark:isPageBookmarked(pn_or_xp) - local page = pn_or_xp or self:getCurrentPageNumber() - return self:getDogearBookmarkIndex(page) and true or false +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.ui:getCurrentPage() + self:gotoBookmark(self:getNextBookmarkedPage(pn_or_xp)) + return true end -function ReaderBookmark:setDogearVisibility(pn_or_xp) - self.view.dogear:onSetDogearVisibility(self:isPageBookmarked(pn_or_xp)) +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.ui:getCurrentPage() + self:gotoBookmark(self:getFirstBookmarkedPage(pn_or_xp)) + return true end -function ReaderBookmark:onPageUpdate(pageno) - local pn_or_xp = self.ui.paging and pageno or self.ui.document:getXPointer() - self:setDogearVisibility(pn_or_xp) +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.ui:getCurrentPage() + self:gotoBookmark(self:getLastBookmarkedPage(pn_or_xp)) + return true end -function ReaderBookmark:onPosUpdate(pos) - self:setDogearVisibility(self.ui.document:getXPointer()) +-- bookmarks misc info, helpers + +function ReaderBookmark:hasBookmarks() + return #self.ui.annotation.annotations > 0 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)) +function ReaderBookmark:getNumberOfBookmarks() + return #self.ui.annotation.annotations +end + +function ReaderBookmark:getNumberOfHighlightsAndNotes() -- for Statistics plugin + local highlights = 0 + local notes = 0 + for _, v in ipairs(self.ui.annotation.annotations) do + local bm_type = self:getBookmarkType(v) + if bm_type == "highlight" then + highlights = highlights + 1 + elseif bm_type == "note" then + notes = notes + 1 + end + end + return highlights, notes +end + +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" + else + return "highlight" + end + else + return "bookmark" end 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 + page = self.ui.pagemap:getXPointerPageLabel(page, true) + else + page = self.ui.document:getPageFromXPointer(page) + 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 + 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(match_table) self.show_edited_only = nil self.select_mode = false self.filtered_mode = match_table and true or false + -- build up item_table local item_table = {} - local is_reverse_sorting = G_reader_settings:nilOrTrue("bookmarks_items_reverse_sorting") + local is_reverse_sorting = G_reader_settings:nilOrTrue("bookmarks_items_reverse_sorting") -- page numbers descending local curr_page_num = self:getCurrentPageNumber() local curr_page_string = self:getBookmarkPageString(curr_page_num) - local curr_page_index = self:getBookmarkInsertionIndexBinary({page = curr_page_num}) - 1 - local num = #self.bookmarks + 1 - curr_page_index = is_reverse_sorting and curr_page_index or num - curr_page_index + local curr_page_index = self.ui.annotation:getInsertionIndex({page = curr_page_num}) + local num = #self.ui.annotation.annotations + 1 + curr_page_index = is_reverse_sorting and num - curr_page_index or curr_page_index local curr_page_index_filtered = curr_page_index - for i = 1, #self.bookmarks do - -- bookmarks are internally sorted by descending page numbers - local v = self.bookmarks[is_reverse_sorting and i or num - i] - if v.text == nil or v.text == "" then - v.text = self:getBookmarkAutoText(v) - end + for i = 1, #self.ui.annotation.annotations do + local v = self.ui.annotation.annotations[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 match_table or self:doesBookmarkMatchTable(item, match_table) then - item.text_orig = item.text or item.notes - item.text = self.display_prefix[item.type] .. item.text_orig + item.text = self.display_prefix[item.type] .. (item.note or item.text_orig) item.mandatory = self:getBookmarkPageString(item.page) if (not is_reverse_sorting and i >= curr_page_index) or (is_reverse_sorting and i <= curr_page_index) then item.after_curr_page = true @@ -486,13 +615,10 @@ function ReaderBookmark:onShowBookmark(match_table) function bm_menu:onMenuHold(item) local bm_view = bookmark._getDialogHeader(item) .. "\n\n" - if item.type == "bookmark" then - bm_view = bm_view .. item.text - else - bm_view = bm_view .. bookmark.display_prefix["highlight"] .. item.notes - if item.type == "note" then - bm_view = bm_view .. "\n\n" .. item.text - end + local prefix = item.type == "bookmark" and bookmark.display_prefix["bookmark"] or bookmark.display_prefix["highlight"] + bm_view = bm_view .. prefix .. item.text_orig + if item.note then + bm_view = bm_view .. "\n\n" .. bookmark.display_prefix["note"] .. item.note end local not_select_mode = not self.select_mode and not bookmark.ui.highlight.select_mode local textviewer @@ -504,7 +630,7 @@ function ReaderBookmark:onShowBookmark(match_table) { { text = _("Reset text"), - enabled = item.highlighted and not_select_mode and item.edited or false, + enabled = item.drawer and not_select_mode and item.text_edited or false, callback = function() UIManager:close(textviewer) bookmark:setHighlightedText(item) @@ -516,7 +642,7 @@ function ReaderBookmark:onShowBookmark(match_table) }, { text = _("Edit text"), - enabled = item.highlighted and not_select_mode or false, + enabled = item.drawer and not_select_mode or false, callback = function() UIManager:close(textviewer) bookmark:editHighlightedText(item) @@ -532,7 +658,7 @@ function ReaderBookmark:onShowBookmark(match_table) text = _("Remove this bookmark?"), ok_text = _("Remove"), ok_callback = function() - bookmark:removeHighlight(item) + bookmark:removeItem(item) table.remove(item_table, item.idx) bm_menu:switchItemTable(nil, item_table, -1) UIManager:close(textviewer) @@ -541,7 +667,7 @@ function ReaderBookmark:onShowBookmark(match_table) end, }, { - text = bookmark:getBookmarkNote(item) and _("Edit note") or _("Add note"), + text = item.note and _("Edit note") or _("Add note"), enabled = not self.select_mode, callback = function() bookmark:setBookmarkNote(item) @@ -650,18 +776,17 @@ function ReaderBookmark:onShowBookmark(match_table) end, }, { - text = _("Reset"), - enabled = G_reader_settings:isFalse("bookmarks_items_auto_text") - and actions_enabled and not bookmark.ui.highlight.select_mode, + text = _("Delete note"), + enabled = actions_enabled, callback = function() UIManager:show(ConfirmBox:new{ - text = _("Reset page number / timestamp?"), - ok_text = _("Reset"), + 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:removeBookmark(v, true) -- reset_auto_text_only=true + bookmark:deleteItemNote(v) end end bm_menu:onClose() @@ -690,7 +815,7 @@ function ReaderBookmark:onShowBookmark(match_table) UIManager:close(bm_dialog) for i = #item_table, 1, -1 do if item_table[i].dim then - bookmark:removeHighlight(item_table[i]) + bookmark:removeItem(item_table[i]) table.remove(item_table, i) end end @@ -785,7 +910,7 @@ function ReaderBookmark:onShowBookmark(match_table) callback = function() UIManager:close(bm_dialog) local _, idx = bookmark:getLatestBookmark() - idx = is_reverse_sorting and idx or #item_table - idx + 1 + idx = is_reverse_sorting and #item_table - idx + 1 or idx bm_menu:switchItemTable(nil, item_table, idx) bm_menu:onMenuHold(item_table[idx]) end, @@ -829,7 +954,6 @@ function ReaderBookmark:onShowBookmark(match_table) self.refresh = function() bm_menu:updateItems() - self:onSaveSettings() end bm_menu:switchItemTable(nil, item_table, curr_page_index_filtered) @@ -837,186 +961,32 @@ function ReaderBookmark:onShowBookmark(match_table) return true end -function ReaderBookmark:isBookmarkMatch(item, pn_or_xp) - if self.ui.paging then - return item.page == pn_or_xp - else - return self.ui.document:getPageFromXPointer(item.page) == self.ui.document:getPageFromXPointer(pn_or_xp) - end -end - -function ReaderBookmark:getDogearBookmarkIndex(pn_or_xp) - local _middle - local _start, _end = 1, #self.bookmarks - while _start <= _end do - _middle = math.floor((_start + _end)/2) - local v = self.bookmarks[_middle] - if not v.highlighted and self:isBookmarkMatch(v, 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 - -function ReaderBookmark:isBookmarkSame(item1, item2) - if item1.notes ~= item2.notes then return false end - if self.ui.paging then - return item1.pos0 and item1.pos1 and item2.pos0 and item2.pos1 - and item1.pos0.page == item2.pos0.page - and item1.pos0.x == item2.pos0.x and item1.pos0.y == item2.pos0.y - and item1.pos1.x == item2.pos1.x and item1.pos1.y == item2.pos1.y - else - return item1.page == item2.page - and item1.pos0 == item2.pos0 and item1.pos1 == item2.pos1 - end -end - -function ReaderBookmark:getBookmarkIndexFullScan(item) - for i, v in ipairs(self.bookmarks) do - if item.datetime == v.datetime then - return i - end - end -end - -function ReaderBookmark:getBookmarkIndexBinarySearch(item) - local _start, _end, _middle = 1, #self.bookmarks - while _start <= _end do - _middle = bit.rshift(_start + _end, 1) - local v = self.bookmarks[_middle] - if item.datetime == v.datetime and item.page == v.page then - return _middle - elseif self:isBookmarkInPositionOrder(item, v) then - _end = _middle - 1 - else - _start = _middle + 1 - end - end -end - -function ReaderBookmark:getBookmarkInsertionIndexBinary(item) - local _start, _end, _middle, direction = 1, #self.bookmarks, 1, 0 - while _start <= _end do - _middle = bit.rshift(_start + _end, 1) - if self:isBookmarkInPositionOrder(item, self.bookmarks[_middle]) then - _end, direction = _middle - 1, 0 - else - _start, direction = _middle + 1, 1 - end - end - return _middle + direction -end - -function ReaderBookmark:addBookmark(item) - local index = self:getBookmarkInsertionIndexBinary(item) - table.insert(self.bookmarks, index, item) - self.ui:handleEvent(Event:new("BookmarkAdded", item)) - self.view.footer:onUpdateFooter(self.view.footer_visible) -end - -function ReaderBookmark:isBookmarkAdded(item) - -- binary search of sorted bookmarks (without check of datetime, for dictquicklookup) - local _middle - local _start, _end = 1, #self.bookmarks - while _start <= _end do - _middle = math.floor((_start + _end)/2) - if self:isBookmarkSame(item, self.bookmarks[_middle]) then - return true - end - if self:isBookmarkInPageOrder(item, self.bookmarks[_middle]) then - _end = _middle - 1 - else - _start = _middle + 1 - end - end - return false -end - -function ReaderBookmark:removeHighlight(item) - if item.pos0 then - self.ui:handleEvent(Event:new("Unhighlight", item)) - else -- dogear bookmark, update it in case we removed a bookmark for current page - self:removeBookmark(item) - self:setDogearVisibility(self:getCurrentPageNumber()) - end -end - -function ReaderBookmark:removeBookmark(item, reset_auto_text_only) - -- If we haven't found item in binary search, it may be because there are multiple - -- bookmarks on the same page, and the above binary search decided to - -- not search on one side of one it found on page, where item could be. - -- Fallback to do a full scan. - local index = self:getBookmarkIndexBinarySearch(item) or self:getBookmarkIndexFullScan(item) - local bookmark = self.bookmarks[index] - if reset_auto_text_only then - if self:isBookmarkAutoText(bookmark) then - bookmark.text = nil - end - else - local bookmark_type = item.type or self:getBookmarkType(bookmark) - if bookmark_type == "highlight" then - self.ui:handleEvent(Event:new("DelHighlight")) - elseif bookmark_type == "note" then - self.ui:handleEvent(Event:new("DelNote")) - end - self.ui:handleEvent(Event:new("BookmarkRemoved", bookmark)) - table.remove(self.bookmarks, index) - self.view.footer:onUpdateFooter(self.view.footer_visible) - end -end - -function ReaderBookmark:updateBookmark(item) - -- Called from Highlights when changing highlight boundaries (positions). - -- Binary search cannot be used. - local index = self:getBookmarkIndexFullScan(item) - local v = self.bookmarks[index] - local bookmark_before = util.tableDeepCopy(v) - local is_auto_text_before = self:isBookmarkAutoText(v) - v.page = item.updated_highlight.pos0 - v.pos0 = item.updated_highlight.pos0 - v.pos1 = item.updated_highlight.pos1 - v.notes = item.updated_highlight.text - v.datetime = item.updated_highlight.datetime - v.chapter = item.updated_highlight.chapter - if is_auto_text_before then - v.text = self:getBookmarkAutoText(v) - end - self.ui:handleEvent(Event:new("BookmarkUpdated", v, bookmark_before)) - self:onSaveSettings() -end - function ReaderBookmark._getDialogHeader(bookmark) return T(_("Page: %1"), bookmark.mandatory) .. " " .. T(_("Time: %1"), bookmark.datetime) end -function ReaderBookmark:setBookmarkNote(item, from_highlight, is_new_note, new_text) - local bookmark +function ReaderBookmark:setBookmarkNote(item_or_index, is_new_note, new_note) + local item, index + local from_highlight = type(item_or_index) == "number" if from_highlight then - local bm = self.bookmarks[self:getBookmarkIndexFullScan(item)] - if bm.text == nil or bm.text == "" then - bm.text = self:getBookmarkAutoText(bm) - end - bookmark = util.tableDeepCopy(bm) - bookmark.type = self:getBookmarkType(bookmark) - bookmark.text_orig = bm.text or bm.notes - bookmark.mandatory = self:getBookmarkPageString(bm.page) + index = item_or_index else - bookmark = item + item = item_or_index -- in item_table + index = self.filtered_mode and self.ui.annotation:getItemIndex(item) or item.idx end - local input_text = self:getBookmarkNote(bookmark) and bookmark.text_orig or nil - if new_text then + 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_text + input_text = input_text .. "\n\n" .. new_note else - input_text = new_text + input_text = new_note end end self.input = InputDialog:new{ title = _("Edit note"), - description = " " .. self._getDialogHeader(bookmark), + description = " " .. self._getDialogHeader(annotation), input = input_text, allow_newline = true, add_scroll_buttons = true, @@ -1028,16 +998,15 @@ function ReaderBookmark:setBookmarkNote(item, from_highlight, is_new_note, new_t id = "close", callback = function() UIManager:close(self.input) - if is_new_note then -- "Add note" cancelled, remove saved highlight - local index = self:getBookmarkIndexBinarySearch(bookmark) or self:getBookmarkIndexFullScan(bookmark) - self:removeHighlight(self.bookmarks[index]) + 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 (auto-text) + text = _("Paste"), -- insert highlighted text callback = function() - self.input._input_widget:addChars(bookmark.text_orig) + self.input._input_widget:addChars(annotation.text) end, }, { @@ -1045,14 +1014,13 @@ function ReaderBookmark:setBookmarkNote(item, from_highlight, is_new_note, new_t is_enter_default = true, callback = function() local value = self.input:getInputText() - if value == "" then -- blank input resets the 'text' field to auto-text - value = self:getBookmarkAutoText(bookmark) + if value == "" then -- blank input deletes note + value = nil end - bookmark.text = value or bookmark.notes - local bookmark_type = bookmark.type - bookmark.type = self:getBookmarkType(bookmark) - if bookmark_type ~= bookmark.type then - if bookmark_type == "highlight" then + 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("DelHighlight")) self.ui:handleEvent(Event:new("AddNote")) else @@ -1060,12 +1028,9 @@ function ReaderBookmark:setBookmarkNote(item, from_highlight, is_new_note, new_t self.ui:handleEvent(Event:new("DelNote")) end end - local index = self:getBookmarkIndexBinarySearch(bookmark) or self:getBookmarkIndexFullScan(bookmark) - local bm = self.bookmarks[index] - bm.text = value - self.ui:handleEvent(Event:new("BookmarkEdited", bm)) - if bookmark.highlighted then - self.ui.highlight:writePdfAnnotation("content", bookmark.page, bookmark, bookmark.text) + self.ui:handleEvent(Event:new("BookmarkEdited", annotation)) + if annotation.drawer then + self.ui.highlight:writePdfAnnotation("content", annotation.page, annotation, annotation.note) end UIManager:close(self.input) if from_highlight then @@ -1073,8 +1038,9 @@ function ReaderBookmark:setBookmarkNote(item, from_highlight, is_new_note, new_t UIManager:setDirty(self.dialog, "ui") -- refresh note marker end else - bookmark.text_orig = bookmark.text - bookmark.text = self.display_prefix[bookmark.type] .. bookmark.text + item.note = value + item.text = self.display_prefix[type_after] .. (value or item.text_orig) + item.type = type_after self.refresh() end end, @@ -1091,7 +1057,7 @@ function ReaderBookmark:editHighlightedText(item) input_dialog = InputDialog:new{ title = _("Edit highlighted text"), description = " " .. self._getDialogHeader(item), - input = item.notes, + input = item.text_orig, allow_newline = true, add_scroll_buttons = true, use_available_height = true, @@ -1130,24 +1096,14 @@ function ReaderBookmark:setHighlightedText(item, text) text = self.ui.document:getTextFromPositions(item.pos0, item.pos1).text end end - -- highlight - local hl = self.ui.highlight:getHighlightByDatetime(item.datetime) - hl.text = text - hl.edited = edited - -- bookmark - local index = self:getBookmarkIndexBinarySearch(item) or self:getBookmarkIndexFullScan(item) - local bm = self.bookmarks[index] - local is_auto_text_before = self:isBookmarkAutoText(bm) - bm.notes = text - if is_auto_text_before then - bm.text = self:getBookmarkAutoText(bm) - end - bm.edited = edited + -- annotation + local index = (self.filtered_mode or self.show_edited_only) and self.ui.annotation:getItemIndex(item) or item.idx + self.ui.annotation.annotations[index].text = text + self.ui.annotation.annotations[index].text_edited = edited -- item table - item.notes = text - item.text_orig = bm.text or text - item.text = self.display_prefix[item.type] .. item.text_orig - item.edited = edited + item.text_orig = text + item.text = self.display_prefix[item.type] .. (item.note or text) + item.text_edited = edited if edited then self.refresh() end @@ -1250,7 +1206,7 @@ end function ReaderBookmark:filterByEditedText(bm_menu) self.show_edited_only = true for i = #bm_menu.item_table, 1, -1 do - if not bm_menu.item_table[i].edited then + if not bm_menu.item_table[i].text_edited then table.remove(bm_menu.item_table, i) end end @@ -1272,14 +1228,13 @@ end function ReaderBookmark:doesBookmarkMatchTable(item, match_table) if match_table.drawer then -- filter by highlight style - return item.highlighted - and match_table.drawer == self.ui.highlight:getHighlightByDatetime(item.datetime).drawer + return match_table.drawer == item.drawer end if match_table[item.type] then if match_table.search_str then - local text = item.notes - if item.text then -- search in the highlighted text and in the note - text = text .. "\u{FFFF}" .. item.text + 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 match_table.case_sensitive then text = Utf8Proc.lowercase(util.fixUtf8(text, "?")) @@ -1290,261 +1245,4 @@ function ReaderBookmark:doesBookmarkMatchTable(item, match_table) end end -function ReaderBookmark:toggleBookmark(pageno) - local pn_or_xp - 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 - self.ui:handleEvent(Event:new("BookmarkRemoved", self.bookmarks[index])) - table.remove(self.bookmarks, index) - else - -- build notes from TOC - local notes = self.ui.toc:getTocTitleByPage(pn_or_xp) - local chapter_name = notes - if notes ~= "" then - -- @translators In which chapter title (%1) a note is found. - notes = T(_("in %1"), notes) - end - self:addBookmark({ - page = pn_or_xp, - datetime = os.date("%Y-%m-%d %H:%M:%S"), - notes = notes, - chapter = chapter_name, - }) - end -end - -function ReaderBookmark:getPreviousBookmarkedPage(pn_or_xp) - logger.dbg("go to next bookmark from", pn_or_xp) - for i = 1, #self.bookmarks do - if self:isBookmarkInPageOrder({page = pn_or_xp}, self.bookmarks[i]) then - return self.bookmarks[i].page - end - end -end - -function ReaderBookmark:getNextBookmarkedPage(pn_or_xp) - logger.dbg("go to next bookmark from", pn_or_xp) - for i = #self.bookmarks, 1, -1 do - if self:isBookmarkInReversePageOrder({page = pn_or_xp}, self.bookmarks[i]) then - return self.bookmarks[i].page - end - end -end - -function ReaderBookmark:getPreviousBookmarkedPageFromPage(pn_or_xp) - logger.dbg("go to next bookmark from", pn_or_xp) - for i = 1, #self.bookmarks do - if self:isBookmarkPageInPageOrder(pn_or_xp, self.bookmarks[i]) then - return self.bookmarks[i].page - end - end -end - -function ReaderBookmark:getNextBookmarkedPageFromPage(pn_or_xp) - logger.dbg("go to next bookmark from", pn_or_xp) - for i = #self.bookmarks, 1, -1 do - if self:isBookmarkPageInReversePageOrder(pn_or_xp, self.bookmarks[i]) then - return self.bookmarks[i].page - end - end -end - -function ReaderBookmark:getFirstBookmarkedPageFromPage(pn_or_xp) - if #self.bookmarks > 0 then - local first = #self.bookmarks - if self:isBookmarkPageInPageOrder(pn_or_xp, self.bookmarks[first]) then - return self.bookmarks[first].page - end - end -end - -function ReaderBookmark:getLastBookmarkedPageFromPage(pn_or_xp) - if #self.bookmarks > 0 then - local last = 1 - if self:isBookmarkPageInReversePageOrder(pn_or_xp, self.bookmarks[last]) then - return self.bookmarks[last].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:onGotoNextBookmarkFromPage(add_current_location_to_stack) - if add_current_location_to_stack ~= false then -- nil or true - self.ui.link:addCurrentLocationToStack() - end - self:gotoBookmark(self:getNextBookmarkedPageFromPage(self.ui:getCurrentPage())) - 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 - self:gotoBookmark(self:getPreviousBookmarkedPageFromPage(self.ui:getCurrentPage())) - 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 - self:gotoBookmark(self:getFirstBookmarkedPageFromPage(self.ui:getCurrentPage())) - 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 - self:gotoBookmark(self:getLastBookmarkedPageFromPage(self.ui:getCurrentPage())) - return true -end - -function ReaderBookmark:getLatestBookmark() - local latest_bookmark, latest_bookmark_idx - local latest_bookmark_datetime = "0" - for i, v in ipairs(self.bookmarks) 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:hasBookmarks() - return self.bookmarks and #self.bookmarks > 0 -end - -function ReaderBookmark:getNumberOfBookmarks() - return self.bookmarks and #self.bookmarks or 0 -end - -function ReaderBookmark:getNumberOfHighlightsAndNotes() -- for Statistics plugin - local highlights = 0 - local notes = 0 - for _, v in ipairs(self.bookmarks) do - local bm_type = self:getBookmarkType(v) - if bm_type == "highlight" then - highlights = highlights + 1 - elseif bm_type == "note" then - notes = notes + 1 - end - end - return highlights, notes -end - -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.highlighted then - if self:isBookmarkAutoText(bookmark) then - return "highlight" - else - return "note" - end - else - return "bookmark" - end -end - -function ReaderBookmark:getBookmarkPageString(page) - if self.ui.rolling then - if self.ui.pagemap and self.ui.pagemap:wantsPageLabels() then - page = self.ui.pagemap:getXPointerPageLabel(page, true) - else - page = self.ui.document:getPageFromXPointer(page) - 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 - end - end - return tostring(page) -end - -function ReaderBookmark:getBookmarkedPages() - local pages = {} - for _, bm in ipairs(self.bookmarks) 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:getBookmarkAutoText(bookmark, force_auto_text) - if G_reader_settings:nilOrTrue("bookmarks_items_auto_text") or force_auto_text then - local page = self:getBookmarkPageString(bookmark.page) - return T(_("Page %1 %2 @ %3"), page, bookmark.notes, bookmark.datetime) - else - -- When not auto_text, and 'text' would be identical to 'notes', leave 'text' be nil - return nil - end -end - ---- Check if the 'text' field has not been edited manually -function ReaderBookmark:isBookmarkAutoText(bookmark) - return (bookmark.text == nil) or (bookmark.text == "") or (bookmark.text == bookmark.notes) - or (bookmark.text == self:getBookmarkAutoText(bookmark, true)) -end - -function ReaderBookmark:getBookmarkNote(item) - for _, bm in ipairs(self.bookmarks) do - if item.datetime == bm.datetime then - return not self:isBookmarkAutoText(bm) and bm.text - end - end -end - -function ReaderBookmark:getBookmarkForHighlight(item) - return self.bookmarks[self:getBookmarkIndexFullScan(item)] -end - -function ReaderBookmark:getAnnotationAutoText(annotation, force_auto_text) - if force_auto_text or G_reader_settings:nilOrTrue("bookmarks_items_auto_text") then - local page = self:getBookmarkPageString(annotation.page) - return T(_("Page %1 %2 @ %3"), page, annotation.text, annotation.datetime) - end -end - ---- Check if the note has not been edited manually -function ReaderBookmark:isAnnotationAutoText(annotation) - local note = annotation.note - return (note == nil) or (note == annotation.text) or (note == self:getAnnotationAutoText(annotation, true)) -end - return ReaderBookmark