diff --git a/sys/apis/class.lua b/sys/apis/class.lua index 4b5ed06..0466bc8 100644 --- a/sys/apis/class.lua +++ b/sys/apis/class.lua @@ -1,8 +1,6 @@ -- From http://lua-users.org/wiki/SimpleLuaClasses -- (with some modifications) -local uid = 1 - -- class.lua -- Compatible with Lua 5.1 (not 5.0). return function(base) @@ -21,8 +19,7 @@ return function(base) -- expose a constructor which can be called by () setmetatable(c, { __call = function(class_tbl, ...) - local obj = { __uid = uid } - uid = uid + 1 + local obj = { } setmetatable(obj,c) if class_tbl.init then class_tbl.init(obj, ...) diff --git a/sys/apis/ui.lua b/sys/apis/ui.lua index 2f1ee9f..389c1a0 100644 --- a/sys/apis/ui.lua +++ b/sys/apis/ui.lua @@ -4,10 +4,16 @@ local Event = require('event') local Transition = require('ui.transition') local Util = require('util') -local _rep = string.rep -local _sub = string.sub -local colors = _G.colors -local keys = _G.keys +local _rep = string.rep +local _sub = string.sub +local clipboard = _G.clipboard +local colors = _G.colors +local device = _G.device +local fs = _G.fs +local keys = _G.keys +local multishell = _ENV.multishell +local os = _G.os +local term = _G.term --[[ Using the shorthand window definition, elements are created from @@ -35,12 +41,6 @@ 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() @@ -232,11 +232,10 @@ function Manager:configure(appName, ...) if not dev then error('Invalid display device') end - local device = self.Device({ + self:setDefaultDevice(self.Device({ device = dev, textScale = defaults.device.textScale, - }) - self:setDefaultDevice(device) + })) end if defaults.theme then @@ -273,7 +272,6 @@ function Manager:emitEvent(event) end function Manager:inputEvent(parent, event) - while parent do if parent.accelerators then local acc = parent.accelerators[event.key] @@ -376,9 +374,9 @@ function Manager:click(button, x, y) end end -function Manager:setDefaultDevice(device) - self.defaultDevice = device - self.term = device +function Manager:setDefaultDevice(dev) + self.defaultDevice = dev + self.term = dev end function Manager:addPage(name, page) @@ -490,8 +488,8 @@ function Manager:setProperties(obj, args) end end -function Manager:dump(el) - if el then +function Manager:dump(inEl) + if inEl then local function clean(el) local o = el el = Util.shallowCopy(el) @@ -511,7 +509,7 @@ function Manager:dump(el) return el end - return clean(el) + return clean(inEl) end end @@ -519,6 +517,7 @@ local UI = Manager() --[[-- Basic drawable area --]]-- UI.Window = class() +UI.Window.uid = 1 UI.Window.defaults = { UIElement = 'Window', x = 1, @@ -528,12 +527,46 @@ UI.Window.defaults = { offy = 0, cursorX = 1, cursorY = 1, - -- accelerators = { }, } function UI.Window:init(args) - local defaults = UI:getDefaults(UI.Window, args) + -- merge defaults for all subclasses + local defaults = args + local m = self + repeat + defaults = UI:getDefaults(m, defaults) + m = m._base + until not m UI:setProperties(self, defaults) + + -- each element has a unique ID + self.uid = UI.Window.uid + UI.Window.uid = UI.Window.uid + 1 + + -- at this time, the object has all the properties set + + -- postInit is a special constructor. the element does not need to implement + -- the method. But we need to guarantee that each subclass which has this + -- method is called. + m = self + local lpi + repeat + if m.postInit and m.postInit ~= lpi then +--debug('calling ' .. m.defaults.UIElement) +--debug(rawget(m, 'postInit')) + m.postInit(self) + lpi = m.postInit +-- else +--debug('skipping ' .. m.defaults.UIElement) +--debug(rawget(m, 'postInit')) + end + m = m._base + until not m +end + +function UI.Window:postInit() if self.parent then + -- this will cascade down the whole tree of elements starting at the + -- top level window (which has a device as a parent) self:setParent() end end @@ -749,7 +782,7 @@ function UI.Window:print(text, bg, fg) if #result > 1 and result[2] > cx then return _sub(line, cx, result[2] + 1) elseif #result > 0 and result[1] == cx then - result = { line:find("(%w+)", result[2] + 1) } + result = { line:find("(%w+)", result[2]) } if #result > 0 then return _sub(line, cx, result[1] + 1) end @@ -811,10 +844,10 @@ function UI.Window:print(text, bg, fg) if self.cursorX + #word > width then self.cursorX = marginLeft + 1 self.cursorY = self.cursorY + 1 - w = word:gsub(' ', '') + w = word:gsub('^ ', '') end self:write(self.cursorX, self.cursorY, w, bg, fg) - self.cursorX = self.cursorX + #word + self.cursorX = self.cursorX + #w lx = lx + #word end end @@ -829,7 +862,6 @@ function UI.Window:print(text, bg, fg) end function UI.Window:setFocus(focus) - assertElement(focus, 'UI.Window:setFocus: Invalid element passed') if self.parent then self.parent:setFocus(focus) end @@ -864,7 +896,6 @@ function UI.Window:getFocusables() end function UI.Window:focusFirst() - local focusables = self:getFocusables() local focused = focusables[1] if focused then @@ -904,7 +935,6 @@ end function UI.Window:addLayer(bg, fg) local canvas = self:getCanvas() - return canvas:addLayer(self, bg, fg) end @@ -947,23 +977,19 @@ UI.Device.defaults = { textScale = 1, effectsEnabled = true, } -function UI.Device:init(args) - local defaults = UI:getDefaults(UI.Device) - defaults.device = term.current() - UI:setProperties(defaults, args) +function UI.Device:postInit() + self.device = self.device or term.current() - if defaults.deviceType then - defaults.device = device[defaults.deviceType] + if self.deviceType then + self.device = device[self.deviceType] end - if not defaults.device.setTextScale then - defaults.device.setTextScale = function() end + if not self.device.setTextScale then + self.device.setTextScale = function() end end - defaults.device.setTextScale(defaults.textScale) - defaults.width, defaults.height = defaults.device.getSize() - - UI.Window.init(self, defaults) + self.device.setTextScale(self.textScale) + self.width, self.height = self.device.getSize() self.isColor = self.device.isColor() @@ -979,7 +1005,6 @@ function UI.Device:resize() self.lines = { } self.canvas:resize(self.width, self.height) self.canvas:clear(self.backgroundColor, self.textColor) - --UI.Window.resize(self) end function UI.Device:setCursorPos(x, y) @@ -1029,7 +1054,6 @@ function UI.Device:addTransition(effect, args) end function UI.Device:runTransitions(transitions, canvas) - for _,t in ipairs(transitions) do canvas:punch(t.args) -- punch out the effect areas end @@ -1051,7 +1075,6 @@ function UI.Device:runTransitions(transitions, canvas) end function UI.Device:sync() - local transitions if self.transitions and self.effectsEnabled then transitions = self.transitions @@ -1146,14 +1169,14 @@ UI.Page.defaults = { backgroundColor = colors.cyan, textColor = colors.white, } -function UI.Page:init(args) - local defaults = UI:getDefaults(UI.Page) - defaults.parent = UI.defaultDevice - UI:setProperties(defaults, args) - UI.Window.init(self, defaults) +function UI.Page:postInit() + self.parent = self.parent or UI.defaultDevice +end +function UI.Page:setParent() + UI.Window.setParent(self) if self.z then - self.canvas = self.parent.canvas:addLayer(self, self.backgroundColor, self.textColor) + self.canvas = self:addLayer(self.backgroundColor, self.textColor) else self.canvas = self.parent.canvas end @@ -1179,7 +1202,6 @@ function UI.Page:getFocused() end function UI.Page:focusPrevious() - local function getPreviousFocus(focused) local focusables = self:getFocusables() for k, v in ipairs(focusables) do @@ -1199,7 +1221,6 @@ function UI.Page:focusPrevious() end function UI.Page:focusNext() - local function getNextFocus(focused) local focusables = self:getFocusables() for k, v in ipairs(focusables) do @@ -1219,8 +1240,6 @@ 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 @@ -1287,9 +1306,8 @@ UI.Grid.defaults = { [ 'control-f' ] = 'scroll_pageDown', }, } -function UI.Grid:init(args) - local defaults = UI:getDefaults(UI.Grid, args) - UI.Window.init(self, defaults) +function UI.Grid:setParent() + UI.Window.setParent(self) for _,c in pairs(self.columns) do c.cw = c.width @@ -1297,10 +1315,7 @@ function UI.Grid:init(args) c.heading = '' end end -end -function UI.Grid:setParent() - UI.Window.setParent(self) self:update() if not self.pageSize then @@ -1324,7 +1339,6 @@ function UI.Grid:resize() end function UI.Grid:adjustWidth() - local t = { } -- cols without width local w = self.width - #self.columns - 1 - self.marginRight -- width remaining @@ -1449,7 +1463,6 @@ end -- Something about the displayed table has changed -- resort the table function UI.Grid:update() - local function sort(a, b) if not a[self.sortColumn] then return false @@ -1618,7 +1631,6 @@ function UI.Grid:setPage(pageNo) end function UI.Grid:eventHandler(event) - if event.type == 'mouse_click' or event.type == 'mouse_doubleclick' then if not self.disableHeader then if event.y == 1 then @@ -1686,8 +1698,7 @@ UI.ScrollingGrid.defaults = { scrollOffset = 0, marginRight = 1, } -function UI.ScrollingGrid:init(args) - UI.Grid.init(self, UI:getDefaults(UI.ScrollingGrid, args)) +function UI.ScrollingGrid:postInit() self.scrollBar = UI.ScrollBar() end @@ -1743,12 +1754,9 @@ UI.Menu.defaults = { disableHeader = true, columns = { { heading = 'Prompt', key = 'prompt', width = 20 } }, } -function UI.Menu:init(args) - local defaults = UI:getDefaults(UI.Menu) - defaults.values = args['menuItems'] - UI:setProperties(defaults, args) - UI.Grid.init(self, defaults) - self.pageSize = #args.menuItems +function UI.Menu:postInit() + self.values = self.menuItems + self.pageSize = #self.menuItems end function UI.Menu:setParent() @@ -1813,11 +1821,6 @@ UI.Viewport.defaults = { [ 'control-f' ] = 'scroll_pageDown', }, } -function UI.Viewport:init(args) - local defaults = UI:getDefaults(UI.Viewport, args) - UI.Window.init(self, defaults) -end - function UI.Viewport:setScrollPosition(offset) local oldOffset = self.offy self.offy = math.max(offset, 0) @@ -1849,7 +1852,6 @@ function UI.Viewport:getViewArea() end function UI.Viewport:eventHandler(event) - if event.type == 'scroll_down' then self:setScrollPosition(self.offy + 1) elseif event.type == 'scroll_up' then @@ -1868,36 +1870,6 @@ function UI.Viewport:eventHandler(event) return true end ---[[-- ScrollingText --]]-- -UI.ScrollingText = class(UI.Window) -UI.ScrollingText.defaults = { - UIElement = 'ScrollingText', - backgroundColor = colors.black, - buffer = { }, -} -function UI.ScrollingText:init(args) - local defaults = UI:getDefaults(UI.ScrollingText, args) - UI.Window.init(self, defaults) -end - -function UI.ScrollingText:appendLine(text) - if #self.buffer+1 >= self.height then - table.remove(self.buffer, 1) - end - table.insert(self.buffer, text) -end - -function UI.ScrollingText:clear() - self.buffer = { } - UI.Window.clear(self) -end - -function UI.ScrollingText:draw() - for k,text in ipairs(self.buffer) do - self:write(1, k, Util.widthify(text, self.width), self.backgroundColor) - end -end - --[[-- TitleBar --]]-- UI.TitleBar = class(UI.Window) UI.TitleBar.defaults = { @@ -1909,11 +1881,6 @@ UI.TitleBar.defaults = { frameChar = '-', closeInd = '*', } -function UI.TitleBar:init(args) - local defaults = UI:getDefaults(UI.TitleBar, args) - UI.Window.init(self, defaults) -end - function UI.TitleBar:draw() local sb = SB:new(self.width) sb:fill(2, self.frameChar, sb.width - 3) @@ -1960,11 +1927,6 @@ UI.Button.defaults = { mouse_click = 'button_activate', } } -function UI.Button:init(args) - local defaults = UI:getDefaults(UI.Button, args) - UI.Window.init(self, defaults) -end - function UI.Button:setParent() if not self.width and not self.ex then self.width = #self.text + 2 @@ -2017,10 +1979,6 @@ UI.MenuItem.defaults = { backgroundFocusColor = colors.lightGray, } -function UI.MenuItem:init(args) - UI.Button.init(self, UI:getDefaults(UI.MenuItem, args)) -end - --[[-- MenuBar --]]-- UI.MenuBar = class(UI.Window) UI.MenuBar.defaults = { @@ -2036,25 +1994,8 @@ UI.MenuBar.defaults = { } UI.MenuBar.spacer = { spacer = true, text = 'spacer', inactive = true } -function UI.MenuBar:init(args) - local defaults = UI:getDefaults(UI.MenuBar, args) - UI.Window.init(self, defaults) - - if not self.children then - self.children = { } - end - +function UI.MenuBar:postInit() self:addButtons(self.buttons) - if self.showBackButton then -- need to remove - table.insert(self.children, UI.MenuItem({ - x = UI.term.width - 2, - width = 3, - backgroundColor = self.backgroundColor, - textColor = self.textColor, - text = '^-', - event = 'back', - })) - end end function UI.MenuBar:addButtons(buttons) @@ -2126,11 +2067,6 @@ UI.DropMenuItem.defaults = { textInactiveColor = colors.lightGray, backgroundFocusColor = colors.lightGray, } - -function UI.DropMenuItem:init(args) - UI.Button.init(self, UI:getDefaults(UI.DropMenuItem, args)) -end - function UI.DropMenuItem:eventHandler(event) if event.type == 'button_activate' then self.parent:hide() @@ -2145,11 +2081,6 @@ UI.DropMenu.defaults = { backgroundColor = colors.white, buttonClass = 'DropMenuItem', } -function UI.DropMenu:init(args) - local defaults = UI:getDefaults(UI.DropMenu, args) - UI.MenuBar.init(self, defaults) -end - function UI.DropMenu:setParent() UI.MenuBar.setParent(self) @@ -2180,7 +2111,6 @@ function UI.DropMenu:enable() end function UI.DropMenu:show(x, y) - self.x, self.y = x, y self.canvas:move(x, y) self.canvas:setVisible(true) @@ -2226,14 +2156,10 @@ UI.TabBar.defaults = { selectedBackgroundColor = colors.cyan, focusBackgroundColor = colors.green, } -function UI.TabBar:init(args) - local defaults = UI:getDefaults(UI.TabBar, args) - UI.MenuBar.init(self, defaults) -end - function UI.TabBar:selectTab(text) local selected, lastSelected - for k,child in pairs(self.children) do + + for k,child in pairs(self:getFocusables()) do if child.selected then lastSelected = k end @@ -2258,23 +2184,19 @@ UI.Tabs = class(UI.Window) UI.Tabs.defaults = { UIElement = 'Tabs', } -function UI.Tabs:init(args) - local defaults = UI:getDefaults(UI.Tabs, args) - UI.Window.init(self, defaults) - +function UI.Tabs:postInit() self:add(self) end function UI.Tabs:add(children) - local buttons = { } for _,child in pairs(children) do - if type(child) == 'table' and child.UIElement then + if type(child) == 'table' and child.UIElement and child.tabTitle then child.y = 2 table.insert(buttons, { - text = child.tabTitle or '', + text = child.tabTitle, event = 'tab_select', - tabUid = child.__uid, + tabUid = child.uid, }) end end @@ -2294,32 +2216,35 @@ end function UI.Tabs:enable() self.enabled = true - - local _, menuItem = Util.first(self.tabBar.children, function(a, b) return a.x < b.x end) - if menuItem then - self:activateTab(Util.find(self.children, '__uid', menuItem.tabUid)) - end self.tabBar:enable() + + -- focus first tab + local menuItem = self.tabBar:getFocusables()[1] + if menuItem then + self:activateTab(menuItem.tabUid) + end end -function UI.Tabs:activateTab(tab) - for _,child in ipairs(self.children) do - if child ~= self.tabBar then - child:disable() +function UI.Tabs:activateTab(uid) + local tab = Util.find(self.children, 'uid', uid) + if tab then + for _,child in pairs(self.children) do + if child.uid ~= uid and child.tabTitle then + child:disable() + end + end + if not tab.enabled then + self.tabBar:selectTab(tab.tabTitle) + tab:enable() + tab:draw() + self:emit({ type = 'tab_activate', activated = tab, element = self }) end end - self.tabBar:selectTab(tab.tabTitle) - tab:enable() - tab:draw() - self:emit({ type = 'tab_activate', activated = tab, element = self }) end function UI.Tabs:eventHandler(event) if event.type == 'tab_select' then - local child = Util.find(self.children, '__uid', event.button.tabUid) - if child then - self:activateTab(child) - end + self:activateTab(event.button.tabUid) elseif event.type == 'tab_change' then for _,tab in ipairs(self.children) do if tab ~= self.tabBar then @@ -2340,11 +2265,6 @@ UI.WindowScroller.defaults = { UIElement = 'WindowScroller', children = { }, } -function UI.WindowScroller:init(args) - local defaults = UI:getDefaults(UI.WindowScroller, args) - UI.Window.init(self, defaults) -end - function UI.WindowScroller:enable() self.enabled = true if #self.children > 0 then @@ -2385,11 +2305,6 @@ UI.Notification.defaults = { backgroundColor = colors.gray, height = 3, } -function UI.Notification:init(args) - local defaults = UI:getDefaults(UI.Notification, args) - UI.Window.init(self, defaults) -end - function UI.Notification:draw() end @@ -2430,8 +2345,7 @@ function UI.Notification:display(value, timeout) 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.canvas = self:addLayer(self.backgroundColor, self.textColor) self:addTransition('expandUp', { ticks = self.height }) self.canvas:setVisible(true) self:clear() @@ -2461,12 +2375,6 @@ UI.Throttle.defaults = { ' //) (O ). @ \\-d ) (@ ' } } - -function UI.Throttle:init(args) - local defaults = UI:getDefaults(UI.Throttle, args) - UI.Window.init(self, defaults) -end - function UI.Throttle:setParent() self.x = math.ceil((self.parent.width - self.width) / 2) self.y = math.ceil((self.parent.height - self.height) / 2) @@ -2495,7 +2403,7 @@ function UI.Throttle:update() self.c = os.clock() self.enabled = true if not self.canvas then - self.canvas = UI.term.canvas:addLayer(self, self.backgroundColor, colors.cyan) + self.canvas = self:addLayer(self.backgroundColor, colors.cyan) self.canvas:setVisible(true) self:clear(colors.cyan) end @@ -2520,11 +2428,6 @@ UI.StatusBar.defaults = { height = 1, ey = -1, } -function UI.StatusBar:init(args) - local defaults = UI:getDefaults(UI.StatusBar, args) - UI.Window.init(self, defaults) -end - function UI.StatusBar:adjustWidth() -- Can only have 1 adjustable width if self.columns then @@ -2624,11 +2527,6 @@ UI.ProgressBar.defaults = { height = 1, value = 0, } -function UI.ProgressBar:init(args) - local defaults = UI:getDefaults(UI.ProgressBar, args) - UI.Window.init(self, defaults) -end - function UI.ProgressBar:draw() self:clear() local width = math.ceil(self.value / 100 * self.width) @@ -2644,11 +2542,6 @@ UI.VerticalMeter.defaults = { width = 1, value = 0, } -function UI.VerticalMeter:init(args) - local defaults = UI:getDefaults(UI.VerticalMeter, args) - UI.Window.init(self, defaults) -end - function UI.VerticalMeter:draw() local height = self.height - math.ceil(self.value / 100 * self.height) self:clear() @@ -2672,9 +2565,7 @@ UI.TextEntry.defaults = { [ 'control-c' ] = 'copy', } } -function UI.TextEntry:init(args) - local defaults = UI:getDefaults(UI.TextEntry, args) - UI.Window.init(self, defaults) +function UI.TextEntry:postInit() self.value = tostring(self.value) end @@ -2721,6 +2612,9 @@ function UI.TextEntry:draw() if self.scroll and self.scroll > 0 then text = text:sub(1 + self.scroll) end + if self.mask then + text = _rep('*', #text) + end else tc = colors.gray text = self.shadowText @@ -2844,15 +2738,9 @@ UI.Chooser.defaults = { UIElement = 'Chooser', choices = { }, nochoice = 'Select', - --backgroundColor = colors.lightGray, backgroundFocusColor = colors.lightGray, height = 1, } -function UI.Chooser:init(args) - local defaults = UI:getDefaults(UI.Chooser, args) - UI.Window.init(self, defaults) -end - function UI.Chooser:setParent() if not self.width and not self.ex then self.width = 1 @@ -2926,11 +2814,6 @@ UI.Text.defaults = { value = '', height = 1, } -function UI.Text:init(args) - local defaults = UI:getDefaults(UI.Text, args) - UI.Window.init(self, defaults) -end - function UI.Text:setParent() if not self.width and not self.ex then self.width = #tostring(self.value) @@ -2956,10 +2839,6 @@ UI.ScrollBar.defaults = { x = -1, ey = -1, } -function UI.ScrollBar:init(args) - UI.Window.init(self, UI:getDefaults(UI.ScrollBar, args)) -end - function UI.ScrollBar:draw() local parent = self.parent local view = parent:getViewArea() @@ -3000,7 +2879,6 @@ function UI.ScrollBar:draw() end function UI.ScrollBar:eventHandler(event) - if event.type == 'mouse_click' or event.type == 'mouse_doubleclick' then if event.x == 1 then local view = self.parent:getViewArea() @@ -3025,8 +2903,7 @@ UI.TextArea.defaults = { marginRight = 2, value = '', } -function UI.TextArea:init(args) - UI.Viewport.init(self, UI:getDefaults(UI.TextArea, args)) +function UI.TextArea:postInit() self.scrollBar = UI.ScrollBar() end @@ -3062,9 +2939,7 @@ UI.Form.defaults = { margin = 2, event = 'form_complete', } -function UI.Form:init(args) - local defaults = UI:getDefaults(UI.Form, args) - UI.Window.init(self, defaults) +function UI.Form:postInit() self:createForm() end @@ -3185,20 +3060,17 @@ UI.Dialog.defaults = { textColor = colors.black, backgroundColor = colors.white, } -function UI.Dialog:init(args) - local defaults = UI:getDefaults(UI.Dialog, args) - - if not defaults.width then - defaults.width = UI.term.width-11 - end - defaults.titleBar = UI.TitleBar({ previousPage = true, title = defaults.title }) - UI.Page.init(self, defaults) +function UI.Dialog:postInit() + self.titleBar = UI.TitleBar({ previousPage = true, title = self.title }) end function UI.Dialog:setParent() - UI.Window.setParent(self) + if not self.width then + self.width = self.parent.width - 11 + end self.x = math.floor((self.parent.width - self.width) / 2) + 1 self.y = math.floor((self.parent.height - self.height) / 2) + 1 + UI.Page.setParent(self) end function UI.Dialog:disable() @@ -3226,11 +3098,6 @@ UI.Image.defaults = { UIElement = 'Image', event = 'button_press', } -function UI.Image:init(args) - local defaults = UI:getDefaults(UI.Image, args) - UI.Window.init(self, defaults) -end - function UI.Image:setParent() if self.image then self.height = #self.image @@ -3270,11 +3137,6 @@ UI.NftImage.defaults = { UIElement = 'NftImage', event = 'button_press', } -function UI.NftImage:init(args) - local defaults = UI:getDefaults(UI.NftImage, args) - UI.Window.init(self, defaults) -end - function UI.NftImage:setParent() if self.image then self.height = self.image.height @@ -3286,7 +3148,6 @@ function UI.NftImage:setParent() end function UI.NftImage:draw() --- self:clear() if self.image then for y = 1, self.image.height do for x = 1, #self.image.text[y] do diff --git a/sys/apis/ui/canvas.lua b/sys/apis/ui/canvas.lua index c7d3c9f..943f218 100644 --- a/sys/apis/ui/canvas.lua +++ b/sys/apis/ui/canvas.lua @@ -22,8 +22,6 @@ end function Canvas:init(args) self.x = 1 self.y = 1 - self.bg = colors.black - self.fg = colors.white self.layers = { } Util.merge(self, args) @@ -88,7 +86,7 @@ function Canvas:copy() return b end -function Canvas:addLayer(layer, bg, fg) +function Canvas:addLayer(layer) local canvas = Canvas({ x = layer.x, y = layer.y, @@ -96,8 +94,6 @@ function Canvas:addLayer(layer, bg, fg) height = layer.height, isColor = self.isColor, }) - canvas:clear(bg, fg) - canvas.parent = self table.insert(self.layers, canvas) return canvas @@ -199,8 +195,8 @@ end function Canvas:clear(bg, fg) local text = _rep(' ', self.width) - fg = _rep(self.palette[fg or self.fg], self.width) - bg = _rep(self.palette[bg or self.bg], self.width) + fg = _rep(self.palette[fg or colors.white], self.width) + bg = _rep(self.palette[bg or colors.black], self.width) for i = 1, self.height do self:writeLine(i, text, fg, bg) end diff --git a/sys/apps/Lua.lua b/sys/apps/Lua.lua index ceda415..0138b18 100644 --- a/sys/apps/Lua.lua +++ b/sys/apps/Lua.lua @@ -204,7 +204,7 @@ function page:setResult(result) if Util.size(v) == 0 then entry.value = 'table: (empty)' else - entry.value = 'table' + entry.value = tostring(v) end end table.insert(t, entry) diff --git a/sys/apps/Network.lua b/sys/apps/Network.lua index a069c51..22b0487 100644 --- a/sys/apps/Network.lua +++ b/sys/apps/Network.lua @@ -34,6 +34,7 @@ local page = UI.Page { UI.MenuBar.spacer, { text = 'Reboot r', event = 'reboot' }, } }, + { text = 'Chat', event = 'chat' }, { text = 'Trust', dropdown = { { text = 'Establish', event = 'trust' }, { text = 'Remove', event = 'untrust' }, @@ -81,7 +82,7 @@ end function page:eventHandler(event) local t = self.grid:getSelected() if t then - if event.type == 'telnet' or event.type == 'grid_select' then + if event.type == 'telnet' then multishell.openTab({ path = 'sys/apps/telnet.lua', focused = true, @@ -108,6 +109,13 @@ function page:eventHandler(event) trustList[t.id] = nil Util.writeTable('usr/.known_hosts', trustList) + elseif event.type == 'chat' then + multishell.openTab({ + path = 'sys/apps/shell', + args = { 'chat join opusChat-' .. t.id .. ' guest'}, + title = 'Chatroom', + focused = true, + }) elseif event.type == 'reboot' then sendCommand(t.id, 'reboot') diff --git a/sys/apps/Overview.lua b/sys/apps/Overview.lua index 4d566bf..e492942 100644 --- a/sys/apps/Overview.lua +++ b/sys/apps/Overview.lua @@ -118,12 +118,12 @@ local function parseIcon(iconText) end UI.VerticalTabBar = class(UI.TabBar) -function UI.VerticalTabBar:init(args) - UI.TabBar.init(self, args) +function UI.VerticalTabBar:setParent() self.x = 1 self.width = 8 self.height = nil self.ey = -1 + UI.TabBar.setParent(self) for k,c in pairs(self.children) do c.x = 1 c.y = k + 1 @@ -160,16 +160,11 @@ local page = UI.Page { } UI.Icon = class(UI.Window) -function UI.Icon:init(args) - local defaults = { - UIElement = 'Icon', - width = 14, - height = 4, - } - UI:setProperties(defaults, args) - UI.Window.init(self, defaults) -end - +UI.Icon.defaults = { + UIElement = 'Icon', + width = 14, + height = 4, +} function UI.Icon:eventHandler(event) if event.type == 'mouse_click' then self:setFocus(self.button) @@ -498,7 +493,6 @@ function editor:eventHandler(event) width = self.width, height = self.height, }) - --fileui:setTransition(UI.effect.explode) UI:setPage(fileui, fs.getDir(self.iconFile), function(fileName) if fileName then self.iconFile = fileName diff --git a/sys/apps/System.lua b/sys/apps/System.lua index 906dcba..ea2b1ce 100644 --- a/sys/apps/System.lua +++ b/sys/apps/System.lua @@ -1,8 +1,10 @@ _G.requireInjector() -local Config = require('config') -local UI = require('ui') -local Util = require('util') +local Config = require('config') +local Security = require('security') +local SHA1 = require('sha1') +local UI = require('ui') +local Util = require('util') local fs = _G.fs local multishell = _ENV.multishell @@ -24,7 +26,7 @@ end local env = { path = shell.path(), aliases = shell.aliases(), - lua_path = LUA_PATH, + lua_path = _ENV.LUA_PATH, } Config.load('shell', env) @@ -77,6 +79,36 @@ local systemPage = UI.Page { }, }, + passwordTab = UI.Window { + tabTitle = 'Password', + oldPass = UI.TextEntry { + x = 2, y = 2, ex = -2, + limit = 32, + mask = true, + shadowText = 'old password', + inactive = not Security.getPassword(), + }, + newPass = UI.TextEntry { + y = 3, x = 2, ex = -2, + limit = 32, + mask = true, + shadowText = 'new password', + accelerators = { + enter = 'new_password', + }, + }, + button = UI.Button { + x = 2, y = 5, + text = 'Update', + event = 'update_password', + }, + info = UI.TextArea { + x = 2, ex = -2, + y = 7, + value = 'Add a password to enable other computers to connect to this one.', + } + }, + infoTab = UI.Window { tabTitle = 'Info', labelText = UI.Text { @@ -205,6 +237,22 @@ function systemPage.tabs.aliasTab:eventHandler(event) end end +function systemPage.tabs.passwordTab:eventHandler(event) + if event.type == 'update_password' then + if #self.newPass.value == 0 then + systemPage.notification:error('Invalid password') + elseif Security.getPassword() and not Security.verifyPassword(SHA1.sha1(self.oldPass.value)) then + systemPage.notification:error('Passwords do not match') + else + Security.updatePassword(SHA1.sha1(self.newPass.value)) + self.oldPass.inactive = false + systemPage.notification:success('Password updated') + end + + return true + end +end + function systemPage.tabs.infoTab:eventHandler(event) if event.type == 'update_label' then os.setComputerLabel(self.label.value) diff --git a/sys/apps/multishell b/sys/apps/multishell index b9ddfb5..aa07d8d 100644 --- a/sys/apps/multishell +++ b/sys/apps/multishell @@ -395,6 +395,13 @@ function multishell.hideTab(tabId) local tab = tabs[tabId] if tab then tab.hidden = true + if currentTab.tabId == tabId then + if tabs[currentTab.previousTabId] then + multishell.setFocus(currentTab.previousTabId) + else + multishell.setFocus(overviewTab.tabId) + end + end redrawMenu() end end diff --git a/sys/apps/trust.lua b/sys/apps/trust.lua index 946ff4d..54e7a29 100644 --- a/sys/apps/trust.lua +++ b/sys/apps/trust.lua @@ -29,10 +29,10 @@ if not password then end print('connecting...') -local socket = Socket.connect(remoteId, 19) +local socket, msg = Socket.connect(remoteId, 19) if not socket then - error('Unable to connect to ' .. remoteId .. ' on port 19') + error(msg) end local publicKey = Security.getPublicKey() diff --git a/sys/etc/app.db b/sys/etc/app.db index 1c6a4d8..957a7f3 100644 --- a/sys/etc/app.db +++ b/sys/etc/app.db @@ -222,7 +222,7 @@ [ "d8c298dd41e4a4ec20e8307901797b64688b3b77" ] = { title = "GPS Deploy", category = "Apps", - run = "http://pastebin.com/raw/qLthLak5", + run = "http://pastebin.com/raw/VXAyXqBv", requires = "turtle", }, [ "53a5d150062b1e03206b9e15854b81060e3c7552" ] = { diff --git a/sys/services/chat.lua b/sys/services/chat.lua new file mode 100644 index 0000000..8f273f0 --- /dev/null +++ b/sys/services/chat.lua @@ -0,0 +1,58 @@ +local device = _G.device +local multishell = _ENV.multishell +local os = _G.os +local parallel = _G.parallel + +if device.wireless_modem then + + multishell.setTitle(multishell.getCurrent(), 'Chat Daemon') + + local tab + + local function chatClient() + + _G.requireInjector() + + local Event = require('event') + local Util = require('util') + + local h = Event.addRoutine(function() + while true do + Util.run(_ENV, 'rom/programs/rednet/chat', + 'join', 'opusChat-' .. os.getComputerID(), 'owner') + end + end) + + while true do + local e = { os.pullEventRaw() } + if e[1] == 'terminate' then + multishell.hideTab(tab.tabId) + else + if e[1] == 'rednet_message' and e[4] == 'chat' and e[3].sType == 'chat' then + if tab.hidden then + multishell.unhideTab(tab.tabId) + end + end + h:resume(unpack(e)) + end + end + end + + parallel.waitForAll( + function() + os.run(_ENV, 'rom/programs/rednet/chat', + 'host', 'opusChat-' .. os.getComputerID()) + end, + function() + os.sleep(3) + local tabId = multishell.openTab({ + fn = chatClient, + title = 'Chatroom', + hidden = true, + }) + tab = multishell.getTab(tabId) + end + ) + + print('Chat daemon stopped') +end