2
0
mirror of https://github.com/koreader/koreader synced 2024-11-10 01:10:34 +00:00
koreader/frontend/apps/reader/modules/readerhighlight.lua

1056 lines
41 KiB
Lua

local ButtonDialog = require("ui/widget/buttondialog")
local ConfirmBox = require("ui/widget/confirmbox")
local Device = require("device")
local Event = require("ui/event")
local InfoMessage = require("ui/widget/infomessage")
local InputContainer = require("ui/widget/container/inputcontainer")
local TimeVal = require("ui/timeval")
local Translator = require("ui/translator")
local UIManager = require("ui/uimanager")
local logger = require("logger")
local _ = require("gettext")
local T = require("ffi/util").template
local Screen = Device.screen
local ReaderHighlight = InputContainer:new{}
function ReaderHighlight:init()
self.ui:registerPostInitCallback(function()
self.ui.menu:registerToMainMenu(self)
end)
end
function ReaderHighlight:setupTouchZones()
-- deligate gesture listener to readerui
self.ges_events = {}
self.onGesture = nil
if not Device:isTouchDevice() then return end
self.ui:registerTouchZones({
{
id = "readerhighlight_tap",
ges = "tap",
screen_zone = {
ratio_x = 0, ratio_y = 0, ratio_w = 1, ratio_h = 1,
},
overrides = { 'tap_forward', 'tap_backward', 'readermenu_tap', 'readerconfigmenu_tap', },
handler = function(ges) return self:onTap(nil, ges) end
},
{
id = "readerhighlight_hold",
ges = "hold",
screen_zone = {
ratio_x = 0, ratio_y = 0, ratio_w = 1, ratio_h = 1,
},
handler = function(ges) return self:onHold(nil, ges) end
},
{
id = "readerhighlight_hold_release",
ges = "hold_release",
screen_zone = {
ratio_x = 0, ratio_y = 0, ratio_w = 1, ratio_h = 1,
},
handler = function() return self:onHoldRelease() end
},
{
id = "readerhighlight_hold_pan",
ges = "hold_pan",
rate = 2.0,
screen_zone = {
ratio_x = 0, ratio_y = 0, ratio_w = 1, ratio_h = 1,
},
handler = function(ges) return self:onHoldPan(nil, ges) end
},
})
end
function ReaderHighlight:onReaderReady()
self:setupTouchZones()
end
function ReaderHighlight:addToMainMenu(menu_items)
-- insert table to main reader menu
menu_items.highlight_options = {
text = _("Highlighting"),
sub_item_table = self:genHighlightDrawerMenu(),
}
menu_items.translation_settings = Translator:genSettingsMenu()
end
local highlight_style = {
lighten = _("Lighten"),
underscore = _("Underline"),
invert = _("Invert"),
}
function ReaderHighlight:genHighlightDrawerMenu()
local get_highlight_style = function(style)
return {
text = highlight_style[style],
checked_func = function()
return self.view.highlight.saved_drawer == style
end,
enabled_func = function()
return not self.view.highlight.disabled
end,
callback = function()
self.view.highlight.saved_drawer = style
end
}
end
return {
{
text = _("Allow highlighting"),
checked_func = function()
return not self.view.highlight.disabled
end,
callback = function()
self.view.highlight.disabled = not self.view.highlight.disabled
end,
hold_callback = function(touchmenu_instance)
self:makeDefault(not self.view.highlight.disabled)
end,
separator = true,
},
get_highlight_style("lighten"),
get_highlight_style("underscore"),
get_highlight_style("invert"),
}
end
-- Returns a unique id, that can be provided on delayed call to :clear(id)
-- to ensure current highlight has not already been cleared, and that we
-- are not going to clear a new highlight
function ReaderHighlight:getClearId()
self.clear_id = TimeVal.now() -- can act as a unique id
return self.clear_id
end
function ReaderHighlight:clear(clear_id)
if clear_id then -- should be provided by delayed call to clear()
if clear_id ~= self.clear_id then
-- if clear_id is no more valid, highlight has already been
-- cleared since this clear_id was given
return
end
end
self.clear_id = nil -- invalidate id
if self.ui.document.info.has_pages then
self.view.highlight.temp = {}
else
self.ui.document:clearSelection()
end
if self.restore_page_mode_func then
self.restore_page_mode_func()
self.restore_page_mode_func = nil
end
self.selected_text_start_xpointer = nil
if self.hold_pos then
self.hold_pos = nil
self.selected_text = nil
UIManager:setDirty(self.dialog, "ui")
return true
end
end
function ReaderHighlight:onClearHighlight()
self:clear()
return true
end
function ReaderHighlight:onTap(_, ges)
if not self:clear() then
if self.ui.document.info.has_pages then
return self:onTapPageSavedHighlight(ges)
else
return self:onTapXPointerSavedHighlight(ges)
end
end
end
local function inside_box(pos, box)
if pos then
local x, y = pos.x, pos.y
if box.x <= x and box.y <= y
and box.x + box.w >= x
and box.y + box.h >= y then
return true
end
end
end
function ReaderHighlight:onTapPageSavedHighlight(ges)
local pages = self.view:getCurrentPageList()
local pos = self.view:screenToPageTransform(ges.pos)
for key, page in pairs(pages) do
local items = self.view.highlight.saved[page]
if items then
for i = 1, #items do
local pos0, pos1 = items[i].pos0, items[i].pos1
local boxes = self.ui.document:getPageBoxesFromPositions(page, pos0, pos1)
if boxes then
for index, box in pairs(boxes) do
if inside_box(pos, box) then
logger.dbg("Tap on highlight")
return self:onShowHighlightDialog(page, i)
end
end
end
end
end
end
end
function ReaderHighlight:onTapXPointerSavedHighlight(ges)
-- Getting screen boxes is done for each tap on screen (changing pages,
-- showing menu...). We might want to cache these boxes per page (and
-- clear that cache when page layout change or highlights are added
-- or removed).
local cur_page
-- In scroll mode, we'll need to check for highlights in previous or next
-- page too as some parts of them may be displayed
local neighbour_pages = self.view.view_mode ~= "page" and 1 or 0
local pos = self.view:screenToPageTransform(ges.pos)
for page, _ in pairs(self.view.highlight.saved) do
local items = self.view.highlight.saved[page]
if items then
for i = 1, #items do
if not cur_page then
cur_page = self.ui.document:getPageFromXPointer(self.ui.document:getXPointer())
end
local pos0, pos1 = items[i].pos0, items[i].pos1
-- document:getScreenBoxesFromPositions() is expensive, so we
-- first check this item is on current page
local page0 = self.ui.document:getPageFromXPointer(pos0)
local page1 = self.ui.document:getPageFromXPointer(pos1)
local start_page = math.min(page0, page1)
local end_page = math.max(page0, page1)
-- In scroll mode, we may be displaying cur_page and cur_page+1, so
-- we have to check the highlight start_page is <= cur_page+1.
-- Same thinking with highlight's end_page >= cur_page-1 as we may
-- be displaying a part of cur_page-1.
-- (A highlight starting on cur_page-17 and ending on cur_page+13 is
-- a highlight to consider)
if start_page <= cur_page + neighbour_pages and end_page >= cur_page - neighbour_pages then
local boxes = self.ui.document:getScreenBoxesFromPositions(pos0, pos1, true) -- get_segments=true
if boxes then
for index, box in pairs(boxes) do
if inside_box(pos, box) then
logger.dbg("Tap on highlight")
return self:onShowHighlightDialog(page, i)
end
end
end
end
end
end
end
end
function ReaderHighlight:updateHighlight(page, index, side, direction, move_by_char)
if self.ui.document.info.has_pages then -- we do this only if it's epub file
return
end
local highlight = self.view.highlight.saved[page][index]
local highlight_time = highlight.datetime
local highlight_beginning = highlight.pos0
local highlight_end = highlight.pos1
if side == 0 then -- we move pos0
local updated_highlight_beginning
if direction == 1 then -- move highlight to the right
if move_by_char then
updated_highlight_beginning = self.ui.document:getNextVisibleChar(highlight_beginning)
else
updated_highlight_beginning = self.ui.document:getNextVisibleWordStart(highlight_beginning)
end
else -- move highlight to the left
if move_by_char then
updated_highlight_beginning = self.ui.document:getPrevVisibleChar(highlight_beginning)
else
updated_highlight_beginning = self.ui.document:getPrevVisibleWordStart(highlight_beginning)
end
end
if updated_highlight_beginning then
local order = self.ui.document:compareXPointers(updated_highlight_beginning, highlight_end)
if order and order > 0 then -- only if beginning did not go past end
self.view.highlight.saved[page][index].pos0 = updated_highlight_beginning
end
end
else -- we move pos1
local updated_highlight_end
if direction == 1 then -- move highlight to the right
if move_by_char then
updated_highlight_end = self.ui.document:getNextVisibleChar(highlight_end)
else
updated_highlight_end = self.ui.document:getNextVisibleWordEnd(highlight_end)
end
else -- move highlight to the left
if move_by_char then
updated_highlight_end = self.ui.document:getPrevVisibleChar(highlight_end)
else
updated_highlight_end = self.ui.document:getPrevVisibleWordEnd(highlight_end)
end
end
if updated_highlight_end then
local order = self.ui.document:compareXPointers(highlight_beginning, updated_highlight_end)
if order and order > 0 then -- only if end did not go back past beginning
self.view.highlight.saved[page][index].pos1 = updated_highlight_end
end
end
end
local new_beginning = self.view.highlight.saved[page][index].pos0
local new_end = self.view.highlight.saved[page][index].pos1
local new_text = self.ui.document:getTextFromXPointers(new_beginning, new_end)
self.view.highlight.saved[page][index].text = new_text
local new_highlight = self.view.highlight.saved[page][index]
self.ui.bookmark:updateBookmark({
page = highlight_beginning,
datetime = highlight_time,
updated_highlight = new_highlight
}, true)
UIManager:setDirty(self.dialog, "ui")
end
function ReaderHighlight:onShowHighlightDialog(page, index)
local buttons = {
{
{
text = _("Delete"),
callback = function()
self:deleteHighlight(page, index)
-- other part outside of the dialog may be dirty
UIManager:close(self.edit_highlight_dialog, "ui")
end,
},
{
text = _("Edit"),
callback = function()
self:editHighlight(page, index)
UIManager:close(self.edit_highlight_dialog)
end,
},
}
}
if not self.ui.document.info.has_pages then
table.insert(buttons, {
{
text = "◁⇱",
callback = function()
self:updateHighlight(page, index, 0, -1, false)
end,
hold_callback = function()
self:updateHighlight(page, index, 0, -1, true)
return true
end
},
{
text = "⇱▷",
callback = function()
self:updateHighlight(page, index, 0, 1, false)
end,
hold_callback = function()
self:updateHighlight(page, index, 0, 1, true)
return true
end
},
{
text = "◁⇲",
callback = function()
self:updateHighlight(page, index, 1, -1, false)
end,
hold_callback = function()
self:updateHighlight(page, index, 1, -1, true)
end
},
{
text = "⇲▷",
callback = function()
self:updateHighlight(page, index, 1, 1, false)
end,
hold_callback = function()
self:updateHighlight(page, index, 1, 1, true)
end
}
})
end
self.edit_highlight_dialog = ButtonDialog:new{
buttons = buttons
}
UIManager:show(self.edit_highlight_dialog)
return true
end
function ReaderHighlight:onHold(arg, ges)
-- disable hold gesture if highlighting is disabled
if self.view.highlight.disabled then return true end
self:clear() -- clear previous highlight (delayed clear may not have done it yet)
self.hold_ges_pos = ges.pos -- remember hold original gesture position
self.hold_pos = self.view:screenToPageTransform(ges.pos)
logger.dbg("hold position in page", self.hold_pos)
if not self.hold_pos then
logger.dbg("not inside page area")
return true
end
-- check if we were holding on an image
-- we provide want_frames=true, so we get a list of images for
-- animated GIFs (supported by ImageViewer)
local image = self.ui.document:getImageFromPosition(self.hold_pos, true)
if image then
logger.dbg("hold on image")
local ImageViewer = require("ui/widget/imageviewer")
local imgviewer = ImageViewer:new{
image = image,
-- title_text = _("Document embedded image"),
-- No title, more room for image
with_title_bar = false,
fullscreen = true,
}
UIManager:show(imgviewer)
return true
end
-- otherwise, we must be holding on text
local ok, word = pcall(self.ui.document.getWordFromPosition, self.ui.document, self.hold_pos)
if ok and word then
logger.dbg("selected word:", word)
self.selected_word = word
local link = self.ui.link:getLinkFromGes(ges)
self.selected_link = nil
if link then
logger.dbg("link:", link)
self.selected_link = link
end
if self.ui.document.info.has_pages then
local boxes = {}
table.insert(boxes, self.selected_word.sbox)
self.view.highlight.temp[self.hold_pos.page] = boxes
end
UIManager:setDirty(self.dialog, "ui")
-- TODO: only mark word?
-- Unfortunately, CREngine does not return good coordinates
-- UIManager:setDirty(self.dialog, "partial", self.selected_word.sbox)
self.hold_start_tv = TimeVal.now()
end
return true
end
function ReaderHighlight:onHoldPan(_, ges)
if self.hold_pos == nil then
logger.dbg("no previous hold position")
return true
end
local page_area = self.view:getScreenPageArea(self.hold_pos.page)
if ges.pos:notIntersectWith(page_area) then
logger.dbg("not inside page area", ges, page_area)
return true
end
self.holdpan_pos = self.view:screenToPageTransform(ges.pos)
logger.dbg("holdpan position in page", self.holdpan_pos)
if not self.ui.document.info.has_pages and self.selected_text_start_xpointer then
-- With CreDocuments, allow text selection across multiple pages
-- by (temporarily) switching to scroll mode when panning to the
-- top left or bottom right corners.
local is_in_top_left_corner = self.holdpan_pos.y < 1/8*Screen:getHeight()
and self.holdpan_pos.x < 1/8*Screen:getWidth()
local is_in_bottom_right_corner = self.holdpan_pos.y > 7/8*Screen:getHeight()
and self.holdpan_pos.x > 7/8*Screen:getWidth()
if is_in_top_left_corner or is_in_bottom_right_corner then
if self.was_in_some_corner then
-- Do nothing, wait for the user to move his finger out of that corner
return true
end
self.was_in_some_corner = true
-- We'll adjust hold_pos.y after the mode switch and the scroll
-- so it's accurate in the new screen coordinates
local orig_y = self.ui.document:getScreenPositionFromXPointer(self.selected_text_start_xpointer)
if self.view.view_mode ~= "scroll" then
-- Switch from page mode to scroll mode
local restore_page_mode_xpointer = self.ui.document:getXPointer() -- top of current page
self.restore_page_mode_func = function()
self.ui:handleEvent(Event:new("SetViewMode", "page"))
self.ui.rolling:onGotoXPointer(restore_page_mode_xpointer, self.selected_text_start_xpointer)
end
self.ui:handleEvent(Event:new("SetViewMode", "scroll"))
end
-- (using rolling:onGotoViewRel(1/3) has some strange side effects)
local scroll_distance = math.floor(Screen:getHeight() * 1/3)
local move_y = is_in_bottom_right_corner and scroll_distance or -scroll_distance
self.ui.rolling:_gotoPos(self.ui.document:getCurrentPos() + move_y)
local new_y = self.ui.document:getScreenPositionFromXPointer(self.selected_text_start_xpointer)
self.hold_pos.y = self.hold_pos.y - orig_y + new_y
UIManager:setDirty(self.dialog, "ui")
return true
else
self.was_in_some_corner = nil
end
end
local old_text = self.selected_text and self.selected_text.text
self.selected_text = self.ui.document:getTextFromPositions(self.hold_pos, self.holdpan_pos)
if self.selected_text and self.selected_text.pos0 then
-- Remember original highlight start position, so we can show
-- a marker when back from scroll mode if that switch happened.
-- (getTextFromPositions() does order pos0 and pos1, so it's not
-- certain pos0 is where we started from - we get the one from the
-- first pan, which is hopefully small enough to not span too
-- much height, so the marker points to the start position if the
-- user later pans backward)
if not self.selected_text_start_xpointer then
self.selected_text_start_xpointer = self.selected_text.pos0
end
end
if self.selected_text and old_text and old_text == self.selected_text.text then
-- no modification
return
end
logger.dbg("selected text:", self.selected_text)
if self.selected_text then
self.view.highlight.temp[self.hold_pos.page] = self.selected_text.sboxes
-- remove selected word if hold moves out of word box
if not self.selected_text.sboxes or #self.selected_text.sboxes == 0 then
self.selected_word = nil
elseif self.selected_word and not self.selected_word.sbox:contains(self.selected_text.sboxes[1]) or
#self.selected_text.sboxes > 1 then
self.selected_word = nil
end
end
UIManager:setDirty(self.dialog, "ui")
end
local info_message_ocr_text = _([[
No OCR results or no language data.
KOReader has a build-in OCR engine for recognizing words in scanned PDF and DjVu documents. In order to use OCR in scanned pages, you need to install tesseract trained data for your document language.
You can download language data files for version 3.04 from https://github.com/tesseract-ocr/tesseract/wiki/Data-Files
Copy the language data files for Tesseract 3.04 (e.g., eng.traineddata for English and spa.traineddata for Spanish) into koreader/data/tessdata]])
function ReaderHighlight:lookup(selected_word, selected_link)
-- if we extracted text directly
if selected_word.word then
local word_box = self.view:pageToScreenTransform(self.hold_pos.page, selected_word.sbox)
self.ui:handleEvent(Event:new("LookupWord", selected_word.word, word_box, self, selected_link))
-- or we will do OCR
elseif selected_word.sbox and self.hold_pos then
local word = self.ui.document:getOCRWord(self.hold_pos.page, selected_word)
logger.dbg("OCRed word:", word)
if word and word ~= "" then
local word_box = self.view:pageToScreenTransform(self.hold_pos.page, selected_word.sbox)
self.ui:handleEvent(Event:new("LookupWord", word, word_box, self, selected_link))
else
UIManager:show(InfoMessage:new{
text = info_message_ocr_text,
})
end
end
end
function ReaderHighlight:viewSelectionHTML(debug_view)
if self.ui.document.info.has_pages then
return
end
if self.selected_text and self.selected_text.pos0 and self.selected_text.pos1 then
-- For available flags, see the "#define WRITENODEEX_*" in crengine/src/lvtinydom.cpp
local html_flags = 0x3030 -- valid and classic displayed HTML, with only block nodes indented
if debug_view then
-- Each node on a line, with markers and numbers of skipped chars and siblings shown,
-- with possibly invalid HTML (text nodes not escaped)
html_flags = 0x3353
-- html_flags = 0x3753 -- use this to additionally see rendering methods
end
local html, css_files = self.ui.document:getHTMLFromXPointers(self.selected_text.pos0,
self.selected_text.pos1, html_flags, true)
if html then
-- Make some invisible chars visible
if debug_view then
html = html:gsub("\xC2\xA0", "") -- no break space: open box
html = html:gsub("\xC2\xAD", "") -- soft hyphen: dot operator (smaller than middle dot ·)
end
local TextViewer = require("ui/widget/textviewer")
local Font = require("ui/font")
local textviewer
local buttons_table = {}
if css_files then
for i=1, #css_files do
local button = {
text = T(_("View %1"), css_files[i]),
callback = function()
local css_text = self.ui.document:getDocumentFileContent(css_files[i])
local cssviewer
cssviewer = TextViewer:new{
title = css_files[i],
text = css_text or _("Failed getting CSS content"),
text_face = Font:getFace("smallinfont"),
justified = false,
buttons_table = {
{{
text = _("Prettify"),
enabled = css_text and true or false,
callback = function()
UIManager:close(cssviewer)
-- This is not perfect, but enough to make
-- some ugly CSS readable
css_text = css_text:gsub("%s*{%s*", " {\n ")
css_text = css_text:gsub(";%s*}%s*", ";\n}\n")
css_text = css_text:gsub(";%s*([^}])", ";\n %1")
css_text = css_text:gsub("%s*}%s*", "\n}\n")
css_text = css_text:gsub("%s*,%s*", " ,\n")
-- The last one is wrong inside {}, eg. with
-- "font-family: Georgia, serif"
UIManager:show(TextViewer:new{
title = css_files[i],
text = css_text,
text_face = Font:getFace("smallinfont"),
justified = false,
})
end,
}},
{{
text = _("Close"),
callback = function()
UIManager:close(cssviewer)
end,
}},
}
}
UIManager:show(cssviewer)
end,
}
-- One button per row, too make room for the possibly long css filename
table.insert(buttons_table, {button})
end
end
table.insert(buttons_table, {{
text = debug_view and _("Switch to standard view") or _("Switch to debug view"),
callback = function()
UIManager:close(textviewer)
self:viewSelectionHTML(not debug_view)
end,
}})
table.insert(buttons_table, {{
text = _("Close"),
callback = function()
UIManager:close(textviewer)
end,
}})
textviewer = TextViewer:new{
title = _("Selection HTML"),
text = html,
text_face = Font:getFace("smallinfont"),
justified = false,
buttons_table = buttons_table,
}
UIManager:show(textviewer)
else
UIManager:show(InfoMessage:new{
text = _("Failed getting HTML for selection"),
})
end
end
end
function ReaderHighlight:translate(selected_text)
if selected_text.text ~= "" then
self:onTranslateText(selected_text.text)
-- or we will do OCR
else
local text = self.ui.document:getOCRText(self.hold_pos.page, selected_text)
logger.dbg("OCRed text:", text)
if text and text ~= "" then
self:onTranslateText(text)
else
UIManager:show(InfoMessage:new{
text = info_message_ocr_text,
})
end
end
end
function ReaderHighlight:onTranslateText(text)
Translator:showTranslation(text)
end
function ReaderHighlight:onHoldRelease()
if self.hold_start_tv then
local hold_duration = TimeVal.now() - self.hold_start_tv
hold_duration = hold_duration.sec + hold_duration.usec/1000000
self.hold_start_tv = nil
if hold_duration > 3.0 and self.selected_word then
-- if we were holding for more than 3 seconds on a word, make
-- it behave like we panned and selected more words, so we can
-- directly access the highlight menu and avoid a dict lookup
self:onHoldPan(nil, {pos=self.hold_ges_pos})
end
end
if self.selected_text then
local default_highlight_action = G_reader_settings:readSetting("default_highlight_action")
if not default_highlight_action then
local highlight_buttons = {
{
{
text = _("Highlight"),
callback = function()
self:saveHighlight()
self:onClose()
end,
},
{
text = _("Add Note"),
enabled = false,
callback = function()
self:addNote()
self:onClose()
end,
},
},
{
{
text = "Copy",
enabled = Device:hasClipboard(),
callback = function()
Device.input.setClipboardText(self.selected_text.text)
end,
},
{
text = _("View HTML"),
enabled = not self.ui.document.info.has_pages,
callback = function()
self:viewSelectionHTML()
end,
},
},
{
{
text = _("Wikipedia"),
callback = function()
UIManager:scheduleIn(0.1, function()
self:lookupWikipedia()
-- We don't call self:onClose(), we need the highlight
-- to still be there, as we may Highlight it from the
-- dict lookup widget
end)
end,
},
{
text = _("Dictionary"),
callback = function()
self:onHighlightDictLookup()
-- We don't call self:onClose(), same reason as above
end,
},
},
{
{
text = _("Translate"),
callback = function()
self:translate(self.selected_text)
-- We don't call self:onClose(), so one can still see
-- the highlighted text when moving the translated
-- text window, and also if NetworkMgr:promptWifiOn()
-- is needed, so the user can just tap again on this
-- button and does not need to select the text again.
end,
},
{
text = _("Search"),
callback = function()
self:onHighlightSearch()
UIManager:close(self.highlight_dialog)
end,
},
},
}
if self.selected_link ~= nil then
table.insert(highlight_buttons, { -- for now, a single button in an added row
{
text = _("Follow Link"),
callback = function()
self.ui.link:onGotoLink(self.selected_link)
self:onClose()
end,
},
})
end
self.highlight_dialog = ButtonDialog:new{
buttons = highlight_buttons,
tap_close_callback = function() self:handleEvent(Event:new("Tap")) end,
}
UIManager:show(self.highlight_dialog)
elseif default_highlight_action == "highlight" then
self:saveHighlight()
self:onClose()
elseif default_highlight_action == "translate" then
self:translate(self.selected_text)
self:onClose()
elseif default_highlight_action == "wikipedia" then
self:lookupWikipedia()
self:onClose()
end
elseif self.selected_word then
self:lookup(self.selected_word, self.selected_link)
self.selected_word = nil
end
return true
end
function ReaderHighlight:highlightFromHoldPos()
if self.hold_pos then
if not self.selected_text then
self.selected_text = self.ui.document:getTextFromPositions(self.hold_pos, self.hold_pos)
logger.dbg("selected text:", self.selected_text)
end
end
end
function ReaderHighlight:onHighlight()
self:saveHighlight()
end
function ReaderHighlight:onUnhighlight(bookmark_item)
local page
local sel_text
local sel_pos0
local datetime
local idx
if bookmark_item then -- called from Bookmarks menu onHold
page = bookmark_item.page
sel_text = bookmark_item.notes
sel_pos0 = bookmark_item.pos0
datetime = bookmark_item.datetime
else -- called from DictQuickLookup Unhighlight button
page = self.hold_pos.page
sel_text = self.selected_text.text
sel_pos0 = self.selected_text.pos0
end
if self.ui.document.info.has_pages then -- We can safely use page
for index = 1, #self.view.highlight.saved[page] do
local highlight = self.view.highlight.saved[page][index]
-- pos0 are tables and can't be compared directly, except when from
-- DictQuickLookup where these are the same object.
-- If bookmark_item provided, just check datetime
if highlight.text == sel_text and (
(datetime == nil and highlight.pos0 == sel_pos0) or
(datetime ~= nil and highlight.datetime == datetime)) then
idx = index
break
end
end
else -- page is a xpointer
-- The original page could be found in bookmark_item.text, but
-- no more if it has been renamed: we need to loop through all
-- highlights on all page slots
for p, highlights in pairs(self.view.highlight.saved) do
for index = 1, #highlights do
local highlight = highlights[index]
-- pos0 are strings and can be compared directly
if highlight.text == sel_text and (
(datetime == nil and highlight.pos0 == sel_pos0) or
(datetime ~= nil and highlight.datetime == datetime)) then
page = p -- this is the original page slot
idx = index
break
end
end
if idx then
break
end
end
end
if bookmark_item and not idx then
logger.warn("unhighlight: bookmark_item not found among highlights", bookmark_item)
-- Remove it from bookmarks anyway, so we're not stuck with an
-- unremovable bookmark
self.ui.bookmark:removeBookmark(bookmark_item)
return
end
logger.dbg("found highlight to delete on page", page, idx)
self:deleteHighlight(page, idx, bookmark_item)
return true
end
function ReaderHighlight:getHighlightBookmarkItem()
if self.hold_pos and not self.selected_text then
self:highlightFromHoldPos()
end
if self.selected_text and self.selected_text.pos0 and self.selected_text.pos1 then
local datetime = os.date("%Y-%m-%d %H:%M:%S")
local page = self.ui.document.info.has_pages and
self.hold_pos.page or self.selected_text.pos0
return {
page = page,
pos0 = self.selected_text.pos0,
pos1 = self.selected_text.pos1,
datetime = datetime,
notes = self.selected_text.text,
highlighted = true,
}
end
end
function ReaderHighlight:saveHighlight()
self.ui:handleEvent(Event:new("AddHighlight"))
logger.dbg("save highlight")
local page = self.hold_pos.page
if self.hold_pos and self.selected_text and self.selected_text.pos0
and self.selected_text.pos1 then
if not self.view.highlight.saved[page] then
self.view.highlight.saved[page] = {}
end
local datetime = os.date("%Y-%m-%d %H:%M:%S")
local hl_item = {
datetime = datetime,
text = self.selected_text.text,
pos0 = self.selected_text.pos0,
pos1 = self.selected_text.pos1,
pboxes = self.selected_text.pboxes,
drawer = self.view.highlight.saved_drawer,
}
table.insert(self.view.highlight.saved[page], hl_item)
local bookmark_item = self:getHighlightBookmarkItem()
if bookmark_item then
self.ui.bookmark:addBookmark(bookmark_item)
end
--[[
-- disable exporting highlights to My Clippings
-- since it's not portable and there is a better Evernote plugin
-- to do the same thing
if self.selected_text.text ~= "" then
self:exportToClippings(page, hl_item)
end
--]]
if self.selected_text.pboxes then
self:exportToDocument(page, hl_item)
end
end
end
--[[
function ReaderHighlight:exportToClippings(page, item)
logger.dbg("export highlight to clippings", item)
local clippings = io.open("/mnt/us/documents/My Clippings.txt", "a+")
if clippings and item.text then
local current_locale = os.setlocale()
os.setlocale("C")
clippings:write(self.document.file:gsub("(.*/)(.*)", "%2").."\n")
clippings:write("- KOReader Highlight Page "..page.." ")
clippings:write("| Added on "..os.date("%A, %b %d, %Y %I:%M:%S %p\n\n"))
-- My Clippings only holds one line of highlight
clippings:write(item["text"]:gsub("\n", " ").."\n")
clippings:write("==========\n")
clippings:close()
os.setlocale(current_locale)
end
end
--]]
function ReaderHighlight:exportToDocument(page, item)
logger.dbg("export highlight to document", item)
self.ui.document:saveHighlight(page, item)
end
function ReaderHighlight:addNote()
self:handleEvent(Event:new("addNote"))
logger.dbg("add Note")
end
function ReaderHighlight:lookupWikipedia()
if self.selected_text then
self.ui:handleEvent(Event:new("LookupWikipedia", self.selected_text.text))
end
end
function ReaderHighlight:onHighlightSearch()
logger.dbg("search highlight")
self:highlightFromHoldPos()
if self.selected_text then
local text = require("util").stripePunctuations(self.selected_text.text)
self.ui:handleEvent(Event:new("ShowSearchDialog", text))
end
end
function ReaderHighlight:onHighlightDictLookup()
logger.dbg("dictionary lookup highlight")
self:highlightFromHoldPos()
if self.selected_text then
self.ui:handleEvent(Event:new("LookupWord", self.selected_text.text))
end
end
function ReaderHighlight:shareHighlight()
logger.info("share highlight")
end
function ReaderHighlight:moreAction()
logger.info("more action")
end
function ReaderHighlight:deleteHighlight(page, i, bookmark_item)
self.ui:handleEvent(Event:new("DelHighlight"))
logger.dbg("delete highlight", page, i)
local removed = table.remove(self.view.highlight.saved[page], i)
if bookmark_item then
self.ui.bookmark:removeBookmark(bookmark_item)
else
self.ui.bookmark:removeBookmark({
page = self.ui.document.info.has_pages and page or removed.pos0,
datetime = removed.datetime,
})
end
end
function ReaderHighlight:editHighlight(page, i)
local item = self.view.highlight.saved[page][i]
self.ui.bookmark:renameBookmark({
page = self.ui.document.info.has_pages and page or item.pos0,
datetime = item.datetime,
}, true)
end
function ReaderHighlight:onReadSettings(config)
self.view.highlight.saved_drawer = config:readSetting("highlight_drawer") or self.view.highlight.saved_drawer
local disable_highlight = config:readSetting("highlight_disabled")
if disable_highlight == nil then
disable_highlight = G_reader_settings:readSetting("highlight_disabled") or false
end
self.view.highlight.disabled = disable_highlight
end
function ReaderHighlight:onSaveSettings()
self.ui.doc_settings:saveSetting("highlight_drawer", self.view.highlight.saved_drawer)
self.ui.doc_settings:saveSetting("highlight_disabled", self.view.highlight.disabled)
end
function ReaderHighlight:onClose()
UIManager:close(self.highlight_dialog)
-- clear highlighted text
self:clear()
end
function ReaderHighlight:makeDefault(highlight_disabled)
local new_text
if highlight_disabled then
new_text = _("Disable highlight by default.")
else
new_text = _("Enable highlight by default.")
end
UIManager:show(ConfirmBox:new{
text = new_text,
ok_callback = function()
G_reader_settings:saveSetting("highlight_disabled", highlight_disabled)
end,
})
end
return ReaderHighlight