TextViewer: add Find (#9507)

pull/9516/head
hius07 2 years ago committed by GitHub
parent c3cd88e019
commit 56388aa491
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1366,6 +1366,7 @@ function ReaderHighlight:viewSelectionHTML(debug_view, no_css_files_buttons)
justified = false,
para_direction_rtl = false,
auto_para_direction = false,
add_default_buttons = true,
buttons_table = {
{{
text = _("Prettify"),
@ -1382,19 +1383,13 @@ function ReaderHighlight:viewSelectionHTML(debug_view, no_css_files_buttons)
})
end,
}},
{{
text = _("Close"),
callback = function()
UIManager:close(cssviewer)
end,
}},
}
}
UIManager:show(cssviewer)
end,
hold_callback = buttons_hold_callback,
}
-- One button per row, too make room for the possibly long css filename
-- One button per row, to make room for the possibly long css filename
table.insert(buttons_table, {button})
end
end
@ -1418,13 +1413,6 @@ function ReaderHighlight:viewSelectionHTML(debug_view, no_css_files_buttons)
end,
hold_callback = buttons_hold_callback,
}})
table.insert(buttons_table, {{
text = _("Close"),
callback = function()
UIManager:close(textviewer)
end,
hold_callback = buttons_hold_callback,
}})
textviewer = TextViewer:new{
title = _("Selection HTML"),
text = html,
@ -1432,6 +1420,8 @@ function ReaderHighlight:viewSelectionHTML(debug_view, no_css_files_buttons)
justified = false,
para_direction_rtl = false,
auto_para_direction = false,
add_default_buttons = true,
default_hold_callback = buttons_hold_callback,
buttons_table = buttons_table,
}
UIManager:show(textviewer)

