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 end -- if pen_x < text_width
if char_pads then if char_pads then
char_idx = char_idx + 1 char_idx = char_idx + 1
pen_x = pen_x + char_pads[char_idx] -- or 0 pen_x = pen_x + (char_pads[char_idx] or 0)
-- will fail if we didnt count the same number of chars, we'll see -- 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
end end

@ -385,6 +385,14 @@ function InputDialog:setInputText(text)
self._input_widget:setText(text) self._input_widget:setText(text)
end end
function InputDialog:isTextEditable()
return self._input_widget:isTextEditable()
end
function InputDialog:isTextEdited()
return self._input_widget:isTextEdited()
end
function InputDialog:onShow() function InputDialog:onShow()
UIManager:setDirty(self, function() UIManager:setDirty(self, function()
return "ui", self.dialog_frame.dimen return "ui", self.dialog_frame.dimen

@ -3,8 +3,10 @@ local CheckButton = require("ui/widget/checkbutton")
local Device = require("device") local Device = require("device")
local FrameContainer = require("ui/widget/container/framecontainer") local FrameContainer = require("ui/widget/container/framecontainer")
local Font = require("ui/font") local Font = require("ui/font")
local Geom = require("ui/geometry")
local GestureRange = require("ui/gesturerange") local GestureRange = require("ui/gesturerange")
local InputContainer = require("ui/widget/container/inputcontainer") local InputContainer = require("ui/widget/container/inputcontainer")
local Notification = require("ui/widget/notification")
local ScrollTextWidget = require("ui/widget/scrolltextwidget") local ScrollTextWidget = require("ui/widget/scrolltextwidget")
local Size = require("ui/size") local Size = require("ui/size")
local TextBoxWidget = require("ui/widget/textboxwidget") 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 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) 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_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 -- only use PhysicalKeyboard if the device does not have touch screen
@ -167,12 +171,47 @@ else
function InputText:initEventListener() end function InputText:initEventListener() end
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() function InputText:init()
if self.text_type == "password" then if self.text_type == "password" then
-- text_type changes from "password" to "text" when we toggle password -- text_type changes from "password" to "text" when we toggle password
self.is_password_type = true self.is_password_type = true
end end
self:initTextBox(self.text) self:initTextBox(self.text)
self:checkTextEditability()
self.is_text_edited = false
if self.readonly ~= true then if self.readonly ~= true then
self:initKeyboard() self:initKeyboard()
self:initEventListener() self:initEventListener()
@ -224,7 +263,7 @@ function InputText:initTextBox(text, char_added)
self.text_type = "text" self.text_type = "text"
self._check_button:check() self._check_button:check()
end end
self:setText(self:getText()) self:setText(self:getText(), true)
end, end,
padding = self.padding, padding = self.padding,
@ -246,12 +285,18 @@ function InputText:initTextBox(text, char_added)
-- If no height provided, measure the text widget height -- If no height provided, measure the text widget height
-- we would start with, and use a ScrollTextWidget with that -- we would start with, and use a ScrollTextWidget with that
-- height, so widget does not overflow container if we extend -- 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{ local text_widget = TextBoxWidget:new{
text = show_text, text = show_text,
charlist = show_charlist, charlist = show_charlist,
face = self.face, face = self.face,
width = self.width, width = text_width,
} }
self.height = text_widget:getTextHeight() self.height = text_widget:getTextHeight()
self.scroll = true self.scroll = true
@ -356,6 +401,9 @@ function InputText:getLineHeight()
end end
function InputText:getKeyboardDimen() function InputText:getKeyboardDimen()
if self.readonly then
return Geom:new{w = 0, h = 0}
end
return self.keyboard.dimen return self.keyboard.dimen
end end
@ -364,18 +412,47 @@ function InputText:addChars(chars)
UIManager:scheduleIn(0.3, function() self.enter_callback() end) UIManager:scheduleIn(0.3, function() self.enter_callback() end)
return return
end end
if not self:isTextEditable(true) then
return
end
self.is_text_edited = true
table.insert(self.charlist, self.charpos, chars) table.insert(self.charlist, self.charpos, chars)
self.charpos = self.charpos + #util.splitToChars(chars) self.charpos = self.charpos + #util.splitToChars(chars)
self:initTextBox(table.concat(self.charlist), true) self:initTextBox(table.concat(self.charlist), true)
end end
function InputText:delChar() function InputText:delChar()
if not self:isTextEditable(true) then
return
end
if self.charpos == 1 then return end if self.charpos == 1 then return end
self.charpos = self.charpos - 1 self.charpos = self.charpos - 1
self.is_text_edited = true
table.remove(self.charlist, self.charpos) table.remove(self.charlist, self.charpos)
self:initTextBox(table.concat(self.charlist)) self:initTextBox(table.concat(self.charlist))
end 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 -- For the following cursor/scroll methods, the text_widget deals
-- itself with setDirty'ing the appropriate regions -- itself with setDirty'ing the appropriate regions
function InputText:leftChar() function InputText:leftChar()
@ -423,16 +500,23 @@ end
function InputText:clear() function InputText:clear()
self.charpos = nil self.charpos = nil
self.top_line_num = 1 self.top_line_num = 1
self.is_text_edited = true
self:initTextBox("") self:initTextBox("")
self:checkTextEditability()
end end
function InputText:getText() function InputText:getText()
return self.text return self.text
end end
function InputText:setText(text) function InputText:setText(text, keep_edited_state)
-- Keep previous charpos and top_line_num -- Keep previous charpos and top_line_num
self:initTextBox(text) 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 end
return InputText return InputText

@ -13,6 +13,9 @@ local VerticalScrollBar = Widget:new{
bordercolor = Blitbuffer.COLOR_BLACK, bordercolor = Blitbuffer.COLOR_BLACK,
radius = 0, radius = 0,
rectcolor = Blitbuffer.COLOR_BLACK, 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() function VerticalScrollBar:getSize()
@ -33,7 +36,8 @@ function VerticalScrollBar:paintTo(bb, x, y)
self.bordersize, self.bordercolor, self.radius) self.bordersize, self.bordercolor, self.radius)
bb:paintRect(x + self.bordersize, y + self.bordersize + self.low * self.height, bb:paintRect(x + self.bordersize, y + self.bordersize + self.low * self.height,
self.width - 2 * self.bordersize, 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 end
return VerticalScrollBar return VerticalScrollBar

@ -52,7 +52,7 @@ function VirtualKey:init()
self.skiptap = true self.skiptap = true
elseif self.label == "Backspace" then elseif self.label == "Backspace" then
self.callback = function () self.keyboard:delChar() end 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 --self.skiphold = true
elseif self.label =="" then elseif self.label =="" then
self.callback = function() self.keyboard:leftChar() end self.callback = function() self.keyboard:leftChar() end
@ -373,6 +373,11 @@ function VirtualKeyboard:delChar()
self.inputbox:delChar() self.inputbox:delChar()
end end
function VirtualKeyboard:delToStartOfLine()
logger.dbg("delete to start of line")
self.inputbox:delToStartOfLine()
end
function VirtualKeyboard:leftChar() function VirtualKeyboard:leftChar()
self.inputbox:leftChar() self.inputbox:leftChar()
end end

@ -463,7 +463,7 @@ function util.getMenuText(item)
else else
text = item.text text = item.text
end 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" text = text .. " \226\150\184"
end end
return text return text

Loading…
Cancel
Save