Highlights: anchored dialogs (#12810)

This commit is contained in:
hius07
2024-12-05 18:53:59 +02:00
committed by GitHub
parent cdc0cc58e7
commit ea46d71634
3 changed files with 72 additions and 65 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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