From 339e16636be2f329d8f637891290e689074512e2 Mon Sep 17 00:00:00 2001 From: Hzj_jie Date: Sat, 1 Apr 2017 23:50:24 -0700 Subject: [PATCH] Terminal plugin (#2646) --- .../ui/elements/filemanager_menu_order.lua | 1 + frontend/ui/elements/reader_menu_order.lua | 1 + frontend/ui/widget/infomessage.lua | 100 ++++++++++++++---- frontend/ui/widget/inputtext.lua | 1 + frontend/ui/widget/scrolltextwidget.lua | 2 +- frontend/ui/widget/textboxwidget.lua | 11 +- frontend/ui/widget/verticalscrollbar.lua | 12 +-- plugins/terminal.koplugin/main.lua | 96 +++++++++++++++++ 8 files changed, 189 insertions(+), 35 deletions(-) create mode 100644 plugins/terminal.koplugin/main.lua diff --git a/frontend/ui/elements/filemanager_menu_order.lua b/frontend/ui/elements/filemanager_menu_order.lua index 0715a65f0..30f428ffb 100644 --- a/frontend/ui/elements/filemanager_menu_order.lua +++ b/frontend/ui/elements/filemanager_menu_order.lua @@ -37,6 +37,7 @@ local order = { "storage_stat", "cloud_storage", "read_timer", + "terminal", "----------------------------", "advanced_settings", "developer_options", diff --git a/frontend/ui/elements/reader_menu_order.lua b/frontend/ui/elements/reader_menu_order.lua index c3cfcc48f..1fbed45f6 100644 --- a/frontend/ui/elements/reader_menu_order.lua +++ b/frontend/ui/elements/reader_menu_order.lua @@ -55,6 +55,7 @@ local order = { "synchronize_time", "progress_sync", "zsync", + "terminal", }, search = { "dictionary_lookup", diff --git a/frontend/ui/widget/infomessage.lua b/frontend/ui/widget/infomessage.lua index 2b8f5a78f..e5601f889 100644 --- a/frontend/ui/widget/infomessage.lua +++ b/frontend/ui/widget/infomessage.lua @@ -1,30 +1,56 @@ -local InputContainer = require("ui/widget/container/inputcontainer") +local Blitbuffer = require("ffi/blitbuffer") local CenterContainer = require("ui/widget/container/centercontainer") -local Font = require("ui/font") local Device = require("device") -local GestureRange = require("ui/gesturerange") +local Font = require("ui/font") local FrameContainer = require("ui/widget/container/framecontainer") -local HorizontalGroup = require("ui/widget/horizontalgroup") -local ImageWidget = require("ui/widget/imagewidget") -local TextBoxWidget = require("ui/widget/textboxwidget") -local HorizontalSpan = require("ui/widget/horizontalspan") -local UIManager = require("ui/uimanager") local Geom = require("ui/geometry") +local GestureRange = require("ui/gesturerange") +local HorizontalGroup = require("ui/widget/horizontalgroup") +local HorizontalSpan = require("ui/widget/horizontalspan") +local ImageWidget = require("ui/widget/imagewidget") +local InputContainer = require("ui/widget/container/inputcontainer") +local ScrollTextWidget = require("ui/widget/scrolltextwidget") +local TextBoxWidget = require("ui/widget/textboxwidget") +local UIManager = require("ui/uimanager") +local WidgetContainer = require("ui/widget/container/widgetcontainer") +local _ = require("gettext") local Input = require("device").input local Screen = require("device").screen -local _ = require("gettext") -local Blitbuffer = require("ffi/blitbuffer") --[[ Widget that displays an informational message it vanishes on key press or after a given timeout + +Example: + local _ = require("gettext") + local UIManager = require("ui/uimanager") + local sample + sample = InfoMessage:new{ + text = _("Some message"), + -- Usually the hight of a InfoMessage is self-adaptive. If this field is actively set, a + -- scrollbar may be shown. This variable is usually helpful to display a large chunk of text + -- which may exceed the height of the screen. + height = 400, + -- Set to false to hide the icon, and also the span between the icon and text. + show_icon = false, + timeout = 5, -- This widget will vanish in 5 seconds. + } + sample_input:onShowKeyboard() + UIManager:show(sample_input) ]] local InfoMessage = InputContainer:new{ modal = true, face = Font:getFace("infofont", 25), text = "", timeout = nil, -- in seconds + width = nil, -- The width of the InfoMessage. Keep it nil to use default value. + height = nil, -- The height of the InfoMessage. If this field is set, a scrollbar may be shown. + image = nil, -- The image shows at the left of the InfoMessage. + image_width = nil, -- The image width if image is used. Keep it nil to use original width. + image_height = nil, -- The image height if image is used. Keep it nil to use original height. + -- Whether the icon should be shown. If it is false, self.image will be ignored. + show_icon = true, } function InfoMessage:init() @@ -46,16 +72,48 @@ function InfoMessage:init() } } end + local image_widget - if self.image then - image_widget = ImageWidget:new{ - image = self.image, - width = self.image_width, - height = self.image_height, + if self.show_icon then + if self.image then + image_widget = ImageWidget:new{ + image = self.image, + width = self.image_width, + height = self.image_height, + } + else + image_widget = ImageWidget:new{ + file = "resources/info-i.png", + } + end + else + image_widget = WidgetContainer:new() + end + + local text_width + if self.width == nil then + text_width = Screen:getWidth() * 2 / 3 + else + text_width = self.width - image_widget:getSize().w + if text_width < 0 then + text_width = 0 + end + end + + local text_widget + if self.height then + text_widget = ScrollTextWidget:new{ + text = self.text, + face = self.face, + width = text_width, + height = self.height, + dialog = self, } else - image_widget = ImageWidget:new{ - file = "resources/info-i.png", + text_widget = TextBoxWidget:new{ + text = self.text, + face = self.face, + width = text_width, } end -- we construct the actual content here because self.text is only available now @@ -67,12 +125,8 @@ function InfoMessage:init() HorizontalGroup:new{ align = "center", image_widget, - HorizontalSpan:new{ width = 10 }, - TextBoxWidget:new{ - text = self.text, - face = self.face, - width = Screen:getWidth()*2/3, - } + HorizontalSpan:new{ width = (self.show_icon and 10 or 0) }, + text_widget, } } } diff --git a/frontend/ui/widget/inputtext.lua b/frontend/ui/widget/inputtext.lua index 0ed6a9323..c737e751d 100644 --- a/frontend/ui/widget/inputtext.lua +++ b/frontend/ui/widget/inputtext.lua @@ -108,6 +108,7 @@ function InputText:initTextBox(text, char_added) fgcolor = fgcolor, width = self.width, height = self.height, + dialog = self.parent, } else self.text_widget = TextBoxWidget:new{ diff --git a/frontend/ui/widget/scrolltextwidget.lua b/frontend/ui/widget/scrolltextwidget.lua index 6f423c296..7f0ada2bb 100644 --- a/frontend/ui/widget/scrolltextwidget.lua +++ b/frontend/ui/widget/scrolltextwidget.lua @@ -46,7 +46,7 @@ function ScrollTextWidget:init() self.v_scroll_bar = VerticalScrollBar:new{ enable = visible_line_count < total_line_count, low = 0, - high = visible_line_count/total_line_count, + high = visible_line_count / total_line_count, width = self.scroll_bar_width, height = self.height, } diff --git a/frontend/ui/widget/textboxwidget.lua b/frontend/ui/widget/textboxwidget.lua index f3f7ef33e..c559dc29b 100644 --- a/frontend/ui/widget/textboxwidget.lua +++ b/frontend/ui/widget/textboxwidget.lua @@ -13,14 +13,14 @@ Example: ]] local Blitbuffer = require("ffi/blitbuffer") -local Widget = require("ui/widget/widget") +local Geom = require("ui/geometry") local LineWidget = require("ui/widget/linewidget") local RenderText = require("ui/rendertext") local Screen = require("device").screen -local Geom = require("ui/geometry") -local util = require("util") -local logger = require("logger") local TimeVal = require("ui/timeval") +local Widget = require("ui/widget/widget") +local logger = require("logger") +local util = require("util") local TextBoxWidget = Widget:new{ text = nil, @@ -225,7 +225,8 @@ function TextBoxWidget:_renderText(start_row_idx, end_row_idx) if start_row_idx < 1 then start_row_idx = 1 end if end_row_idx > #self.vertical_string_list then end_row_idx = #self.vertical_string_list end local row_count = end_row_idx == 0 and 1 or end_row_idx - start_row_idx + 1 - local h = self.line_height_px * row_count + local h = self.line_height_px * row_count + if self._bb then self._bb:free() end self._bb = Blitbuffer.new(self.width, h) self._bb:fill(Blitbuffer.COLOR_WHITE) local y = font_height diff --git a/frontend/ui/widget/verticalscrollbar.lua b/frontend/ui/widget/verticalscrollbar.lua index e828fbdef..d63f98870 100644 --- a/frontend/ui/widget/verticalscrollbar.lua +++ b/frontend/ui/widget/verticalscrollbar.lua @@ -1,6 +1,6 @@ -local Widget = require("ui/widget/widget") -local Geom = require("ui/geometry") local Blitbuffer = require("ffi/blitbuffer") +local Geom = require("ui/geometry") +local Widget = require("ui/widget/widget") local VerticalScrollBar = Widget:new{ enable = true, @@ -29,10 +29,10 @@ end function VerticalScrollBar:paintTo(bb, x, y) if not self.enable then return end bb:paintBorder(x, y, self.width, self.height, - 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 * (self.high - self.low), self.rectcolor) + 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) end return VerticalScrollBar diff --git a/plugins/terminal.koplugin/main.lua b/plugins/terminal.koplugin/main.lua new file mode 100644 index 000000000..5afb25b1e --- /dev/null +++ b/plugins/terminal.koplugin/main.lua @@ -0,0 +1,96 @@ + +local DataStorage = require("datastorage") +local Font = require("ui/font") +local InfoMessage = require("ui/widget/infomessage") +local InputDialog = require("ui/widget/inputdialog") +local UIManager = require("ui/uimanager") +local WidgetContainer = require("ui/widget/container/widgetcontainer") +local logger = require("logger") +local util = require("ffi/util") +local _ = require("gettext") +local Screen = require("device").screen + +local Terminal = WidgetContainer:new{ + name = "terminal", + dump_file = util.realpath(DataStorage:getDataDir()) .. "/terminal_output.txt", +} + +function Terminal:init() + self.ui.menu:registerToMainMenu(self) +end + +function Terminal:start() + self.input = InputDialog:new{ + title = _("Enter a command and press \"Execute\""), + text_height = Screen:getHeight() * 0.4, + input_type = "string", + buttons = {{{ + text = _("Cancel"), + callback = function() + UIManager:close(self.input) + end, + }, { + text = _("Execute"), + is_enter_default = true, + callback = function() + UIManager:close(self.input) + self:execute() + end, + }}}, + } + self.input:onShowKeyboard() + UIManager:show(self.input) +end + +function Terminal:execute() + local command = self.input:getInputText() + UIManager:show(InfoMessage:new{ + text = _("Executing ..."), + timeout = 0.1, + }) + UIManager:forceRePaint() + local std_out = io.popen(command) + local entries = { command } + if std_out then + while true do + local line = std_out:read() + if line == nil then break end + table.insert(entries, line) + end + std_out:close() + else + table.insert(entries, _("Failed to execute command.")) + end + self:dump(entries) + table.insert(entries, _("Output will also be dumped to")) + table.insert(entries, self.dump_file) + UIManager:show(InfoMessage:new{ + cface = Font:getFace("ffont", 18), + text = _("Command output\n") .. table.concat(entries, "\n"), + show_icon = false, + width = Screen:getWidth() * 0.8, + height = Screen:getHeight() * 0.8, + }) +end + +function Terminal:dump(entries) + local content = table.concat(entries, "\n") + local file = io.open(self.dump_file, "w") + if file then + file:write(content) + file:close() + else + logger.warn("Failed to dump terminal output " .. content .. " to " .. self.dump_file) + end +end + +function Terminal:addToMainMenu(menu_items) + menu_items.terminal = { + text = _("Terminal emulator"), + callback = function() + self:start() + end, + } +end + +return Terminal