mirror of
https://github.com/koreader/koreader.git
synced 2025-08-10 00:52:38 +00:00
Highlights: anchored dialogs (#12810)
This commit is contained in:
@@ -13,7 +13,6 @@ local SpinWidget = require("ui/widget/spinwidget")
|
||||
local TextViewer = require("ui/widget/textviewer")
|
||||
local Translator = require("ui/translator")
|
||||
local UIManager = require("ui/uimanager")
|
||||
local dbg = require("dbg")
|
||||
local ffiUtil = require("ffi/util")
|
||||
local logger = require("logger")
|
||||
local util = require("util")
|
||||
@@ -125,11 +124,11 @@ function ReaderHighlight:init()
|
||||
end,
|
||||
}
|
||||
end,
|
||||
["06_dictionary"] = function(this)
|
||||
["06_dictionary"] = function(this, index)
|
||||
return {
|
||||
text = _("Dictionary"),
|
||||
callback = function()
|
||||
this:onHighlightDictLookup()
|
||||
this:lookupDict(index)
|
||||
-- We don't call this:onClose(), same reason as above
|
||||
end,
|
||||
}
|
||||
@@ -1030,6 +1029,18 @@ function ReaderHighlight:onTap(_, ges)
|
||||
end
|
||||
end
|
||||
|
||||
function ReaderHighlight:getHighlightVisibleBoxes(index)
|
||||
local boxes = {}
|
||||
for _, box in ipairs(self.view.highlight.visible_boxes) do
|
||||
if box.index == index then
|
||||
table.insert(boxes, box.rect)
|
||||
end
|
||||
end
|
||||
if next(boxes) ~= nil then
|
||||
return boxes
|
||||
end
|
||||
end
|
||||
|
||||
function ReaderHighlight:updateHighlight(index, side, direction, move_by_char)
|
||||
local highlight = self.ui.annotation.annotations[index]
|
||||
local highlight_before = util.tableDeepCopy(highlight)
|
||||
@@ -1150,6 +1161,9 @@ function ReaderHighlight:showHighlightNoteOrDialog(index)
|
||||
text = bookmark_note,
|
||||
width = math.floor(math.min(self.screen_w, self.screen_h) * 0.8),
|
||||
height = math.floor(math.max(self.screen_w, self.screen_h) * 0.4),
|
||||
anchor = function()
|
||||
return self:_getDialogAnchor(textviewer, index)
|
||||
end,
|
||||
buttons_table = {
|
||||
{
|
||||
{
|
||||
@@ -1314,7 +1328,7 @@ function ReaderHighlight:onShowHighlightDialog(index)
|
||||
self.edit_highlight_dialog = ButtonDialog:new{ -- in self for unit tests
|
||||
buttons = buttons,
|
||||
anchor = function()
|
||||
return self:_getDialogAnchor(self.edit_highlight_dialog, item)
|
||||
return self:_getDialogAnchor(self.edit_highlight_dialog, index)
|
||||
end,
|
||||
}
|
||||
UIManager:show(self.edit_highlight_dialog)
|
||||
@@ -1355,7 +1369,7 @@ function ReaderHighlight:onShowHighlightMenu(index)
|
||||
self.highlight_dialog = ButtonDialog:new{
|
||||
buttons = highlight_buttons,
|
||||
anchor = function()
|
||||
return self:_getDialogAnchor(self.highlight_dialog, not self.gest_pos and self.ui.annotation.annotations[index])
|
||||
return self:_getDialogAnchor(self.highlight_dialog, index)
|
||||
end,
|
||||
tap_close_callback = function() self:handleEvent(Event:new("Tap")) end,
|
||||
}
|
||||
@@ -1363,55 +1377,42 @@ function ReaderHighlight:onShowHighlightMenu(index)
|
||||
-- or the buggy Sage kernel may alpha-blend it into the page (with a bogus alpha value, to boot)...
|
||||
UIManager:show(self.highlight_dialog, "[ui]")
|
||||
end
|
||||
dbg:guard(ReaderHighlight, "onShowHighlightMenu",
|
||||
function(self)
|
||||
assert(self.selected_text ~= nil,
|
||||
"onShowHighlightMenu must not be called with nil self.selected_text!")
|
||||
end)
|
||||
|
||||
function ReaderHighlight:_getDialogAnchor(dialog, item)
|
||||
function ReaderHighlight:_getDialogAnchor(dialog, index)
|
||||
local position = G_reader_settings:readSetting("highlight_dialog_position", "center")
|
||||
if position == "center" then return end
|
||||
local padding = Size.padding.small -- vertical padding, do not stick to the highlight box or to the screen edge
|
||||
local dialog_box = dialog:getContentSize()
|
||||
local anchor_x = math.floor((self.screen_w - dialog_box.w) / 2) -- center by width
|
||||
local anchor_y, prefers_pop_down
|
||||
if position == "top" then
|
||||
anchor_y = Size.padding.small -- do not stick to the edge
|
||||
anchor_y = padding
|
||||
prefers_pop_down = true
|
||||
elseif position == "bottom" then
|
||||
anchor_y = self.screen_h - Size.padding.small
|
||||
anchor_y = self.screen_h - padding
|
||||
else -- "gesture"
|
||||
local pos0, pos1
|
||||
if item then -- highlight
|
||||
if self.ui.rolling then
|
||||
local y, x = self.ui.document:getScreenPositionFromXPointer(item.pos0)
|
||||
pos0 = x ~= nil and y ~= nil and { x = x, y = y } or nil
|
||||
y, x = self.ui.document:getScreenPositionFromXPointer(item.pos1)
|
||||
pos1 = x ~= nil and y ~= nil and { x = x, y = y } or nil
|
||||
else
|
||||
pos0, pos1 = item.pos0, item.pos1
|
||||
end
|
||||
else -- gesture
|
||||
pos0, pos1 = unpack(self.gest_pos)
|
||||
self.gest_pos = nil
|
||||
local boxes = index and self:getHighlightVisibleBoxes(index) or (self.selected_text.sboxes or self.selected_text.pboxes)
|
||||
if boxes == nil then return end -- fallback to "center"
|
||||
local box0, box1 = boxes[1], boxes[#boxes]
|
||||
if box0.y > box1.y then
|
||||
box0, box1 = box1, box0
|
||||
end
|
||||
if pos0 == nil or pos1 == nil then return end -- fallback to "center"
|
||||
if pos0.y > pos1.y then -- try to show the dialog below the highlight
|
||||
pos1 = pos0
|
||||
if self.ui.paging then
|
||||
local page = index and self.ui.annotation.annotations[index].pos0.page or self.selected_text.pos0.page
|
||||
box0 = self.view:pageToScreenTransform(page, box0)
|
||||
box1 = self.view:pageToScreenTransform(page, box1)
|
||||
if box0 == nil or box1 == nil then return end
|
||||
end
|
||||
local text_box = self.ui.document:getWordFromPosition(pos1, true)
|
||||
if text_box then
|
||||
text_box = text_box.sbox
|
||||
if text_box and self.ui.paging then
|
||||
text_box = self.view:pageToScreenTransform(self.ui.paging.current_page, text_box)
|
||||
end
|
||||
end
|
||||
if text_box == nil then return end -- fallback to "center"
|
||||
anchor_y = text_box.y + text_box.h + Size.padding.small -- do not stick to the box
|
||||
if anchor_y + dialog_box.h <= self.screen_h - Size.padding.small then -- enough room below box with gest_pos
|
||||
local y0 = box0.y
|
||||
local y1 = box1.y + box1.h
|
||||
local dialog_box_h = dialog_box.h + 2 * padding
|
||||
if y1 + dialog_box_h <= self.screen_h then -- below highlight, preferable
|
||||
anchor_y = y1 + padding
|
||||
prefers_pop_down = true
|
||||
else -- above box with gest_pos
|
||||
anchor_y = text_box.y - Size.padding.small
|
||||
elseif dialog_box_h <= y0 then -- above highlight
|
||||
anchor_y = y0 - padding
|
||||
else -- not enough room below and above, fallback to "center"
|
||||
return
|
||||
end
|
||||
end
|
||||
return { x = anchor_x, y = anchor_y, h = 0, w = 0 }, prefers_pop_down
|
||||
@@ -1741,7 +1742,7 @@ function ReaderHighlight:onHoldPan(_, ges)
|
||||
end
|
||||
self:_resetHoldTimer() -- selection updated
|
||||
logger.dbg("selected text:", self.selected_text)
|
||||
if self.selected_text then
|
||||
if self.ui.paging and self.selected_text then
|
||||
self.view.highlight.temp[self.hold_pos.page] = self.selected_text.sboxes
|
||||
end
|
||||
UIManager:setDirty(self.dialog, "ui")
|
||||
@@ -1756,36 +1757,33 @@ You can download language data files for Tesseract version 5.3.4 from https://te
|
||||
|
||||
Copy the language data files (e.g., eng.traineddata for English and spa.traineddata for Spanish) into koreader/data/tessdata]])
|
||||
|
||||
function ReaderHighlight:lookup(selected_text, selected_link)
|
||||
function ReaderHighlight:lookupDictWord()
|
||||
-- convert sboxes to word boxes
|
||||
local word_boxes = {}
|
||||
for i, sbox in ipairs(selected_text.sboxes) do
|
||||
for i, sbox in ipairs(self.selected_text.sboxes) do
|
||||
word_boxes[i] = self.view:pageToScreenTransform(self.hold_pos.page, sbox)
|
||||
end
|
||||
|
||||
-- if we extracted text directly
|
||||
if #selected_text.text > 0 and self.hold_pos then
|
||||
self.ui:handleEvent(Event:new("LookupWord", selected_text.text, false, word_boxes, self, selected_link))
|
||||
if #self.selected_text.text > 0 then
|
||||
self.ui.dictionary:onLookupWord(self.selected_text.text, false, word_boxes, self, self.selected_link)
|
||||
-- or we will do OCR
|
||||
elseif selected_text.sboxes and self.hold_pos then
|
||||
local text = self.ui.document:getOCRText(self.hold_pos.page, selected_text.sboxes)
|
||||
elseif self.selected_text.sboxes then
|
||||
local text = self.ui.document:getOCRText(self.hold_pos.page, self.selected_text.sboxes)
|
||||
if not text then
|
||||
-- getOCRText is not implemented in some document backends, but
|
||||
-- getOCRWord is implemented everywhere. As such, fall back to
|
||||
-- getOCRWord.
|
||||
text = ""
|
||||
for _, sbox in ipairs(selected_text.sboxes) do
|
||||
for _, sbox in ipairs(self.selected_text.sboxes) do
|
||||
local word = self.ui.document:getOCRWord(self.hold_pos.page, { sbox = sbox })
|
||||
logger.dbg("OCRed word:", word)
|
||||
--- @fixme This might produce incorrect results on RTL text.
|
||||
if word and word ~= "" then
|
||||
text = text .. word
|
||||
end
|
||||
text = text .. (word or "")
|
||||
end
|
||||
end
|
||||
logger.dbg("OCRed text:", text)
|
||||
if text and text ~= "" then
|
||||
self.ui:handleEvent(Event:new("LookupWord", text, false, word_boxes, self, selected_link))
|
||||
self.ui.dictionary:onLookupWord(text, false, word_boxes, self, self.selected_link)
|
||||
else
|
||||
UIManager:show(InfoMessage:new{
|
||||
text = info_message_ocr_text,
|
||||
@@ -1793,11 +1791,6 @@ function ReaderHighlight:lookup(selected_text, selected_link)
|
||||
end
|
||||
end
|
||||
end
|
||||
dbg:guard(ReaderHighlight, "lookup",
|
||||
function(self, selected_text, selected_link)
|
||||
assert(selected_text ~= nil,
|
||||
"lookup must not be called with nil selected_text!")
|
||||
end)
|
||||
|
||||
function ReaderHighlight:getSelectedWordContext(nb_words)
|
||||
if not self.selected_text then return end
|
||||
@@ -1908,7 +1901,7 @@ function ReaderHighlight:onHoldRelease()
|
||||
|
||||
if self.selected_text then
|
||||
if self.is_word_selection then
|
||||
self:lookup(self.selected_text, self.selected_link)
|
||||
self:lookupDictWord()
|
||||
else
|
||||
if long_final_hold or default_highlight_action == "ask" then
|
||||
-- bypass default action and show popup if long final hold
|
||||
@@ -1928,7 +1921,7 @@ function ReaderHighlight:onHoldRelease()
|
||||
self:lookupWikipedia()
|
||||
self:onClose()
|
||||
elseif default_highlight_action == "dictionary" then
|
||||
self:onHighlightDictLookup()
|
||||
self:lookupDict()
|
||||
self:onClose()
|
||||
elseif default_highlight_action == "search" then
|
||||
self:onHighlightSearch()
|
||||
@@ -2110,11 +2103,19 @@ function ReaderHighlight:onHighlightSearch()
|
||||
end
|
||||
end
|
||||
|
||||
function ReaderHighlight:onHighlightDictLookup()
|
||||
function ReaderHighlight:lookupDict(index)
|
||||
logger.dbg("dictionary lookup highlight")
|
||||
self:highlightFromHoldPos()
|
||||
if self.selected_text then
|
||||
self.ui:handleEvent(Event:new("LookupWord", util.cleanupSelectedText(self.selected_text.text)))
|
||||
local boxes = index and self:getHighlightVisibleBoxes(index) or (self.selected_text.sboxes or self.selected_text.pboxes)
|
||||
local word_boxes
|
||||
if boxes ~= nil then
|
||||
word_boxes = {}
|
||||
for i, box in ipairs(boxes) do
|
||||
word_boxes[i] = self.view:pageToScreenTransform(self.selected_text.pos0.page, box)
|
||||
end
|
||||
end
|
||||
self.ui.dictionary:onLookupWord(util.cleanupSelectedText(self.selected_text.text), false, word_boxes)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2295,6 +2296,7 @@ function ReaderHighlight:extendSelection()
|
||||
-- pos0 and pos1 are in order within highlights
|
||||
new_pos0 = self.ui.document:compareXPointers(item1.pos0, item2_pos0) == 1 and item1.pos0 or item2_pos0
|
||||
new_pos1 = self.ui.document:compareXPointers(item1.pos1, item2_pos1) == 1 and item2_pos1 or item1.pos1
|
||||
new_pboxes = self.document:getScreenBoxesFromPositions(new_pos0, new_pos1)
|
||||
-- true to draw
|
||||
new_text = self.ui.document:getTextFromXPointers(new_pos0, new_pos1, true)
|
||||
end
|
||||
|
||||
@@ -713,12 +713,12 @@ function CreDocument:getTextFromPositions(pos0, pos1, do_not_draw_selection)
|
||||
drawSelection, drawSegmentedSelection)
|
||||
logger.dbg("CreDocument: get text range", text_range)
|
||||
if text_range then
|
||||
-- local line_boxes = self:getScreenBoxesFromPositions(text_range.pos0, text_range.pos1)
|
||||
local line_boxes = self:getScreenBoxesFromPositions(text_range.pos0, text_range.pos1)
|
||||
return {
|
||||
text = text_range.text,
|
||||
pos0 = text_range.pos0,
|
||||
pos1 = text_range.pos1,
|
||||
--sboxes = line_boxes, -- boxes on screen
|
||||
sboxes = line_boxes, -- boxes on screen
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -339,6 +339,7 @@ function TextViewer:init(reinit)
|
||||
-- a few things before forwarding them
|
||||
"touch", "pan", "pan_release",
|
||||
},
|
||||
anchor = self.anchor,
|
||||
self.frame,
|
||||
}
|
||||
self[1] = WidgetContainer:new{
|
||||
@@ -348,6 +349,10 @@ function TextViewer:init(reinit)
|
||||
}
|
||||
end
|
||||
|
||||
function TextViewer:getContentSize()
|
||||
return self.movable.dimen
|
||||
end
|
||||
|
||||
function TextViewer:onCloseWidget()
|
||||
UIManager:setDirty(nil, function()
|
||||
return "partial", self.frame.dimen
|
||||
|
||||
Reference in New Issue
Block a user