1
0
mirror of https://github.com/kepler155c/opus synced 2024-12-25 16:10:26 +00:00

Forms - dialogs

This commit is contained in:
kepler155c@gmail.com 2016-12-26 22:26:43 -05:00
parent d61260ec9b
commit 5c12b20fae
14 changed files with 365 additions and 292 deletions

View File

@ -34,6 +34,7 @@ local page = UI.Page({
up = 'history_back', up = 'history_back',
down = 'history_forward', down = 'history_forward',
mouse_rightclick = 'clear_prompt', mouse_rightclick = 'clear_prompt',
[ 'control-space' ] = 'autocomplete',
}, },
}), }),
grid = UI.ScrollingGrid({ grid = UI.ScrollingGrid({
@ -72,20 +73,65 @@ function page:enable()
end end
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) function page:eventHandler(event)
if event.type == 'global' then if event.type == 'global' then
page:setPrompt('', true) self:setPrompt('', true)
self:executeStatement('getfenv(0)') self:executeStatement('getfenv(0)')
command = nil command = nil
elseif event.type == 'local' then elseif event.type == 'local' then
page:setPrompt('', true) self:setPrompt('', true)
self:executeStatement('getfenv(1)') self:executeStatement('getfenv(1)')
command = nil 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 elseif event.type == 'device' then
page:setPrompt('device', true) self:setPrompt('device', true)
self:executeStatement('device') self:executeStatement('device')
elseif event.type == 'history_back' then elseif event.type == 'history_back' then

View File

@ -121,14 +121,14 @@ function page.grid:draw()
end end
end end
function updateComputers() Event.addThread(function()
while true do while true do
page.grid:update() page.grid:update()
page.grid:draw() page.grid:draw()
page:sync() page:sync()
os.sleep(1) os.sleep(1)
end end
end end)
Event.addHandler('device_attach', function(h, deviceName) Event.addHandler('device_attach', function(h, deviceName)
if deviceName == 'wireless_modem' then if deviceName == 'wireless_modem' then
@ -149,5 +149,5 @@ if not device.wireless_modem then
end end
UI:setPage(page) UI:setPage(page)
Event.pullEvents(updateComputers) Event.pullEvents()
UI.term:reset() UI.term:reset()

View File

@ -332,76 +332,64 @@ end
local formWidth = math.max(UI.term.width - 14, 26) local formWidth = math.max(UI.term.width - 14, 26)
local editor = UI.Page { local editor = UI.Dialog {
backgroundColor = colors.white,
x = math.ceil((UI.term.width - formWidth) / 2) + 1,
y = math.ceil((UI.term.height - 11) / 2) + 1,
z = 2,
height = 11, height = 11,
width = formWidth, width = formWidth,
titleBar = UI.TitleBar {
title = 'Edit application', title = 'Edit application',
},
inset = UI.Window {
x = 2,
y = 3,
rex = -2,
rey = -3,
form = UI.Form { form = UI.Form {
textColor = colors.black, y = 2,
fields = { height = 9,
{ label = 'Title', key = 'title', width = formWidth - 11, limit = 11, display = UI.Form.D.entry, title = UI.TextEntry {
help = 'Application title' }, formLabel = 'Title', formKey = 'title', limit = 11, help = 'Application title',
{ label = 'Run', key = 'run', width = formWidth - 11, limit = 100, display = UI.Form.D.entry, required = true,
help = 'Full path to application' }, },
{ label = 'Category', key = 'category', width = formWidth - 11, limit = 11, display = UI.Form.D.entry, run = UI.TextEntry {
help = 'Category of application' }, formLabel = 'Run', formKey = 'run', limit = 100, help = 'Full path to application',
{ text = 'Icon', event = 'loadIcon', display = UI.Form.D.button, required = true,
x = 10, y = 5, textColor = colors.white, help = 'Select icon' }, },
{ text = 'Ok', event = 'accept', display = UI.Form.D.button, category = UI.TextEntry {
x = formWidth - 14, y = 7, textColor = colors.white }, formLabel = 'Category', formKey = 'category', limit = 11, help = 'Category of application',
{ text = 'Cancel', event = 'cancel', display = UI.Form.D.button, required = true,
x = formWidth - 9, y = 7, textColor = colors.white }, },
loadIcon = UI.Button {
x = 11, y = 6,
text = 'Icon', event = 'loadIcon', help = 'Select icon'
}, },
labelWidth = 8,
image = UI.NftImage { image = UI.NftImage {
y = 5, y = 6,
x = 1, x = 2,
height = 3, height = 3,
width = 8, width = 8,
}, },
}, },
},
statusBar = UI.StatusBar(), statusBar = UI.StatusBar(),
notification = UI.Notification(),
iconFile = '', iconFile = '',
} }
function editor:enable(app) function editor:enable(app)
if app then if app then
self.original = app self.form:setValues(app)
self.inset.form:setValues(Util.shallowCopy(app))
local icon local icon
if app.icon then if app.icon then
icon = parseIcon(app.icon) icon = parseIcon(app.icon)
end end
self.inset.form.image:setImage(icon) self.form.image:setImage(icon)
end end
UI.Page.enable(self) UI.Page.enable(self)
self:focusFirst() self:focusFirst()
end end
function editor.inset.form.image:draw() function editor.form.image:draw()
self:clear() self:clear()
UI.NftImage.draw(self) UI.NftImage.draw(self)
end end
function editor:updateApplications(app, original) function editor:updateApplications(app)
if original.run then for k,v in pairs(applications) do
local _,k = Util.find(applications, 'run', original.run) if v == app then
if k then
applications[k] = nil applications[k] = nil
break
end end
end end
table.insert(applications, app) table.insert(applications, app)
@ -410,7 +398,7 @@ end
function editor:eventHandler(event) function editor:eventHandler(event)
if event.type == 'cancel' then if event.type == 'form_cancel' or event.type == 'cancel' then
UI:setPreviousPage() UI:setPreviousPage()
elseif event.type == 'focus_change' then elseif event.type == 'focus_change' then
@ -438,29 +426,26 @@ function editor:eventHandler(event)
if not icon then if not icon then
error(m) error(m)
end end
self.inset.form.values.icon = iconLines self.form.values.icon = iconLines
self.inset.form.image:setImage(icon) self.form.image:setImage(icon)
self.inset.form.image:draw() self.form.image:draw()
end) end)
if not s and m then if not s and m then
local msg = m:gsub('.*: (.*)', '%1') local msg = m:gsub('.*: (.*)', '%1')
self.notification:error(msg) page.notification:error(msg)
end end
end end
end) end)
elseif event.type == 'accept' then elseif event.type == 'form_invalid' then
local values = self.inset.form.values page.notification:error(event.message)
if #values.run > 0 and #values.title > 0 and #values.category > 0 then
elseif event.type == 'form_complete' then
local values = self.form.values
UI:setPreviousPage() UI:setPreviousPage()
self:updateApplications(values, self.original) self:updateApplications(values)
page:refresh() page:refresh()
page:draw() page:draw()
else
self.notification:error('Require fields missing')
--self.statusBar:setStatus('Require fields missing')
--self.statusBar:draw()
end
else else
return UI.Page.eventHandler(self, event) return UI.Page.eventHandler(self, event)
end end

View File

@ -33,6 +33,12 @@ local clipboard = { size, internal }
local searchPattern local searchPattern
local undo = { chain = { }, pointer = 0 } local undo = { chain = { }, pointer = 0 }
if _G.__CLIPBOARD then
clipboard = _G.__CLIPBOARD
else
_G.__CLIPBOARD = clipboard
end
local color = { local color = {
textColor = '0', textColor = '0',
keywordColor = '4', keywordColor = '4',

View File

@ -452,51 +452,54 @@ function watchResources(items)
return itemList return itemList
end end
itemPage = UI.Page({ itemPage = UI.Page {
backgroundColor = colors.lightGray, backgroundColor = colors.lightGray,
titleBar = UI.TitleBar({ titleBar = UI.TitleBar {
title = 'Limit Resource', title = 'Limit Resource',
previousPage = true, previousPage = true,
event = 'form_cancel',
backgroundColor = colors.green backgroundColor = colors.green
}), },
idField = UI.Text({ idField = UI.Text {
x = 5, x = 5, y = 3, width = UI.term.width - 10,
y = 3, },
width = UI.term.width - 10 form = UI.Form {
}), x = 4, y = 4, height = 8, rex = -4,
form = UI.Form({ [1] = UI.TextEntry {
fields = { width = 7,
{ label = 'Min', key = 'low', width = 7, display = UI.Form.D.entry, backgroundColor = colors.gray,
help = 'Craft if below min' }, backgroundFocusColor = colors.gray,
{ label = 'Max', key = 'limit', width = 7, display = UI.Form.D.entry, formLabel = 'Min', formKey = 'low', help = 'Craft if below min'
validation = UI.Form.V.number, dataType = UI.Form.T.number, },
help = 'Eject if above max' }, [2] = UI.TextEntry {
{ label = 'Autocraft', key = 'auto', width = 7, display = UI.Form.D.chooser, 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', nochoice = 'No',
choices = { choices = {
{ name = 'Yes', value = 'yes' }, { name = 'Yes', value = 'yes' },
{ name = 'No', value = 'no' }, { name = 'No', value = 'no' },
}, },
help = 'Craft until out of ingredients' }, help = 'Craft until out of ingredients'
{ label = 'Ignore Dmg', key = 'ignore_dmg', width = 7, display = UI.Form.D.chooser, },
[4] = UI.Chooser {
width = 7,
formLabel = 'Ignore Dmg', formKey = 'ignore_dmg',
nochoice = 'No', nochoice = 'No',
choices = { choices = {
{ name = 'Yes', value = 'yes' }, { name = 'Yes', value = 'yes' },
{ name = 'No', value = 'no' }, { name = 'No', value = 'no' },
}, },
help = 'Ignore damage of item' }, 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 },
}, },
labelWidth = 10, },
x = 5, statusBar = UI.StatusBar { }
y = 5, }
height = 6
}),
statusBar = UI.StatusBar()
})
function itemPage:enable() function itemPage:enable()
UI.Page.enable(self) UI.Page.enable(self)
@ -504,12 +507,14 @@ function itemPage:enable()
end end
function itemPage:eventHandler(event) function itemPage:eventHandler(event)
if event.type == 'cancel' then if event.type == 'form_cancel' then
UI:setPreviousPage() UI:setPreviousPage()
elseif event.type == 'focus_change' then elseif event.type == 'focus_change' then
self.statusBar:setStatus(event.focused.help) self.statusBar:setStatus(event.focused.help)
self.statusBar:draw() self.statusBar:draw()
elseif event.type == 'accept' then
elseif event.type == 'form_complete' then
local values = self.form.values local values = self.form.values
local t = Util.readTable('resource.limits') or { } local t = Util.readTable('resource.limits') or { }
for k,v in pairs(t) do for k,v in pairs(t) do
@ -527,55 +532,52 @@ function itemPage:eventHandler(event)
table.insert(t, filtered) table.insert(t, filtered)
Util.writeTable('resource.limits', t) Util.writeTable('resource.limits', t)
UI:setPreviousPage() UI:setPreviousPage()
else else
return UI.Page.eventHandler(self, event) return UI.Page.eventHandler(self, event)
end end
return true return true
end end
listingPage = UI.Page({ listingPage = UI.Page {
menuBar = UI.MenuBar({ menuBar = UI.MenuBar {
buttons = { buttons = {
{ text = 'Learn', event = 'learn' }, { text = 'Learn', event = 'learn' },
{ text = 'Forget', event = 'forget' }, { text = 'Forget', event = 'forget' },
}, },
}), },
grid = UI.Grid({ grid = UI.Grid {
y = 2, height = UI.term.height - 2,
columns = { columns = {
{ heading = 'Name', key = 'name' , width = 22 }, { heading = 'Name', key = 'name' , width = 22 },
{ heading = 'Qty', key = 'qty' , width = 5 }, { heading = 'Qty', key = 'qty' , width = 5 },
{ heading = 'Min', key = 'low' , width = 4 }, { heading = 'Min', key = 'low' , width = 4 },
{ heading = 'Max', key = 'limit', width = 4 }, { heading = 'Max', key = 'limit', width = 4 },
}, },
y = 2,
sortColumn = 'name', sortColumn = 'name',
height = UI.term.height-2, },
}), statusBar = UI.StatusBar {
statusBar = UI.StatusBar({
backgroundColor = colors.gray, backgroundColor = colors.gray,
width = UI.term.width, width = UI.term.width,
filterText = UI.Text({ filterText = UI.Text {
x = 2, width = 6,
value = 'Filter', value = 'Filter',
x = 2, },
width = 6, filter = UI.TextEntry {
}), x = 9, width = 19,
filter = UI.TextEntry({
width = 19,
limit = 50, limit = 50,
x = 9, },
}), refresh = UI.Button {
refresh = UI.Button({ x = 31, width = 8,
text = 'Refresh', text = 'Refresh',
event = 'refresh', event = 'refresh',
x = 31, },
width = 8 },
}),
}),
accelerators = { accelerators = {
r = 'refresh', r = 'refresh',
q = 'quit', q = 'quit',
} }
}) }
function listingPage.grid:getRowTextColor(row, selected) function listingPage.grid:getRowTextColor(row, selected)
if row.is_craftable then if row.is_craftable then
@ -622,24 +624,26 @@ end
function listingPage:eventHandler(event) function listingPage:eventHandler(event)
if event.type == 'quit' then if event.type == 'quit' then
Event.exitPullEvents() Event.exitPullEvents()
elseif event.type == 'grid_select' then elseif event.type == 'grid_select' then
local selected = event.selected local selected = event.selected
itemPage.form:setValues(selected) itemPage.form:setValues(selected)
itemPage.titleBar.title = selected.name itemPage.titleBar.title = selected.name
itemPage.idField.value = selected.id itemPage.idField.value = selected.id
UI:setPage('item') UI:setPage('item')
elseif event.type == 'refresh' then elseif event.type == 'refresh' then
self:refresh() self:refresh()
self.grid:draw() self.grid:draw()
elseif event.type == 'learn' then elseif event.type == 'learn' then
if not duckAntenna then if not duckAntenna then
self.statusBar:timedStatus('Missing peripherals', 3) self.statusBar:timedStatus('Missing peripherals', 3)
else else
UI:getPage('craft').form:setValues( { ignore_dmg = 'no' } )
UI:setPage('craft') UI:setPage('craft')
end end
elseif event.type == 'forget' then
elseif event.type == 'forget' then
local item = self.grid:getSelected() local item = self.grid:getSelected()
if item then if item then
local recipes = Util.readTable('recipes') or { } local recipes = Util.readTable('recipes') or { }
@ -673,6 +677,7 @@ function listingPage:eventHandler(event)
self:applyFilter() self:applyFilter()
self.grid:draw() self.grid:draw()
self.statusBar.filter:focus() self.statusBar.filter:focus()
else else
UI.Page.eventHandler(self, event) UI.Page.eventHandler(self, event)
end end
@ -747,7 +752,7 @@ local function filter(t, filter)
end end
end end
local function learnRecipe(page, ignore_dmg) local function learnRecipe(page)
local t = Util.readTable('recipes') or { } local t = Util.readTable('recipes') or { }
local recipe = { } local recipe = { }
local ingredients = duckAntenna.getAllStacks(false) -- getTurtleInventory() local ingredients = duckAntenna.getAllStacks(false) -- getTurtleInventory()
@ -773,7 +778,7 @@ local function learnRecipe(page, ignore_dmg)
end end
end end
recipe.ingredients = ingredients recipe.ingredients = ingredients
recipe.ignore_dmg = 'no' -- ignore_dmg recipe.ignore_dmg = 'no'
t[key] = recipe t[key] = recipe
@ -794,69 +799,52 @@ local function learnRecipe(page, ignore_dmg)
end end
end end
craftPage = UI.Page({ craftPage = UI.Dialog {
x = 4, height = 7, width = UI.term.width - 6,
y = math.floor((UI.term.height - 8) / 2) + 1,
height = 7,
width = UI.term.width - 6,
backgroundColor = colors.lightGray, backgroundColor = colors.lightGray,
titleBar = UI.TitleBar({ titleBar = UI.TitleBar {
title = 'Learn Recipe', title = 'Learn Recipe',
previousPage = true, previousPage = true,
}), },
idField = UI.Text({ idField = UI.Text {
x = 5, x = 5,
y = 3, y = 3,
width = UI.term.width - 10, width = UI.term.width - 10,
value = 'Place recipe in turtle' 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' }, accept = UI.Button {
--]] rx = -13, ry = -2,
{ text = 'Accept', event = 'accept', display = UI.Form.D.button, text = 'Ok', event = 'accept',
x = 1, y = 1, width = 10 },
{ text = 'Cancel', event = 'cancel', display = UI.Form.D.button,
x = 16, y = 1, width = 10 },
}, },
labelWidth = 13, cancel = UI.Button {
x = 5, rx = -8, ry = -2,
y = 5, text = 'Cancel', event = 'cancel'
height = 2 },
}), statusBar = UI.StatusBar {
statusBar = UI.StatusBar({
status = 'Crafting paused' status = 'Crafting paused'
}) }
}) }
function craftPage:enable() function craftPage:enable()
craftingPaused = true craftingPaused = true
self:focusFirst() self:focusFirst()
UI.Page.enable(self) UI.Dialog.enable(self)
end end
function craftPage:disable() function craftPage:disable()
craftingPaused = false craftingPaused = false
UI.Dialog.disable(self)
end end
function craftPage:eventHandler(event) function craftPage:eventHandler(event)
if event.type == 'cancel' then if event.type == 'cancel' then
UI:setPreviousPage() UI:setPreviousPage()
elseif event.type == 'accept' then elseif event.type == 'accept' then
local values = self.form.values if learnRecipe(self) then
if learnRecipe(self, values.ignore_dmg) then
UI:setPreviousPage() UI:setPreviousPage()
end end
else else
return UI.Page.eventHandler(self, event) return UI.Dialog.eventHandler(self, event)
end end
return true return true
end end

