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.
pull/4132/head
poire-z 6 years ago committed by GitHub
parent 19b1c919d6
commit 620542b055
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

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

Loading…
Cancel
Save