@ -578,38 +578,10 @@ function Translator:_showTranslation(text, target_lang, source_lang, from_highli
-- table.insert(output, require("dump")(result)) -- for debugging
local text_all = table.concat(output, "\n")
local textviewer
local buttons_table = {
{
{
text = _("Close"),
is_enter_default = true,
callback = function()
textviewer:onClose()
end,
},
},
}
if Device:hasClipboard() then
table.insert(buttons_table, 1,
{
{
text = _("Copy main translation"),
callback = function()
Device.input.setClipboardText(text_main)
end,
},
{
text = _("Copy all"),
callback = function()
Device.input.setClipboardText(text_all)
end,
},
}
)
end
local buttons_table = {}
if from_highlight then
local ui = require("apps/reader/readerui").instance
table.insert(buttons_table, 1,
table.insert(buttons_table,
{
{
text = _("Save main translation to note"),
@ -640,6 +612,24 @@ function Translator:_showTranslation(text, target_lang, source_lang, from_highli
}
)
end
if Device:hasClipboard() then
table.insert(buttons_table,
{
{
text = _("Copy main translation"),
callback = function()
Device.input.setClipboardText(text_main)
end,
},
{
text = _("Copy all"),
callback = function()
Device.input.setClipboardText(text_all)
end,
},
}
)
end
textviewer = TextViewer:new{
title = T(_("Translation from %1"), self:getLanguageName(source_lang, "?")),
title_multilines = true,
@ -648,6 +638,7 @@ function Translator:_showTranslation(text, target_lang, source_lang, from_highli
text = text_all,
height = math.floor(Screen:getHeight() * 0.8),
justified = G_reader_settings:nilOrTrue("dict_justify"),
add_default_buttons = true,
buttons_table = buttons_table,
close_callback = function()
if from_highlight then

@ -117,6 +117,7 @@ local VerticalGroup = require("ui/widget/verticalgroup")
local VerticalSpan = require("ui/widget/verticalspan")
local Screen = Device.screen
local T = require("ffi/util").template
local util = require("util")
local _ = require("gettext")
local InputDialog = FocusManager:new{
@ -793,46 +794,14 @@ function InputDialog:_addScrollButtons(nav_bar)
{
text = _("Find first"),
callback = function()
self.search_value = input_dialog:getInputText()
if self.search_value ~= "" then
UIManager:close(input_dialog)
self.keyboard_hidden = keyboard_hidden_state
self:toggleKeyboard()
local msg
local char_pos = self._input_widget:searchString(self.search_value, self.case_sensitive, 1)
if char_pos > 0 then
self._input_widget:moveCursorToCharPos(char_pos)
msg = T(_("Found in line %1."), self._input_widget:getLineNums())
else
msg = _("Not found.")
end
UIManager:show(Notification:new{
text = msg,
})
end
self:findCallback(keyboard_hidden_state, input_dialog, true)
end,
},
{
text = _("Find next"),
is_enter_default = true,
callback = function()
self.search_value = input_dialog:getInputText()
if self.search_value ~= "" then
UIManager:close(input_dialog)
self.keyboard_hidden = keyboard_hidden_state
self:toggleKeyboard()
local msg
local char_pos = self._input_widget:searchString(self.search_value, self.case_sensitive)
if char_pos > 0 then
self._input_widget:moveCursorToCharPos(char_pos)
msg = T(_("Found in line %1."), self._input_widget:getLineNums())
else
msg = _("Not found.")
end
UIManager:show(Notification:new{
text = msg,
})
end
self:findCallback(keyboard_hidden_state, input_dialog)
end,
},
},
@ -967,4 +936,24 @@ function InputDialog:_addScrollButtons(nav_bar)
end
end
function InputDialog:findCallback(keyboard_hidden_state, input_dialog, find_first)
self.search_value = input_dialog:getInputText()
if self.search_value == "" then return end
UIManager:close(input_dialog)
self.keyboard_hidden = keyboard_hidden_state
self:toggleKeyboard()
local start_pos = find_first and 1 or self._charpos + 1
local char_pos = util.stringSearch(self.input, self.search_value, self.case_sensitive, start_pos)
local msg
if char_pos > 0 then
self._input_widget:moveCursorToCharPos(char_pos)
msg = T(_("Found in line %1."), self._input_widget:getLineNums())
else
msg = _("Not found.")
end
UIManager:show(Notification:new{
text = msg,
})
end
return InputDialog

@ -12,7 +12,6 @@ local ScrollTextWidget = require("ui/widget/scrolltextwidget")
local Size = require("ui/size")
local TextBoxWidget = require("ui/widget/textboxwidget")
local UIManager = require("ui/uimanager")
local Utf8Proc = require("ffi/utf8proc")
local VerticalGroup = require("ui/widget/verticalgroup")
local dbg = require("dbg")
local util = require("util")
@ -189,6 +188,7 @@ if Device:isTouchDevice() or Device:hasDPad() then
width = math.floor(math.min(Screen:getWidth(), Screen:getHeight()) * 0.8),
height = math.floor(math.max(Screen:getWidth(), Screen:getHeight()) * 0.4),
justified = false,
modal = true,
stop_events_propagation = true,
buttons_table = {
{
@ -759,36 +759,6 @@ function InputText:getStringPos(left_delimiter, right_delimiter, char_pos)
return start_pos, end_pos
end
--- Search for a string.
-- if start_pos not set, starts a search from the next to cursor position
-- returns first found position or 0 if not found
function InputText:searchString(str, case_sensitive, start_pos)
local str_charlist = util.splitToChars(str)
local str_len = #str_charlist
local char_pos, found = 0, 0
start_pos = start_pos and (start_pos - 1) or self.charpos
for i = start_pos, #self.charlist - str_len do
for j = 1, str_len do
local char_txt = self.charlist[i + j]
local char_str = str_charlist[j]
if not case_sensitive then
char_txt = Utf8Proc.lowercase(util.fixUtf8(char_txt, "?"))
char_str = Utf8Proc.lowercase(util.fixUtf8(char_str, "?"))
end
if char_txt ~= char_str then
found = 0
break
end
found = found + 1
end
if found == str_len then
char_pos = i + 1
break
end
end
return char_pos
end
--- Return the character at the given offset. If is_absolute is truthy then the
-- offset is the absolute position, otherwise the offset is added to the current
-- cursor position (negative offsets are allowed).

@ -144,6 +144,15 @@ function ScrollTextWidget:getCharPos()
return self.text_widget:getCharPos()
end
function ScrollTextWidget:getCharPosAtXY(x, y)
return self.text_widget:getCharPosAtXY(x, y)
end
function ScrollTextWidget:getCharPosLineNum(charpos)
local _, _, line_num = self.text_widget:_getXYForCharPos(charpos)
return line_num -- screen line number
end
function ScrollTextWidget:updateScrollBar(is_partial)
local low, high = self.text_widget:getVisibleHeightRatios()
if low ~= self.prev_low or high ~= self.prev_high then
@ -187,8 +196,12 @@ function ScrollTextWidget:resetScroll()
self.v_scroll_bar.enable = visible_line_count < total_line_count
end
function ScrollTextWidget:moveCursorToCharPos(charpos)
self.text_widget:moveCursorToCharPos(charpos)
function ScrollTextWidget:moveCursorToCharPos(charpos, centered_lines_count)
if centered_lines_count then
self.text_widget:moveCursorToCharPosKeepingViewCentered(charpos, centered_lines_count)
else
self.text_widget:moveCursorToCharPos(charpos)
end
self:updateScrollBar()
end

@ -1363,6 +1363,7 @@ end
-- Return the coordinates (relative to current view, so negative y is possible)
-- of the left of char at charpos (use self.charpos if none provided)
-- and the number of the line with charpos on the screen
function TextBoxWidget:_getXYForCharPos(charpos)
if not charpos then
charpos = self.charpos
@ -1389,6 +1390,7 @@ function TextBoxWidget:_getXYForCharPos(charpos)
end
end
local y = (ln - self.virtual_line_num) * self.line_height_px
local screen_line_num = ln - self.virtual_line_num + 1
-- Find the x offset in the current line.
@ -1441,7 +1443,7 @@ function TextBoxWidget:_getXYForCharPos(charpos)
end
end
-- logger.dbg("_getXYForCharPos(", charpos, "):", x, y)
return x, y
return x, y, screen_line_num
end
-- Only when not self.use_xtext:
@ -1458,7 +1460,7 @@ function TextBoxWidget:_getXYForCharPos(charpos)
-- Cursor can be drawn at x, it will be on the left of the char pointed by charpos
-- (x=0 for first char of line - for end of line, it will be before the \n, the \n
-- itself being not displayed)
return x, y
return x, y, screen_line_num
end
-- Return the charpos at provided coordinates (relative to current view,
@ -1565,11 +1567,6 @@ local CURSOR_USE_REFRESH_FUNCS = G_reader_settings:nilOrTrue("ui_cursor_use_refr
-- Update charpos to the one provided; if out of current view, update
-- virtual_line_num to move it to view, and draw the cursor
function TextBoxWidget:moveCursorToCharPos(charpos)
if not self.editable then
-- we shouldn't have been called if not editable
logger.warn("TextBoxWidget:moveCursorToCharPos called, but not editable")
return
end
self.charpos = charpos
self.prev_virtual_line_num = self.virtual_line_num
local x, y = self:_getXYForCharPos() -- we can get y outside current view
@ -1711,6 +1708,30 @@ function TextBoxWidget:moveCursorToCharPos(charpos)
end
end
-- Update view to show the line with charpos not far than <centered_lines_count> lines away
-- from the center of the screen, and draw the cursor.
function TextBoxWidget:moveCursorToCharPosKeepingViewCentered(charpos, centered_lines_count)
local old_virtual_line_num = self.virtual_line_num
self.for_measurement_only = true
self:moveCursorToCharPos(charpos)
self.for_measurement_only = false
local _, _, screen_line_num = self:_getXYForCharPos(charpos)
local new_virtual_line_num = self.virtual_line_num + screen_line_num - self.lines_per_page / 2
local max_virtual_line_num = #self.vertical_string_list - self.lines_per_page + 1
if new_virtual_line_num < 1 then
new_virtual_line_num = 1
elseif new_virtual_line_num > max_virtual_line_num then
new_virtual_line_num = max_virtual_line_num
end
if math.abs(new_virtual_line_num - old_virtual_line_num) > centered_lines_count then
self.virtual_line_num = new_virtual_line_num
else
self.virtual_line_num = old_virtual_line_num
end
self:_updateLayout()
self:moveCursorToCharPos(charpos)
end
function TextBoxWidget:moveCursorToXY(x, y, restrict_to_view)
if restrict_to_view then
-- Wrap y to current view (when getting coordinates from gesture)

@ -12,24 +12,28 @@ local BD = require("ui/bidi")
local Blitbuffer = require("ffi/blitbuffer")
local ButtonTable = require("ui/widget/buttontable")
local CenterContainer = require("ui/widget/container/centercontainer")
local CheckButton = require("ui/widget/checkbutton")
local Device = require("device")
local Geom = require("ui/geometry")
local Font = require("ui/font")
local FrameContainer = require("ui/widget/container/framecontainer")
local GestureRange = require("ui/gesturerange")
local InputContainer = require("ui/widget/container/inputcontainer")
local InputDialog = require("ui/widget/inputdialog")
local MovableContainer = require("ui/widget/container/movablecontainer")
local Notification = require("ui/widget/notification")
local ScrollTextWidget = require("ui/widget/scrolltextwidget")
local Size = require("ui/size")
local TitleBar = require("ui/widget/titlebar")
local UIManager = require("ui/uimanager")
local VerticalGroup = require("ui/widget/verticalgroup")
local WidgetContainer = require("ui/widget/container/widgetcontainer")
local T = require("ffi/util").template
local util = require("util")
local _ = require("gettext")
local Screen = Device.screen
local TextViewer = InputContainer:new{
modal = true,
title = nil,
text = nil,
width = nil,
@ -56,6 +60,10 @@ local TextViewer = InputContainer:new{
text_padding = Size.padding.large,
text_margin = Size.margin.small,
button_padding = Size.padding.default,
-- Bottom row with Close, Find buttons. Also added when no caller's buttons defined.
add_default_buttons = nil,
default_hold_callback = nil, -- on each default button
find_centered_lines_count = 5, -- line with find results to be not far from the center
}
function TextViewer:init()
@ -69,6 +77,10 @@ function TextViewer:init()
self.width = self.width or Screen:getWidth() - Screen:scaleBySize(30)
self.height = self.height or Screen:getHeight() - Screen:scaleBySize(30)
self._find_next = false
self._find_next_button = false
self._old_virtual_line_num = 1
if Device:hasKeys() then
self.key_events = {
Close = { {Device.input.group.Back}, doc = "close text viewer" }
@ -112,25 +124,48 @@ function TextViewer:init()
show_parent = self,
}
local buttons = self.buttons_table or
local default_buttons =
{
{
{
text = _("Close"),
callback = function()
self:onClose()
end,
},
text = _("Close"),
callback = function()
self:onClose()
end,
hold_callback = self.default_hold_callback,
},
{
text = _("Find"),
id = "find",
callback = function()
if self._find_next then
self:findCallback()
else
self:findDialog()
end
end,
hold_callback = function()
if self._find_next then
self:findDialog()
else
if self.default_hold_callback then
self.default_hold_callback()
end
end
end,
},
}
local button_table = ButtonTable:new{
local buttons = self.buttons_table or {}
if self.add_default_buttons or not self.buttons_table then
table.insert(buttons, default_buttons)
end
self.button_table = ButtonTable:new{
width = self.width - 2*self.button_padding,
buttons = buttons,
zero_sep = true,
show_parent = self,
}
local textw_height = self.height - titlebar:getHeight() - button_table:getSize().h
local textw_height = self.height - titlebar:getHeight() - self.button_table:getSize().h
self.scroll_text_w = ScrollTextWidget:new{
text = self.text,
@ -170,9 +205,9 @@ function TextViewer:init()
CenterContainer:new{
dimen = Geom:new{
w = self.width,
h = button_table:getSize().h,
h = self.button_table:getSize().h,
},
button_table,
self.button_table,
}
}
}
@ -244,4 +279,90 @@ function TextViewer:onSwipe(arg, ges)
return self.movable:onMovableSwipe(arg, ges)
end
function TextViewer:findDialog()
local input_dialog
input_dialog = InputDialog:new{
title = _("Enter text to search for"),
input = self.search_value,
buttons = {
{
{
text = _("Cancel"),
callback = function()
UIManager:close(input_dialog)
end,
},
{
text = _("Find first"),
callback = function()
self._find_next = false
self:findCallback(input_dialog)
end,
},
{
text = _("Find next"),
is_enter_default = true,
callback = function()
self._find_next = true
self:findCallback(input_dialog)
end,
},
},
},
}
self.check_button_case = CheckButton:new{
text = _("Case sensitive"),
checked = self.case_sensitive,
parent = input_dialog,
callback = function()
self.case_sensitive = self.check_button_case.checked
end,
}
input_dialog:addWidget(self.check_button_case)
UIManager:show(input_dialog)
input_dialog:onShowKeyboard()
end
function TextViewer:findCallback(input_dialog)
if input_dialog then
self.search_value = input_dialog:getInputText()
if self.search_value == "" then return end
UIManager:close(input_dialog)
end
local start_pos = 1
if self._find_next then
local charpos, new_virtual_line_num = self.scroll_text_w:getCharPos()
if math.abs(new_virtual_line_num - self._old_virtual_line_num) > self.find_centered_lines_count then
start_pos = self.scroll_text_w:getCharPosAtXY(0, 0) -- first char of the top line
else
start_pos = (charpos or 0) + 1 -- previous search result
end
end
local char_pos = util.stringSearch(self.text, self.search_value, self.case_sensitive, start_pos)
local msg
if char_pos > 0 then
self.scroll_text_w:moveCursorToCharPos(char_pos, self.find_centered_lines_count)
msg = T(_("Found, screen line %1."), self.scroll_text_w:getCharPosLineNum())
self._find_next = true
self._old_virtual_line_num = select(2, self.scroll_text_w:getCharPos())
else
msg = _("Not found.")
self._find_next = false
self._old_virtual_line_num = 1
end
UIManager:show(Notification:new{
text = msg,
})
if self._find_next_button ~= self._find_next then
self._find_next_button = self._find_next
local button_text = self._find_next and _("Find next") or _("Find")
local find_button = self.button_table:getButtonById("find")
find_button:setText(button_text, find_button.width)
UIManager:setDirty(self, function()
return "ui", find_button.dimen
end)
end
end
return TextViewer

@ -3,6 +3,7 @@ This module contains miscellaneous helper functions for the KOReader frontend.
]]
local BaseUtil = require("ffi/util")
local Utf8Proc = require("ffi/utf8proc")
local _ = require("gettext")
local C_ = _.pgettext
local T = BaseUtil.template
@ -1334,6 +1335,41 @@ function util.stringEndsWith(str, ending)
return ending == "" or str:sub(-#ending) == ending
end
--- Search a string in a text.
-- @string or table txt Text (char list) to search in
-- @string str String to search for
-- @boolean case_sensitive
-- @number start_pos Position number in text to start search from
-- @treturn number Position number or 0 if not found
function util.stringSearch(txt, str, case_sensitive, start_pos)
if not case_sensitive then
str = Utf8Proc.lowercase(util.fixUtf8(str, "?"))
end
local txt_charlist = type(txt) == "table" and txt or util.splitToChars(txt)
local str_charlist = util.splitToChars(str)
local str_len = #str_charlist
local char_pos, found = 0, 0
for i = start_pos - 1, #txt_charlist - str_len do
for j = 1, str_len do
local char_txt = txt_charlist[i + j]
local char_str = str_charlist[j]
if not case_sensitive then
char_txt = Utf8Proc.lowercase(util.fixUtf8(char_txt, "?"))
end
if char_txt ~= char_str then
found = 0
break
end
found = found + 1
end
if found == str_len then
char_pos = i + 1
break
end
end
return char_pos
end
local WrappedFunction_mt = {
__call = function(self, ...)
if self.before_callback then

Loading…
Cancel
Save