View File

@ -1,4 +1,5 @@
local Util = require('util') local Util = require('util')
local Process = require('process')
local Event = { local Event = {
uid = 1, -- unique id for handlers uid = 1, -- unique id for handlers
@ -123,8 +124,9 @@ local function _pullEvents()
--exitPullEvents = false --exitPullEvents = false
while true do while true do
local e = Event.pullEvent() local e = { Process:pullEvent() }
if exitPullEvents or e == 'terminate' then Event.processEvent(e)
if exitPullEvents or e[1] == 'terminate' then
break break
end end
end end
@ -137,13 +139,18 @@ function Event.sleep(t)
until event == 'timer' and id == timerId until event == 'timer' and id == timerId
end end
function Event.addThread(fn)
return Process:addThread(fn)
end
function Event.pullEvents(...) function Event.pullEvents(...)
local routines = { ... } local routines = { ... }
if #routines > 0 then if #routines > 0 then
parallel.waitForAny(_pullEvents, ...) for _, routine in ipairs(routines) do
else Process:addThread(routine)
_pullEvents()
end end
end
_pullEvents()
end end
function Event.exitPullEvents() function Event.exitPullEvents()

View File

@ -15,7 +15,7 @@ return function(args)
args = args or { } args = args or { }
local selectFile = UI.Page { local selectFile = UI.Dialog {
x = args.x or 3, x = args.x or 3,
y = args.y or 2, y = args.y or 2,
z = args.z or 2, z = args.z or 2,
@ -24,11 +24,7 @@ return function(args)
height = args.height, height = args.height,
width = args.width, width = args.width,
backgroundColor = colors.brown, backgroundColor = colors.brown,
titleBar = UI.TitleBar {
title = 'Select file', title = 'Select file',
previousPage = true,
event = 'cancel',
},
grid = UI.ScrollingGrid { grid = UI.ScrollingGrid {
x = 2, x = 2,
y = 2, y = 2,

View File

@ -46,13 +46,13 @@ function urlfs.open(node, fn, fl)
synchronized(node.url, function() synchronized(node.url, function()
c = Util.download(node.url) c = Util.download(node.url)
end) end)
if c and #c > 0 then if c then
node.cache = c node.cache = c
node.size = #c node.size = #c
end end
end end
if not c or #c == 0 then if not c then
return return
end end

View File

@ -1,4 +1,4 @@
local json = require('craigmj.json4lua.master.json.json') local json = require('json')
local Util = require('util') local Util = require('util')
local TREE_URL = 'https://api.github.com/repos/%s/%s/git/trees/%s?recursive=1' local TREE_URL = 'https://api.github.com/repos/%s/%s/git/trees/%s?recursive=1'

View File

@ -89,7 +89,6 @@ local function gitSearcher(modname, env, shell)
local _, count = fname:gsub("/", "") local _, count = fname:gsub("/", "")
if count >= 3 then if count >= 3 then
local url = GIT_URL .. '/' .. fname local url = GIT_URL .. '/' .. fname
debug(url)
local c = loadUrl(url) local c = loadUrl(url)
if c then if c then
return load(c, modname, nil, env) return load(c, modname, nil, env)
@ -153,7 +152,7 @@ local function requireWrapper(env)
return module return module
end end
if msg then if msg then
error(msg) error(msg, 2)
end end
end end
error('Unable to find module ' .. modname) error('Unable to find module ' .. modname)

View File

@ -27,6 +27,11 @@ function Process:threadEvent(...)
end end
end end
function Process:addThread(fn, ...)
return self:newThread(nil, fn, ...)
end
-- deprecated
function Process:newThread(name, fn, ...) function Process:newThread(name, fn, ...)
self.uid = self.uid + 1 self.uid = self.uid + 1
@ -45,7 +50,7 @@ function Process:newThread(name, fn, ...)
local s, m = pcall(function() fn(unpack(args)) end) local s, m = pcall(function() fn(unpack(args)) end)
if not s and m then if not s and m then
if m == 'Terminated' then if m == 'Terminated' then
printError(thread.name .. ' terminated') --printError(thread.name .. ' terminated')
else else
printError(m) printError(m)
end end

View File

@ -64,6 +64,12 @@ local function getPosition(element)
return x, y return x, y
end 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 --]]-- --[[-- Top Level Manager --]]--
local Manager = class() local Manager = class()
function Manager:init(args) function Manager:init(args)
@ -430,6 +436,14 @@ function Manager:getDefaults(element, args)
return defaults return defaults
end end
function Manager:pullEvents(...)
Event.pullEvents(...)
end
function Manager:exitPullEvents()
Event.exitPullEvents()
end
-- inconsistent -- inconsistent
function Manager.setProperties(obj, args) function Manager.setProperties(obj, args)
if args then if args then
@ -743,6 +757,7 @@ function UI.Window:print(text, bg, fg, indent)
end end
function UI.Window:setFocus(focus) function UI.Window:setFocus(focus)
assertElement(focus, 'UI.Window:setFocus: Invalid element passed')
if self.parent then if self.parent then
self.parent:setFocus(focus) self.parent:setFocus(focus)
end end
@ -1378,6 +1393,8 @@ function UI.Page:focusNext()
end end
function UI.Page:setFocus(child) function UI.Page:setFocus(child)
assertElement(child, 'UI.Page:setFocus: Invalid element passed')
if not child.focus then if not child.focus then
return return
end end
@ -1443,16 +1460,12 @@ function UI.Grid:init(args)
h.heading = '' h.heading = ''
end end
end end
self:update()
end
function UI.Grid:enable()
UI.Window.enable(self)
end end
function UI.Grid:setParent() function UI.Grid:setParent()
UI.Window.setParent(self) UI.Window.setParent(self)
self:adjustWidth() self:update()
if not self.pageSize then if not self.pageSize then
if self.disableHeader then if self.disableHeader then
self.pageSize = self.height self.pageSize = self.height
@ -1529,7 +1542,7 @@ end
function UI.Grid:getSelected() function UI.Grid:getSelected()
if self.sorted then if self.sorted then
return self.values[self.sorted[self.index]] return self.values[self.sorted[self.index]], self.sorted[self.index]
end end
end end
@ -1581,6 +1594,8 @@ function UI.Grid:update()
return order(self.values[a], self.values[b]) return order(self.values[a], self.values[b])
end) end)
end end
self:adjustWidth()
end end
function UI.Grid:drawHeadings() function UI.Grid:drawHeadings()
@ -2376,7 +2391,6 @@ UI.Notification.defaults = {
function UI.Notification:init(args) function UI.Notification:init(args)
local defaults = UI:getDefaults(UI.Notification, args) local defaults = UI:getDefaults(UI.Notification, args)
UI.Window.init(self, defaults) UI.Window.init(self, defaults)
Util.print(self)
end end
function UI.Notification:draw() function UI.Notification:draw()
@ -2401,6 +2415,15 @@ function UI.Notification:success(value, timeout)
self:display(value, timeout) self:display(value, timeout)
end 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) function UI.Notification:display(value, timeout)
self.enabled = true self.enabled = true
local lines = Util.wordWrap(value, self.width - 2) local lines = Util.wordWrap(value, self.width - 2)
@ -2409,6 +2432,8 @@ function UI.Notification:display(value, timeout)
if self.canvas then if self.canvas then
self.canvas:removeLayer() self.canvas:removeLayer()
end 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.canvas = UI.term.canvas:addLayer(self, self.backgroundColor, self.textColor or colors.white)
self:addTransition(UI.TransitionExpandUp { self:addTransition(UI.TransitionExpandUp {
x = self.x, x = self.x,
@ -2425,8 +2450,7 @@ function UI.Notification:display(value, timeout)
end end
Event.addNamedTimer('notificationTimer', timeout or 3, false, function() Event.addNamedTimer('notificationTimer', timeout or 3, false, function()
self.enabled = false self:cancel()
self.canvas:removeLayer()
self:sync() self:sync()
end) end)
end end
@ -3012,134 +3036,146 @@ UI.Form = class(UI.Window)
UI.Form.defaults = { UI.Form.defaults = {
UIElement = 'Form', UIElement = 'Form',
values = { }, values = { },
fields = { }, margin = 2,
labelWidth = 20, event = 'form_complete',
accept = function() end,
cancel = function() end,
} }
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) function UI.Form:init(args)
local defaults = UI:getDefaults(UI.Form, args) local defaults = UI:getDefaults(UI.Form, args)
UI.Window.init(self, defaults) UI.Window.init(self, defaults)
self:createForm()
end
self:createFields() function UI.Form:reset()
self:initChildren() for _,child in pairs(self.children) do
if child.reset then
child:reset()
end
end
end end
function UI.Form:setValues(values) function UI.Form:setValues(values)
self:reset()
self.values = values self.values = values
for k,child in pairs(self.children) do for k,child in pairs(self.children) do
if child.key then if child.formKey then
child.value = self.values[child.key] child.value = self.values[child.formKey] or ''
if not child.value then
child.value = ''
end
end end
end end
end end
function UI.Form:createFields() function UI.Form:createForm()
self.children = self.children or { }
if not self.children then if not self.labelWidth then
self.children = { } 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
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 end
local value
if field.key then
value = self.values[field.key]
end end
if not value then
value = ''
end end
value = tostring(value)
local width = #value local y = self.margin
if field.limit then for _, child in pairs(self) do
width = field.limit + 2 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 end
if width == 0 then child.value = self.values[child.formKey] or ''
width = nil
end end
local fieldProperties = { if child.formLabel then
x = self.labelWidth + 2, table.insert(self.children, UI.Text {
y = k, x = self.margin,
width = width, y = y,
value = value, textColor = colors.black,
} width = #child.formLabel,
UI.setProperties(fieldProperties, field) value = child.formLabel,
table.insert(self.children, field.display(fieldProperties)) })
end 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: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 end
function UI.Form:eventHandler(event) function UI.Form:eventHandler(event)
if event.type == 'accept' then if event.type == 'form_ok' then
for _,child in pairs(self.children) do for _,child in pairs(self.children) do
if child.key then if child.formKey then
self.values[child.key] = child.value 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 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 end
--[[-- Dialog --]]-- --[[-- Dialog --]]--
UI.Dialog = class(UI.Page) UI.Dialog = class(UI.Page)
UI.Dialog.defaults = { UI.Dialog.defaults = {
UIElement = 'Dialog',
x = 7, x = 7,
y = 4, y = 4,
z = 2,
height = 7, height = 7,
backgroundColor = colors.lightBlue, backgroundColor = colors.white,
} }
function UI.Dialog:init(args) function UI.Dialog:init(args)
local defaults = UI:getDefaults(UI.Dialog) local defaults = UI:getDefaults(UI.Dialog, args)
UI.setProperties(defaults, { UI.setProperties(defaults, {
width = UI.term.width-11, width = UI.term.width-11,
titleBar = UI.TitleBar({ previousPage = true }), titleBar = UI.TitleBar({ previousPage = true, title = defaults.title }),
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(),
}) })
UI.setProperties(defaults, args) UI.setProperties(defaults, args)
UI.Page.init(self, defaults) UI.Page.init(self, defaults)
end 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) function UI.Dialog:eventHandler(event)
if event.type == 'cancel' then if event.type == 'cancel' then
UI:setPreviousPage() UI:setPreviousPage()

View File

@ -365,6 +365,10 @@ function Util.toBytes(n)
return tostring(n) return tostring(n)
end end
function Util.insertString(os, is, pos)
return os:sub(1, pos - 1) .. is .. os:sub(pos)
end
function Util.split(str, pattern) function Util.split(str, pattern)
pattern = pattern or "(.-)\n" pattern = pattern or "(.-)\n"
local t = {} local t = {}

View File

@ -4,8 +4,8 @@ LUA_PATH = '/sys/apis'
math.randomseed(os.clock()) math.randomseed(os.clock())
_G.debug = function() end
_G.Util = dofile('/sys/apis/util.lua') _G.Util = dofile('/sys/apis/util.lua')
_G.debug = function(...) Util.print(...) end
_G.requireInjector = dofile('/sys/apis/injector.lua') _G.requireInjector = dofile('/sys/apis/injector.lua')
os.run(Util.shallowCopy(getfenv(1)), '/sys/extensions/device.lua') os.run(Util.shallowCopy(getfenv(1)), '/sys/extensions/device.lua')
@ -21,6 +21,7 @@ local mounts = Util.readFile('config/fstab')
if mounts then if mounts then
for _,l in ipairs(Util.split(mounts)) do for _,l in ipairs(Util.split(mounts)) do
if l:sub(1, 1) ~= '#' then if l:sub(1, 1) ~= '#' then
print('mounting ' .. l)
fs.mount(unpack(Util.matches(l))) fs.mount(unpack(Util.matches(l)))
end end
end end