From 5c12b20faeefe29bd0934b870a5776a369f0270c Mon Sep 17 00:00:00 2001 From: "kepler155c@gmail.com" Date: Mon, 26 Dec 2016 22:26:43 -0500 Subject: [PATCH] Forms - dialogs --- apps/Lua.lua | 60 +++++++++-- apps/Network.lua | 6 +- apps/Overview.lua | 109 ++++++++----------- apps/edit.lua | 6 ++ apps/storageManager.lua | 200 ++++++++++++++++------------------ sys/apis/event.lua | 17 ++- sys/apis/fileui.lua | 8 +- sys/apis/fs/urlfs.lua | 4 +- sys/apis/git.lua | 2 +- sys/apis/injector.lua | 3 +- sys/apis/process.lua | 7 +- sys/apis/ui.lua | 228 ++++++++++++++++++++++----------------- sys/apis/util.lua | 4 + sys/boot/multishell.boot | 3 +- 14 files changed, 365 insertions(+), 292 deletions(-) diff --git a/apps/Lua.lua b/apps/Lua.lua index b6c45ac..112a9ea 100644 --- a/apps/Lua.lua +++ b/apps/Lua.lua @@ -30,10 +30,11 @@ local page = UI.Page({ backgroundFocusColor = colors.black, limit = 256, accelerators = { - enter = 'command_enter', - up = 'history_back', - down = 'history_forward', - mouse_rightclick = 'clear_prompt', + enter = 'command_enter', + up = 'history_back', + down = 'history_forward', + mouse_rightclick = 'clear_prompt', + [ 'control-space' ] = 'autocomplete', }, }), grid = UI.ScrollingGrid({ @@ -72,20 +73,65 @@ function page:enable() end end +function autocomplete(env, oLine, x) + + local sLine = oLine:sub(1, x) + local nStartPos = sLine:find("[a-zA-Z0-9_%.]+$") + if nStartPos then + sLine = sLine:sub(nStartPos) + end + + if #sLine > 0 then + local results = textutils.complete(sLine, env) + + if #results == 0 then +-- setError('No completions available') + + elseif #results == 1 then + return Util.insertString(oLine, results[1], x + 1) + + elseif #results > 1 then + local prefix = results[1] + for n = 1, #results do + local result = results[n] + while #prefix > 0 do + if result:find(prefix, 1, true) == 1 then + break + end + prefix = prefix:sub(1, #prefix - 1) + end + end + if #prefix > 0 then + return Util.insertString(oLine, prefix, x + 1) + else +-- setStatus('Too many results') + end + end + end + return oLine +end + function page:eventHandler(event) if event.type == 'global' then - page:setPrompt('', true) + self:setPrompt('', true) self:executeStatement('getfenv(0)') command = nil elseif event.type == 'local' then - page:setPrompt('', true) + self:setPrompt('', true) self:executeStatement('getfenv(1)') command = nil + elseif event.type == 'autocomplete' then + local sz = #self.prompt.value + local pos = self.prompt.pos + self:setPrompt(autocomplete(sandboxEnv, self.prompt.value, self.prompt.pos)) + self.prompt:setPosition(pos + #self.prompt.value - sz) + self.prompt:updateCursor() + elseif event.type == 'device' then - page:setPrompt('device', true) + self:setPrompt('device', true) self:executeStatement('device') elseif event.type == 'history_back' then diff --git a/apps/Network.lua b/apps/Network.lua index fc0d4d8..ab337e9 100644 --- a/apps/Network.lua +++ b/apps/Network.lua @@ -121,14 +121,14 @@ function page.grid:draw() end end -function updateComputers() +Event.addThread(function() while true do page.grid:update() page.grid:draw() page:sync() os.sleep(1) end -end +end) Event.addHandler('device_attach', function(h, deviceName) if deviceName == 'wireless_modem' then @@ -149,5 +149,5 @@ if not device.wireless_modem then end UI:setPage(page) -Event.pullEvents(updateComputers) +Event.pullEvents() UI.term:reset() diff --git a/apps/Overview.lua b/apps/Overview.lua index 80b7045..faf72ee 100644 --- a/apps/Overview.lua +++ b/apps/Overview.lua @@ -332,76 +332,64 @@ end local formWidth = math.max(UI.term.width - 14, 26) -local editor = UI.Page { - backgroundColor = colors.white, - x = math.ceil((UI.term.width - formWidth) / 2) + 1, - y = math.ceil((UI.term.height - 11) / 2) + 1, - z = 2, +local editor = UI.Dialog { height = 11, width = formWidth, - titleBar = UI.TitleBar { - title = 'Edit application', - }, - inset = UI.Window { - x = 2, - y = 3, - rex = -2, - rey = -3, - form = UI.Form { - textColor = colors.black, - fields = { - { label = 'Title', key = 'title', width = formWidth - 11, limit = 11, display = UI.Form.D.entry, - help = 'Application title' }, - { label = 'Run', key = 'run', width = formWidth - 11, limit = 100, display = UI.Form.D.entry, - help = 'Full path to application' }, - { label = 'Category', key = 'category', width = formWidth - 11, limit = 11, display = UI.Form.D.entry, - help = 'Category of application' }, - { text = 'Icon', event = 'loadIcon', display = UI.Form.D.button, - x = 10, y = 5, textColor = colors.white, help = 'Select icon' }, - { text = 'Ok', event = 'accept', display = UI.Form.D.button, - x = formWidth - 14, y = 7, textColor = colors.white }, - { text = 'Cancel', event = 'cancel', display = UI.Form.D.button, - x = formWidth - 9, y = 7, textColor = colors.white }, - }, - labelWidth = 8, - image = UI.NftImage { - y = 5, - x = 1, - height = 3, - width = 8, - }, + title = 'Edit application', + form = UI.Form { + y = 2, + height = 9, + title = UI.TextEntry { + formLabel = 'Title', formKey = 'title', limit = 11, help = 'Application title', + required = true, + }, + run = UI.TextEntry { + formLabel = 'Run', formKey = 'run', limit = 100, help = 'Full path to application', + required = true, + }, + category = UI.TextEntry { + formLabel = 'Category', formKey = 'category', limit = 11, help = 'Category of application', + required = true, + }, + loadIcon = UI.Button { + x = 11, y = 6, + text = 'Icon', event = 'loadIcon', help = 'Select icon' + }, + image = UI.NftImage { + y = 6, + x = 2, + height = 3, + width = 8, }, }, statusBar = UI.StatusBar(), - notification = UI.Notification(), iconFile = '', } function editor:enable(app) if app then - self.original = app - self.inset.form:setValues(Util.shallowCopy(app)) + self.form:setValues(app) local icon if app.icon then icon = parseIcon(app.icon) end - self.inset.form.image:setImage(icon) + self.form.image:setImage(icon) end UI.Page.enable(self) self:focusFirst() end -function editor.inset.form.image:draw() +function editor.form.image:draw() self:clear() UI.NftImage.draw(self) end -function editor:updateApplications(app, original) - if original.run then - local _,k = Util.find(applications, 'run', original.run) - if k then +function editor:updateApplications(app) + for k,v in pairs(applications) do + if v == app then applications[k] = nil + break end end table.insert(applications, app) @@ -410,7 +398,7 @@ end function editor:eventHandler(event) - if event.type == 'cancel' then + if event.type == 'form_cancel' or event.type == 'cancel' then UI:setPreviousPage() elseif event.type == 'focus_change' then @@ -438,29 +426,26 @@ function editor:eventHandler(event) if not icon then error(m) end - self.inset.form.values.icon = iconLines - self.inset.form.image:setImage(icon) - self.inset.form.image:draw() + self.form.values.icon = iconLines + self.form.image:setImage(icon) + self.form.image:draw() end) if not s and m then local msg = m:gsub('.*: (.*)', '%1') - self.notification:error(msg) + page.notification:error(msg) end end end) - elseif event.type == 'accept' then - local values = self.inset.form.values - if #values.run > 0 and #values.title > 0 and #values.category > 0 then - UI:setPreviousPage() - self:updateApplications(values, self.original) - page:refresh() - page:draw() - else - self.notification:error('Require fields missing') - --self.statusBar:setStatus('Require fields missing') - --self.statusBar:draw() - end + elseif event.type == 'form_invalid' then + page.notification:error(event.message) + + elseif event.type == 'form_complete' then + local values = self.form.values + UI:setPreviousPage() + self:updateApplications(values) + page:refresh() + page:draw() else return UI.Page.eventHandler(self, event) end diff --git a/apps/edit.lua b/apps/edit.lua index 55f47e9..2d8d634 100644 --- a/apps/edit.lua +++ b/apps/edit.lua @@ -33,6 +33,12 @@ local clipboard = { size, internal } local searchPattern local undo = { chain = { }, pointer = 0 } +if _G.__CLIPBOARD then + clipboard = _G.__CLIPBOARD +else + _G.__CLIPBOARD = clipboard +end + local color = { textColor = '0', keywordColor = '4', diff --git a/apps/storageManager.lua b/apps/storageManager.lua index 1532f9b..d2ea572 100644 --- a/apps/storageManager.lua +++ b/apps/storageManager.lua @@ -452,51 +452,54 @@ function watchResources(items) return itemList end -itemPage = UI.Page({ +itemPage = UI.Page { backgroundColor = colors.lightGray, - titleBar = UI.TitleBar({ + titleBar = UI.TitleBar { title = 'Limit Resource', previousPage = true, + event = 'form_cancel', backgroundColor = colors.green - }), - idField = UI.Text({ - x = 5, - y = 3, - width = UI.term.width - 10 - }), - form = UI.Form({ - fields = { - { label = 'Min', key = 'low', width = 7, display = UI.Form.D.entry, - help = 'Craft if below min' }, - { label = 'Max', key = 'limit', width = 7, display = UI.Form.D.entry, - validation = UI.Form.V.number, dataType = UI.Form.T.number, - help = 'Eject if above max' }, - { label = 'Autocraft', key = 'auto', width = 7, display = UI.Form.D.chooser, - nochoice = 'No', - choices = { - { name = 'Yes', value = 'yes' }, - { name = 'No', value = 'no' }, - }, - help = 'Craft until out of ingredients' }, - { label = 'Ignore Dmg', key = 'ignore_dmg', width = 7, display = UI.Form.D.chooser, - nochoice = 'No', - choices = { - { name = 'Yes', value = 'yes' }, - { name = 'No', value = 'no' }, - }, - help = 'Ignore damage of item' }, - { text = 'Accept', event = 'accept', display = UI.Form.D.button, - x = 1, y = 6, width = 10 }, - { text = 'Cancel', event = 'cancel', display = UI.Form.D.button, - x = 21, y = 6, width = 10 }, + }, + idField = UI.Text { + x = 5, y = 3, width = UI.term.width - 10, + }, + form = UI.Form { + x = 4, y = 4, height = 8, rex = -4, + [1] = UI.TextEntry { + width = 7, + backgroundColor = colors.gray, + backgroundFocusColor = colors.gray, + formLabel = 'Min', formKey = 'low', help = 'Craft if below min' }, - labelWidth = 10, - x = 5, - y = 5, - height = 6 - }), - statusBar = UI.StatusBar() -}) + [2] = UI.TextEntry { + width = 7, + backgroundColor = colors.gray, + backgroundFocusColor = colors.gray, + formLabel = 'Max', formKey = 'limit', help = 'Eject if above max' + }, + [3] = UI.Chooser { + width = 7, + formLabel = 'Autocraft', formKey = 'auto', + nochoice = 'No', + choices = { + { name = 'Yes', value = 'yes' }, + { name = 'No', value = 'no' }, + }, + help = 'Craft until out of ingredients' + }, + [4] = UI.Chooser { + width = 7, + formLabel = 'Ignore Dmg', formKey = 'ignore_dmg', + nochoice = 'No', + choices = { + { name = 'Yes', value = 'yes' }, + { name = 'No', value = 'no' }, + }, + help = 'Ignore damage of item' + }, + }, + statusBar = UI.StatusBar { } +} function itemPage:enable() UI.Page.enable(self) @@ -504,12 +507,14 @@ function itemPage:enable() end function itemPage:eventHandler(event) - if event.type == 'cancel' then + if event.type == 'form_cancel' then UI:setPreviousPage() + elseif event.type == 'focus_change' then self.statusBar:setStatus(event.focused.help) self.statusBar:draw() - elseif event.type == 'accept' then + + elseif event.type == 'form_complete' then local values = self.form.values local t = Util.readTable('resource.limits') or { } for k,v in pairs(t) do @@ -527,55 +532,52 @@ function itemPage:eventHandler(event) table.insert(t, filtered) Util.writeTable('resource.limits', t) UI:setPreviousPage() + else return UI.Page.eventHandler(self, event) end return true end -listingPage = UI.Page({ - menuBar = UI.MenuBar({ +listingPage = UI.Page { + menuBar = UI.MenuBar { buttons = { { text = 'Learn', event = 'learn' }, { text = 'Forget', event = 'forget' }, }, - }), - grid = UI.Grid({ + }, + grid = UI.Grid { + y = 2, height = UI.term.height - 2, columns = { { heading = 'Name', key = 'name' , width = 22 }, { heading = 'Qty', key = 'qty' , width = 5 }, { heading = 'Min', key = 'low' , width = 4 }, { heading = 'Max', key = 'limit', width = 4 }, }, - y = 2, sortColumn = 'name', - height = UI.term.height-2, - }), - statusBar = UI.StatusBar({ + }, + statusBar = UI.StatusBar { backgroundColor = colors.gray, width = UI.term.width, - filterText = UI.Text({ + filterText = UI.Text { + x = 2, width = 6, value = 'Filter', - x = 2, - width = 6, - }), - filter = UI.TextEntry({ - width = 19, + }, + filter = UI.TextEntry { + x = 9, width = 19, limit = 50, - x = 9, - }), - refresh = UI.Button({ + }, + refresh = UI.Button { + x = 31, width = 8, text = 'Refresh', event = 'refresh', - x = 31, - width = 8 - }), - }), + }, + }, accelerators = { r = 'refresh', q = 'quit', } -}) +} function listingPage.grid:getRowTextColor(row, selected) if row.is_craftable then @@ -622,24 +624,26 @@ end function listingPage:eventHandler(event) if event.type == 'quit' then Event.exitPullEvents() + elseif event.type == 'grid_select' then local selected = event.selected itemPage.form:setValues(selected) itemPage.titleBar.title = selected.name itemPage.idField.value = selected.id UI:setPage('item') + elseif event.type == 'refresh' then self:refresh() self.grid:draw() + elseif event.type == 'learn' then if not duckAntenna then self.statusBar:timedStatus('Missing peripherals', 3) else - UI:getPage('craft').form:setValues( { ignore_dmg = 'no' } ) UI:setPage('craft') end - elseif event.type == 'forget' then + elseif event.type == 'forget' then local item = self.grid:getSelected() if item then local recipes = Util.readTable('recipes') or { } @@ -673,6 +677,7 @@ function listingPage:eventHandler(event) self:applyFilter() self.grid:draw() self.statusBar.filter:focus() + else UI.Page.eventHandler(self, event) end @@ -747,7 +752,7 @@ local function filter(t, filter) end end -local function learnRecipe(page, ignore_dmg) +local function learnRecipe(page) local t = Util.readTable('recipes') or { } local recipe = { } local ingredients = duckAntenna.getAllStacks(false) -- getTurtleInventory() @@ -773,7 +778,7 @@ local function learnRecipe(page, ignore_dmg) end end recipe.ingredients = ingredients - recipe.ignore_dmg = 'no' -- ignore_dmg + recipe.ignore_dmg = 'no' t[key] = recipe @@ -794,69 +799,52 @@ local function learnRecipe(page, ignore_dmg) end end -craftPage = UI.Page({ - x = 4, - y = math.floor((UI.term.height - 8) / 2) + 1, - height = 7, - width = UI.term.width - 6, +craftPage = UI.Dialog { + height = 7, width = UI.term.width - 6, backgroundColor = colors.lightGray, - titleBar = UI.TitleBar({ + titleBar = UI.TitleBar { title = 'Learn Recipe', previousPage = true, - }), - idField = UI.Text({ + }, + idField = UI.Text { x = 5, y = 3, width = UI.term.width - 10, value = 'Place recipe in turtle' - }), - form = UI.Form({ - fields = { - --[[ - { label = 'Ignore Damage', key = 'ignore_dmg', width = 7, display = UI.Form.D.chooser, - nochoice = 'No', - choices = { - { name = 'Yes', value = 'yes' }, - { name = 'No', value = 'no' }, - }, - help = 'Ignore damage of ingredients' }, - --]] - { text = 'Accept', event = 'accept', display = UI.Form.D.button, - x = 1, y = 1, width = 10 }, - { text = 'Cancel', event = 'cancel', display = UI.Form.D.button, - x = 16, y = 1, width = 10 }, - }, - labelWidth = 13, - x = 5, - y = 5, - height = 2 - }), - statusBar = UI.StatusBar({ + }, + accept = UI.Button { + rx = -13, ry = -2, + text = 'Ok', event = 'accept', + }, + cancel = UI.Button { + rx = -8, ry = -2, + text = 'Cancel', event = 'cancel' + }, + statusBar = UI.StatusBar { status = 'Crafting paused' - }) -}) + } +} function craftPage:enable() craftingPaused = true self:focusFirst() - UI.Page.enable(self) + UI.Dialog.enable(self) end function craftPage:disable() craftingPaused = false + UI.Dialog.disable(self) end function craftPage:eventHandler(event) if event.type == 'cancel' then UI:setPreviousPage() elseif event.type == 'accept' then - local values = self.form.values - - if learnRecipe(self, values.ignore_dmg) then + if learnRecipe(self) then UI:setPreviousPage() end else - return UI.Page.eventHandler(self, event) + return UI.Dialog.eventHandler(self, event) end return true end diff --git a/sys/apis/event.lua b/sys/apis/event.lua index 6144424..e110286 100644 --- a/sys/apis/event.lua +++ b/sys/apis/event.lua @@ -1,4 +1,5 @@ local Util = require('util') +local Process = require('process') local Event = { uid = 1, -- unique id for handlers @@ -123,8 +124,9 @@ local function _pullEvents() --exitPullEvents = false while true do - local e = Event.pullEvent() - if exitPullEvents or e == 'terminate' then + local e = { Process:pullEvent() } + Event.processEvent(e) + if exitPullEvents or e[1] == 'terminate' then break end end @@ -137,13 +139,18 @@ function Event.sleep(t) until event == 'timer' and id == timerId end +function Event.addThread(fn) + return Process:addThread(fn) +end + function Event.pullEvents(...) local routines = { ... } if #routines > 0 then - parallel.waitForAny(_pullEvents, ...) - else - _pullEvents() + for _, routine in ipairs(routines) do + Process:addThread(routine) + end end + _pullEvents() end function Event.exitPullEvents() diff --git a/sys/apis/fileui.lua b/sys/apis/fileui.lua index f9a6dc0..163a59d 100644 --- a/sys/apis/fileui.lua +++ b/sys/apis/fileui.lua @@ -15,7 +15,7 @@ return function(args) args = args or { } - local selectFile = UI.Page { + local selectFile = UI.Dialog { x = args.x or 3, y = args.y or 2, z = args.z or 2, @@ -24,11 +24,7 @@ return function(args) height = args.height, width = args.width, backgroundColor = colors.brown, - titleBar = UI.TitleBar { - title = 'Select file', - previousPage = true, - event = 'cancel', - }, + title = 'Select file', grid = UI.ScrollingGrid { x = 2, y = 2, diff --git a/sys/apis/fs/urlfs.lua b/sys/apis/fs/urlfs.lua index 1864908..2e9afad 100644 --- a/sys/apis/fs/urlfs.lua +++ b/sys/apis/fs/urlfs.lua @@ -46,13 +46,13 @@ function urlfs.open(node, fn, fl) synchronized(node.url, function() c = Util.download(node.url) end) - if c and #c > 0 then + if c then node.cache = c node.size = #c end end - if not c or #c == 0 then + if not c then return end diff --git a/sys/apis/git.lua b/sys/apis/git.lua index 3200194..ccaaf5f 100644 --- a/sys/apis/git.lua +++ b/sys/apis/git.lua @@ -1,4 +1,4 @@ -local json = require('craigmj.json4lua.master.json.json') +local json = require('json') local Util = require('util') local TREE_URL = 'https://api.github.com/repos/%s/%s/git/trees/%s?recursive=1' diff --git a/sys/apis/injector.lua b/sys/apis/injector.lua index 3fa6b58..f0014c3 100644 --- a/sys/apis/injector.lua +++ b/sys/apis/injector.lua @@ -89,7 +89,6 @@ local function gitSearcher(modname, env, shell) local _, count = fname:gsub("/", "") if count >= 3 then local url = GIT_URL .. '/' .. fname - debug(url) local c = loadUrl(url) if c then return load(c, modname, nil, env) @@ -153,7 +152,7 @@ local function requireWrapper(env) return module end if msg then - error(msg) + error(msg, 2) end end error('Unable to find module ' .. modname) diff --git a/sys/apis/process.lua b/sys/apis/process.lua index 6cbdc3e..2e69869 100644 --- a/sys/apis/process.lua +++ b/sys/apis/process.lua @@ -27,6 +27,11 @@ function Process:threadEvent(...) end end +function Process:addThread(fn, ...) + return self:newThread(nil, fn, ...) +end + +-- deprecated function Process:newThread(name, fn, ...) self.uid = self.uid + 1 @@ -45,7 +50,7 @@ function Process:newThread(name, fn, ...) local s, m = pcall(function() fn(unpack(args)) end) if not s and m then if m == 'Terminated' then - printError(thread.name .. ' terminated') + --printError(thread.name .. ' terminated') else printError(m) end diff --git a/sys/apis/ui.lua b/sys/apis/ui.lua index 0a0c885..c2dac11 100644 --- a/sys/apis/ui.lua +++ b/sys/apis/ui.lua @@ -64,6 +64,12 @@ local function getPosition(element) return x, y end +local function assertElement(el, msg) + if not el or not type(el) == 'table' or not el.UIElement then + error(msg, 3) + end +end + --[[-- Top Level Manager --]]-- local Manager = class() function Manager:init(args) @@ -430,6 +436,14 @@ function Manager:getDefaults(element, args) return defaults end +function Manager:pullEvents(...) + Event.pullEvents(...) +end + +function Manager:exitPullEvents() + Event.exitPullEvents() +end + -- inconsistent function Manager.setProperties(obj, args) if args then @@ -743,6 +757,7 @@ function UI.Window:print(text, bg, fg, indent) end function UI.Window:setFocus(focus) + assertElement(focus, 'UI.Window:setFocus: Invalid element passed') if self.parent then self.parent:setFocus(focus) end @@ -1378,6 +1393,8 @@ function UI.Page:focusNext() end function UI.Page:setFocus(child) + assertElement(child, 'UI.Page:setFocus: Invalid element passed') + if not child.focus then return end @@ -1443,16 +1460,12 @@ function UI.Grid:init(args) h.heading = '' end end - self:update() -end - -function UI.Grid:enable() - UI.Window.enable(self) end function UI.Grid:setParent() UI.Window.setParent(self) - self:adjustWidth() + self:update() + if not self.pageSize then if self.disableHeader then self.pageSize = self.height @@ -1529,7 +1542,7 @@ end function UI.Grid:getSelected() if self.sorted then - return self.values[self.sorted[self.index]] + return self.values[self.sorted[self.index]], self.sorted[self.index] end end @@ -1581,6 +1594,8 @@ function UI.Grid:update() return order(self.values[a], self.values[b]) end) end + + self:adjustWidth() end function UI.Grid:drawHeadings() @@ -2376,7 +2391,6 @@ UI.Notification.defaults = { function UI.Notification:init(args) local defaults = UI:getDefaults(UI.Notification, args) UI.Window.init(self, defaults) - Util.print(self) end function UI.Notification:draw() @@ -2401,6 +2415,15 @@ function UI.Notification:success(value, timeout) self:display(value, timeout) end +function UI.Notification:cancel() + if self.canvas then + Event.cancelNamedTimer('notificationTimer') + self.enabled = false + self.canvas:removeLayer() + self.canvas = nil + end +end + function UI.Notification:display(value, timeout) self.enabled = true local lines = Util.wordWrap(value, self.width - 2) @@ -2409,6 +2432,8 @@ function UI.Notification:display(value, timeout) if self.canvas then self.canvas:removeLayer() end + + -- need to get the current canvas - not ui.term.canvas self.canvas = UI.term.canvas:addLayer(self, self.backgroundColor, self.textColor or colors.white) self:addTransition(UI.TransitionExpandUp { x = self.x, @@ -2425,8 +2450,7 @@ function UI.Notification:display(value, timeout) end Event.addNamedTimer('notificationTimer', timeout or 3, false, function() - self.enabled = false - self.canvas:removeLayer() + self:cancel() self:sync() end) end @@ -3012,134 +3036,146 @@ UI.Form = class(UI.Window) UI.Form.defaults = { UIElement = 'Form', values = { }, - fields = { }, - labelWidth = 20, - accept = function() end, - cancel = function() end, + margin = 2, + event = 'form_complete', } - -UI.Form.D = { -- display - static = UI.Text, - entry = UI.TextEntry, - chooser = UI.Chooser, - button = UI.Button -} - -UI.Form.V = { -- validation - number = function(value) - return type(value) == 'number' - end -} - -UI.Form.T = { -- data types - number = function(value) - return tonumber(value) - end -} - function UI.Form:init(args) local defaults = UI:getDefaults(UI.Form, args) UI.Window.init(self, defaults) + self:createForm() +end - self:createFields() - self:initChildren() +function UI.Form:reset() + for _,child in pairs(self.children) do + if child.reset then + child:reset() + end + end end function UI.Form:setValues(values) + self:reset() self.values = values for k,child in pairs(self.children) do - if child.key then - child.value = self.values[child.key] - if not child.value then - child.value = '' + if child.formKey then + child.value = self.values[child.formKey] or '' + end + end +end + +function UI.Form:createForm() + self.children = self.children or { } + + if not self.labelWidth then + self.labelWidth = 1 + for _, child in pairs(self) do + if type(child) == 'table' and child.UIElement then + if child.formLabel then + self.labelWidth = math.max(self.labelWidth, #child.formLabel + 2) + end end end end + + local y = self.margin + for _, child in pairs(self) do + if type(child) == 'table' and child.UIElement then + if child.formKey then + child.x = self.labelWidth + self.margin - 1 + child.y = y + if not child.width and not child.rex then + child.rex = -self.margin + end + child.value = self.values[child.formKey] or '' + end + if child.formLabel then + table.insert(self.children, UI.Text { + x = self.margin, + y = y, + textColor = colors.black, + width = #child.formLabel, + value = child.formLabel, + }) + end + if child.formKey or child.formLabel then + y = y + 1 + end + end + end + + table.insert(self.children, UI.Button { + ry = -self.margin + 1, rx = -12 - self.margin + 1, + text = 'Ok', + event = 'form_ok', + }) + table.insert(self.children, UI.Button { + ry = -self.margin + 1, rx = -7 - self.margin + 1, + text = 'Cancel', + event = 'form_cancel', + }) end -function UI.Form:createFields() - - if not self.children then - self.children = { } - end - for k,field in pairs(self.fields) do - if field.label then - table.insert(self.children, UI.Text({ - x = 1, - y = k, - width = #field.label, - value = field.label, - })) - end - local value - if field.key then - value = self.values[field.key] - end - if not value then - value = '' - end - value = tostring(value) - local width = #value - if field.limit then - width = field.limit + 2 - end - if width == 0 then - width = nil - end - local fieldProperties = { - x = self.labelWidth + 2, - y = k, - width = width, - value = value, - } - UI.setProperties(fieldProperties, field) - table.insert(self.children, field.display(fieldProperties)) +function UI.Form:validateField(field) + if field.required then + if not field.value or #field.value == 0 then + return false, 'Field is required' + end end + return true end function UI.Form:eventHandler(event) - if event.type == 'accept' then + if event.type == 'form_ok' then for _,child in pairs(self.children) do - if child.key then - self.values[child.key] = child.value + if child.formKey then + local s, m = self:validateField(child) + if not s then + self:setFocus(child) + self:emit({ type = 'form_invalid', message = m, field = child }) + return false + end end end + for _,child in pairs(self.children) do + if child.formKey then + self.values[child.formKey] = child.value + end + end + self:emit({ type = self.event, UIElement = self }) + else + return UI.Window.eventHandler(self, event) end + return true end --[[-- Dialog --]]-- UI.Dialog = class(UI.Page) UI.Dialog.defaults = { + UIElement = 'Dialog', x = 7, y = 4, + z = 2, height = 7, - backgroundColor = colors.lightBlue, + backgroundColor = colors.white, } function UI.Dialog:init(args) - local defaults = UI:getDefaults(UI.Dialog) + local defaults = UI:getDefaults(UI.Dialog, args) UI.setProperties(defaults, { width = UI.term.width-11, - titleBar = UI.TitleBar({ previousPage = true }), - acceptButton = UI.Button({ - text = 'Accept', - event = 'accept', - x = 5, - y = 5 - }), - cancelButton = UI.Button({ - text = 'Cancel', - event = 'cancel', - x = 17, - y = 5 - }), - statusBar = UI.StatusBar(), + titleBar = UI.TitleBar({ previousPage = true, title = defaults.title }), }) UI.setProperties(defaults, args) UI.Page.init(self, defaults) end +function UI.Dialog:setParent() + UI.Window.setParent(self) + self.x = math.floor((self.parent.width - self.width) / 2) + 1 + self.y = math.floor((self.parent.height - self.height) / 2) + 1 +end + function UI.Dialog:eventHandler(event) if event.type == 'cancel' then UI:setPreviousPage() diff --git a/sys/apis/util.lua b/sys/apis/util.lua index 450df87..1a6f781 100644 --- a/sys/apis/util.lua +++ b/sys/apis/util.lua @@ -365,6 +365,10 @@ function Util.toBytes(n) return tostring(n) end +function Util.insertString(os, is, pos) + return os:sub(1, pos - 1) .. is .. os:sub(pos) +end + function Util.split(str, pattern) pattern = pattern or "(.-)\n" local t = {} diff --git a/sys/boot/multishell.boot b/sys/boot/multishell.boot index 9c8f565..d218ab2 100644 --- a/sys/boot/multishell.boot +++ b/sys/boot/multishell.boot @@ -4,8 +4,8 @@ LUA_PATH = '/sys/apis' math.randomseed(os.clock()) -_G.debug = function() end _G.Util = dofile('/sys/apis/util.lua') +_G.debug = function(...) Util.print(...) end _G.requireInjector = dofile('/sys/apis/injector.lua') os.run(Util.shallowCopy(getfenv(1)), '/sys/extensions/device.lua') @@ -21,6 +21,7 @@ local mounts = Util.readFile('config/fstab') if mounts then for _,l in ipairs(Util.split(mounts)) do if l:sub(1, 1) ~= '#' then + print('mounting ' .. l) fs.mount(unpack(Util.matches(l))) end end