mirror of
https://github.com/koreader/koreader.git
synced 2025-12-13 20:36:53 +01:00
Text input related fixes & enhancements (#4124)
InputText: checks whether provided content can be given back unaltered, which may not be the case after it is splitted to UTF8 chars if the text is binary content. Prevent editing text if that is the case. Adds InputText and InputDialog :isEditable() and :isEdited() methods. Also accounts for the scrollbar width when measuring text to prevent it from being displayed when not needed. Also ensure a minimal size of the scrollbar thumb so it is rendered when huge text with many lines is displayed. Virtual keyboard: Hold on Backspace: delete from cursor to start of line instead of clearing all text content.
This commit is contained in:
@@ -238,8 +238,19 @@ function RenderText:renderUtf8Text(dest_bb, x, baseline, face, text, kerning, bo
|
||||
end -- if pen_x < text_width
|
||||
if char_pads then
|
||||
char_idx = char_idx + 1
|
||||
pen_x = pen_x + char_pads[char_idx] -- or 0
|
||||
-- will fail if we didnt count the same number of chars, we'll see
|
||||
pen_x = pen_x + (char_pads[char_idx] or 0)
|
||||
-- We used to use:
|
||||
-- pen_x = pen_x + char_pads[char_idx]
|
||||
-- above will fail if we didnt count the same number of chars, we'll see
|
||||
-- We saw, and it's pretty robust: it never failed before we tried to
|
||||
-- render some binary content, which messes the utf8 sequencing: the
|
||||
-- split to UTF8 is only reversible if text is valid UTF8 (or nearly UTF8).
|
||||
-- TextBoxWidget did this sequencing, counted the number of chars
|
||||
-- and made out 'char_pads', and gave us back the concatenated utf8
|
||||
-- chars as 'text', that we sequenced again above: we may not get the
|
||||
-- same number of chars as we did previously to make char_pads.
|
||||
-- We'd rather not crash (and have binary stuff displayed, even if
|
||||
-- badly). The mess in char_pads is negligeable when that happens.
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -385,6 +385,14 @@ function InputDialog:setInputText(text)
|
||||
self._input_widget:setText(text)
|
||||
end
|
||||
|
||||
function InputDialog:isTextEditable()
|
||||
return self._input_widget:isTextEditable()
|
||||
end
|
||||
|
||||
function InputDialog:isTextEdited()
|
||||
return self._input_widget:isTextEdited()
|
||||
end
|
||||
|
||||
function InputDialog:onShow()
|
||||
UIManager:setDirty(self, function()
|
||||
return "ui", self.dialog_frame.dimen
|
||||
|
||||
@@ -3,8 +3,10 @@ local CheckButton = require("ui/widget/checkbutton")
|
||||
local Device = require("device")
|
||||
local FrameContainer = require("ui/widget/container/framecontainer")
|
||||
local Font = require("ui/font")
|
||||
local Geom = require("ui/geometry")
|
||||
local GestureRange = require("ui/gesturerange")
|
||||
local InputContainer = require("ui/widget/container/inputcontainer")
|
||||
local Notification = require("ui/widget/notification")
|
||||
local ScrollTextWidget = require("ui/widget/scrolltextwidget")
|
||||
local Size = require("ui/size")
|
||||
local TextBoxWidget = require("ui/widget/textboxwidget")
|
||||
@@ -43,6 +45,8 @@ local InputText = InputContainer:new{
|
||||
charpos = nil, -- position of the cursor, where a new char would be inserted
|
||||
top_line_num = nil, -- virtual_line_num of the text_widget (index of the displayed top line)
|
||||
is_password_type = false, -- set to true if original text_type == "password"
|
||||
is_text_editable = true, -- whether text is utf8 reversible and editing won't mess content
|
||||
is_text_edited = false, -- whether text has been updated
|
||||
}
|
||||
|
||||
-- only use PhysicalKeyboard if the device does not have touch screen
|
||||
@@ -167,12 +171,47 @@ else
|
||||
function InputText:initEventListener() end
|
||||
end
|
||||
|
||||
function InputText:checkTextEditability()
|
||||
-- The split of the 'text' string to a table of utf8 chars may not be
|
||||
-- reversible to the same string, if 'text' comes from a binary file
|
||||
-- (it looks like it does not necessarily need to be proper UTF8 to
|
||||
-- be reversible, some text with latin1 chars is reversible).
|
||||
-- As checking that may be costly, we do that only in init(), setText(),
|
||||
-- and clear().
|
||||
-- When not reversible, we prevent adding and deleting chars to not
|
||||
-- corrupt the original self.text.
|
||||
self.is_text_editable = true
|
||||
if self.text then
|
||||
-- We check that the text obtained from the UTF8 split done
|
||||
-- in :initTextBox(), when concatenated back to a string, matches
|
||||
-- the original text. (If this turns out too expensive, we could
|
||||
-- just compare their lengths)
|
||||
self.is_text_editable = table.concat(self.charlist, "") == self.text
|
||||
end
|
||||
end
|
||||
|
||||
function InputText:isTextEditable(show_warning)
|
||||
if show_warning and not self.is_text_editable then
|
||||
UIManager:show(Notification:new{
|
||||
text = _("Text may be binary content, and is not editable"),
|
||||
timeout = 2
|
||||
})
|
||||
end
|
||||
return self.is_text_editable
|
||||
end
|
||||
|
||||
function InputText:isTextEdited()
|
||||
return self.is_text_edited
|
||||
end
|
||||
|
||||
function InputText:init()
|
||||
if self.text_type == "password" then
|
||||
-- text_type changes from "password" to "text" when we toggle password
|
||||
self.is_password_type = true
|
||||
end
|
||||
self:initTextBox(self.text)
|
||||
self:checkTextEditability()
|
||||
self.is_text_edited = false
|
||||
if self.readonly ~= true then
|
||||
self:initKeyboard()
|
||||
self:initEventListener()
|
||||
@@ -224,7 +263,7 @@ function InputText:initTextBox(text, char_added)
|
||||
self.text_type = "text"
|
||||
self._check_button:check()
|
||||
end
|
||||
self:setText(self:getText())
|
||||
self:setText(self:getText(), true)
|
||||
end,
|
||||
|
||||
padding = self.padding,
|
||||
@@ -246,12 +285,18 @@ function InputText:initTextBox(text, char_added)
|
||||
-- If no height provided, measure the text widget height
|
||||
-- we would start with, and use a ScrollTextWidget with that
|
||||
-- height, so widget does not overflow container if we extend
|
||||
-- the text and increase the number of lines
|
||||
-- the text and increase the number of lines.
|
||||
local text_width = self.width
|
||||
if text_width then
|
||||
-- Account for the scrollbar that will be used
|
||||
local scroll_bar_width = ScrollTextWidget.scroll_bar_width + ScrollTextWidget.text_scroll_span
|
||||
text_width = text_width - scroll_bar_width
|
||||
end
|
||||
local text_widget = TextBoxWidget:new{
|
||||
text = show_text,
|
||||
charlist = show_charlist,
|
||||
face = self.face,
|
||||
width = self.width,
|
||||
width = text_width,
|
||||
}
|
||||
self.height = text_widget:getTextHeight()
|
||||
self.scroll = true
|
||||
@@ -356,6 +401,9 @@ function InputText:getLineHeight()
|
||||
end
|
||||
|
||||
function InputText:getKeyboardDimen()
|
||||
if self.readonly then
|
||||
return Geom:new{w = 0, h = 0}
|
||||
end
|
||||
return self.keyboard.dimen
|
||||
end
|
||||
|
||||
@@ -364,18 +412,47 @@ function InputText:addChars(chars)
|
||||
UIManager:scheduleIn(0.3, function() self.enter_callback() end)
|
||||
return
|
||||
end
|
||||
if not self:isTextEditable(true) then
|
||||
return
|
||||
end
|
||||
self.is_text_edited = true
|
||||
table.insert(self.charlist, self.charpos, chars)
|
||||
self.charpos = self.charpos + #util.splitToChars(chars)
|
||||
self:initTextBox(table.concat(self.charlist), true)
|
||||
end
|
||||
|
||||
function InputText:delChar()
|
||||
if not self:isTextEditable(true) then
|
||||
return
|
||||
end
|
||||
if self.charpos == 1 then return end
|
||||
self.charpos = self.charpos - 1
|
||||
self.is_text_edited = true
|
||||
table.remove(self.charlist, self.charpos)
|
||||
self:initTextBox(table.concat(self.charlist))
|
||||
end
|
||||
|
||||
function InputText:delToStartOfLine()
|
||||
if not self:isTextEditable(true) then
|
||||
return
|
||||
end
|
||||
if self.charpos == 1 then return end
|
||||
-- self.charlist[self.charpos] is the char after the cursor
|
||||
if self.charlist[self.charpos-1] == "\n" then
|
||||
-- If at start of line, just remove the \n and join the previous line
|
||||
self.charpos = self.charpos - 1
|
||||
table.remove(self.charlist, self.charpos)
|
||||
else
|
||||
-- If not, remove chars until first found \n (but keeping it)
|
||||
while self.charpos > 1 and self.charlist[self.charpos-1] ~= "\n" do
|
||||
self.charpos = self.charpos - 1
|
||||
table.remove(self.charlist, self.charpos)
|
||||
end
|
||||
end
|
||||
self.is_text_edited = true
|
||||
self:initTextBox(table.concat(self.charlist))
|
||||
end
|
||||
|
||||
-- For the following cursor/scroll methods, the text_widget deals
|
||||
-- itself with setDirty'ing the appropriate regions
|
||||
function InputText:leftChar()
|
||||
@@ -423,16 +500,23 @@ end
|
||||
function InputText:clear()
|
||||
self.charpos = nil
|
||||
self.top_line_num = 1
|
||||
self.is_text_edited = true
|
||||
self:initTextBox("")
|
||||
self:checkTextEditability()
|
||||
end
|
||||
|
||||
function InputText:getText()
|
||||
return self.text
|
||||
end
|
||||
|
||||
function InputText:setText(text)
|
||||
function InputText:setText(text, keep_edited_state)
|
||||
-- Keep previous charpos and top_line_num
|
||||
self:initTextBox(text)
|
||||
if not keep_edited_state then
|
||||
-- assume new text is set by caller, and we start fresh
|
||||
self.is_text_edited = false
|
||||
self:checkTextEditability()
|
||||
end
|
||||
end
|
||||
|
||||
return InputText
|
||||
|
||||
@@ -13,6 +13,9 @@ local VerticalScrollBar = Widget:new{
|
||||
bordercolor = Blitbuffer.COLOR_BLACK,
|
||||
radius = 0,
|
||||
rectcolor = Blitbuffer.COLOR_BLACK,
|
||||
-- minimal height of the thumb/knob/grip (usually showing the current
|
||||
-- view size and position relative to the whole scrollable height):
|
||||
min_thumb_size = Size.line.thick,
|
||||
}
|
||||
|
||||
function VerticalScrollBar:getSize()
|
||||
@@ -33,7 +36,8 @@ function VerticalScrollBar:paintTo(bb, x, y)
|
||||
self.bordersize, self.bordercolor, self.radius)
|
||||
bb:paintRect(x + self.bordersize, y + self.bordersize + self.low * self.height,
|
||||
self.width - 2 * self.bordersize,
|
||||
(self.height - 2 * self.bordersize) * (self.high - self.low), self.rectcolor)
|
||||
math.max((self.height - 2 * self.bordersize) * (self.high - self.low), self.min_thumb_size),
|
||||
self.rectcolor)
|
||||
end
|
||||
|
||||
return VerticalScrollBar
|
||||
|
||||
@@ -52,7 +52,7 @@ function VirtualKey:init()
|
||||
self.skiptap = true
|
||||
elseif self.label == "Backspace" then
|
||||
self.callback = function () self.keyboard:delChar() end
|
||||
self.hold_callback = function () self.keyboard:clear() end
|
||||
self.hold_callback = function () self.keyboard:delToStartOfLine() end
|
||||
--self.skiphold = true
|
||||
elseif self.label =="←" then
|
||||
self.callback = function() self.keyboard:leftChar() end
|
||||
@@ -373,6 +373,11 @@ function VirtualKeyboard:delChar()
|
||||
self.inputbox:delChar()
|
||||
end
|
||||
|
||||
function VirtualKeyboard:delToStartOfLine()
|
||||
logger.dbg("delete to start of line")
|
||||
self.inputbox:delToStartOfLine()
|
||||
end
|
||||
|
||||
function VirtualKeyboard:leftChar()
|
||||
self.inputbox:leftChar()
|
||||
end
|
||||
|
||||
@@ -463,7 +463,7 @@ function util.getMenuText(item)
|
||||
else
|
||||
text = item.text
|
||||
end
|
||||
if item.sub_item_table ~= nil then
|
||||
if item.sub_item_table ~= nil or item.sub_item_table_func then
|
||||
text = text .. " \226\150\184"
|
||||
end
|
||||
return text
|
||||
|
||||
Reference in New Issue
Block a user