koreader/frontend/apps/reader/modules/readerhighlight.lua
Huang Xin 8938786472 [fix] stop propagation of tap events when triggering reader menu (#2934)
* [fix] stop propagation of tap events
when triggering reader menu
And also get rid of unnecessary screen refreshes on Kindle Voyage.
The "progress" window for dict lookup is also eliminated as most of the time dict lookup is an instant process, and the "progress" window is preserved for wikipedia lookup as it may take longer time to show the result window.

* [up] add an option to disable dictionary fuzzy search

* [fix] tidy up require

* [fix] fix read settings
2017-06-19 00:08:57 +08:00

570 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, "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 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, "ui")
-- 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