ReaderHighlight: allow for 2-steps text selection (#8432)

Add a "Select" button in the highlight dialog to initiate
text selection; on the next text selection, the text between
these 2 points will be selected.
Limited to a single page with non-CRE documents.
Also move "Search" button at end, so it's the one that
will be wide in case of an odd number of buttons.
pull/8460/head
hius07 2 years ago committed by GitHub
parent 0824886fde
commit c7229d90bc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -539,9 +539,10 @@ function ReaderBookmark:onShowBookmark()
{ {
{ {
text = _("Remove bookmark"), text = _("Remove bookmark"),
enabled = not bookmark.ui.highlight.select_mode,
callback = function() callback = function()
UIManager:show(ConfirmBox:new{ UIManager:show(ConfirmBox:new{
text = _("Remove bookmark?"), text = _("Remove this bookmark?"),
ok_text = _("Remove"), ok_text = _("Remove"),
ok_callback = function() ok_callback = function()
bookmark:removeHighlight(item) bookmark:removeHighlight(item)
@ -570,6 +571,7 @@ function ReaderBookmark:onShowBookmark()
{ {
{ {
text = _("Bulk remove"), text = _("Bulk remove"),
enabled = not bookmark.ui.highlight.select_mode,
callback = function() callback = function()
self.select_mode = true self.select_mode = true
self.select_count = 0 self.select_count = 0

@ -45,31 +45,31 @@ local function cleanupSelectedText(text)
end end
function ReaderHighlight:init() function ReaderHighlight:init()
self.select_mode = false -- extended highlighting
self._highlight_buttons = { self._highlight_buttons = {
-- highlight and add_note are for the document itself, -- highlight and add_note are for the document itself,
-- so we put them first. -- so we put them first.
["01_highlight"] = function(_self) ["01_select"] = function(_self)
return { return {
text = _("Highlight"), text = _("Select"),
enabled = _self.hold_pos ~= nil,
callback = function() callback = function()
_self:saveHighlight() _self:startSelection()
_self:onClose() _self:onClose()
end, end,
enabled = _self.hold_pos ~= nil,
} }
end, end,
["02_add_note"] = function(_self) ["02_highlight"] = function(_self)
return { return {
text = _("Add Note"), text = _("Highlight"),
callback = function() callback = function()
_self:addNote() _self:saveHighlight()
_self:onClose() _self:onClose()
end, end,
enabled = _self.hold_pos ~= nil, enabled = _self.hold_pos ~= nil,
} }
end, end,
-- copy and search are internal functions that don't depend on anything,
-- hence the second line.
["03_copy"] = function(_self) ["03_copy"] = function(_self)
return { return {
text = C_("Text", "Copy"), text = C_("Text", "Copy"),
@ -77,22 +77,20 @@ function ReaderHighlight:init()
callback = function() callback = function()
Device.input.setClipboardText(cleanupSelectedText(_self.selected_text.text)) Device.input.setClipboardText(cleanupSelectedText(_self.selected_text.text))
_self:onClose() _self:onClose()
self:clear()
UIManager:show(Notification:new{ UIManager:show(Notification:new{
text = _("Selection copied to clipboard."), text = _("Selection copied to clipboard."),
}) })
end, end,
} }
end, end,
["04_search"] = function(_self) ["04_add_note"] = function(_self)
return { return {
text = _("Search"), text = _("Add Note"),
callback = function() callback = function()
_self:onHighlightSearch() _self:addNote()
-- We don't call _self:onClose(), crengine will highlight _self:onClose()
-- search matches on the current page, and self:clear()
-- would redraw and remove crengine native highlights
end, end,
enabled = _self.hold_pos ~= nil,
} }
end, end,
-- then information lookup functions, putting on the left those that -- then information lookup functions, putting on the left those that
@ -132,22 +130,24 @@ function ReaderHighlight:init()
end, end,
} }
end, end,
} -- 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
-- Text export functions if applicable. ["12_search"] = function(_self)
if not self.ui.document.info.has_pages then
self:addToHighlightDialog("08_view_html", function(_self)
return { return {
text = _("View HTML"), text = _("Search"),
callback = function() callback = function()
_self:viewSelectionHTML() _self:onHighlightSearch()
-- We don't call _self:onClose(), crengine will highlight
-- search matches on the current page, and self:clear()
-- would redraw and remove crengine native highlights
end, end,
} }
end) end,
end }
-- Android devices
if Device:canShareText() then if Device:canShareText() then
self:addToHighlightDialog("09_share_text", function(_self) self:addToHighlightDialog("08_share_text", function(_self)
return { return {
text = _("Share Text"), text = _("Share Text"),
callback = function() callback = function()
@ -160,31 +160,43 @@ function ReaderHighlight:init()
end) end)
end end
-- Links -- cre documents only
self:addToHighlightDialog("10_follow_link", function(_self) if not self.ui.document.info.has_pages then
self:addToHighlightDialog("09_view_html", function(_self)
return {
text = _("View HTML"),
callback = function()
_self:viewSelectionHTML()
end,
}
end)
end
-- User hyphenation dict
self:addToHighlightDialog("10_user_dict", function(_self)
return { return {
text = _("Follow Link"), text= _("Hyphenate"),
show_in_highlight_dialog_func = function() show_in_highlight_dialog_func = function()
return _self.selected_link ~= nil return _self.ui.userhyph and _self.ui.userhyph:isAvailable()
and not _self.selected_text.text:find("[ ,;-%.\n]")
end, end,
callback = function() callback = function()
local link = _self.selected_link.link or _self.selected_link _self.ui.userhyph:modifyUserEntry(_self.selected_text.text)
_self.ui.link:onGotoLink(link)
_self:onClose() _self:onClose()
end, end,
} }
end) end)
-- User hyphenation dict -- Links
self:addToHighlightDialog("11_user_dict", function(_self) self:addToHighlightDialog("11_follow_link", function(_self)
return { return {
text= _("Hyphenate"), text = _("Follow Link"),
show_in_highlight_dialog_func = function() show_in_highlight_dialog_func = function()
return _self.ui.userhyph and _self.ui.userhyph:isAvailable() return _self.selected_link ~= nil
and not _self.selected_text.text:find("[ ,;-%.\n]")
end, end,
callback = function() callback = function()
_self.ui.userhyph:modifyUserEntry(_self.selected_text.text) local link = _self.selected_link.link or _self.selected_link
_self.ui.link:onGotoLink(link)
_self:onClose() _self:onClose()
end, end,
} }
@ -272,6 +284,7 @@ local long_press_action = {
{_("Ask with popup dialog"), "ask"}, {_("Ask with popup dialog"), "ask"},
{_("Do nothing"), "nothing"}, {_("Do nothing"), "nothing"},
{_("Highlight"), "highlight"}, {_("Highlight"), "highlight"},
{_("Select and highlight"), "select"},
{_("Translate"), "translate"}, {_("Translate"), "translate"},
{_("Wikipedia"), "wikipedia"}, {_("Wikipedia"), "wikipedia"},
{_("Dictionary"), "dictionary"}, {_("Dictionary"), "dictionary"},
@ -461,7 +474,7 @@ function ReaderHighlight:onTap(_, ges)
-- ReaderHighlight:clear can only return true if self.hold_pos was set anyway. -- ReaderHighlight:clear can only return true if self.hold_pos was set anyway.
local cleared = self.hold_pos and self:clear() 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. -- We only care about potential taps on existing highlights, not on taps that closed a highlight menu.
if not cleared and ges then if not cleared and ges and not self.select_mode then
if self.ui.document.info.has_pages then if self.ui.document.info.has_pages then
return self:onTapPageSavedHighlight(ges) return self:onTapPageSavedHighlight(ges)
else else
@ -1273,6 +1286,29 @@ function ReaderHighlight:onTranslateText(text)
end end
function ReaderHighlight:onHoldRelease() function ReaderHighlight:onHoldRelease()
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
self:saveHighlight()
self:clear()
else
self:onShowHighlightMenu()
end
end
end
return true
end
local long_final_hold = false local long_final_hold = false
if self.hold_last_tv then if self.hold_last_tv then
local hold_duration = TimeVal:now() - self.hold_last_tv local hold_duration = TimeVal:now() - self.hold_last_tv
@ -1292,13 +1328,15 @@ function ReaderHighlight:onHoldRelease()
if self.is_word_selection then if self.is_word_selection then
self:lookup(self.selected_text, self.selected_link) self:lookup(self.selected_text, self.selected_link)
else else
local default_highlight_action = G_reader_settings:readSetting("default_highlight_action", "ask")
if long_final_hold or default_highlight_action == "ask" then if long_final_hold or default_highlight_action == "ask" then
-- bypass default action and show popup if long final hold -- bypass default action and show popup if long final hold
self:onShowHighlightMenu() self:onShowHighlightMenu()
elseif default_highlight_action == "highlight" then elseif default_highlight_action == "highlight" then
self:saveHighlight() self:saveHighlight()
self:onClose() self:onClose()
elseif default_highlight_action == "select" then
self:startSelection()
self:onClose()
elseif default_highlight_action == "translate" then elseif default_highlight_action == "translate" then
self:translate(self.selected_text) self:translate(self.selected_text)
self:onClose() self:onClose()
@ -1578,14 +1616,6 @@ function ReaderHighlight:onHighlightDictLookup()
end end
end end
function ReaderHighlight:shareHighlight()
logger.info("share highlight")
end
function ReaderHighlight:moreAction()
logger.info("more action")
end
function ReaderHighlight:deleteHighlight(page, i, bookmark_item) function ReaderHighlight:deleteHighlight(page, i, bookmark_item)
self.ui:handleEvent(Event:new("DelHighlight")) self.ui:handleEvent(Event:new("DelHighlight"))
logger.dbg("delete highlight", page, i) logger.dbg("delete highlight", page, i)
@ -1645,6 +1675,70 @@ function ReaderHighlight:editHighlightStyle(page, i)
}) })
end end
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
local is_reflow = self.ui.document.configurable.text_wrap == 1
local new_page = self.hold_pos.page
-- reflow mode doesn't set page in positions
if is_reflow then
item1.pos0.page = new_page
item1.pos1.page = new_page
item2_pos0.page = new_page
item2_pos1.page = new_page
end
-- pos0 and pos1 are not in order within highlights, hence sorting all
local function comparePositions (pos1, pos2)
local box1 = self.ui.document:getWordFromPosition(pos1).pbox
local box2 = self.ui.document:getWordFromPosition(pos2).pbox
if box1.y == box2.y then
return box1.x < box2.x
else
return box1.y < box2.y
end
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
self.ui.document.configurable.text_wrap = is_reflow and 1 or 0 -- restore reflow
-- 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
function ReaderHighlight:onReadSettings(config) function ReaderHighlight:onReadSettings(config)
self.view.highlight.saved_drawer = config:readSetting("highlight_drawer") self.view.highlight.saved_drawer = config:readSetting("highlight_drawer")
or G_reader_settings:readSetting("highlight_drawing_style") or self.view.highlight.saved_drawer or G_reader_settings:readSetting("highlight_drawing_style") or self.view.highlight.saved_drawer

Loading…
Cancel
Save