2019-12-06 21:55:39 +00:00
|
|
|
local BD = require("ui/bidi")
|
2017-07-28 20:39:54 +00:00
|
|
|
local ButtonDialog = require("ui/widget/buttondialog")
|
2014-10-30 18:42:18 +00:00
|
|
|
local Device = require("device")
|
2013-10-22 15:11:31 +00:00
|
|
|
local Event = require("ui/event")
|
2021-10-23 10:12:56 +00:00
|
|
|
local Geom = require("ui/geometry")
|
2018-03-05 15:38:04 +00:00
|
|
|
local InfoMessage = require("ui/widget/infomessage")
|
2017-07-28 20:39:54 +00:00
|
|
|
local InputContainer = require("ui/widget/container/inputcontainer")
|
2019-10-25 15:25:26 +00:00
|
|
|
local Notification = require("ui/widget/notification")
|
2022-01-02 18:09:53 +00:00
|
|
|
local TextViewer = require("ui/widget/textviewer")
|
2018-12-17 13:15:13 +00:00
|
|
|
local Translator = require("ui/translator")
|
2013-10-22 15:11:31 +00:00
|
|
|
local UIManager = require("ui/uimanager")
|
2021-11-06 07:11:06 +00:00
|
|
|
local dbg = require("dbg")
|
2016-12-29 08:10:38 +00:00
|
|
|
local logger = require("logger")
|
2020-06-08 18:47:31 +00:00
|
|
|
local util = require("util")
|
2022-03-12 11:16:50 +00:00
|
|
|
local Size = require("ui/size")
|
2020-11-08 12:07:51 +00:00
|
|
|
local ffiUtil = require("ffi/util")
|
2022-05-05 19:00:22 +00:00
|
|
|
local time = require("ui/time")
|
2013-10-18 20:38:07 +00:00
|
|
|
local _ = require("gettext")
|
2019-08-24 11:45:07 +00:00
|
|
|
local C_ = _.pgettext
|
2018-10-08 16:58:43 +00:00
|
|
|
local T = require("ffi/util").template
|
2019-02-03 09:01:58 +00:00
|
|
|
local Screen = Device.screen
|
2013-04-23 22:59:52 +00:00
|
|
|
|
2020-11-08 12:07:51 +00:00
|
|
|
local ReaderHighlight = InputContainer:new{
|
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
local function cleanupSelectedText(text)
|
|
|
|
-- Trim spaces and new lines at start and end
|
|
|
|
text = text:gsub("^[\n%s]*", "")
|
|
|
|
text = text:gsub("[\n%s]*$", "")
|
|
|
|
-- Trim spaces around newlines
|
|
|
|
text = text:gsub("%s*\n%s*", "\n")
|
|
|
|
-- Trim consecutive spaces (that would probably have collapsed
|
|
|
|
-- in rendered CreDocuments)
|
|
|
|
text = text:gsub("%s%s+", " ")
|
|
|
|
return text
|
|
|
|
end
|
2013-04-23 22:59:52 +00:00
|
|
|
|
|
|
|
function ReaderHighlight:init()
|
2021-11-21 17:31:10 +00:00
|
|
|
self.select_mode = false -- extended highlighting
|
2022-03-12 11:16:50 +00:00
|
|
|
self._start_indicator_highlight = false
|
|
|
|
self._current_indicator_pos = nil
|
|
|
|
self._previous_indicator_pos = nil
|
2022-05-05 19:00:22 +00:00
|
|
|
self._last_indicator_move_args = {dx = 0, dy = 0, distance = 0, time = time:now()}
|
2022-03-12 11:16:50 +00:00
|
|
|
|
|
|
|
if Device:hasDPad() then
|
|
|
|
-- Used for text selection with dpad/keys
|
|
|
|
local QUICK_INDICTOR_MOVE = true
|
|
|
|
self.key_events.StopHighlightIndicator = { {Device.input.group.Back}, doc = "Stop non-touch highlight", args = true } -- true: clear highlight selection
|
|
|
|
self.key_events.UpHighlightIndicator = { {"Up"}, doc = "move indicator up", event = "MoveHighlightIndicator", args = {0, -1} }
|
|
|
|
self.key_events.DownHighlightIndicator = { {"Down"}, doc = "move indicator down", event = "MoveHighlightIndicator", args = {0, 1} }
|
|
|
|
-- let FewKeys device can move indicator left
|
|
|
|
self.key_events.LeftHighlightIndicator = { {"Left"}, doc = "move indicator left", event = "MoveHighlightIndicator", args = {-1, 0} }
|
|
|
|
self.key_events.RightHighlightIndicator = { {"Right"}, doc = "move indicator right", event = "MoveHighlightIndicator", args = {1, 0} }
|
|
|
|
self.key_events.HighlightPress = { {"Press"}, doc = "highlight start or end" }
|
|
|
|
if Device:hasKeys() then
|
2022-04-14 06:59:36 +00:00
|
|
|
self.key_events.QuickUpHighlightIndicator = { {"Shift", "Up"}, doc = "quick move indicator up", event = "MoveHighlightIndicator", args = {0, -1, QUICK_INDICTOR_MOVE} }
|
|
|
|
self.key_events.QuickDownHighlightIndicator = { {"Shift", "Down"}, doc = "quick move indicator down", event = "MoveHighlightIndicator", args = {0, 1, QUICK_INDICTOR_MOVE} }
|
|
|
|
self.key_events.QuickLeftHighlightIndicator = { {"Shift", "Left"}, doc = "quick move indicator left", event = "MoveHighlightIndicator", args = {-1, 0, QUICK_INDICTOR_MOVE} }
|
|
|
|
self.key_events.QuickRightHighlightIndicator = { {"Shift", "Right"}, doc = "quick move indicator right", event = "MoveHighlightIndicator", args = {1, 0, QUICK_INDICTOR_MOVE} }
|
2022-03-12 11:16:50 +00:00
|
|
|
self.key_events.StartHighlightIndicator = { {"H"}, doc = "start non-touch highlight" }
|
|
|
|
end
|
|
|
|
end
|
2021-11-21 17:31:10 +00:00
|
|
|
|
2020-11-08 12:07:51 +00:00
|
|
|
self._highlight_buttons = {
|
|
|
|
-- highlight and add_note are for the document itself,
|
|
|
|
-- so we put them first.
|
2022-06-11 17:06:06 +00:00
|
|
|
["01_select"] = function(this)
|
2020-11-08 12:07:51 +00:00
|
|
|
return {
|
2021-11-21 17:31:10 +00:00
|
|
|
text = _("Select"),
|
2022-06-11 17:06:06 +00:00
|
|
|
enabled = this.hold_pos ~= nil,
|
2020-11-08 12:07:51 +00:00
|
|
|
callback = function()
|
2022-06-11 17:06:06 +00:00
|
|
|
this:startSelection()
|
|
|
|
this:onClose()
|
2020-11-08 12:07:51 +00:00
|
|
|
end,
|
|
|
|
}
|
|
|
|
end,
|
2022-06-11 17:06:06 +00:00
|
|
|
["02_highlight"] = function(this)
|
2020-11-08 12:07:51 +00:00
|
|
|
return {
|
2021-11-21 17:31:10 +00:00
|
|
|
text = _("Highlight"),
|
2020-11-08 12:07:51 +00:00
|
|
|
callback = function()
|
2022-06-11 09:08:40 +00:00
|
|
|
this:saveHighlight(true)
|
2022-06-11 17:06:06 +00:00
|
|
|
this:onClose()
|
2020-11-08 12:07:51 +00:00
|
|
|
end,
|
2022-06-11 17:06:06 +00:00
|
|
|
enabled = this.hold_pos ~= nil,
|
2020-11-08 12:07:51 +00:00
|
|
|
}
|
|
|
|
end,
|
2022-06-11 17:06:06 +00:00
|
|
|
["03_copy"] = function(this)
|
2020-11-08 12:07:51 +00:00
|
|
|
return {
|
|
|
|
text = C_("Text", "Copy"),
|
|
|
|
enabled = Device:hasClipboard(),
|
|
|
|
callback = function()
|
2022-06-11 17:06:06 +00:00
|
|
|
Device.input.setClipboardText(cleanupSelectedText(this.selected_text.text))
|
|
|
|
this:onClose()
|
2021-05-31 18:36:12 +00:00
|
|
|
UIManager:show(Notification:new{
|
|
|
|
text = _("Selection copied to clipboard."),
|
|
|
|
})
|
2020-11-08 12:07:51 +00:00
|
|
|
end,
|
|
|
|
}
|
|
|
|
end,
|
2022-06-11 17:06:06 +00:00
|
|
|
["04_add_note"] = function(this)
|
2020-11-08 12:07:51 +00:00
|
|
|
return {
|
2021-11-21 17:31:10 +00:00
|
|
|
text = _("Add Note"),
|
2020-11-08 12:07:51 +00:00
|
|
|
callback = function()
|
2022-06-11 17:06:06 +00:00
|
|
|
this:addNote()
|
|
|
|
this:onClose()
|
2020-11-08 12:07:51 +00:00
|
|
|
end,
|
2022-06-11 17:06:06 +00:00
|
|
|
enabled = this.hold_pos ~= nil,
|
2020-11-08 12:07:51 +00:00
|
|
|
}
|
|
|
|
end,
|
|
|
|
-- then information lookup functions, putting on the left those that
|
|
|
|
-- depend on an internet connection.
|
2022-06-11 17:06:06 +00:00
|
|
|
["05_wikipedia"] = function(this)
|
2020-11-08 12:07:51 +00:00
|
|
|
return {
|
|
|
|
text = _("Wikipedia"),
|
|
|
|
callback = function()
|
|
|
|
UIManager:scheduleIn(0.1, function()
|
2022-06-11 17:06:06 +00:00
|
|
|
this:lookupWikipedia()
|
|
|
|
-- We don't call this:onClose(), we need the highlight
|
2020-11-08 12:07:51 +00:00
|
|
|
-- to still be there, as we may Highlight it from the
|
2021-03-21 12:57:18 +00:00
|
|
|
-- dict lookup widget.
|
2020-11-08 12:07:51 +00:00
|
|
|
end)
|
|
|
|
end,
|
|
|
|
}
|
|
|
|
end,
|
2022-06-11 17:06:06 +00:00
|
|
|
["06_dictionary"] = function(this)
|
2020-11-08 12:07:51 +00:00
|
|
|
return {
|
|
|
|
text = _("Dictionary"),
|
|
|
|
callback = function()
|
2022-06-11 17:06:06 +00:00
|
|
|
this:onHighlightDictLookup()
|
|
|
|
-- We don't call this:onClose(), same reason as above
|
2020-11-08 12:07:51 +00:00
|
|
|
end,
|
|
|
|
}
|
|
|
|
end,
|
2022-06-11 17:06:06 +00:00
|
|
|
["07_translate"] = function(this, page, index)
|
2020-11-08 12:07:51 +00:00
|
|
|
return {
|
|
|
|
text = _("Translate"),
|
|
|
|
callback = function()
|
2022-06-11 17:06:06 +00:00
|
|
|
this:translate(this.selected_text, page, index)
|
|
|
|
-- We don't call this:onClose(), so one can still see
|
2020-11-08 12:07:51 +00:00
|
|
|
-- 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,
|
|
|
|
}
|
|
|
|
end,
|
2021-11-21 17:31:10 +00:00
|
|
|
-- buttons 08-11 are conditional ones, so the number of buttons can be even or odd
|
|
|
|
-- let the Search button be the last, occasionally narrow or wide, less confusing
|
2022-06-11 17:06:06 +00:00
|
|
|
["12_search"] = function(this)
|
2020-11-08 12:07:51 +00:00
|
|
|
return {
|
2021-11-21 17:31:10 +00:00
|
|
|
text = _("Search"),
|
2020-11-08 12:07:51 +00:00
|
|
|
callback = function()
|
2022-06-11 17:06:06 +00:00
|
|
|
this:onHighlightSearch()
|
|
|
|
-- We don't call this:onClose(), crengine will highlight
|
2021-11-21 17:31:10 +00:00
|
|
|
-- search matches on the current page, and self:clear()
|
|
|
|
-- would redraw and remove crengine native highlights
|
2020-11-08 12:07:51 +00:00
|
|
|
end,
|
|
|
|
}
|
2021-11-21 17:31:10 +00:00
|
|
|
end,
|
|
|
|
}
|
2020-11-08 12:07:51 +00:00
|
|
|
|
2021-11-21 17:31:10 +00:00
|
|
|
-- Android devices
|
2020-11-08 12:07:51 +00:00
|
|
|
if Device:canShareText() then
|
2022-06-09 23:53:44 +00:00
|
|
|
local action = _("Share Text")
|
2022-06-11 17:06:06 +00:00
|
|
|
self:addToHighlightDialog("08_share_text", function(this)
|
2020-11-08 12:07:51 +00:00
|
|
|
return {
|
2022-06-09 23:53:44 +00:00
|
|
|
text = action,
|
2020-11-08 12:07:51 +00:00
|
|
|
callback = function()
|
2022-06-11 17:06:06 +00:00
|
|
|
local text = cleanupSelectedText(this.selected_text.text)
|
2020-11-08 12:07:51 +00:00
|
|
|
-- call self:onClose() before calling the android framework
|
2022-06-11 17:06:06 +00:00
|
|
|
this:onClose()
|
2022-06-09 23:53:44 +00:00
|
|
|
Device:doShareText(text, action)
|
2020-11-08 12:07:51 +00:00
|
|
|
end,
|
|
|
|
}
|
|
|
|
end)
|
|
|
|
end
|
|
|
|
|
2021-11-21 17:31:10 +00:00
|
|
|
-- cre documents only
|
2021-12-21 12:57:19 +00:00
|
|
|
if not self.document.info.has_pages then
|
2022-06-11 17:06:06 +00:00
|
|
|
self:addToHighlightDialog("09_view_html", function(this)
|
2021-11-21 17:31:10 +00:00
|
|
|
return {
|
|
|
|
text = _("View HTML"),
|
|
|
|
callback = function()
|
2022-06-11 17:06:06 +00:00
|
|
|
this:viewSelectionHTML()
|
2021-11-21 17:31:10 +00:00
|
|
|
end,
|
|
|
|
}
|
|
|
|
end)
|
|
|
|
end
|
|
|
|
|
|
|
|
-- User hyphenation dict
|
2022-06-11 17:06:06 +00:00
|
|
|
self:addToHighlightDialog("10_user_dict", function(this)
|
2021-02-06 15:59:09 +00:00
|
|
|
return {
|
2021-11-21 17:31:10 +00:00
|
|
|
text= _("Hyphenate"),
|
2021-02-06 15:59:09 +00:00
|
|
|
show_in_highlight_dialog_func = function()
|
2022-06-11 17:06:06 +00:00
|
|
|
return this.ui.userhyph and this.ui.userhyph:isAvailable()
|
|
|
|
and not this.selected_text.text:find("[ ,;-%.\n]")
|
2021-02-06 15:59:09 +00:00
|
|
|
end,
|
|
|
|
callback = function()
|
2022-06-11 17:06:06 +00:00
|
|
|
this.ui.userhyph:modifyUserEntry(this.selected_text.text)
|
|
|
|
this:onClose()
|
2021-02-06 15:59:09 +00:00
|
|
|
end,
|
|
|
|
}
|
|
|
|
end)
|
2020-11-08 12:07:51 +00:00
|
|
|
|
2021-11-21 17:31:10 +00:00
|
|
|
-- Links
|
2022-06-11 17:06:06 +00:00
|
|
|
self:addToHighlightDialog("11_follow_link", function(this)
|
2021-06-01 10:06:55 +00:00
|
|
|
return {
|
2021-11-21 17:31:10 +00:00
|
|
|
text = _("Follow Link"),
|
2021-06-01 10:06:55 +00:00
|
|
|
show_in_highlight_dialog_func = function()
|
2022-06-11 17:06:06 +00:00
|
|
|
return this.selected_link ~= nil
|
2021-06-01 10:06:55 +00:00
|
|
|
end,
|
|
|
|
callback = function()
|
2022-06-11 17:06:06 +00:00
|
|
|
local link = this.selected_link.link or this.selected_link
|
|
|
|
this.ui.link:onGotoLink(link)
|
|
|
|
this:onClose()
|
2021-06-01 10:06:55 +00:00
|
|
|
end,
|
|
|
|
}
|
|
|
|
end)
|
|
|
|
|
2014-10-09 09:41:23 +00:00
|
|
|
self.ui:registerPostInitCallback(function()
|
|
|
|
self.ui.menu:registerToMainMenu(self)
|
|
|
|
end)
|
2013-04-23 22:59:52 +00:00
|
|
|
end
|
|
|
|
|
2017-01-23 14:54:14 +00:00
|
|
|
function ReaderHighlight:setupTouchZones()
|
|
|
|
-- deligate gesture listener to readerui
|
|
|
|
self.ges_events = {}
|
|
|
|
self.onGesture = nil
|
|
|
|
|
|
|
|
if not Device:isTouchDevice() then return end
|
2020-08-05 22:13:26 +00:00
|
|
|
local hold_pan_rate = G_reader_settings:readSetting("hold_pan_rate")
|
|
|
|
if not hold_pan_rate then
|
|
|
|
hold_pan_rate = Screen.low_pan_rate and 5.0 or 30.0
|
|
|
|
end
|
2017-01-23 14:54:14 +00:00
|
|
|
self.ui:registerTouchZones({
|
|
|
|
{
|
|
|
|
id = "readerhighlight_tap",
|
|
|
|
ges = "tap",
|
|
|
|
screen_zone = {
|
|
|
|
ratio_x = 0, ratio_y = 0, ratio_w = 1, ratio_h = 1,
|
|
|
|
},
|
2019-03-30 20:05:44 +00:00
|
|
|
overrides = {
|
2020-01-24 19:05:21 +00:00
|
|
|
-- Tap on existing highlights have priority over
|
|
|
|
-- everything but tap on links (as links can be
|
|
|
|
-- part of some highlighted text)
|
|
|
|
"tap_top_left_corner",
|
|
|
|
"tap_top_right_corner",
|
|
|
|
"tap_left_bottom_corner",
|
|
|
|
"tap_right_bottom_corner",
|
2020-12-03 16:33:54 +00:00
|
|
|
"readerfooter_tap",
|
|
|
|
"readerconfigmenu_ext_tap",
|
|
|
|
"readerconfigmenu_tap",
|
|
|
|
"readermenu_ext_tap",
|
|
|
|
"readermenu_tap",
|
|
|
|
"tap_forward",
|
|
|
|
"tap_backward",
|
2019-03-30 20:05:44 +00:00
|
|
|
},
|
2017-01-23 14:54:14 +00:00
|
|
|
handler = function(ges) return self:onTap(nil, ges) end
|
2014-03-13 13:52:43 +00:00
|
|
|
},
|
2017-01-23 14:54:14 +00:00
|
|
|
{
|
|
|
|
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
|
2014-03-13 13:52:43 +00:00
|
|
|
},
|
2017-01-23 14:54:14 +00:00
|
|
|
{
|
|
|
|
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
|
2014-03-13 13:52:43 +00:00
|
|
|
},
|
2017-01-23 14:54:14 +00:00
|
|
|
{
|
|
|
|
id = "readerhighlight_hold_pan",
|
|
|
|
ges = "hold_pan",
|
2020-08-05 22:13:26 +00:00
|
|
|
rate = hold_pan_rate,
|
2017-01-23 14:54:14 +00:00
|
|
|
screen_zone = {
|
|
|
|
ratio_x = 0, ratio_y = 0, ratio_w = 1, ratio_h = 1,
|
|
|
|
},
|
|
|
|
handler = function(ges) return self:onHoldPan(nil, ges) end
|
2014-03-13 13:52:43 +00:00
|
|
|
},
|
2017-01-23 14:54:14 +00:00
|
|
|
})
|
|
|
|
end
|
|
|
|
|
|
|
|
function ReaderHighlight:onReaderReady()
|
|
|
|
self:setupTouchZones()
|
2013-04-23 22:59:52 +00:00
|
|
|
end
|
|
|
|
|
2021-10-18 17:17:37 +00:00
|
|
|
local highlight_style = {
|
|
|
|
{_("Lighten"), "lighten"},
|
|
|
|
{_("Underline"), "underscore"},
|
2022-01-25 20:39:03 +00:00
|
|
|
{_("Strikeout"), "strikeout"},
|
2021-10-18 17:17:37 +00:00
|
|
|
{_("Invert"), "invert"},
|
|
|
|
}
|
|
|
|
|
2021-09-11 09:04:48 +00:00
|
|
|
local long_press_action = {
|
|
|
|
{_("Ask with popup dialog"), "ask"},
|
|
|
|
{_("Do nothing"), "nothing"},
|
|
|
|
{_("Highlight"), "highlight"},
|
2021-11-21 17:31:10 +00:00
|
|
|
{_("Select and highlight"), "select"},
|
2021-09-11 09:04:48 +00:00
|
|
|
{_("Translate"), "translate"},
|
|
|
|
{_("Wikipedia"), "wikipedia"},
|
|
|
|
{_("Dictionary"), "dictionary"},
|
|
|
|
{_("Fulltext search"), "search"},
|
|
|
|
}
|
|
|
|
|
2017-03-04 13:46:38 +00:00
|
|
|
function ReaderHighlight:addToMainMenu(menu_items)
|
2014-03-13 13:52:43 +00:00
|
|
|
-- insert table to main reader menu
|
2022-03-16 19:00:14 +00:00
|
|
|
if not Device:isTouchDevice() and Device:hasDPad() then
|
2022-03-12 11:16:50 +00:00
|
|
|
menu_items.start_content_selection = {
|
|
|
|
text = _("Start content selection"),
|
|
|
|
callback = function()
|
|
|
|
self:onStartHighlightIndicator()
|
|
|
|
end,
|
|
|
|
}
|
|
|
|
end
|
2017-03-04 13:46:38 +00:00
|
|
|
menu_items.highlight_options = {
|
2021-09-11 09:04:48 +00:00
|
|
|
text = _("Highlight style"),
|
2021-10-18 17:17:37 +00:00
|
|
|
sub_item_table = {},
|
2017-02-28 21:46:32 +00:00
|
|
|
}
|
2022-02-01 19:56:28 +00:00
|
|
|
for i, v in ipairs(highlight_style) do
|
2021-10-18 17:17:37 +00:00
|
|
|
table.insert(menu_items.highlight_options.sub_item_table, {
|
|
|
|
text_func = function()
|
|
|
|
local text = v[1]
|
|
|
|
if v[2] == G_reader_settings:readSetting("highlight_drawing_style") then
|
|
|
|
text = text .. " ★"
|
|
|
|
end
|
|
|
|
return text
|
|
|
|
end,
|
|
|
|
checked_func = function()
|
|
|
|
return self.view.highlight.saved_drawer == v[2]
|
|
|
|
end,
|
2022-02-01 19:56:28 +00:00
|
|
|
radio = true,
|
2021-10-18 17:17:37 +00:00
|
|
|
callback = function()
|
|
|
|
self.view.highlight.saved_drawer = v[2]
|
|
|
|
end,
|
|
|
|
hold_callback = function(touchmenu_instance)
|
|
|
|
G_reader_settings:saveSetting("highlight_drawing_style", v[2])
|
|
|
|
if touchmenu_instance then touchmenu_instance:updateItems() end
|
|
|
|
end,
|
2022-02-01 19:56:28 +00:00
|
|
|
separator = i == #highlight_style,
|
2021-10-18 17:17:37 +00:00
|
|
|
})
|
|
|
|
end
|
|
|
|
table.insert(menu_items.highlight_options.sub_item_table, {
|
|
|
|
text_func = function()
|
|
|
|
return T(_("Highlight opacity: %1"), G_reader_settings:readSetting("highlight_lighten_factor", 0.2))
|
|
|
|
end,
|
|
|
|
enabled_func = function()
|
|
|
|
return self.view.highlight.saved_drawer == "lighten"
|
|
|
|
end,
|
|
|
|
callback = function(touchmenu_instance)
|
|
|
|
local SpinWidget = require("ui/widget/spinwidget")
|
|
|
|
local curr_val = G_reader_settings:readSetting("highlight_lighten_factor", 0.2)
|
|
|
|
local spin_widget = SpinWidget:new{
|
|
|
|
value = curr_val,
|
|
|
|
value_min = 0,
|
|
|
|
value_max = 1,
|
|
|
|
precision = "%.2f",
|
|
|
|
value_step = 0.1,
|
|
|
|
value_hold_step = 0.25,
|
|
|
|
default_value = 0.2,
|
|
|
|
keep_shown_on_apply = true,
|
|
|
|
title_text = _("Highlight opacity"),
|
|
|
|
info_text = _("The higher the value, the darker the highlight."),
|
|
|
|
callback = function(spin)
|
|
|
|
G_reader_settings:saveSetting("highlight_lighten_factor", spin.value)
|
|
|
|
self.view.highlight.lighten_factor = spin.value
|
|
|
|
UIManager:setDirty(self.dialog, "ui")
|
|
|
|
if touchmenu_instance then touchmenu_instance:updateItems() end
|
|
|
|
end
|
|
|
|
}
|
|
|
|
UIManager:show(spin_widget)
|
|
|
|
end,
|
|
|
|
})
|
2020-09-24 13:17:37 +00:00
|
|
|
if self.document.info.has_pages then
|
|
|
|
menu_items.panel_zoom_options = {
|
|
|
|
text = _("Panel zoom (manga/comic)"),
|
|
|
|
sub_item_table = self:genPanelZoomMenu(),
|
|
|
|
}
|
|
|
|
end
|
2018-12-17 13:15:13 +00:00
|
|
|
menu_items.translation_settings = Translator:genSettingsMenu()
|
2022-01-02 14:01:08 +00:00
|
|
|
|
2021-09-11 09:04:48 +00:00
|
|
|
menu_items.long_press = {
|
|
|
|
text = _("Long-press on text"),
|
|
|
|
sub_item_table = {
|
2022-03-12 11:16:50 +00:00
|
|
|
{
|
2022-05-23 22:25:50 +00:00
|
|
|
text_func = function()
|
|
|
|
return T(_("Highlight long-press interval: %1 s"),
|
|
|
|
G_reader_settings:readSetting("highlight_long_hold_threshold_s", 3))
|
|
|
|
end,
|
2022-03-12 11:16:50 +00:00
|
|
|
keep_menu_open = true,
|
2022-05-23 22:25:50 +00:00
|
|
|
callback = function(touchmenu_instance)
|
2022-03-12 11:16:50 +00:00
|
|
|
local SpinWidget = require("ui/widget/spinwidget")
|
|
|
|
local items = SpinWidget:new{
|
|
|
|
title_text = _("Highlight long-press interval"),
|
|
|
|
info_text = _([[
|
|
|
|
If a touch is not released in this interval, it is considered a long-press. On document text, single word selection will not be triggered.
|
|
|
|
|
|
|
|
The interval value is in seconds and can range from 3 to 20 seconds.]]),
|
|
|
|
width = math.floor(Screen:getWidth() * 0.75),
|
2022-05-23 22:25:50 +00:00
|
|
|
value = G_reader_settings:readSetting("highlight_long_hold_threshold_s", 3),
|
2022-03-12 11:16:50 +00:00
|
|
|
value_min = 3,
|
|
|
|
value_max = 20,
|
|
|
|
value_step = 1,
|
|
|
|
value_hold_step = 5,
|
2022-05-23 22:25:50 +00:00
|
|
|
unit = C_("Time", "s"),
|
2022-03-12 11:16:50 +00:00
|
|
|
ok_text = _("Set interval"),
|
|
|
|
default_value = 3,
|
|
|
|
callback = function(spin)
|
2022-05-23 22:25:50 +00:00
|
|
|
G_reader_settings:saveSetting("highlight_long_hold_threshold_s", spin.value)
|
|
|
|
if touchmenu_instance then touchmenu_instance:updateItems() end
|
2022-03-12 11:16:50 +00:00
|
|
|
end
|
|
|
|
}
|
|
|
|
UIManager:show(items)
|
|
|
|
end,
|
|
|
|
},
|
2021-09-11 09:04:48 +00:00
|
|
|
{
|
|
|
|
text = _("Dictionary on single word selection"),
|
|
|
|
checked_func = function()
|
|
|
|
return not self.view.highlight.disabled and G_reader_settings:nilOrFalse("highlight_action_on_single_word")
|
|
|
|
end,
|
|
|
|
enabled_func = function()
|
|
|
|
return not self.view.highlight.disabled
|
|
|
|
end,
|
|
|
|
callback = function()
|
|
|
|
G_reader_settings:flipNilOrFalse("highlight_action_on_single_word")
|
|
|
|
end,
|
|
|
|
separator = true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
for _, v in ipairs(long_press_action) do
|
|
|
|
table.insert(menu_items.long_press.sub_item_table, {
|
|
|
|
text = v[1],
|
|
|
|
checked_func = function()
|
|
|
|
return G_reader_settings:readSetting("default_highlight_action", "ask") == v[2]
|
|
|
|
end,
|
2022-02-01 19:56:28 +00:00
|
|
|
radio = true,
|
2021-09-11 09:04:48 +00:00
|
|
|
callback = function()
|
|
|
|
G_reader_settings:saveSetting("default_highlight_action", v[2])
|
|
|
|
self.view.highlight.disabled = v[2] == "nothing"
|
|
|
|
end,
|
|
|
|
})
|
|
|
|
end
|
2022-03-12 11:16:50 +00:00
|
|
|
-- long_press menu is under taps_and_gestures menu which is not available for non touch device
|
|
|
|
-- Clone long_press menu and change label making much meaning for non touch devices
|
2022-03-16 19:00:14 +00:00
|
|
|
if not Device:isTouchDevice() and Device:hasDPad() then
|
2022-03-12 11:16:50 +00:00
|
|
|
menu_items.selection_text = util.tableDeepCopy(menu_items.long_press)
|
|
|
|
menu_items.selection_text.text = _("Select on text")
|
|
|
|
end
|
2014-01-17 19:05:17 +00:00
|
|
|
end
|
|
|
|
|
2020-09-24 13:17:37 +00:00
|
|
|
function ReaderHighlight:genPanelZoomMenu()
|
|
|
|
return {
|
|
|
|
{
|
|
|
|
text = _("Allow panel zoom"),
|
|
|
|
checked_func = function()
|
|
|
|
return self.panel_zoom_enabled
|
|
|
|
end,
|
|
|
|
callback = function()
|
|
|
|
self:onTogglePanelZoomSetting()
|
|
|
|
end,
|
|
|
|
hold_callback = function()
|
|
|
|
local ext = util.getFileNameSuffix(self.ui.document.file)
|
2020-12-24 08:07:27 +00:00
|
|
|
local curr_val = G_reader_settings:getSettingForExt("panel_zoom_enabled", ext)
|
|
|
|
G_reader_settings:saveSettingForExt("panel_zoom_enabled", not curr_val, ext)
|
|
|
|
end,
|
|
|
|
separator = true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
text = _("Fall back to text selection"),
|
|
|
|
checked_func = function()
|
|
|
|
return self.panel_zoom_fallback_to_text_selection
|
|
|
|
end,
|
|
|
|
callback = function()
|
|
|
|
self:onToggleFallbackTextSelection()
|
|
|
|
end,
|
|
|
|
hold_callback = function()
|
|
|
|
local ext = util.getFileNameSuffix(self.ui.document.file)
|
|
|
|
G_reader_settings:saveSettingForExt("panel_zoom_fallback_to_text_selection", self.panel_zoom_fallback_to_text_selection, ext)
|
2020-09-24 13:17:37 +00:00
|
|
|
end,
|
|
|
|
separator = true,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
end
|
|
|
|
|
2017-09-20 15:35:30 +00:00
|
|
|
-- 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()
|
The great Input/GestureDetector/TimeVal spring cleanup (a.k.a., a saner main loop) (#7415)
* ReaderDictionary: Port delay computations to TimeVal
* ReaderHighlight: Port delay computations to TimeVal
* ReaderView: Port delay computations to TimeVal
* Android: Reset gesture detection state on APP_CMD_TERM_WINDOW.
This prevents potentially being stuck in bogus gesture states when switching apps.
* GestureDetector:
* Port delay computations to TimeVal
* Fixed delay computations to handle time warps (large and negative deltas).
* Simplified timed callback handling to invalidate timers much earlier, preventing accumulating useless timers that no longer have any chance of ever detecting a gesture.
* Fixed state clearing to handle the actual effective slots, instead of hard-coding slot 0 & slot 1.
* Simplified timed callback handling in general, and added support for a timerfd backend for better performance and accuracy.
* The improved timed callback handling allows us to detect and honor (as much as possible) the three possible clock sources usable by Linux evdev events.
The only case where synthetic timestamps are used (and that only to handle timed callbacks) is limited to non-timerfd platforms where input events use
a clock source that is *NOT* MONOTONIC.
AFAICT, that's pretty much... PocketBook, and that's it?
* Input:
* Use the <linux/input.h> FFI module instead of re-declaring every constant
* Fixed (verbose) debug logging of input events to actually translate said constants properly.
* Completely reset gesture detection state on suspend. This should prevent bogus gesture detection on resume.
* Refactored the waitEvent loop to make it easier to comprehend (hopefully) and much more efficient.
Of specific note, it no longer does a crazy select spam every 100µs, instead computing and relying on sane timeouts,
as afforded by switching the UI event/input loop to the MONOTONIC time base, and the refactored timed callbacks in GestureDetector.
* reMarkable: Stopped enforcing synthetic timestamps on input events, as it should no longer be necessary.
* TimeVal:
* Refactored and simplified, especially as far as metamethods are concerned (based on <bsd/sys/time.h>).
* Added a host of new methods to query the various POSIX clock sources, and made :now default to MONOTONIC.
* Removed the debug guard in __sub, as time going backwards can be a perfectly normal occurrence.
* New methods:
* Clock sources: :realtime, :monotonic, :monotonic_coarse, :realtime_coarse, :boottime
* Utility: :tonumber, :tousecs, :tomsecs, :fromnumber, :isPositive, :isZero
* UIManager:
* Ported event loop & scheduling to TimeVal, and switched to the MONOTONIC time base.
This ensures reliable and consistent scheduling, as time is ensured never to go backwards.
* Added a :getTime() method, that returns a cached TimeVal:now(), updated at the top of every UI frame.
It's used throughout the codebase to cadge a syscall in circumstances where we are guaranteed that a syscall would return a mostly identical value,
because very few time has passed.
The only code left that does live syscalls does it because it's actually necessary for accuracy,
and the only code left that does that in a REALTIME time base is code that *actually* deals with calendar time (e.g., Statistics).
* DictQuickLookup: Port delay computations to TimeVal
* FootNoteWidget: Port delay computations to TimeVal
* HTMLBoxWidget: Port delay computations to TimeVal
* Notification: Port delay computations to TimeVal
* TextBoxWidget: Port delay computations to TimeVal
* AutoSuspend: Port to TimeVal
* AutoTurn:
* Fix it so that settings are actually honored.
* Port to TimeVal
* BackgroundRunner: Port to TimeVal
* Calibre: Port benchmarking code to TimeVal
* BookInfoManager: Removed unnecessary yield in the metadata extraction subprocess now that subprocesses get scheduled properly.
* All in all, these changes reduced the CPU cost of a single tap by a factor of ten (!), and got rid of an insane amount of weird poll/wakeup cycles that must have been hell on CPU schedulers and batteries..
2021-03-30 00:57:59 +00:00
|
|
|
self.clear_id = UIManager:getTime() -- can act as a unique id
|
2017-09-20 15:35:30 +00:00
|
|
|
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
|
The great Input/GestureDetector/TimeVal spring cleanup (a.k.a., a saner main loop) (#7415)
* ReaderDictionary: Port delay computations to TimeVal
* ReaderHighlight: Port delay computations to TimeVal
* ReaderView: Port delay computations to TimeVal
* Android: Reset gesture detection state on APP_CMD_TERM_WINDOW.
This prevents potentially being stuck in bogus gesture states when switching apps.
* GestureDetector:
* Port delay computations to TimeVal
* Fixed delay computations to handle time warps (large and negative deltas).
* Simplified timed callback handling to invalidate timers much earlier, preventing accumulating useless timers that no longer have any chance of ever detecting a gesture.
* Fixed state clearing to handle the actual effective slots, instead of hard-coding slot 0 & slot 1.
* Simplified timed callback handling in general, and added support for a timerfd backend for better performance and accuracy.
* The improved timed callback handling allows us to detect and honor (as much as possible) the three possible clock sources usable by Linux evdev events.
The only case where synthetic timestamps are used (and that only to handle timed callbacks) is limited to non-timerfd platforms where input events use
a clock source that is *NOT* MONOTONIC.
AFAICT, that's pretty much... PocketBook, and that's it?
* Input:
* Use the <linux/input.h> FFI module instead of re-declaring every constant
* Fixed (verbose) debug logging of input events to actually translate said constants properly.
* Completely reset gesture detection state on suspend. This should prevent bogus gesture detection on resume.
* Refactored the waitEvent loop to make it easier to comprehend (hopefully) and much more efficient.
Of specific note, it no longer does a crazy select spam every 100µs, instead computing and relying on sane timeouts,
as afforded by switching the UI event/input loop to the MONOTONIC time base, and the refactored timed callbacks in GestureDetector.
* reMarkable: Stopped enforcing synthetic timestamps on input events, as it should no longer be necessary.
* TimeVal:
* Refactored and simplified, especially as far as metamethods are concerned (based on <bsd/sys/time.h>).
* Added a host of new methods to query the various POSIX clock sources, and made :now default to MONOTONIC.
* Removed the debug guard in __sub, as time going backwards can be a perfectly normal occurrence.
* New methods:
* Clock sources: :realtime, :monotonic, :monotonic_coarse, :realtime_coarse, :boottime
* Utility: :tonumber, :tousecs, :tomsecs, :fromnumber, :isPositive, :isZero
* UIManager:
* Ported event loop & scheduling to TimeVal, and switched to the MONOTONIC time base.
This ensures reliable and consistent scheduling, as time is ensured never to go backwards.
* Added a :getTime() method, that returns a cached TimeVal:now(), updated at the top of every UI frame.
It's used throughout the codebase to cadge a syscall in circumstances where we are guaranteed that a syscall would return a mostly identical value,
because very few time has passed.
The only code left that does live syscalls does it because it's actually necessary for accuracy,
and the only code left that does that in a REALTIME time base is code that *actually* deals with calendar time (e.g., Statistics).
* DictQuickLookup: Port delay computations to TimeVal
* FootNoteWidget: Port delay computations to TimeVal
* HTMLBoxWidget: Port delay computations to TimeVal
* Notification: Port delay computations to TimeVal
* TextBoxWidget: Port delay computations to TimeVal
* AutoSuspend: Port to TimeVal
* AutoTurn:
* Fix it so that settings are actually honored.
* Port to TimeVal
* BackgroundRunner: Port to TimeVal
* Calibre: Port benchmarking code to TimeVal
* BookInfoManager: Removed unnecessary yield in the metadata extraction subprocess now that subprocesses get scheduled properly.
* All in all, these changes reduced the CPU cost of a single tap by a factor of ten (!), and got rid of an insane amount of weird poll/wakeup cycles that must have been hell on CPU schedulers and batteries..
2021-03-30 00:57:59 +00:00
|
|
|
-- if clear_id is no longer valid, highlight has already been
|
2017-09-20 15:35:30 +00:00
|
|
|
-- cleared since this clear_id was given
|
|
|
|
return
|
|
|
|
end
|
|
|
|
end
|
|
|
|
self.clear_id = nil -- invalidate id
|
2021-05-19 20:57:46 +00:00
|
|
|
if not self.ui.document then
|
|
|
|
-- might happen if scheduled and run after document is closed
|
|
|
|
return
|
|
|
|
end
|
2014-11-05 04:28:11 +00:00
|
|
|
if self.ui.document.info.has_pages then
|
|
|
|
self.view.highlight.temp = {}
|
|
|
|
else
|
|
|
|
self.ui.document:clearSelection()
|
|
|
|
end
|
2019-02-03 09:01:58 +00:00
|
|
|
if self.restore_page_mode_func then
|
|
|
|
self.restore_page_mode_func()
|
|
|
|
self.restore_page_mode_func = nil
|
|
|
|
end
|
2021-10-23 10:12:56 +00:00
|
|
|
self.is_word_selection = false
|
2019-02-03 09:01:58 +00:00
|
|
|
self.selected_text_start_xpointer = nil
|
2014-03-13 13:52:43 +00:00
|
|
|
if self.hold_pos then
|
|
|
|
self.hold_pos = nil
|
2014-07-02 09:46:17 +00:00
|
|
|
self.selected_text = nil
|
2017-06-18 16:08:57 +00:00
|
|
|
UIManager:setDirty(self.dialog, "ui")
|
2014-03-13 13:52:43 +00:00
|
|
|
return true
|
|
|
|
end
|
2014-08-26 12:29:53 +00:00
|
|
|
end
|
|
|
|
|
2014-11-05 04:28:11 +00:00
|
|
|
function ReaderHighlight:onClearHighlight()
|
|
|
|
self:clear()
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
2016-03-28 00:18:25 +00:00
|
|
|
function ReaderHighlight:onTap(_, ges)
|
2020-11-08 01:18:50 +00:00
|
|
|
-- We only actually need to clear if we have something to clear in the first place.
|
|
|
|
-- (We mainly want to avoid CRe's clearSelection,
|
|
|
|
-- which may incur a redraw as it invalidates the cache, c.f., #6854)
|
|
|
|
-- ReaderHighlight:clear can only return true if self.hold_pos was set anyway.
|
|
|
|
local cleared = self.hold_pos and self:clear()
|
|
|
|
-- We only care about potential taps on existing highlights, not on taps that closed a highlight menu.
|
2021-11-21 17:31:10 +00:00
|
|
|
if not cleared and ges and not self.select_mode then
|
2014-08-26 12:29:53 +00:00
|
|
|
if self.ui.document.info.has_pages then
|
|
|
|
return self:onTapPageSavedHighlight(ges)
|
|
|
|
else
|
|
|
|
return self:onTapXPointerSavedHighlight(ges)
|
|
|
|
end
|
2014-03-13 13:52:43 +00:00
|
|
|
end
|
2014-01-17 19:05:17 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
function ReaderHighlight:onTapPageSavedHighlight(ges)
|
2014-03-13 13:52:43 +00:00
|
|
|
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]
|
2017-01-23 14:54:14 +00:00
|
|
|
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
|
2017-11-20 20:58:58 +00:00
|
|
|
logger.dbg("Tap on highlight")
|
2022-01-02 18:09:53 +00:00
|
|
|
return self:onShowHighlightNoteOrDialog(page, i)
|
2017-01-23 14:54:14 +00:00
|
|
|
end
|
2014-03-13 13:52:43 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2014-01-17 19:05:17 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
function ReaderHighlight:onTapXPointerSavedHighlight(ges)
|
2018-10-08 16:57:59 +00:00
|
|
|
-- 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).
|
2019-03-13 12:05:50 +00:00
|
|
|
local cur_view_top, cur_view_bottom
|
2014-03-13 13:52:43 +00:00
|
|
|
local pos = self.view:screenToPageTransform(ges.pos)
|
2020-11-08 01:18:50 +00:00
|
|
|
-- NOTE: By now, pos.page is set, but if a highlight spans across multiple pages,
|
|
|
|
-- it's stored under the hash of its *starting* point,
|
|
|
|
-- so we can't just check the current page, hence the giant hashwalk of death...
|
|
|
|
-- We can't even limit the walk to page <= pos.page,
|
|
|
|
-- because pos.page isn't super accurate in continuous mode
|
|
|
|
-- (it's the page number for what's it the topleft corner of the screen,
|
|
|
|
-- i.e., often a bit earlier)...
|
|
|
|
for page, items in pairs(self.view.highlight.saved) do
|
2017-01-23 14:54:14 +00:00
|
|
|
if items then
|
|
|
|
for i = 1, #items do
|
2020-11-08 01:18:50 +00:00
|
|
|
local item = items[i]
|
|
|
|
local pos0, pos1 = item.pos0, item.pos1
|
2017-11-20 20:58:58 +00:00
|
|
|
-- document:getScreenBoxesFromPositions() is expensive, so we
|
|
|
|
-- first check this item is on current page
|
2019-03-13 12:05:50 +00:00
|
|
|
if not cur_view_top then
|
|
|
|
-- Even in page mode, it's safer to use pos and ui.dimen.h
|
|
|
|
-- than pages' xpointers pos, even if ui.dimen.h is a bit
|
|
|
|
-- larger than pages' heights
|
|
|
|
cur_view_top = self.ui.document:getCurrentPos()
|
|
|
|
if self.view.view_mode == "page" and self.ui.document:getVisiblePageCount() > 1 then
|
|
|
|
cur_view_bottom = cur_view_top + 2 * self.ui.dimen.h
|
|
|
|
else
|
|
|
|
cur_view_bottom = cur_view_top + self.ui.dimen.h
|
2019-02-26 06:16:43 +00:00
|
|
|
end
|
|
|
|
end
|
2019-03-13 12:05:50 +00:00
|
|
|
local spos0 = self.ui.document:getPosFromXPointer(pos0)
|
|
|
|
local spos1 = self.ui.document:getPosFromXPointer(pos1)
|
|
|
|
local start_pos = math.min(spos0, spos1)
|
|
|
|
local end_pos = math.max(spos0, spos1)
|
|
|
|
if start_pos <= cur_view_bottom and end_pos >= cur_view_top then
|
2018-10-10 16:50:24 +00:00
|
|
|
local boxes = self.ui.document:getScreenBoxesFromPositions(pos0, pos1, true) -- get_segments=true
|
2017-11-20 20:58:58 +00:00
|
|
|
if boxes then
|
|
|
|
for index, box in pairs(boxes) do
|
|
|
|
if inside_box(pos, box) then
|
|
|
|
logger.dbg("Tap on highlight")
|
2022-01-02 18:09:53 +00:00
|
|
|
return self:onShowHighlightNoteOrDialog(page, i)
|
2017-11-20 20:58:58 +00:00
|
|
|
end
|
2017-01-23 14:54:14 +00:00
|
|
|
end
|
2014-03-13 13:52:43 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2013-04-23 22:59:52 +00:00
|
|
|
end
|
|
|
|
|
2019-02-15 23:42:27 +00:00
|
|
|
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
|
2019-02-17 15:15:32 +00:00
|
|
|
local updated_highlight_beginning
|
2019-02-15 23:42:27 +00:00
|
|
|
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
|
2019-02-17 15:15:32 +00:00
|
|
|
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
|
2019-02-15 23:42:27 +00:00
|
|
|
self.view.highlight.saved[page][index].pos0 = updated_highlight_beginning
|
|
|
|
end
|
|
|
|
end
|
|
|
|
else -- we move pos1
|
2019-02-17 15:15:32 +00:00
|
|
|
local updated_highlight_end
|
2019-02-15 23:42:27 +00:00
|
|
|
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
|
2019-02-17 15:15:32 +00:00
|
|
|
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
|
2019-02-15 23:42:27 +00:00
|
|
|
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)
|
2020-06-17 21:51:02 +00:00
|
|
|
local new_chapter = self.ui.toc:getTocTitleByPage(new_beginning)
|
2020-07-21 22:14:57 +00:00
|
|
|
self.view.highlight.saved[page][index].text = cleanupSelectedText(new_text)
|
2020-06-17 21:51:02 +00:00
|
|
|
self.view.highlight.saved[page][index].chapter = new_chapter
|
2019-02-15 23:42:27 +00:00
|
|
|
local new_highlight = self.view.highlight.saved[page][index]
|
|
|
|
self.ui.bookmark:updateBookmark({
|
|
|
|
page = highlight_beginning,
|
|
|
|
datetime = highlight_time,
|
|
|
|
updated_highlight = new_highlight
|
2021-10-18 17:17:37 +00:00
|
|
|
})
|
2020-03-20 20:58:58 +00:00
|
|
|
if side == 0 then
|
|
|
|
-- Ensure we show the page with the new beginning of highlight
|
|
|
|
if not self.ui.document:isXPointerInCurrentPage(new_beginning) then
|
|
|
|
self.ui:handleEvent(Event:new("GotoXPointer", new_beginning))
|
|
|
|
end
|
|
|
|
else
|
|
|
|
-- Ensure we show the page with the new end of highlight
|
|
|
|
if not self.ui.document:isXPointerInCurrentPage(new_end) then
|
|
|
|
if self.view.view_mode == "page" then
|
|
|
|
self.ui:handleEvent(Event:new("GotoXPointer", new_end))
|
|
|
|
else
|
|
|
|
-- Not easy to get the y that would show the whole line
|
|
|
|
-- containing new_end. So, we scroll so that new_end
|
|
|
|
-- is at 2/3 of the screen.
|
|
|
|
local end_y = self.ui.document:getPosFromXPointer(new_end)
|
|
|
|
local top_y = end_y - math.floor(Screen:getHeight() * 2/3)
|
|
|
|
self.ui.rolling:_gotoPos(top_y)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2019-02-15 23:42:27 +00:00
|
|
|
UIManager:setDirty(self.dialog, "ui")
|
|
|
|
end
|
|
|
|
|
2022-01-02 18:09:53 +00:00
|
|
|
function ReaderHighlight:onShowHighlightNoteOrDialog(page, index)
|
2021-10-18 17:17:37 +00:00
|
|
|
local item = self.view.highlight.saved[page][index]
|
2022-01-02 18:09:53 +00:00
|
|
|
local bookmark_note = self.ui.bookmark:getBookmarkNote({
|
2021-10-18 17:17:37 +00:00
|
|
|
page = self.ui.document.info.has_pages and item.pos0.page or item.pos0,
|
|
|
|
datetime = item.datetime,
|
|
|
|
})
|
2022-01-02 18:09:53 +00:00
|
|
|
if bookmark_note then
|
|
|
|
local textviewer
|
|
|
|
textviewer = TextViewer:new{
|
|
|
|
title = _("Note"),
|
|
|
|
text = bookmark_note,
|
|
|
|
width = math.floor(math.min(Screen:getWidth(), Screen:getHeight()) * 0.8),
|
|
|
|
height = math.floor(math.max(Screen:getWidth(), Screen:getHeight()) * 0.4),
|
|
|
|
justified = G_reader_settings:nilOrTrue("dict_justify"),
|
|
|
|
buttons_table = {
|
|
|
|
{
|
|
|
|
{
|
|
|
|
text = _("Close"),
|
|
|
|
callback = function()
|
|
|
|
UIManager:close(textviewer)
|
|
|
|
end,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
text = _("Edit highlight"),
|
|
|
|
callback = function()
|
|
|
|
UIManager:close(textviewer)
|
|
|
|
self:onShowHighlightDialog(page, index, false)
|
|
|
|
end,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
UIManager:show(textviewer)
|
|
|
|
else
|
|
|
|
self:onShowHighlightDialog(page, index, true)
|
|
|
|
end
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
|
|
|
function ReaderHighlight:onShowHighlightDialog(page, index, is_auto_text)
|
2019-02-15 23:42:27 +00:00
|
|
|
local buttons = {
|
|
|
|
{
|
2014-03-13 13:52:43 +00:00
|
|
|
{
|
2019-02-15 23:42:27 +00:00
|
|
|
text = _("Delete"),
|
|
|
|
callback = function()
|
|
|
|
self:deleteHighlight(page, index)
|
|
|
|
-- other part outside of the dialog may be dirty
|
|
|
|
UIManager:close(self.edit_highlight_dialog, "ui")
|
2021-03-21 12:57:18 +00:00
|
|
|
self.edit_highlight_dialog = nil
|
2019-02-15 23:42:27 +00:00
|
|
|
end,
|
2014-03-13 13:52:43 +00:00
|
|
|
},
|
2019-02-15 23:42:27 +00:00
|
|
|
{
|
2021-12-04 11:51:38 +00:00
|
|
|
text = C_("Highlight", "Style"),
|
2021-10-18 17:17:37 +00:00
|
|
|
callback = function()
|
|
|
|
self:editHighlightStyle(page, index)
|
|
|
|
UIManager:close(self.edit_highlight_dialog)
|
|
|
|
self.edit_highlight_dialog = nil
|
|
|
|
end,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
text = is_auto_text and _("Add note") or _("Edit note"),
|
2019-02-15 23:42:27 +00:00
|
|
|
callback = function()
|
|
|
|
self:editHighlight(page, index)
|
|
|
|
UIManager:close(self.edit_highlight_dialog)
|
2021-03-21 12:57:18 +00:00
|
|
|
self.edit_highlight_dialog = nil
|
2019-02-15 23:42:27 +00:00
|
|
|
end,
|
|
|
|
},
|
2019-10-06 21:47:53 +00:00
|
|
|
{
|
2022-05-29 20:32:16 +00:00
|
|
|
text = "…",
|
2019-10-06 21:47:53 +00:00
|
|
|
callback = function()
|
|
|
|
self.selected_text = self.view.highlight.saved[page][index]
|
2022-01-16 19:54:08 +00:00
|
|
|
self:onShowHighlightMenu(page, index)
|
2019-10-06 21:47:53 +00:00
|
|
|
UIManager:close(self.edit_highlight_dialog)
|
2021-03-21 12:57:18 +00:00
|
|
|
self.edit_highlight_dialog = nil
|
2019-10-06 21:47:53 +00:00
|
|
|
end,
|
|
|
|
},
|
2019-02-15 23:42:27 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-10 09:06:16 +00:00
|
|
|
if self.ui.rolling then
|
2021-05-20 21:04:57 +00:00
|
|
|
local start_prev = "◁▒▒"
|
|
|
|
local start_next = "▷▒▒"
|
|
|
|
local end_prev = "▒▒◁"
|
|
|
|
local end_next = "▒▒▷"
|
2019-12-06 21:55:39 +00:00
|
|
|
if BD.mirroredUILayout() then
|
2021-05-20 21:04:57 +00:00
|
|
|
-- BiDi will mirror the arrows, and this just works
|
|
|
|
start_prev, start_next = start_next, start_prev
|
|
|
|
end_prev, end_next = end_next, end_prev
|
2019-12-06 21:55:39 +00:00
|
|
|
end
|
2019-02-15 23:42:27 +00:00
|
|
|
table.insert(buttons, {
|
|
|
|
{
|
2019-12-06 21:55:39 +00:00
|
|
|
text = start_prev,
|
2019-02-15 23:42:27 +00:00
|
|
|
callback = function()
|
|
|
|
self:updateHighlight(page, index, 0, -1, false)
|
|
|
|
end,
|
|
|
|
hold_callback = function()
|
|
|
|
self:updateHighlight(page, index, 0, -1, true)
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
},
|
|
|
|
{
|
2019-12-06 21:55:39 +00:00
|
|
|
text = start_next,
|
2019-02-15 23:42:27 +00:00
|
|
|
callback = function()
|
|
|
|
self:updateHighlight(page, index, 0, 1, false)
|
|
|
|
end,
|
|
|
|
hold_callback = function()
|
|
|
|
self:updateHighlight(page, index, 0, 1, true)
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
},
|
|
|
|
{
|
2019-12-06 21:55:39 +00:00
|
|
|
text = end_prev,
|
2019-02-15 23:42:27 +00:00
|
|
|
callback = function()
|
|
|
|
self:updateHighlight(page, index, 1, -1, false)
|
|
|
|
end,
|
|
|
|
hold_callback = function()
|
|
|
|
self:updateHighlight(page, index, 1, -1, true)
|
|
|
|
end
|
|
|
|
},
|
|
|
|
{
|
2019-12-06 21:55:39 +00:00
|
|
|
text = end_next,
|
2019-02-15 23:42:27 +00:00
|
|
|
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
|
2014-03-13 13:52:43 +00:00
|
|
|
}
|
|
|
|
UIManager:show(self.edit_highlight_dialog)
|
|
|
|
return true
|
2014-01-17 19:05:17 +00:00
|
|
|
end
|
|
|
|
|
2020-11-08 12:07:51 +00:00
|
|
|
function ReaderHighlight:addToHighlightDialog(idx, fn_button)
|
|
|
|
-- fn_button is a function that takes the ReaderHighlight instance
|
|
|
|
-- as argument, and returns a table describing the button to be added.
|
|
|
|
self._highlight_buttons[idx] = fn_button
|
|
|
|
end
|
|
|
|
|
|
|
|
function ReaderHighlight:removeFromHighlightDialog(idx)
|
|
|
|
local button = self._highlight_buttons[idx]
|
|
|
|
self._highlight_buttons[idx] = nil
|
|
|
|
return button
|
|
|
|
end
|
|
|
|
|
2022-01-16 19:54:08 +00:00
|
|
|
function ReaderHighlight:onShowHighlightMenu(page, index)
|
2021-10-31 16:36:00 +00:00
|
|
|
if not self.selected_text then
|
|
|
|
return
|
|
|
|
end
|
|
|
|
|
2020-11-08 12:07:51 +00:00
|
|
|
local highlight_buttons = {{}}
|
|
|
|
|
|
|
|
local columns = 2
|
|
|
|
for idx, fn_button in ffiUtil.orderedPairs(self._highlight_buttons) do
|
2022-01-16 19:54:08 +00:00
|
|
|
local button = fn_button(self, page, index)
|
2021-02-06 15:59:09 +00:00
|
|
|
if not button.show_in_highlight_dialog_func or button.show_in_highlight_dialog_func() then
|
|
|
|
if #highlight_buttons[#highlight_buttons] >= columns then
|
|
|
|
table.insert(highlight_buttons, {})
|
|
|
|
end
|
|
|
|
table.insert(highlight_buttons[#highlight_buttons], button)
|
|
|
|
logger.dbg("ReaderHighlight", idx..": line "..#highlight_buttons..", col "..#highlight_buttons[#highlight_buttons])
|
2020-11-08 12:07:51 +00:00
|
|
|
end
|
2020-01-05 11:56:01 +00:00
|
|
|
end
|
|
|
|
|
2019-10-06 21:47:53 +00:00
|
|
|
self.highlight_dialog = ButtonDialog:new{
|
|
|
|
buttons = highlight_buttons,
|
|
|
|
tap_close_callback = function() self:handleEvent(Event:new("Tap")) end,
|
|
|
|
}
|
|
|
|
UIManager:show(self.highlight_dialog)
|
|
|
|
end
|
2021-11-06 07:11:06 +00:00
|
|
|
dbg:guard(ReaderHighlight, "onShowHighlightMenu",
|
|
|
|
function(self)
|
|
|
|
assert(self.selected_text ~= nil,
|
|
|
|
"onShowHighlightMenu must not be called with nil self.selected_text!")
|
|
|
|
end)
|
2019-10-06 21:47:53 +00:00
|
|
|
|
2020-05-05 16:56:30 +00:00
|
|
|
function ReaderHighlight:_resetHoldTimer(clear)
|
|
|
|
if clear then
|
2022-05-05 19:00:22 +00:00
|
|
|
self.hold_last_time = nil
|
2020-05-05 16:56:30 +00:00
|
|
|
else
|
2022-05-05 19:00:22 +00:00
|
|
|
self.hold_last_time = UIManager:getTime()
|
2020-05-05 16:56:30 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2020-09-24 13:17:37 +00:00
|
|
|
function ReaderHighlight:onTogglePanelZoomSetting(arg, ges)
|
|
|
|
if not self.document.info.has_pages then return end
|
|
|
|
self.panel_zoom_enabled = not self.panel_zoom_enabled
|
|
|
|
end
|
|
|
|
|
2020-12-24 08:07:27 +00:00
|
|
|
function ReaderHighlight:onToggleFallbackTextSelection(arg, ges)
|
|
|
|
if not self.document.info.has_pages then return end
|
|
|
|
self.panel_zoom_fallback_to_text_selection = not self.panel_zoom_fallback_to_text_selection
|
|
|
|
end
|
|
|
|
|
2020-09-24 13:17:37 +00:00
|
|
|
function ReaderHighlight:onPanelZoom(arg, ges)
|
|
|
|
self:clear()
|
|
|
|
local hold_pos = self.view:screenToPageTransform(ges.pos)
|
|
|
|
if not hold_pos then return false end -- outside page boundary
|
|
|
|
local rect = self.ui.document:getPanelFromPage(hold_pos.page, hold_pos)
|
|
|
|
if not rect then return false end -- panel not found, return
|
|
|
|
local image = self.ui.document:getPagePart(hold_pos.page, rect, 0)
|
|
|
|
|
|
|
|
if image then
|
|
|
|
local ImageViewer = require("ui/widget/imageviewer")
|
|
|
|
local imgviewer = ImageViewer:new{
|
|
|
|
image = image,
|
|
|
|
with_title_bar = false,
|
|
|
|
fullscreen = true,
|
|
|
|
}
|
|
|
|
UIManager:show(imgviewer)
|
2020-12-24 08:07:27 +00:00
|
|
|
return true
|
2020-09-24 13:17:37 +00:00
|
|
|
end
|
2020-12-24 08:07:27 +00:00
|
|
|
return false
|
2020-09-24 13:17:37 +00:00
|
|
|
end
|
|
|
|
|
2017-01-15 20:51:43 +00:00
|
|
|
function ReaderHighlight:onHold(arg, ges)
|
2020-09-24 13:17:37 +00:00
|
|
|
if self.document.info.has_pages and self.panel_zoom_enabled then
|
2020-12-24 08:07:27 +00:00
|
|
|
local res = self:onPanelZoom(arg, ges)
|
|
|
|
if res or not self.panel_zoom_fallback_to_text_selection then
|
|
|
|
return res
|
|
|
|
end
|
2020-09-24 13:17:37 +00:00
|
|
|
end
|
|
|
|
|
2017-09-20 15:35:30 +00:00
|
|
|
self:clear() -- clear previous highlight (delayed clear may not have done it yet)
|
2018-02-26 16:25:34 +00:00
|
|
|
self.hold_ges_pos = ges.pos -- remember hold original gesture position
|
2014-03-13 13:52:43 +00:00
|
|
|
self.hold_pos = self.view:screenToPageTransform(ges.pos)
|
2016-12-29 08:10:38 +00:00
|
|
|
logger.dbg("hold position in page", self.hold_pos)
|
2014-03-13 13:52:43 +00:00
|
|
|
if not self.hold_pos then
|
2016-12-29 08:10:38 +00:00
|
|
|
logger.dbg("not inside page area")
|
2020-01-24 19:05:21 +00:00
|
|
|
return false
|
2014-03-13 13:52:43 +00:00
|
|
|
end
|
2013-10-12 15:07:13 +00:00
|
|
|
|
2017-01-15 20:51:43 +00:00
|
|
|
-- check if we were holding on an image
|
2018-04-21 20:03:04 +00:00
|
|
|
-- 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)
|
2017-01-15 20:51:43 +00:00
|
|
|
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)
|
2022-03-12 11:16:50 +00:00
|
|
|
self:onStopHighlightIndicator()
|
2017-01-15 20:51:43 +00:00
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
|
|
|
-- otherwise, we must be holding on text
|
2021-09-11 09:04:48 +00:00
|
|
|
if self.view.highlight.disabled then return false end -- Long-press action "Do nothing" checked
|
2014-03-13 13:52:43 +00:00
|
|
|
local ok, word = pcall(self.ui.document.getWordFromPosition, self.ui.document, self.hold_pos)
|
|
|
|
if ok and word then
|
2016-12-29 08:10:38 +00:00
|
|
|
logger.dbg("selected word:", word)
|
2021-10-23 10:12:56 +00:00
|
|
|
-- Convert "word selection" table to "text selection" table because we
|
2021-10-23 10:13:09 +00:00
|
|
|
-- use text selections throughout readerhighlight in order to allow the
|
|
|
|
-- highlight to be corrected by language-specific plugins more easily.
|
2021-10-23 10:12:56 +00:00
|
|
|
self.is_word_selection = true
|
|
|
|
self.selected_text = {
|
|
|
|
text = word.word or "",
|
|
|
|
pos0 = word.pos0 or word.pos,
|
|
|
|
pos1 = word.pos1 or word.pos,
|
|
|
|
sboxes = word.sbox and { word.sbox },
|
|
|
|
pboxes = word.pbox and { word.pbox },
|
|
|
|
}
|
2017-09-10 12:26:49 +00:00
|
|
|
local link = self.ui.link:getLinkFromGes(ges)
|
2017-09-09 16:30:00 +00:00
|
|
|
self.selected_link = nil
|
|
|
|
if link then
|
|
|
|
logger.dbg("link:", link)
|
|
|
|
self.selected_link = link
|
|
|
|
end
|
2021-10-23 10:13:09 +00:00
|
|
|
|
2021-10-25 06:45:26 +00:00
|
|
|
if self.ui.languagesupport and self.ui.languagesupport:hasActiveLanguagePlugins() then
|
2021-10-23 10:13:09 +00:00
|
|
|
-- If this is a language where pan-less word selection needs some
|
|
|
|
-- extra work above and beyond what the document engine gives us
|
|
|
|
-- from getWordFromPosition, call the relevant language-specific
|
|
|
|
-- plugin.
|
|
|
|
local new_selected_text = self.ui.languagesupport:improveWordSelection(self.selected_text)
|
|
|
|
if new_selected_text then
|
|
|
|
self.selected_text = new_selected_text
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2014-03-13 13:52:43 +00:00
|
|
|
if self.ui.document.info.has_pages then
|
2021-10-23 10:12:56 +00:00
|
|
|
self.view.highlight.temp[self.hold_pos.page] = self.selected_text.sboxes
|
2021-01-09 20:59:29 +00:00
|
|
|
-- Unfortunately, getWordFromPosition() may not return good coordinates,
|
|
|
|
-- so refresh the whole page
|
|
|
|
UIManager:setDirty(self.dialog, "ui")
|
|
|
|
else
|
2021-10-23 10:12:56 +00:00
|
|
|
-- With crengine, getWordFromPosition() does return good coordinates.
|
|
|
|
UIManager:setDirty(self.dialog, "ui", Geom.boundingBox(self.selected_text.sboxes))
|
2014-03-13 13:52:43 +00:00
|
|
|
end
|
2020-05-05 16:56:30 +00:00
|
|
|
self:_resetHoldTimer()
|
2019-03-13 12:10:32 +00:00
|
|
|
if word.pos0 then
|
|
|
|
-- Remember original highlight start position, so we can show
|
|
|
|
-- a marker when back from across-pages text selection, which
|
|
|
|
-- is handled in onHoldPan()
|
|
|
|
self.selected_text_start_xpointer = word.pos0
|
|
|
|
end
|
2020-01-24 19:05:21 +00:00
|
|
|
return true
|
2014-03-13 13:52:43 +00:00
|
|
|
end
|
2020-01-24 19:05:21 +00:00
|
|
|
return false
|
2013-06-15 15:13:19 +00:00
|
|
|
end
|
|
|
|
|
2016-03-28 00:18:25 +00:00
|
|
|
function ReaderHighlight:onHoldPan(_, ges)
|
2021-12-10 09:06:16 +00:00
|
|
|
if self.view.highlight.disabled then return false end -- Long-press action "Do nothing" checked
|
2014-03-13 13:52:43 +00:00
|
|
|
if self.hold_pos == nil then
|
2016-12-29 08:10:38 +00:00
|
|
|
logger.dbg("no previous hold position")
|
2020-05-05 16:56:30 +00:00
|
|
|
self:_resetHoldTimer(true)
|
2014-03-13 13:52:43 +00:00
|
|
|
return true
|
|
|
|
end
|
2014-07-02 08:38:09 +00:00
|
|
|
local page_area = self.view:getScreenPageArea(self.hold_pos.page)
|
|
|
|
if ges.pos:notIntersectWith(page_area) then
|
2016-12-29 08:10:38 +00:00
|
|
|
logger.dbg("not inside page area", ges, page_area)
|
2020-05-05 16:56:30 +00:00
|
|
|
self:_resetHoldTimer()
|
2014-07-02 08:38:09 +00:00
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
2014-03-13 13:52:43 +00:00
|
|
|
self.holdpan_pos = self.view:screenToPageTransform(ges.pos)
|
2016-12-29 08:10:38 +00:00
|
|
|
logger.dbg("holdpan position in page", self.holdpan_pos)
|
2019-02-03 09:01:58 +00:00
|
|
|
|
2021-12-10 09:06:16 +00:00
|
|
|
if self.ui.rolling and self.selected_text_start_xpointer then
|
2019-02-03 09:01:58 +00:00
|
|
|
-- With CreDocuments, allow text selection across multiple pages
|
|
|
|
-- by (temporarily) switching to scroll mode when panning to the
|
|
|
|
-- top left or bottom right corners.
|
2020-01-06 20:09:00 +00:00
|
|
|
local mirrored_reading = BD.mirroredUILayout()
|
2021-11-21 17:33:51 +00:00
|
|
|
if self.view.inverse_reading_order then
|
2020-01-06 20:09:00 +00:00
|
|
|
mirrored_reading = not mirrored_reading
|
|
|
|
end
|
|
|
|
local is_in_prev_page_corner, is_in_next_page_corner
|
|
|
|
if mirrored_reading then
|
2019-12-06 21:55:39 +00:00
|
|
|
-- Note: this might not be really usable, as crengine native selection
|
|
|
|
-- is not adapted to RTL text
|
2020-01-06 20:09:00 +00:00
|
|
|
-- top right corner
|
|
|
|
is_in_prev_page_corner = self.holdpan_pos.y < 1/8*Screen:getHeight()
|
2019-12-06 21:55:39 +00:00
|
|
|
and self.holdpan_pos.x > 7/8*Screen:getWidth()
|
2020-01-06 20:09:00 +00:00
|
|
|
-- bottom left corner
|
|
|
|
is_in_next_page_corner = self.holdpan_pos.y > 7/8*Screen:getHeight()
|
2019-12-06 21:55:39 +00:00
|
|
|
and self.holdpan_pos.x < 1/8*Screen:getWidth()
|
2020-01-06 20:09:00 +00:00
|
|
|
else -- default in LTR UI with no inverse_reading_order
|
|
|
|
-- top left corner
|
|
|
|
is_in_prev_page_corner = self.holdpan_pos.y < 1/8*Screen:getHeight()
|
|
|
|
and self.holdpan_pos.x < 1/8*Screen:getWidth()
|
|
|
|
-- bottom right corner
|
|
|
|
is_in_next_page_corner = self.holdpan_pos.y > 7/8*Screen:getHeight()
|
|
|
|
and self.holdpan_pos.x > 7/8*Screen:getWidth()
|
2019-12-06 21:55:39 +00:00
|
|
|
end
|
2020-01-06 20:09:00 +00:00
|
|
|
if is_in_prev_page_corner or is_in_next_page_corner then
|
2020-05-05 16:56:30 +00:00
|
|
|
self:_resetHoldTimer()
|
2019-02-03 09:01:58 +00:00
|
|
|
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
|
2019-03-13 12:10:32 +00:00
|
|
|
if self.ui.document:getVisiblePageCount() == 1 then -- single page mode
|
|
|
|
-- 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)
|
2020-01-06 20:09:00 +00:00
|
|
|
local move_y = is_in_next_page_corner and scroll_distance or -scroll_distance
|
2019-03-13 12:10:32 +00:00
|
|
|
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 -- two pages mode
|
|
|
|
-- We don't switch to scroll mode: we just turn 1 page to
|
|
|
|
-- allow continuing the selection.
|
|
|
|
-- Unlike in 1-page mode, we have a limitation here: we can't adjust
|
|
|
|
-- the selection to further than current page and prev/next one.
|
|
|
|
-- So don't handle another corner if we already handled one:
|
2020-01-06 20:09:00 +00:00
|
|
|
-- Note that this feature won't work well with the RTL UI or
|
|
|
|
-- if inverse_reading_order as crengine currently always displays
|
|
|
|
-- the first page on the left and the second on the right in
|
|
|
|
-- dual page mode.
|
2019-03-13 12:10:32 +00:00
|
|
|
if self.restore_page_mode_func then
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
-- Also, we are not able to move hold_pos.x out of screen,
|
|
|
|
-- so if we started on the right page, ignore top left corner,
|
|
|
|
-- and if we started on the left page, ignore bottom right corner.
|
2020-06-14 00:21:41 +00:00
|
|
|
local screen_half_width = math.floor(Screen:getWidth() * 0.5)
|
2020-01-06 20:09:00 +00:00
|
|
|
if self.hold_pos.x >= screen_half_width and is_in_prev_page_corner then
|
2019-03-13 12:10:32 +00:00
|
|
|
return true
|
2020-01-06 20:09:00 +00:00
|
|
|
elseif self.hold_pos.x <= screen_half_width and is_in_next_page_corner then
|
2019-03-13 12:10:32 +00:00
|
|
|
return true
|
|
|
|
end
|
2021-01-29 00:02:19 +00:00
|
|
|
-- To be able to browse half-page when 2 visible pages as 1 page number,
|
|
|
|
-- we must work with internal page numbers
|
|
|
|
local cur_page = self.ui.document:getCurrentPage(true)
|
2019-02-03 09:01:58 +00:00
|
|
|
local restore_page_mode_xpointer = self.ui.document:getXPointer() -- top of current page
|
2021-01-29 00:02:19 +00:00
|
|
|
self.ui.document.no_page_sync = true -- avoid CreDocument:drawCurrentViewByPage() to resync page
|
2019-02-03 09:01:58 +00:00
|
|
|
self.restore_page_mode_func = function()
|
2021-01-29 00:02:19 +00:00
|
|
|
self.ui.document.no_page_sync = nil
|
2019-02-03 09:01:58 +00:00
|
|
|
self.ui.rolling:onGotoXPointer(restore_page_mode_xpointer, self.selected_text_start_xpointer)
|
|
|
|
end
|
2020-01-06 20:09:00 +00:00
|
|
|
if is_in_next_page_corner then -- bottom right corner in LTR UI
|
2021-01-29 00:02:19 +00:00
|
|
|
self.ui.rolling:_gotoPage(cur_page + 1, true, true) -- no odd left page enforcement
|
2019-03-13 12:10:32 +00:00
|
|
|
self.hold_pos.x = self.hold_pos.x - screen_half_width
|
2020-01-06 20:09:00 +00:00
|
|
|
else -- top left corner in RTL UI
|
2021-01-29 00:02:19 +00:00
|
|
|
self.ui.rolling:_gotoPage(cur_page - 1, true, true) -- no odd left page enforcement
|
2019-03-13 12:10:32 +00:00
|
|
|
self.hold_pos.x = self.hold_pos.x + screen_half_width
|
|
|
|
end
|
|
|
|
UIManager:setDirty(self.dialog, "ui")
|
|
|
|
return true
|
2019-02-03 09:01:58 +00:00
|
|
|
end
|
|
|
|
else
|
|
|
|
self.was_in_some_corner = nil
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2014-11-30 00:12:00 +00:00
|
|
|
local old_text = self.selected_text and self.selected_text.text
|
2014-03-13 13:52:43 +00:00
|
|
|
self.selected_text = self.ui.document:getTextFromPositions(self.hold_pos, self.holdpan_pos)
|
2021-10-23 10:12:56 +00:00
|
|
|
self.is_word_selection = false
|
2019-02-03 09:01:58 +00:00
|
|
|
|
|
|
|
if self.selected_text and self.selected_text.pos0 then
|
|
|
|
if not self.selected_text_start_xpointer then
|
2019-03-13 12:10:32 +00:00
|
|
|
-- This should have been set in onHold(), where we would get
|
|
|
|
-- a precise pos0 on the first word selected.
|
|
|
|
-- Do it here too in case onHold() missed it, but it could be
|
|
|
|
-- less precise (getTextFromPositions() does order pos0 and pos1,
|
|
|
|
-- so it's not certain pos0 is where we started from; we get
|
|
|
|
-- the ones from the first pan, and if it is not small enough
|
|
|
|
-- and spans quite some height, the marker could point away
|
|
|
|
-- from the start position)
|
2019-02-03 09:01:58 +00:00
|
|
|
self.selected_text_start_xpointer = self.selected_text.pos0
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2014-11-30 00:12:00 +00:00
|
|
|
if self.selected_text and old_text and old_text == self.selected_text.text then
|
|
|
|
-- no modification
|
|
|
|
return
|
|
|
|
end
|
2020-05-05 16:56:30 +00:00
|
|
|
self:_resetHoldTimer() -- selection updated
|
2016-12-29 08:10:38 +00:00
|
|
|
logger.dbg("selected text:", self.selected_text)
|
2014-03-13 13:52:43 +00:00
|
|
|
if self.selected_text then
|
|
|
|
self.view.highlight.temp[self.hold_pos.page] = self.selected_text.sboxes
|
|
|
|
end
|
2014-12-03 06:06:43 +00:00
|
|
|
UIManager:setDirty(self.dialog, "ui")
|
2013-06-15 15:13:19 +00:00
|
|
|
end
|
|
|
|
|
2019-08-22 15:11:47 +00:00
|
|
|
local info_message_ocr_text = _([[
|
|
|
|
No OCR results or no language data.
|
2018-03-05 15:38:04 +00:00
|
|
|
|
|
|
|
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.
|
|
|
|
|
2020-07-30 20:15:02 +00:00
|
|
|
You can download language data files for version 3.04 from https://tesseract-ocr.github.io/tessdoc/Data-Files
|
2018-03-05 15:38:04 +00:00
|
|
|
|
2018-03-17 17:13:04 +00:00
|
|
|
Copy the language data files for Tesseract 3.04 (e.g., eng.traineddata for English and spa.traineddata for Spanish) into koreader/data/tessdata]])
|
2018-03-05 15:38:04 +00:00
|
|
|
|
2021-10-23 10:12:56 +00:00
|
|
|
function ReaderHighlight:lookup(selected_text, selected_link)
|
|
|
|
-- convert sboxes to word boxes
|
|
|
|
local word_boxes = {}
|
|
|
|
for i, sbox in ipairs(selected_text.sboxes) do
|
|
|
|
word_boxes[i] = self.view:pageToScreenTransform(self.hold_pos.page, sbox)
|
|
|
|
end
|
|
|
|
|
2014-03-13 13:52:43 +00:00
|
|
|
-- if we extracted text directly
|
2021-10-23 10:12:56 +00:00
|
|
|
if selected_text.text and self.hold_pos then
|
|
|
|
self.ui:handleEvent(Event:new("LookupWord", selected_text.text, false, word_boxes, self, selected_link))
|
2014-03-13 13:52:43 +00:00
|
|
|
-- or we will do OCR
|
2021-10-23 10:12:56 +00:00
|
|
|
elseif selected_text.sboxes and self.hold_pos then
|
|
|
|
local text = self.ui.document:getOCRText(self.hold_pos.page, 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
|
|
|
|
local word = self.ui.document:getOCRWord(self.hold_pos.page, { sbox = sbox })
|
|
|
|
logger.dbg("OCRed word:", word)
|
2021-10-23 14:29:00 +00:00
|
|
|
--- @fixme This might produce incorrect results on RTL text.
|
2021-10-23 10:12:56 +00:00
|
|
|
if word and word ~= "" then
|
|
|
|
text = text .. word
|
|
|
|
end
|
|
|
|
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))
|
2018-03-05 15:38:04 +00:00
|
|
|
else
|
|
|
|
UIManager:show(InfoMessage:new{
|
|
|
|
text = info_message_ocr_text,
|
|
|
|
})
|
|
|
|
end
|
2014-03-13 13:52:43 +00:00
|
|
|
end
|
2013-07-19 18:49:03 +00:00
|
|
|
end
|
2021-11-06 07:11:06 +00:00
|
|
|
dbg:guard(ReaderHighlight, "lookup",
|
|
|
|
function(self, selected_text, selected_link)
|
|
|
|
assert(selected_text ~= nil,
|
|
|
|
"lookup must not be called with nil selected_text!")
|
|
|
|
end)
|
2013-07-19 18:49:03 +00:00
|
|
|
|
2022-06-12 19:34:17 +00:00
|
|
|
function ReaderHighlight:getSelectedWordContext(nb_words)
|
|
|
|
if not self.ui.rolling or not self.selected_text then return nil end
|
|
|
|
local pos_start = self.selected_text.pos0
|
|
|
|
local pos_end = self.selected_text.pos1
|
|
|
|
|
|
|
|
for i=0, nb_words do
|
|
|
|
local ok, start = pcall(self.ui.document.getPrevVisibleWordStart, self.ui.document, pos_start)
|
|
|
|
if ok then pos_start = start
|
|
|
|
else break end
|
|
|
|
end
|
|
|
|
|
|
|
|
for i=0, nb_words do
|
|
|
|
local ok, ending = pcall(self.ui.document.getNextVisibleWordEnd, self.ui.document, pos_end)
|
|
|
|
if ok then pos_end = ending
|
|
|
|
else break end
|
|
|
|
end
|
|
|
|
|
|
|
|
local ok_prev, prev = pcall(self.ui.document.getTextFromXPointers, self.ui.document, pos_start, self.selected_text.pos0)
|
|
|
|
local ok_next, next = pcall(self.ui.document.getTextFromXPointers, self.ui.document, self.selected_text.pos1, pos_end)
|
|
|
|
|
|
|
|
return ok_prev and prev, ok_next and next
|
|
|
|
end
|
|
|
|
|
2021-09-01 12:17:35 +00:00
|
|
|
function ReaderHighlight:viewSelectionHTML(debug_view, no_css_files_buttons)
|
2018-10-08 16:58:43 +00:00
|
|
|
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
|
2020-01-14 00:14:15 +00:00
|
|
|
-- Start with valid and classic displayed HTML (with only block nodes indented),
|
2020-06-05 08:06:37 +00:00
|
|
|
-- including styles found in <HEAD>, linked CSS files content, and misc info.
|
|
|
|
local html_flags = 0x6830
|
2019-08-17 17:10:54 +00:00
|
|
|
if not debug_view then
|
|
|
|
debug_view = 0
|
|
|
|
end
|
|
|
|
if debug_view == 1 then
|
2018-10-08 16:58:43 +00:00
|
|
|
-- Each node on a line, with markers and numbers of skipped chars and siblings shown,
|
|
|
|
-- with possibly invalid HTML (text nodes not escaped)
|
2020-06-05 08:06:37 +00:00
|
|
|
html_flags = 0x6B5A
|
2019-08-17 17:10:54 +00:00
|
|
|
elseif debug_view == 2 then
|
2020-01-31 10:50:13 +00:00
|
|
|
-- Additionally see rendering methods of each node
|
2020-06-05 08:06:37 +00:00
|
|
|
html_flags = 0x6F5A
|
2020-01-31 10:50:13 +00:00
|
|
|
elseif debug_view == 3 then
|
|
|
|
-- Or additionally see unicode codepoint of each char
|
2020-06-05 08:06:37 +00:00
|
|
|
html_flags = 0x6B5E
|
2018-10-08 16:58:43 +00:00
|
|
|
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
|
2019-08-17 17:10:54 +00:00
|
|
|
if debug_view >= 1 then
|
2018-10-08 16:58:43 +00:00
|
|
|
html = html:gsub("\xC2\xA0", "␣") -- no break space: open box
|
|
|
|
html = html:gsub("\xC2\xAD", "⋅") -- soft hyphen: dot operator (smaller than middle dot ·)
|
2019-07-14 20:18:33 +00:00
|
|
|
-- Prettify inlined CSS (from <HEAD>, put in an internal
|
|
|
|
-- <body><stylesheet> element by crengine (the opening tag may
|
|
|
|
-- include some href=, or end with " ~X>" with some html_flags)
|
|
|
|
-- (We do that in debug_view mode only: as this may increase
|
|
|
|
-- the height of this section, we don't want to have to scroll
|
|
|
|
-- many pages to get to the HTML content on the initial view.)
|
|
|
|
html = html:gsub("(<stylesheet[^>]*>)%s*(.-)%s*(</stylesheet>)", function(pre, css_text, post)
|
2020-06-08 18:47:31 +00:00
|
|
|
return pre .. "\n" .. util.prettifyCSS(css_text) .. post
|
2019-07-14 20:18:33 +00:00
|
|
|
end)
|
2018-10-08 16:58:43 +00:00
|
|
|
end
|
|
|
|
local Font = require("ui/font")
|
|
|
|
local textviewer
|
2021-09-01 12:17:35 +00:00
|
|
|
local buttons_hold_callback = function()
|
|
|
|
-- Allow hiding css files buttons if there are too many
|
|
|
|
-- and the available height for text is too short
|
|
|
|
UIManager:close(textviewer)
|
|
|
|
self:viewSelectionHTML(debug_view, not no_css_files_buttons)
|
|
|
|
end
|
2018-10-08 16:58:43 +00:00
|
|
|
local buttons_table = {}
|
2021-09-01 12:17:35 +00:00
|
|
|
if css_files and not no_css_files_buttons then
|
2018-10-08 16:58:43 +00:00
|
|
|
for i=1, #css_files do
|
|
|
|
local button = {
|
2020-01-04 00:18:51 +00:00
|
|
|
text = T(_("View %1"), BD.filepath(css_files[i])),
|
2018-10-08 16:58:43 +00:00
|
|
|
callback = function()
|
|
|
|
local css_text = self.ui.document:getDocumentFileContent(css_files[i])
|
2018-10-22 16:47:01 +00:00
|
|
|
local cssviewer
|
|
|
|
cssviewer = TextViewer:new{
|
2018-10-08 16:58:43 +00:00
|
|
|
title = css_files[i],
|
|
|
|
text = css_text or _("Failed getting CSS content"),
|
|
|
|
text_face = Font:getFace("smallinfont"),
|
|
|
|
justified = false,
|
2019-12-06 21:55:35 +00:00
|
|
|
para_direction_rtl = false,
|
|
|
|
auto_para_direction = false,
|
2018-10-22 16:47:01 +00:00
|
|
|
buttons_table = {
|
|
|
|
{{
|
|
|
|
text = _("Prettify"),
|
|
|
|
enabled = css_text and true or false,
|
|
|
|
callback = function()
|
|
|
|
UIManager:close(cssviewer)
|
|
|
|
UIManager:show(TextViewer:new{
|
|
|
|
title = css_files[i],
|
2020-06-08 18:47:31 +00:00
|
|
|
text = util.prettifyCSS(css_text),
|
2018-10-22 16:47:01 +00:00
|
|
|
text_face = Font:getFace("smallinfont"),
|
|
|
|
justified = false,
|
2019-12-06 21:55:35 +00:00
|
|
|
para_direction_rtl = false,
|
|
|
|
auto_para_direction = false,
|
2018-10-22 16:47:01 +00:00
|
|
|
})
|
|
|
|
end,
|
|
|
|
}},
|
|
|
|
{{
|
|
|
|
text = _("Close"),
|
|
|
|
callback = function()
|
|
|
|
UIManager:close(cssviewer)
|
|
|
|
end,
|
|
|
|
}},
|
|
|
|
}
|
2018-10-08 16:58:43 +00:00
|
|
|
}
|
|
|
|
UIManager:show(cssviewer)
|
|
|
|
end,
|
2021-09-01 12:17:35 +00:00
|
|
|
hold_callback = buttons_hold_callback,
|
2018-10-08 16:58:43 +00:00
|
|
|
}
|
|
|
|
-- One button per row, too make room for the possibly long css filename
|
|
|
|
table.insert(buttons_table, {button})
|
|
|
|
end
|
|
|
|
end
|
2019-08-17 17:10:54 +00:00
|
|
|
local next_debug_text
|
|
|
|
local next_debug_view = debug_view + 1
|
|
|
|
if next_debug_view == 1 then
|
|
|
|
next_debug_text = _("Switch to debug view")
|
|
|
|
elseif next_debug_view == 2 then
|
2020-01-31 10:50:13 +00:00
|
|
|
next_debug_text = _("Switch to rendering debug view")
|
|
|
|
elseif next_debug_view == 3 then
|
|
|
|
next_debug_text = _("Switch to unicode debug view")
|
2019-08-17 17:10:54 +00:00
|
|
|
else
|
|
|
|
next_debug_view = 0
|
|
|
|
next_debug_text = _("Switch to standard view")
|
|
|
|
end
|
2018-10-08 16:58:43 +00:00
|
|
|
table.insert(buttons_table, {{
|
2019-08-17 17:10:54 +00:00
|
|
|
text = next_debug_text,
|
2018-10-08 16:58:43 +00:00
|
|
|
callback = function()
|
|
|
|
UIManager:close(textviewer)
|
2021-09-01 12:17:35 +00:00
|
|
|
self:viewSelectionHTML(next_debug_view, no_css_files_buttons)
|
2018-10-08 16:58:43 +00:00
|
|
|
end,
|
2021-09-01 12:17:35 +00:00
|
|
|
hold_callback = buttons_hold_callback,
|
2018-10-08 16:58:43 +00:00
|
|
|
}})
|
|
|
|
table.insert(buttons_table, {{
|
|
|
|
text = _("Close"),
|
|
|
|
callback = function()
|
|
|
|
UIManager:close(textviewer)
|
|
|
|
end,
|
2021-09-01 12:17:35 +00:00
|
|
|
hold_callback = buttons_hold_callback,
|
2018-10-08 16:58:43 +00:00
|
|
|
}})
|
|
|
|
textviewer = TextViewer:new{
|
|
|
|
title = _("Selection HTML"),
|
|
|
|
text = html,
|
|
|
|
text_face = Font:getFace("smallinfont"),
|
|
|
|
justified = false,
|
2019-12-06 21:55:35 +00:00
|
|
|
para_direction_rtl = false,
|
|
|
|
auto_para_direction = false,
|
2018-10-08 16:58:43 +00:00
|
|
|
buttons_table = buttons_table,
|
|
|
|
}
|
|
|
|
UIManager:show(textviewer)
|
|
|
|
else
|
|
|
|
UIManager:show(InfoMessage:new{
|
|
|
|
text = _("Failed getting HTML for selection"),
|
|
|
|
})
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2022-01-16 19:54:08 +00:00
|
|
|
function ReaderHighlight:translate(selected_text, page, index)
|
2022-06-11 09:08:40 +00:00
|
|
|
if self.ui.rolling then
|
|
|
|
-- Extend the selected text to include any punctuation at start or end,
|
|
|
|
-- which may give a better translation with the added context.
|
|
|
|
local extended_text = self.ui.document:extendXPointersToSentenceSegment(selected_text.pos0, selected_text.pos1)
|
|
|
|
if extended_text then
|
|
|
|
selected_text = extended_text
|
|
|
|
end
|
|
|
|
end
|
2014-03-13 13:52:43 +00:00
|
|
|
if selected_text.text ~= "" then
|
2022-01-16 19:54:08 +00:00
|
|
|
self:onTranslateText(selected_text.text, page, index)
|
2014-03-13 13:52:43 +00:00
|
|
|
-- or we will do OCR
|
2021-05-16 17:54:46 +00:00
|
|
|
elseif self.hold_pos then
|
2014-03-13 13:52:43 +00:00
|
|
|
local text = self.ui.document:getOCRText(self.hold_pos.page, selected_text)
|
2016-12-29 08:10:38 +00:00
|
|
|
logger.dbg("OCRed text:", text)
|
2018-03-05 15:38:04 +00:00
|
|
|
if text and text ~= "" then
|
2018-12-16 17:02:38 +00:00
|
|
|
self:onTranslateText(text)
|
2018-03-05 15:38:04 +00:00
|
|
|
else
|
|
|
|
UIManager:show(InfoMessage:new{
|
|
|
|
text = info_message_ocr_text,
|
|
|
|
})
|
|
|
|
end
|
2014-03-13 13:52:43 +00:00
|
|
|
end
|
2013-07-19 18:49:03 +00:00
|
|
|
end
|
2021-11-06 07:11:06 +00:00
|
|
|
dbg:guard(ReaderHighlight, "translate",
|
|
|
|
function(self, selected_text)
|
|
|
|
assert(selected_text ~= nil,
|
|
|
|
"translate must not be called with nil selected_text!")
|
|
|
|
end)
|
2013-07-19 18:49:03 +00:00
|
|
|
|
2022-01-16 19:54:08 +00:00
|
|
|
function ReaderHighlight:onTranslateText(text, page, index)
|
|
|
|
Translator:showTranslation(text, false, false, true, page, index)
|
2018-12-16 17:02:38 +00:00
|
|
|
end
|
|
|
|
|
2014-08-11 13:49:42 +00:00
|
|
|
function ReaderHighlight:onHoldRelease()
|
2022-01-08 15:58:32 +00:00
|
|
|
if self.clear_id then
|
|
|
|
-- Something has requested a clear id and is about to clear
|
|
|
|
-- the highlight: it may be a onHoldClose() that handled
|
|
|
|
-- "hold" and was closed, and can't handle "hold_release":
|
|
|
|
-- ignore this "hold_release" event.
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
2021-11-21 17:31:10 +00:00
|
|
|
local default_highlight_action = G_reader_settings:readSetting("default_highlight_action", "ask")
|
|
|
|
|
|
|
|
if self.select_mode then -- extended highlighting, ending fragment
|
|
|
|
if self.selected_text then
|
|
|
|
if self.ui.document.info.has_pages and self.ui.paging.current_page ~= self.highlight_page then
|
|
|
|
self.ui.paging:_gotoPage(self.highlight_page)
|
|
|
|
UIManager:show(Notification:new{
|
|
|
|
text = _("Fragments must be within one page"),
|
|
|
|
})
|
|
|
|
else
|
|
|
|
self.select_mode = false
|
|
|
|
self:extendSelection()
|
|
|
|
if default_highlight_action == "select" then
|
2022-06-11 09:08:40 +00:00
|
|
|
self:saveHighlight(true)
|
2021-11-21 17:31:10 +00:00
|
|
|
self:clear()
|
|
|
|
else
|
|
|
|
self:onShowHighlightMenu()
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
return true
|
|
|
|
end
|
2021-11-21 17:41:07 +00:00
|
|
|
|
2020-05-05 16:56:30 +00:00
|
|
|
local long_final_hold = false
|
2022-05-05 19:00:22 +00:00
|
|
|
if self.hold_last_time then
|
|
|
|
local hold_duration = time.now() - self.hold_last_time
|
2022-05-23 22:25:50 +00:00
|
|
|
local long_hold_threshold_s = G_reader_settings:readSetting("highlight_long_hold_threshold_s", 3) -- seconds
|
2022-05-05 19:00:22 +00:00
|
|
|
if hold_duration > time.s(long_hold_threshold_s) then
|
2020-05-05 16:56:30 +00:00
|
|
|
-- We stayed 3 seconds before release without updating selection
|
|
|
|
long_final_hold = true
|
|
|
|
end
|
2022-05-05 19:00:22 +00:00
|
|
|
self.hold_last_time = nil
|
2020-05-05 16:56:30 +00:00
|
|
|
end
|
2021-10-23 10:12:56 +00:00
|
|
|
if self.is_word_selection then -- single-word selection
|
2020-05-05 16:56:30 +00:00
|
|
|
if long_final_hold or G_reader_settings:isTrue("highlight_action_on_single_word") then
|
2021-10-23 10:12:56 +00:00
|
|
|
self.is_word_selection = false
|
2017-09-10 18:35:27 +00:00
|
|
|
end
|
|
|
|
end
|
2019-01-17 21:12:38 +00:00
|
|
|
|
2021-11-06 07:11:06 +00:00
|
|
|
if self.selected_text then
|
|
|
|
if self.is_word_selection then
|
|
|
|
self:lookup(self.selected_text, self.selected_link)
|
|
|
|
else
|
|
|
|
if long_final_hold or default_highlight_action == "ask" then
|
|
|
|
-- bypass default action and show popup if long final hold
|
|
|
|
self:onShowHighlightMenu()
|
|
|
|
elseif default_highlight_action == "highlight" then
|
2022-06-11 09:08:40 +00:00
|
|
|
self:saveHighlight(true)
|
2021-11-06 07:11:06 +00:00
|
|
|
self:onClose()
|
2021-11-21 17:31:10 +00:00
|
|
|
elseif default_highlight_action == "select" then
|
|
|
|
self:startSelection()
|
|
|
|
self:onClose()
|
2021-11-06 07:11:06 +00:00
|
|
|
elseif default_highlight_action == "translate" then
|
|
|
|
self:translate(self.selected_text)
|
|
|
|
elseif default_highlight_action == "wikipedia" then
|
|
|
|
self:lookupWikipedia()
|
|
|
|
self:onClose()
|
|
|
|
elseif default_highlight_action == "dictionary" then
|
|
|
|
self:onHighlightDictLookup()
|
|
|
|
self:onClose()
|
|
|
|
elseif default_highlight_action == "search" then
|
|
|
|
self:onHighlightSearch()
|
|
|
|
-- No self:onClose() to not remove the selected text
|
|
|
|
-- which will have been the first search result
|
|
|
|
end
|
2018-12-16 17:02:38 +00:00
|
|
|
end
|
2014-03-13 13:52:43 +00:00
|
|
|
end
|
|
|
|
return true
|
2013-04-23 22:59:52 +00:00
|
|
|
end
|
|
|
|
|
2019-03-14 14:33:04 +00:00
|
|
|
function ReaderHighlight:onCycleHighlightAction()
|
2021-09-11 09:04:48 +00:00
|
|
|
local current_action = G_reader_settings:readSetting("default_highlight_action", "ask")
|
|
|
|
local next_action_num
|
|
|
|
for i, v in ipairs(long_press_action) do
|
|
|
|
if v[2] == current_action then
|
|
|
|
next_action_num = i + 1
|
|
|
|
break
|
|
|
|
end
|
|
|
|
end
|
|
|
|
if next_action_num > #long_press_action then
|
|
|
|
next_action_num = 1
|
2019-03-14 14:33:04 +00:00
|
|
|
end
|
2021-09-11 09:04:48 +00:00
|
|
|
G_reader_settings:saveSetting("default_highlight_action", long_press_action[next_action_num][2])
|
|
|
|
UIManager:show(Notification:new{
|
|
|
|
text = T(_("Default highlight action changed to '%1'."), long_press_action[next_action_num][1]),
|
|
|
|
})
|
2019-04-18 10:12:38 +00:00
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
|
|
|
function ReaderHighlight:onCycleHighlightStyle()
|
2021-10-18 17:17:37 +00:00
|
|
|
local current_style = self.view.highlight.saved_drawer
|
|
|
|
local next_style_num
|
|
|
|
for i, v in ipairs(highlight_style) do
|
|
|
|
if v[2] == current_style then
|
|
|
|
next_style_num = i + 1
|
|
|
|
break
|
|
|
|
end
|
|
|
|
end
|
|
|
|
if next_style_num > #highlight_style then
|
|
|
|
next_style_num = 1
|
|
|
|
end
|
|
|
|
self.view.highlight.saved_drawer = highlight_style[next_style_num][2]
|
2019-04-18 10:12:38 +00:00
|
|
|
self.ui.doc_settings:saveSetting("highlight_drawer", self.view.highlight.saved_drawer)
|
|
|
|
UIManager:show(Notification:new{
|
2021-10-18 17:17:37 +00:00
|
|
|
text = T(_("Default highlight style changed to '%1'."), highlight_style[next_style_num][1]),
|
2019-04-18 10:12:38 +00:00
|
|
|
})
|
|
|
|
return true
|
2019-03-14 14:33:04 +00:00
|
|
|
end
|
|
|
|
|
2014-11-05 04:28:11 +00:00
|
|
|
function ReaderHighlight:highlightFromHoldPos()
|
2014-08-11 13:49:42 +00:00
|
|
|
if self.hold_pos then
|
|
|
|
if not self.selected_text then
|
|
|
|
self.selected_text = self.ui.document:getTextFromPositions(self.hold_pos, self.hold_pos)
|
2021-10-25 06:45:26 +00:00
|
|
|
if self.ui.languagesupport and self.ui.languagesupport:hasActiveLanguagePlugins() then
|
2021-10-23 10:13:09 +00:00
|
|
|
-- Match language-specific expansion you'd get from self:onHold().
|
|
|
|
local new_selected_text = self.ui.languagesupport:improveWordSelection(self.selected_text)
|
|
|
|
if new_selected_text then
|
|
|
|
self.selected_text = new_selected_text
|
|
|
|
end
|
|
|
|
end
|
2016-12-29 08:10:38 +00:00
|
|
|
logger.dbg("selected text:", self.selected_text)
|
2014-08-11 13:49:42 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2014-11-05 04:28:11 +00:00
|
|
|
function ReaderHighlight:onHighlight()
|
|
|
|
self:saveHighlight()
|
|
|
|
end
|
|
|
|
|
2017-10-18 15:19:06 +00:00
|
|
|
function ReaderHighlight:onUnhighlight(bookmark_item)
|
2017-07-28 20:39:54 +00:00
|
|
|
local page
|
|
|
|
local sel_text
|
|
|
|
local sel_pos0
|
2017-10-18 15:19:06 +00:00
|
|
|
local datetime
|
2017-07-28 20:39:54 +00:00
|
|
|
local idx
|
2017-10-18 15:19:06 +00:00
|
|
|
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
|
2021-05-16 17:54:46 +00:00
|
|
|
--- @fixme: is this self.hold_pos access safe?
|
2017-07-28 20:39:54 +00:00
|
|
|
page = self.hold_pos.page
|
2020-07-21 22:14:57 +00:00
|
|
|
sel_text = cleanupSelectedText(self.selected_text.text)
|
2017-07-28 20:39:54 +00:00
|
|
|
sel_pos0 = self.selected_text.pos0
|
|
|
|
end
|
2017-10-18 15:19:06 +00:00
|
|
|
if self.ui.document.info.has_pages then -- We can safely use page
|
2021-07-18 17:56:17 +00:00
|
|
|
-- As we may have changed spaces and hyphens handling in the extracted
|
|
|
|
-- text over the years, check text identities with them removed
|
|
|
|
local sel_text_cleaned = sel_text:gsub("[ -]", ""):gsub("\xC2\xAD", "")
|
2017-10-18 15:19:06 +00:00
|
|
|
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
|
2021-07-18 17:56:17 +00:00
|
|
|
if ( (datetime == nil and highlight.pos0 == sel_pos0) or
|
|
|
|
(datetime ~= nil and highlight.datetime == datetime) ) then
|
|
|
|
if highlight.text:gsub("[ -]", ""):gsub("\xC2\xAD", "") == sel_text_cleaned then
|
|
|
|
idx = index
|
|
|
|
break
|
|
|
|
end
|
2017-10-18 15:19:06 +00:00
|
|
|
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
|
2017-07-28 20:39:54 +00:00
|
|
|
end
|
|
|
|
end
|
2017-10-18 15:19:06 +00:00
|
|
|
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)
|
2017-07-28 20:39:54 +00:00
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
2015-03-12 10:50:57 +00:00
|
|
|
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
|
|
|
|
return {
|
2022-01-21 15:56:08 +00:00
|
|
|
page = self.ui.paging and self.hold_pos.page or self.selected_text.pos0,
|
2015-03-12 10:50:57 +00:00
|
|
|
pos0 = self.selected_text.pos0,
|
|
|
|
pos1 = self.selected_text.pos1,
|
2020-07-21 22:14:57 +00:00
|
|
|
notes = cleanupSelectedText(self.selected_text.text),
|
2015-03-12 10:50:57 +00:00
|
|
|
highlighted = true,
|
|
|
|
}
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2022-06-11 09:08:40 +00:00
|
|
|
function ReaderHighlight:saveHighlight(extend_to_sentence)
|
2017-07-27 17:00:37 +00:00
|
|
|
self.ui:handleEvent(Event:new("AddHighlight"))
|
2016-12-29 08:10:38 +00:00
|
|
|
logger.dbg("save highlight")
|
2021-05-16 17:54:46 +00:00
|
|
|
if self.hold_pos and self.selected_text and self.selected_text.pos0 and self.selected_text.pos1 then
|
2022-06-11 09:08:40 +00:00
|
|
|
if extend_to_sentence and self.ui.rolling then
|
|
|
|
local extended_text = self.ui.document:extendXPointersToSentenceSegment(self.selected_text.pos0, self.selected_text.pos1)
|
|
|
|
if extended_text then
|
|
|
|
self.selected_text = extended_text
|
|
|
|
end
|
|
|
|
end
|
2021-05-16 17:54:46 +00:00
|
|
|
local page = self.hold_pos.page
|
2014-03-13 13:52:43 +00:00
|
|
|
if not self.view.highlight.saved[page] then
|
|
|
|
self.view.highlight.saved[page] = {}
|
|
|
|
end
|
2014-11-27 13:59:27 +00:00
|
|
|
local datetime = os.date("%Y-%m-%d %H:%M:%S")
|
2022-01-21 15:56:08 +00:00
|
|
|
local pg_or_xp = self.ui.paging and page or self.selected_text.pos0
|
2020-06-17 21:51:02 +00:00
|
|
|
local chapter_name = self.ui.toc:getTocTitleByPage(pg_or_xp)
|
2014-11-27 13:59:27 +00:00
|
|
|
local hl_item = {
|
|
|
|
datetime = datetime,
|
2020-07-21 22:14:57 +00:00
|
|
|
text = cleanupSelectedText(self.selected_text.text),
|
2014-11-27 13:59:27 +00:00
|
|
|
pos0 = self.selected_text.pos0,
|
|
|
|
pos1 = self.selected_text.pos1,
|
|
|
|
pboxes = self.selected_text.pboxes,
|
|
|
|
drawer = self.view.highlight.saved_drawer,
|
2022-01-21 15:56:08 +00:00
|
|
|
chapter = chapter_name,
|
2014-11-27 13:59:27 +00:00
|
|
|
}
|
2014-03-13 13:52:43 +00:00
|
|
|
table.insert(self.view.highlight.saved[page], hl_item)
|
2015-03-12 10:50:57 +00:00
|
|
|
local bookmark_item = self:getHighlightBookmarkItem()
|
|
|
|
if bookmark_item then
|
2022-01-21 15:56:08 +00:00
|
|
|
bookmark_item.datetime = datetime
|
|
|
|
bookmark_item.chapter = chapter_name
|
2015-03-12 10:50:57 +00:00
|
|
|
self.ui.bookmark:addBookmark(bookmark_item)
|
|
|
|
end
|
2014-03-13 13:52:43 +00:00
|
|
|
if self.selected_text.pboxes then
|
|
|
|
self:exportToDocument(page, hl_item)
|
|
|
|
end
|
2019-03-12 19:14:34 +00:00
|
|
|
return page, #self.view.highlight.saved[page]
|
2014-03-13 13:52:43 +00:00
|
|
|
end
|
2013-06-15 15:13:19 +00:00
|
|
|
end
|
|
|
|
|
2014-02-01 16:16:51 +00:00
|
|
|
function ReaderHighlight:exportToDocument(page, item)
|
2020-02-25 20:26:19 +00:00
|
|
|
local setting = G_reader_settings:readSetting("save_document")
|
|
|
|
if setting == "disable" then return end
|
2016-12-29 08:10:38 +00:00
|
|
|
logger.dbg("export highlight to document", item)
|
2020-02-25 20:26:19 +00:00
|
|
|
local can_write = self.ui.document:saveHighlight(page, item)
|
|
|
|
if can_write == false and not self.warned_once then
|
|
|
|
self.warned_once = true
|
|
|
|
UIManager:show(InfoMessage:new{
|
|
|
|
text = _([[
|
|
|
|
Highlights in this document will be saved in the settings file, but they won't be written in the document itself because the file is in a read-only location.
|
|
|
|
|
|
|
|
If you wish your highlights to be saved in the document, just move it to a writable directory first.]]),
|
|
|
|
timeout = 5,
|
|
|
|
})
|
|
|
|
end
|
2014-02-01 16:16:51 +00:00
|
|
|
end
|
|
|
|
|
2022-01-16 19:54:08 +00:00
|
|
|
function ReaderHighlight:addNote(text)
|
2022-06-11 09:08:40 +00:00
|
|
|
local page, index = self:saveHighlight(true)
|
2022-01-16 19:54:08 +00:00
|
|
|
if text then self:clear() end
|
|
|
|
self:editHighlight(page, index, true, text)
|
2019-03-12 19:14:34 +00:00
|
|
|
UIManager:close(self.edit_highlight_dialog)
|
2021-03-21 12:57:18 +00:00
|
|
|
self.edit_highlight_dialog = nil
|
2019-03-12 19:14:34 +00:00
|
|
|
self.ui:handleEvent(Event:new("AddNote"))
|
2013-06-15 15:13:19 +00:00
|
|
|
end
|
|
|
|
|
2014-08-20 06:41:45 +00:00
|
|
|
function ReaderHighlight:lookupWikipedia()
|
|
|
|
if self.selected_text then
|
2020-07-21 22:14:57 +00:00
|
|
|
self.ui:handleEvent(Event:new("LookupWikipedia", cleanupSelectedText(self.selected_text.text)))
|
2014-08-20 06:41:45 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2014-11-05 04:28:11 +00:00
|
|
|
function ReaderHighlight:onHighlightSearch()
|
2016-12-29 08:10:38 +00:00
|
|
|
logger.dbg("search highlight")
|
2021-03-21 12:57:18 +00:00
|
|
|
-- First, if our dialog is still shown, close it.
|
|
|
|
if self.highlight_dialog then
|
|
|
|
UIManager:close(self.highlight_dialog)
|
|
|
|
self.highlight_dialog = nil
|
|
|
|
end
|
2014-11-05 04:28:11 +00:00
|
|
|
self:highlightFromHoldPos()
|
|
|
|
if self.selected_text then
|
2020-07-21 22:14:57 +00:00
|
|
|
local text = util.stripPunctuation(cleanupSelectedText(self.selected_text.text))
|
2015-03-12 09:41:20 +00:00
|
|
|
self.ui:handleEvent(Event:new("ShowSearchDialog", text))
|
2014-11-05 04:28:11 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2015-06-15 10:11:42 +00:00
|
|
|
function ReaderHighlight:onHighlightDictLookup()
|
2016-12-29 08:10:38 +00:00
|
|
|
logger.dbg("dictionary lookup highlight")
|
2015-06-15 10:11:42 +00:00
|
|
|
self:highlightFromHoldPos()
|
|
|
|
if self.selected_text then
|
2020-07-21 22:14:57 +00:00
|
|
|
self.ui:handleEvent(Event:new("LookupWord", cleanupSelectedText(self.selected_text.text)))
|
2015-06-15 10:11:42 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-10-18 15:19:06 +00:00
|
|
|
function ReaderHighlight:deleteHighlight(page, i, bookmark_item)
|
2017-07-27 17:00:37 +00:00
|
|
|
self.ui:handleEvent(Event:new("DelHighlight"))
|
2017-10-18 15:19:06 +00:00
|
|
|
logger.dbg("delete highlight", page, i)
|
2020-11-08 01:18:50 +00:00
|
|
|
-- The per-page table is a pure array
|
2014-11-27 13:59:27 +00:00
|
|
|
local removed = table.remove(self.view.highlight.saved[page], i)
|
2020-11-08 01:18:50 +00:00
|
|
|
-- But the main, outer table is a hash, so clear the table for this page if there are no longer any highlights on it
|
|
|
|
if #self.view.highlight.saved[page] == 0 then
|
|
|
|
self.view.highlight.saved[page] = nil
|
|
|
|
end
|
2017-10-18 15:19:06 +00:00
|
|
|
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
|
2021-03-23 09:31:52 +00:00
|
|
|
local setting = G_reader_settings:readSetting("save_document")
|
|
|
|
if setting ~= "disable" then
|
|
|
|
logger.dbg("delete highlight from document", removed)
|
|
|
|
self.ui.document:deleteHighlight(page, removed)
|
|
|
|
end
|
2022-01-16 19:54:08 +00:00
|
|
|
UIManager:setDirty(self.dialog, "ui")
|
2013-06-15 15:13:19 +00:00
|
|
|
end
|
|
|
|
|
2022-01-16 19:54:08 +00:00
|
|
|
function ReaderHighlight:editHighlight(page, i, is_new_note, text)
|
2017-10-18 15:19:06 +00:00
|
|
|
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,
|
2021-03-23 09:31:52 +00:00
|
|
|
pboxes = item.pboxes
|
2022-01-16 19:54:08 +00:00
|
|
|
}, true, is_new_note, text)
|
2013-06-15 15:13:19 +00:00
|
|
|
end
|
2013-10-18 20:38:07 +00:00
|
|
|
|
2021-10-18 17:17:37 +00:00
|
|
|
function ReaderHighlight:editHighlightStyle(page, i)
|
|
|
|
local item = self.view.highlight.saved[page][i]
|
2022-01-25 20:39:03 +00:00
|
|
|
local save_document = self.ui.paging and G_reader_settings:readSetting("save_document") ~= "disable"
|
2021-10-18 17:17:37 +00:00
|
|
|
local radio_buttons = {}
|
|
|
|
for _, v in ipairs(highlight_style) do
|
|
|
|
table.insert(radio_buttons, {
|
|
|
|
{
|
2022-01-25 20:39:03 +00:00
|
|
|
text = v[1],
|
|
|
|
checked = item.drawer == v[2],
|
|
|
|
provider = v[2],
|
2021-10-18 17:17:37 +00:00
|
|
|
},
|
|
|
|
})
|
|
|
|
end
|
|
|
|
UIManager:show(require("ui/widget/radiobuttonwidget"):new{
|
|
|
|
title_text = _("Highlight style"),
|
|
|
|
width_factor = 0.5,
|
|
|
|
keep_shown_on_apply = true,
|
|
|
|
radio_buttons = radio_buttons,
|
|
|
|
default_provider = self.view.highlight.saved_drawer or
|
|
|
|
G_reader_settings:readSetting("highlight_drawing_style", "lighten"),
|
|
|
|
callback = function(radio)
|
2022-01-25 20:39:03 +00:00
|
|
|
if save_document then
|
|
|
|
self.ui.document:deleteHighlight(page, item)
|
|
|
|
end
|
2022-01-04 20:58:56 +00:00
|
|
|
item.drawer = radio.provider
|
2022-01-25 20:39:03 +00:00
|
|
|
if save_document then
|
|
|
|
self.ui.document:saveHighlight(page, item)
|
|
|
|
end
|
2021-10-18 17:17:37 +00:00
|
|
|
UIManager:setDirty(self.dialog, "ui")
|
2022-01-04 20:58:56 +00:00
|
|
|
self.ui:handleEvent(Event:new("BookmarkUpdated",
|
|
|
|
self.ui.bookmark:getBookmarkForHighlight({
|
2022-01-25 20:39:03 +00:00
|
|
|
page = self.ui.paging and page or item.pos0,
|
2022-01-04 20:58:56 +00:00
|
|
|
datetime = item.datetime,
|
|
|
|
})))
|
2021-10-18 17:17:37 +00:00
|
|
|
end,
|
|
|
|
})
|
|
|
|
end
|
|
|
|
|
2021-11-21 17:31:10 +00:00
|
|
|
function ReaderHighlight:startSelection()
|
|
|
|
self.highlight_page, self.highlight_idx = self:saveHighlight()
|
|
|
|
self.select_mode = true
|
|
|
|
UIManager:show(Notification:new{
|
|
|
|
text = _("Select ending fragment"),
|
|
|
|
})
|
|
|
|
end
|
|
|
|
|
|
|
|
function ReaderHighlight:extendSelection()
|
|
|
|
-- item1 - starting fragment (saved), item2 - ending fragment (currently selected)
|
|
|
|
-- new extended highlight includes item1, item2 and the text between them
|
|
|
|
local item1 = self.view.highlight.saved[self.highlight_page][self.highlight_idx]
|
|
|
|
local item2_pos0, item2_pos1 = self.selected_text.pos0, self.selected_text.pos1
|
|
|
|
-- getting starting and ending positions, text and pboxes of extended highlight
|
|
|
|
local new_pos0, new_pos1, new_text, new_pboxes
|
|
|
|
if self.ui.document.info.has_pages then
|
2022-01-06 19:54:33 +00:00
|
|
|
local is_reflow = self.ui.document.configurable.text_wrap
|
2021-11-21 17:31:10 +00:00
|
|
|
local new_page = self.hold_pos.page
|
|
|
|
-- reflow mode doesn't set page in positions
|
2022-01-06 19:54:33 +00:00
|
|
|
item1.pos0.page = new_page
|
|
|
|
item1.pos1.page = new_page
|
|
|
|
item2_pos0.page = new_page
|
|
|
|
item2_pos1.page = new_page
|
2021-11-21 17:31:10 +00:00
|
|
|
-- pos0 and pos1 are not in order within highlights, hence sorting all
|
|
|
|
local function comparePositions (pos1, pos2)
|
2022-01-06 19:54:33 +00:00
|
|
|
return self.ui.document:comparePositions(pos1, pos2) == 1
|
2021-11-21 17:31:10 +00:00
|
|
|
end
|
|
|
|
local positions = {item1.pos0, item1.pos1, item2_pos0, item2_pos1}
|
|
|
|
self.ui.document.configurable.text_wrap = 0 -- native positions
|
|
|
|
table.sort(positions, comparePositions)
|
|
|
|
new_pos0 = positions[1]
|
|
|
|
new_pos1 = positions[4]
|
|
|
|
local text_boxes = self.ui.document:getTextFromPositions(new_pos0, new_pos1)
|
|
|
|
new_text = text_boxes.text
|
|
|
|
new_pboxes = text_boxes.pboxes
|
2022-01-06 19:54:33 +00:00
|
|
|
self.ui.document.configurable.text_wrap = is_reflow -- restore reflow
|
2021-11-21 17:31:10 +00:00
|
|
|
-- draw
|
|
|
|
self.view.highlight.temp[new_page] = self.ui.document:getPageBoxesFromPositions(new_page, new_pos0, new_pos1)
|
|
|
|
else
|
|
|
|
-- 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
|
|
|
|
self.hold_pos.page = self.ui.document:getPageFromXPointer(new_pos0)
|
|
|
|
-- true to draw
|
|
|
|
new_text = self.ui.document:getTextFromXPointers(new_pos0, new_pos1, true)
|
|
|
|
end
|
|
|
|
self:deleteHighlight(self.highlight_page, self.highlight_idx) -- starting fragment
|
|
|
|
self.selected_text = {
|
|
|
|
text = new_text,
|
|
|
|
pos0 = new_pos0,
|
|
|
|
pos1 = new_pos1,
|
|
|
|
pboxes = new_pboxes,
|
|
|
|
}
|
|
|
|
UIManager:setDirty(self.dialog, "ui")
|
|
|
|
end
|
|
|
|
|
2014-01-17 19:05:17 +00:00
|
|
|
function ReaderHighlight:onReadSettings(config)
|
2021-09-02 20:46:27 +00:00
|
|
|
self.view.highlight.saved_drawer = config:readSetting("highlight_drawer")
|
|
|
|
or G_reader_settings:readSetting("highlight_drawing_style") or self.view.highlight.saved_drawer
|
2021-09-11 09:04:48 +00:00
|
|
|
self.view.highlight.disabled = G_reader_settings:has("default_highlight_action")
|
|
|
|
and G_reader_settings:readSetting("default_highlight_action") == "nothing"
|
2020-09-24 13:17:37 +00:00
|
|
|
|
|
|
|
-- panel zoom settings isn't supported in EPUB
|
|
|
|
if self.document.info.has_pages then
|
2020-12-24 08:07:27 +00:00
|
|
|
local ext = util.getFileNameSuffix(self.ui.document.file)
|
|
|
|
G_reader_settings:initializeExtSettings("panel_zoom_enabled", {cbz = true, cbt = true})
|
|
|
|
G_reader_settings:initializeExtSettings("panel_zoom_fallback_to_text_selection", {pdf = true})
|
2021-03-06 21:44:18 +00:00
|
|
|
if config:has("panel_zoom_enabled") then
|
|
|
|
self.panel_zoom_enabled = config:isTrue("panel_zoom_enabled")
|
|
|
|
else
|
2020-12-24 08:07:27 +00:00
|
|
|
self.panel_zoom_enabled = G_reader_settings:getSettingForExt("panel_zoom_enabled", ext) or false
|
|
|
|
end
|
2021-03-06 21:44:18 +00:00
|
|
|
if config:has("panel_zoom_fallback_to_text_selection") then
|
|
|
|
self.panel_zoom_fallback_to_text_selection = config:isTrue("panel_zoom_fallback_to_text_selection")
|
|
|
|
else
|
2020-12-24 08:07:27 +00:00
|
|
|
self.panel_zoom_fallback_to_text_selection = G_reader_settings:getSettingForExt("panel_zoom_fallback_to_text_selection", ext) or false
|
2020-09-24 13:17:37 +00:00
|
|
|
end
|
|
|
|
end
|
2014-01-17 19:05:17 +00:00
|
|
|
end
|
|
|
|
|
2020-08-05 22:13:26 +00:00
|
|
|
function ReaderHighlight:onUpdateHoldPanRate()
|
|
|
|
self:setupTouchZones()
|
|
|
|
end
|
|
|
|
|
2014-01-17 19:05:17 +00:00
|
|
|
function ReaderHighlight:onSaveSettings()
|
2014-03-13 13:52:43 +00:00
|
|
|
self.ui.doc_settings:saveSetting("highlight_drawer", self.view.highlight.saved_drawer)
|
2020-09-24 13:17:37 +00:00
|
|
|
self.ui.doc_settings:saveSetting("panel_zoom_enabled", self.panel_zoom_enabled)
|
2014-01-17 19:05:17 +00:00
|
|
|
end
|
|
|
|
|
2014-08-20 06:41:45 +00:00
|
|
|
function ReaderHighlight:onClose()
|
|
|
|
UIManager:close(self.highlight_dialog)
|
2021-03-21 12:57:18 +00:00
|
|
|
self.highlight_dialog = nil
|
2014-08-20 06:41:45 +00:00
|
|
|
-- clear highlighted text
|
2014-08-26 12:29:53 +00:00
|
|
|
self:clear()
|
2014-08-20 06:41:45 +00:00
|
|
|
end
|
|
|
|
|
2022-03-12 11:16:50 +00:00
|
|
|
function ReaderHighlight:onHighlightPress()
|
|
|
|
if self._current_indicator_pos then
|
|
|
|
if not self._start_indicator_highlight then
|
|
|
|
-- try a tap at current indicator position to open any existing highlight
|
|
|
|
if not self:onTap(nil, self:_createHighlightGesture("tap")) then
|
|
|
|
-- no existing highlight at current indicator position: start hold
|
|
|
|
self._start_indicator_highlight = true
|
|
|
|
self:onHold(nil, self:_createHighlightGesture("hold"))
|
2022-03-16 19:00:14 +00:00
|
|
|
-- With crengine, selected_text.sboxes does return good coordinates.
|
|
|
|
if self.ui.rolling and self.selected_text and self.selected_text.sboxes and #self.selected_text.sboxes > 0 then
|
2022-03-12 11:16:50 +00:00
|
|
|
local pos = self.selected_text.sboxes[1]
|
|
|
|
-- set hold_pos to center of selected_test to make center selection more stable, not jitted at edge
|
|
|
|
self.hold_pos = self.view:screenToPageTransform({
|
|
|
|
x = pos.x + pos.w / 2,
|
|
|
|
y = pos.y + pos.h / 2
|
|
|
|
})
|
|
|
|
-- move indicator to center selected text making succeed same row selection much accurate.
|
|
|
|
UIManager:setDirty(self.dialog, "ui", self._current_indicator_pos)
|
|
|
|
self._current_indicator_pos.x = pos.x + pos.w / 2 - self._current_indicator_pos.w / 2
|
|
|
|
self._current_indicator_pos.y = pos.y + pos.h / 2 - self._current_indicator_pos.h / 2
|
|
|
|
UIManager:setDirty(self.dialog, "ui", self._current_indicator_pos)
|
|
|
|
end
|
|
|
|
else
|
|
|
|
self:onStopHighlightIndicator(true) -- need_clear_selection=true
|
|
|
|
end
|
|
|
|
else
|
|
|
|
self:onHoldRelease(nil, self:_createHighlightGesture("hold_release"))
|
|
|
|
self:onStopHighlightIndicator()
|
|
|
|
end
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
|
|
|
|
function ReaderHighlight:onStartHighlightIndicator()
|
|
|
|
if self.view.visible_area and not self._current_indicator_pos then
|
|
|
|
-- set start position to centor of page
|
|
|
|
local rect = self._previous_indicator_pos
|
|
|
|
if not rect then
|
|
|
|
rect = Geom:new()
|
|
|
|
rect.x = self.view.visible_area.w / 2
|
|
|
|
rect.y = self.view.visible_area.h / 2
|
|
|
|
rect.w = Size.item.height_default
|
|
|
|
rect.h = rect.w
|
|
|
|
end
|
|
|
|
self._current_indicator_pos = rect
|
|
|
|
self.view.highlight.indicator = rect
|
|
|
|
UIManager:setDirty(self.dialog, "ui", rect)
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
|
|
|
|
function ReaderHighlight:onStopHighlightIndicator(need_clear_selection)
|
|
|
|
if self._current_indicator_pos then
|
|
|
|
local rect = self._current_indicator_pos
|
|
|
|
self._previous_indicator_pos = rect
|
|
|
|
self._start_indicator_highlight = false
|
|
|
|
self._current_indicator_pos = nil
|
|
|
|
self.view.highlight.indicator = nil
|
|
|
|
UIManager:setDirty(self.dialog, "ui", rect)
|
|
|
|
if need_clear_selection then
|
|
|
|
self:clear()
|
|
|
|
end
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
|
|
|
|
function ReaderHighlight:onMoveHighlightIndicator(args)
|
|
|
|
if self.view.visible_area and self._current_indicator_pos then
|
|
|
|
local dx, dy, quick_move = unpack(args)
|
2022-04-14 06:59:36 +00:00
|
|
|
local quick_move_distance_dx = self.view.visible_area.w / 5 -- quick move distance: fifth of visible_area
|
|
|
|
local quick_move_distance_dy = self.view.visible_area.h / 5
|
|
|
|
-- single move distance, small and capable to move on word with small font size and narrow line height
|
|
|
|
local move_distance = Size.item.height_default / 4
|
2022-03-12 11:16:50 +00:00
|
|
|
local rect = self._current_indicator_pos:copy()
|
2022-04-14 06:59:36 +00:00
|
|
|
if quick_move then
|
|
|
|
rect.x = rect.x + quick_move_distance_dx * dx
|
|
|
|
rect.y = rect.y + quick_move_distance_dy * dy
|
|
|
|
else
|
2022-05-05 19:00:22 +00:00
|
|
|
local now = time:now()
|
2022-04-14 06:59:36 +00:00
|
|
|
if dx == self._last_indicator_move_args.dx and dy == self._last_indicator_move_args.dy then
|
|
|
|
local diff = now - self._last_indicator_move_args.time
|
|
|
|
-- if press same arrow key in 1 second, speed up
|
|
|
|
-- double press: 4 single move distances, usually move to next word or line
|
|
|
|
-- triple press: 16 single distances, usually skip several words or lines
|
|
|
|
-- quadruple press: 54 single distances, almost move to screen edge
|
2022-05-05 19:00:22 +00:00
|
|
|
if diff < time.s(1) then
|
2022-04-14 06:59:36 +00:00
|
|
|
move_distance = self._last_indicator_move_args.distance * 4
|
|
|
|
end
|
|
|
|
end
|
|
|
|
rect.x = rect.x + move_distance * dx
|
|
|
|
rect.y = rect.y + move_distance * dy
|
|
|
|
self._last_indicator_move_args.distance = move_distance
|
|
|
|
self._last_indicator_move_args.dx = dx
|
|
|
|
self._last_indicator_move_args.dy = dy
|
|
|
|
self._last_indicator_move_args.time = now
|
|
|
|
end
|
2022-03-12 11:16:50 +00:00
|
|
|
if rect.x < 0 then
|
|
|
|
rect.x = 0
|
|
|
|
end
|
|
|
|
if rect.x + rect.w > self.view.visible_area.w then
|
|
|
|
rect.x = self.view.visible_area.w - rect.w
|
|
|
|
end
|
|
|
|
if rect.y < 0 then
|
|
|
|
rect.y = 0
|
|
|
|
end
|
|
|
|
if rect.y + rect.h > self.view.visible_area.h then
|
|
|
|
rect.y = self.view.visible_area.h - rect.h
|
|
|
|
end
|
|
|
|
UIManager:setDirty(self.dialog, "ui", self._current_indicator_pos)
|
|
|
|
self._current_indicator_pos = rect
|
|
|
|
self.view.highlight.indicator = rect
|
|
|
|
UIManager:setDirty(self.dialog, "ui", rect)
|
|
|
|
if self._start_indicator_highlight then
|
|
|
|
self:onHoldPan(nil, self:_createHighlightGesture("hold_pan"))
|
|
|
|
end
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
|
|
|
|
function ReaderHighlight:_createHighlightGesture(gesture)
|
|
|
|
local point = self._current_indicator_pos:copy()
|
|
|
|
point.x = point.x + point.w / 2
|
|
|
|
point.y = point.y + point.h / 2
|
|
|
|
point.w = 0
|
|
|
|
point.h = 0
|
|
|
|
return {
|
|
|
|
ges = gesture,
|
|
|
|
pos = point,
|
2022-05-05 19:00:22 +00:00
|
|
|
time = time.realtime(),
|
2022-03-12 11:16:50 +00:00
|
|
|
}
|
|
|
|
end
|
|
|
|
|
|
|
|
|
2013-10-18 20:38:07 +00:00
|
|
|
return ReaderHighlight
|