diff --git a/frontend/ui/rendertext.lua b/frontend/ui/rendertext.lua index 1732048f0..2211bd4df 100644 --- a/frontend/ui/rendertext.lua +++ b/frontend/ui/rendertext.lua @@ -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 diff --git a/frontend/ui/widget/inputdialog.lua b/frontend/ui/widget/inputdialog.lua index 113ca7b88..c992eb27c 100644 --- a/frontend/ui/widget/inputdialog.lua +++ b/frontend/ui/widget/inputdialog.lua @@ -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 diff --git a/frontend/ui/widget/inputtext.lua b/frontend/ui/widget/inputtext.lua index 371b6ef57..7a6f8ddde 100644 --- a/frontend/ui/widget/inputtext.lua +++ b/frontend/ui/widget/inputtext.lua @@ -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 diff --git a/frontend/ui/widget/verticalscrollbar.lua b/frontend/ui/widget/verticalscrollbar.lua index d2d661d90..120e3c0c7 100644 --- a/frontend/ui/widget/verticalscrollbar.lua +++ b/frontend/ui/widget/verticalscrollbar.lua @@ -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 diff --git a/frontend/ui/widget/virtualkeyboard.lua b/frontend/ui/widget/virtualkeyboard.lua index 39a74326a..c10e074a9 100644 --- a/frontend/ui/widget/virtualkeyboard.lua +++ b/frontend/ui/widget/virtualkeyboard.lua @@ -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 diff --git a/frontend/util.lua b/frontend/util.lua index dd9c1bd64..f2e15c2df 100644 --- a/frontend/util.lua +++ b/frontend/util.lua @@ -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