You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
koreader/frontend/apps/reader/modules/readerhighlight.lua

571 lines
19 KiB
Lua

local InputContainer = require("ui/widget/container/inputcontainer")
local Device = require("device")
local Event = require("ui/event")
local UIManager = require("ui/uimanager")
local ButtonDialog = require("ui/widget/buttondialog")
local logger = require("logger")
local _ = require("gettext")
local ConfirmBox = require("ui/widget/confirmbox")
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', },
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 = _("Highlight options"),
sub_item_table = self:genHighlightDrawerMenu(),
}
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_func = function()
return self.view.highlight.disabled and _("Enable") or _("Disable")
end,
callback = function()
self.view.highlight.disabled = not self.view.highlight.disabled
end,
hold_callback = function() self:makeDefault(not self.view.highlight.disabled) end,
},
get_highlight_style("lighten"),
get_highlight_style("underscore"),
get_highlight_style("invert"),
}
end
function ReaderHighlight:clear()
if self.ui.document.info.has_pages then
self.view.highlight.temp = {}
else
self.ui.document:clearSelection()
end
if self.hold_pos then
self.hold_pos = nil
self.selected_text = nil
UIManager:setDirty(self.dialog, "partial")
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 hightlight")
return self:onShowHighlightDialog(page, i)
end
end
end
end
end
end
end
function ReaderHighlight:onTapXPointerSavedHighlight(ges)
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
local pos0, pos1 = items[i].pos0, items[i].pos1
local boxes = self.ui.document:getScreenBoxesFromPositions(pos0, pos1)
if boxes then
for index, box in pairs(boxes) do
if inside_box(pos, box) then
logger.dbg("Tap on hightlight")
return self:onShowHighlightDialog(page, i)
end
end
end
end
end
end
end
function ReaderHighlight:onShowHighlightDialog(page, index)
self.edit_highlight_dialog = ButtonDialog:new{
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"),
enabled = false,
callback = function()
self:editHighlight()
UIManager:close(self.edit_highlight_dialog)
end,
},
},
},
}
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.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
local image = self.ui.document:getImageFromPosition(self.hold_pos)
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
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, "partial")
-- TODO: only mark word?
-- Unfortunately, CREngine does not return good coordinates
-- UIManager:setDirty(self.dialog, "partial", self.selected_word.sbox)
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)
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 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
function ReaderHighlight:lookup(selected_word)
-- 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))
-- 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)
local word_box = self.view:pageToScreenTransform(self.hold_pos.page, selected_word.sbox)
self.ui:handleEvent(Event:new("LookupWord", word, word_box, self))
end
end
function ReaderHighlight:translate(selected_text)
if selected_text.text ~= "" then
self.ui:handleEvent(Event:new("TranslateText", self, 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)
self.ui:handleEvent(Event:new("TranslateText", self, text))
end
end
function ReaderHighlight:onHoldRelease()
if self.selected_word then
self:lookup(self.selected_word)
self.selected_word = nil
elseif self.selected_text then
logger.dbg("show highlight dialog")
self.highlight_dialog = ButtonDialog:new{
buttons = {
{
{
text = _("Highlight"),
callback = function()
self:saveHighlight()
self:onClose()
end,
},
{
text = _("Add Note"),
enabled = false,
callback = function()
self:addNote()
self:onClose()
end,
},
},
{
{
text = _("Wikipedia"),
callback = function()
UIManager:scheduleIn(0.1, function()
self:lookupWikipedia()
self:onClose()
end)
end,
},
{
text = _("Translate"),
enabled = false,
callback = function()
self:translate(self.selected_text)
self:onClose()
end,
},
},
{
{
text = _("Search"),
callback = function()
self:onHighlightSearch()
UIManager:close(self.highlight_dialog)
end,
},
{
text = _("Dictionary"),
callback = function()
self:onHighlightDictLookup()
self:onClose()
end,
},
},
},
tap_close_callback = function() self:handleEvent(Event:new("Tap")) end,
}
UIManager:show(self.highlight_dialog)
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: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: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 hightlights 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)
logger.dbg("delete highlight")
local removed = table.remove(self.view.highlight.saved[page], i)
self.ui.bookmark:removeBookmark({
page = self.ui.document.info.has_pages and page or removed.pos0,
datetime = removed.datetime,
})
end
function ReaderHighlight:editHighlight()
logger.info("edit highlight")
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,
})
self.view.highlight.disabled = highlight_disabled
end
return ReaderHighlight