From 12d82569a26b696f32d3d5ea5d5254f77e438f83 Mon Sep 17 00:00:00 2001 From: rservices Date: Thu, 26 Dec 2019 09:36:17 -0600 Subject: [PATCH 001/125] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index fbe12b7..d157c09 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -darkprograms +Outraged Programs ============ -Programs witten by Darkrising for use with the Minecraft mod: Computercraft. +Programs witten by Outraged Security .INC for use with the Minecraft mod: Computercraft. These Files are written in: Lua From 941d4220d9ade8e2c1b1fe3da32e43a4a92964a3 Mon Sep 17 00:00:00 2001 From: rservices Date: Thu, 26 Dec 2019 09:38:40 -0600 Subject: [PATCH 002/125] Update darkretriever.lua --- darkretriever.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/darkretriever.lua b/darkretriever.lua index c23c554..b309ab7 100644 --- a/darkretriever.lua +++ b/darkretriever.lua @@ -48,7 +48,7 @@ local function header(text) end local function gitUpdate(ProgramName, Filename, ProgramVersion) if http then - local status, getGit = pcall(http.get, "https://raw.github.com/darkrising/darkprograms/darkprograms/programVersions") + local status, getGit = pcall(http.get, "https://raw.githubusercontent.com/rservices/darkprograms/darkprograms/programVersions") if not status then print("\nFailed to get Program Versions file.") print("Error: ".. getGit) @@ -301,4 +301,4 @@ function runMenu() end end -runMenu() \ No newline at end of file +runMenu() From 80687449a050fec0ce6ecf01e0eafc84137c0908 Mon Sep 17 00:00:00 2001 From: rservices Date: Thu, 26 Dec 2019 09:41:15 -0600 Subject: [PATCH 003/125] Update darkretriever.lua --- darkretriever.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/darkretriever.lua b/darkretriever.lua index b309ab7..128445e 100644 --- a/darkretriever.lua +++ b/darkretriever.lua @@ -40,7 +40,7 @@ function term.write(text) term.oldWrite(text) end local function header(text) - tc("white","blue") + tc("white","green") writeC(string.rep(" ",x),1) writeC(string.rep(" ",x),y) writeC(text,1) @@ -83,7 +83,7 @@ sleep(1) x,y = term.getSize() cs() write("-> Grabbing file...") -cat = getUrlFile("https://raw.github.com/darkrising/darkprograms/darkprograms/programVersions") +cat = getUrlFile("https://raw.githubusercontent.com/rservices/darkprograms/darkprograms/programVersions") cat = textutils.unserialize(cat) write(" Done.") sleep(1) @@ -128,10 +128,10 @@ function selection(no,list,totpage) term.write("[".. list[no] .. "]") tc("white","black") term.setCursorPos(1,y) - tc("white","blue") + tc("white","green") term.write("Page: ".. page + 1 .. "/" .. totpage) term.setCursorPos(x - 14, y) - term.write("By Darkrising") + term.write("By Outraged Security .INC") tc("white","black") end function draw(tbl) From 97d3bf881f438ed38038604564683ada1da9ec80 Mon Sep 17 00:00:00 2001 From: rservices Date: Thu, 26 Dec 2019 09:46:15 -0600 Subject: [PATCH 004/125] Update programVersions --- programVersions | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/programVersions b/programVersions index 30d9c83..55e90dd 100644 --- a/programVersions +++ b/programVersions @@ -1,6 +1,6 @@ { ["server"]={ - ["GitURL"]="https://raw.github.com/darkrising/darkprograms/darkprograms/darksecurity/server.lua", + ["GitURL"]="https://raw.githubusercontent.com/rservices/darkprograms/darkprograms/darksecurity/server.lua", ["Version"]=6.37, ["Type"]="program", ["Name"]="Dark Programs Server", @@ -9,7 +9,7 @@ ["Package"]="Dark Security", }, ["client"]={ - ["GitURL"]="https://raw.github.com/darkrising/darkprograms/darkprograms/darksecurity/client.lua", + ["GitURL"]="https://raw.githubusercontent.com/rservices/darkprograms/darkprograms/darksecurity/client.lua", ["Version"]=4.26, ["Type"]="program", ["Name"]="Dark Programs Client", @@ -18,16 +18,16 @@ ["Package"]="Dark Security", }, ["dark"]={ - ["GitURL"]="https://raw.github.com/darkrising/darkprograms/darkprograms/api/dark.lua", + ["GitURL"]="https://raw.githubusercontent.com/rservices/darkprograms/darkprograms/api/dark.lua", ["Version"]=3.25, ["Type"]="api", ["Name"]="Dark API", - ["Description"]="API that is used in most Dark Programs", + ["Description"]="API that is used in most Outraged Security .INC Software", ["Author"]="Darkrising", ["Package"]="API", }, ["darkfile"]={ - ["GitURL"]="https://raw.github.com/darkrising/darkprograms/darkprograms/api/darkfile.lua", + ["GitURL"]="https://raw.githubusercontent.com/rservices/darkprograms/darkprograms/api/darkfile.lua", ["Version"]=1.101, ["Type"]="api", ["Name"]="Dark File API", @@ -36,57 +36,57 @@ ["Package"]="API", }, ["screen"]={ - ["GitURL"]="https://raw.github.com/darkrising/darkprograms/darkprograms/screen.lua", + ["GitURL"]="https://raw.githubusercontent.com/rservices/darkprograms/darkprograms/screen.lua", ["Version"]=3.361, ["Type"]="program", ["Name"]="Dark Screen", ["Description"]="Used to display information on monitors", - ["Author"]="Darkrising", + ["Author"]="Outraged Security .INC", ["Package"]="Standalone", }, ["chat"]={ - ["GitURL"]="https://raw.github.com/darkrising/darkprograms/darkprograms/chat.lua", + ["GitURL"]="https://raw.githubusercontent.com/rservices/darkprograms/darkprograms/chat.lua", ["Version"]=1.51, ["Type"]="program", - ["Name"]="Dark Chat", + ["Name"]="OSI Chat", ["Description"]="A standalone rednet chat program", - ["Author"]="Darkrising", + ["Author"]="Outraged Security .INC", ["Package"]="Standalone", }, ["darkbuttons"]={ - ["GitURL"]="https://raw.github.com/darkrising/darkprograms/darkprograms/darkbuttons.lua", + ["GitURL"]="https://raw.githubusercontent.com/rservices/darkprograms/darkprograms/darkbuttons.lua", ["Version"]=1.231, ["Type"]="program", - ["Name"]="Dark Buttons", + ["Name"]="Outraged Security Buttons", ["Description"]="Used to control coloured wires with buttons on a monitor", - ["Author"]="Darkrising", + ["Author"]="Outraged Security .INC", ["Package"]="Standalone", }, ["turt"]={ - ["GitURL"]="https://raw.github.com/darkrising/darkprograms/darkprograms/turt.lua", + ["GitURL"]="https://raw.githubusercontent.com/rservices/darkprograms/darkprograms/turt.lua", ["Version"]=1.121, ["Type"]="program", ["Name"]="Inturtlpler", ["Description"]="Short hand turtle command interpreter", - ["Author"]="Darkrising", + ["Author"]="Outraged Security .INC", ["Package"]="Standalone", }, ["stargatetouch"]={ - ["GitURL"]="https://raw.github.com/darkrising/darkprograms/darkprograms/stargate/stargatetouch.lua", + ["GitURL"]="https://raw.githubusercontent.com/rservices/darkprograms/darkprograms/stargatetouch.lua", ["Version"]=1.204, ["Type"]="program", ["Name"]="Stargate Touch Program", ["Description"]="A program that allows you to dial gates using a touch screen, stargate and terminal.", - ["Author"]="Darkrising", + ["Author"]="Outraged Security .INC", ["Package"]="Stargate", }, ["darkretriever"]={ - ["GitURL"]="https://raw.github.com/darkrising/darkprograms/darkprograms/darkretriever.lua", + ["GitURL"]="https://raw.githubusercontent.com/rservices/darkprograms/darkprograms/darkretriever.lua", ["Version"]=2.12, ["Type"]="program", - ["Name"]="Dark Retriever", + ["Name"]="Outraged Security .INC Retriever", ["Description"]="Downloads programs from github", - ["Author"]="Darkrising", + ["Author"]="Outraged Security .INC", ["Package"]="Standalone", }, } From 50f975a7d60ae4d70cdcad8dfd0166e9f8f2141a Mon Sep 17 00:00:00 2001 From: rservices Date: Thu, 26 Dec 2019 09:55:05 -0600 Subject: [PATCH 005/125] Update darkretriever.lua --- darkretriever.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/darkretriever.lua b/darkretriever.lua index 128445e..da4fef0 100644 --- a/darkretriever.lua +++ b/darkretriever.lua @@ -131,7 +131,7 @@ function selection(no,list,totpage) tc("white","green") term.write("Page: ".. page + 1 .. "/" .. totpage) term.setCursorPos(x - 14, y) - term.write("By Outraged Security .INC") + term.write("By OutragedMetro .INC") tc("white","black") end function draw(tbl) From 2da3b6c6901faed87a8c92867ffbdd4a89b2e375 Mon Sep 17 00:00:00 2001 From: rservices Date: Thu, 26 Dec 2019 09:59:15 -0600 Subject: [PATCH 006/125] Update installer.lua --- darksecurity/installer.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/darksecurity/installer.lua b/darksecurity/installer.lua index 3c81d04..6ca5a71 100644 --- a/darksecurity/installer.lua +++ b/darksecurity/installer.lua @@ -41,7 +41,7 @@ until ((installAnswer == "Server") or (installAnswer == "Client") or (installAns end print("Downloading requested programs...") - local status, getGit = pcall(http.get, "https://raw.github.com/darkrising/darkprograms/darkprograms/programVersions") + local status, getGit = pcall(http.get, "https://raw.githubusercontent.com/rservices/darkprograms/darkprograms/programVersions") if not status then print("\nFailed to get Program Versions file.") print("Error: ".. getGit) From 3a0d7748125b424e96c2f213726ac9d0f56ee9bd Mon Sep 17 00:00:00 2001 From: rservices Date: Thu, 26 Dec 2019 10:07:28 -0600 Subject: [PATCH 007/125] Update client.lua --- darksecurity/client.lua | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/darksecurity/client.lua b/darksecurity/client.lua index 97f323e..e922710 100644 --- a/darksecurity/client.lua +++ b/darksecurity/client.lua @@ -12,7 +12,7 @@ if fs.exists("dark") == false then -- load darkAPI print("Missing DarkAPI") sleep(2) print("Attempting to download...") - status, getGit = pcall(http.get,"https://raw.github.com/darkrising/darkprograms/darkprograms/api/dark.lua") + status, getGit = pcall(http.get,"https://raw.githubusercontent.com/rservices/darkprograms/darkprograms/api/dark.lua") if not status then print("\nFailed to get Dark API") print("Error: ".. getGit) @@ -46,18 +46,18 @@ function rednetReceiveE(TimeA) end end function header(text, lText, rText) - dark.printL("-", 1, nil, "blue", "blue") - dark.printA("|", x, 2, nil, "blue", "blue") - dark.printA("|", 1, 2, nil, "blue", "blue") - dark.printC(string.rep(" ", x), 2, nil, "white", "blue") - if lText then dark.printA(lText, 1, 2, nil, "white", "blue") end - if rText then dark.printA(rText, x - #rText, 2, nil, "white", "blue") end - dark.printC(text, 2, nil, "yellow", "blue") - dark.printL("-", 3, 5, "blue", "blue") + dark.printL("-", 1, nil, "green", "green") + dark.printA("|", x, 2, nil, "green", "green") + dark.printA("|", 1, 2, nil, "green", "green") + dark.printC(string.rep(" ", x), 2, nil, "white", "green") + if lText then dark.printA(lText, 1, 2, nil, "white", "green") end + if rText then dark.printA(rText, x - #rText, 2, nil, "white", "green") end + dark.printC(text, 2, nil, "yellow", "green") + dark.printL("-", 3, 5, "green", "green") end function footer() - dark.printL("-", y, nil, "blue", "blue") - dark.printA("by darkrising", x-13, y, nil, "yellow", "blue") + dark.printL("-", y, nil, "green", "green") + dark.printA("by darkrising", x-13, y, nil, "yellow", "green") end function keycard_mainProgram() while true do @@ -252,4 +252,4 @@ elseif config.tType == "password" then elseif config.tType == "both" then parallel.waitForAll(userandpassword_mainProgram, keycard_mainProgram, stealthUpdate) end -os.pullEvent = oldEvent \ No newline at end of file +os.pullEvent = oldEvent From b3177f58c73218df2f4056a22da77b8619c120a4 Mon Sep 17 00:00:00 2001 From: rservices Date: Thu, 26 Dec 2019 10:09:27 -0600 Subject: [PATCH 008/125] Update server.lua --- darksecurity/server.lua | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/darksecurity/server.lua b/darksecurity/server.lua index 18c5577..fd90cba 100644 --- a/darksecurity/server.lua +++ b/darksecurity/server.lua @@ -7,7 +7,7 @@ term.setCursorPos(1,1) if fs.exists("dark") == false then print("Missing DarkAPI") print("Attempting to download...") - status, getGit = pcall(http.get, "https://raw.github.com/darkrising/darkprograms/darkprograms/api/dark.lua") + status, getGit = pcall(http.get, "https://raw.githubusercontent.com/rservices/darkprograms/darkprograms/api/dark.lua") if not status then print("\nFailed to get Dark API") print("Error: ".. getGit) @@ -35,7 +35,7 @@ AutoUpdate = true globalWait = 1 slevel = 1 cliVent = {} -co = "blue" +co = "green" mLog = {} --Fixes @@ -112,7 +112,7 @@ function header(text, lText, rText) if debugMode and (debugMode == true) then co = "red" else - co = "blue" + co = "green" end dark.printL("-", 1, nil, co, co) dark.printA("|", x, 2, nil, co, co) @@ -127,10 +127,10 @@ function footer() if debugMode and (debugMode == true) then co = "red" else - co = "blue" + co = "green" end dark.printL("-", y, nil, co, co) - dark.printA("by darkrising", x-13, y, nil, "yellow", co) + dark.printA("by OutragedMetro", x-13, y, nil, "yellow", co) end function displayTNameColumn(TName, Page, Extrater, Admin) if Extrater then @@ -784,4 +784,4 @@ if fs.exists(".DarkS_conf") == false then end --Finale Stuff databaseLoad() -parallel.waitForAll(runServerBackend, runServerGui, stealthUpdate, listen) \ No newline at end of file +parallel.waitForAll(runServerBackend, runServerGui, stealthUpdate, listen) From b3919f90ba0949702ae865fd3d931aabd27ee9f6 Mon Sep 17 00:00:00 2001 From: rservices Date: Thu, 26 Dec 2019 10:10:59 -0600 Subject: [PATCH 009/125] Update dark.lua --- api/dark.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/dark.lua b/api/dark.lua index 05a5dbd..9e5b627 100644 --- a/api/dark.lua +++ b/api/dark.lua @@ -126,7 +126,7 @@ function getPBFile(PBCode, uPath) -- pastebin code of the file, and path to save end function gitUpdate(ProgramName, Filename, ProgramVersion) if http then - local status, getGit = pcall(http.get,"https://raw.github.com/darkrising/darkprograms/darkprograms/programVersions") + local status, getGit = pcall(http.get,"https://raw.githubusercontent.com/rservices/darkprograms/darkprograms/programVersions") if not status then return false end @@ -372,4 +372,4 @@ function repdeCrypt(Input, numHash) i = i + 1 until Input:byte(i) == nil return dec -end \ No newline at end of file +end From 7fefa21da34c5455357403109c9efd2a6bfce7b5 Mon Sep 17 00:00:00 2001 From: rservices Date: Thu, 26 Dec 2019 10:11:46 -0600 Subject: [PATCH 010/125] Update darkfile.lua --- api/darkfile.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/darkfile.lua b/api/darkfile.lua index 1e2ad51..ad75eac 100644 --- a/api/darkfile.lua +++ b/api/darkfile.lua @@ -3,7 +3,7 @@ version = 1.101 function checkUpdate() if http then - local getGit = http.get("https://raw.github.com/darkrising/darkprograms/darkprograms/programVersions") + local getGit = http.get("https://raw.githubusercontent.com/rservices/darkprograms/darkprograms/programVersions") local getGit = getGit.readAll() NVersion = textutils.unserialize(getGit) if NVersion["darkfile"].Version > version then From 4050543ba216da2b1f036a2e710e329706d2a53b Mon Sep 17 00:00:00 2001 From: rservices Date: Thu, 26 Dec 2019 10:14:57 -0600 Subject: [PATCH 011/125] Update darkbuttons.lua --- darkbuttons.lua | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/darkbuttons.lua b/darkbuttons.lua index cb628ec..4d2c07b 100644 --- a/darkbuttons.lua +++ b/darkbuttons.lua @@ -7,12 +7,12 @@ if not term.isColour() then return end if fs.exists("dark") == false then -- load darkAPI - print("Missing DarkAPI") + print("Missing OSI API") print("Attempting to download...") if not http then - error("Enable the HTTP API to download DarkAPI") + error("Enable the HTTP API to download OSI API") end - getGit = http.get("https://raw.github.com/darkrising/darkprograms/darkprograms/api/dark.lua") + getGit = http.get("https://raw.githubusercontent.com/rservices/darkprograms/darkprograms/api/dark.lua") getGit = getGit.readAll() file = fs.open("dark", "w") file.write(getGit) @@ -28,10 +28,10 @@ AutoUpdate = true --General Functions function Header(text, lText, rText) -- builds a header using functions above from local x,y = term.getSize() - dark.printL("-", 1, nil, "blue", "blue") - dark.printC(string.rep(" ", x+1), 2, nil, "white", "blue") - dark.printC(text, 2, nil, "white", "blue") - dark.printL("-", 3, 5, "blue", "blue") + dark.printL("-", 1, nil, "green, "green") + dark.printC(string.rep(" ", x+1), 2, nil, "white", "green") + dark.printC(text, 2, nil, "white", "green") + dark.printL("-", 3, 5, "green", "green") end function saveState() dark.db.save("state", buttons) @@ -384,7 +384,7 @@ function wizard(mode) end happyness = checkTemp(hx,hy,txd,txu,tyd,tyu) if happyness ~= true then - textBox(":'(",txd,txu,tyd,tyu,colors.white,colors.blue) + textBox(":'(",txd,txu,tyd,tyu,colors.white,colors.green) else textBox(":)",txd,txu,tyd,tyu,colors.white,colors.green) end @@ -464,4 +464,4 @@ options = {} options.wireSide = "bottom" LoadState() getMonitors() -parallel.waitForAll(hitListen, breakListen, terminalMenu, stealthUpdate) \ No newline at end of file +parallel.waitForAll(hitListen, breakListen, terminalMenu, stealthUpdate) From 4ec8513dc633c08950259fb1ba6b5877fe2bbdbb Mon Sep 17 00:00:00 2001 From: rservices Date: Thu, 26 Dec 2019 10:16:23 -0600 Subject: [PATCH 012/125] Update screen.lua --- screen.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/screen.lua b/screen.lua index ca98dd8..91df55a 100644 --- a/screen.lua +++ b/screen.lua @@ -5,13 +5,13 @@ Version = 3.361 --Platform: ComputerCraft Lua Virtual Machine AutoUpdate = true if not fs.exists("dark") then -- load darkAPI - print("Missing DarkAPI") + print("Missing OSI API") if not http then - error("Enable the HTTP API to download DarkAPI") + error("Enable the HTTP API to download OSI API") end sleep(2) print("Attempting to download...") - getGit = http.get("https://raw.github.com/darkrising/darkprograms/darkprograms/api/dark.lua") + getGit = http.get("https://raw.githubusercontent.com/rservices/darkprograms/darkprograms/api/dark.lua") getGit = getGit.readAll() file = fs.open("dark", "w") file.write(getGit) @@ -301,4 +301,4 @@ if arg[1] == "-mc" then Cou = 1 end end -end \ No newline at end of file +end From 8a1ccd19e328450c7289b04c433b6fbe2271f902 Mon Sep 17 00:00:00 2001 From: rservices Date: Thu, 26 Dec 2019 10:16:57 -0600 Subject: [PATCH 013/125] Update turt.lua --- turt.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/turt.lua b/turt.lua index dde1a94..b2ac6e0 100644 --- a/turt.lua +++ b/turt.lua @@ -9,7 +9,7 @@ if #arg == 0 then end local function gitUpdate(ProgramName, Filename, ProgramVersion) if http then - local getGit = http.get("https://raw.github.com/darkrising/darkprograms/darkprograms/programVersions") + local getGit = http.get("https://raw.githubusercontent.com/rservices/darkprograms/darkprograms/programVersions") local getGit = getGit.readAll() NVersion = textutils.unserialize(getGit) if NVersion[ProgramName].Version > ProgramVersion then @@ -185,4 +185,4 @@ end file = fs.open(arg[1], "r") wFile = file.readAll() file.close() -inTur(wFile) \ No newline at end of file +inTur(wFile) From 9f070bb804c4c954c48253c8bec8e625008df315 Mon Sep 17 00:00:00 2001 From: rservices Date: Thu, 26 Dec 2019 10:19:18 -0600 Subject: [PATCH 014/125] Update programVersions --- programVersions | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/programVersions b/programVersions index 55e90dd..043d735 100644 --- a/programVersions +++ b/programVersions @@ -3,9 +3,9 @@ ["GitURL"]="https://raw.githubusercontent.com/rservices/darkprograms/darkprograms/darksecurity/server.lua", ["Version"]=6.37, ["Type"]="program", - ["Name"]="Dark Programs Server", - ["Description"]="Dark Programs Base Security Server", - ["Author"]="Darkrising", + ["Name"]="Outraged Security .INC Server", + ["Description"]="Outraged Security .INC Base Security Server", + ["Author"]="Outraged Security .INC", ["Package"]="Dark Security", }, ["client"]={ @@ -13,8 +13,8 @@ ["Version"]=4.26, ["Type"]="program", ["Name"]="Dark Programs Client", - ["Description"]="Dark Programs Base Security Client", - ["Author"]="Darkrising", + ["Description"]="DOutraged Security .INC Base Security Client", + ["Author"]="Outraged Security .INC", ["Package"]="Dark Security", }, ["dark"]={ @@ -23,23 +23,23 @@ ["Type"]="api", ["Name"]="Dark API", ["Description"]="API that is used in most Outraged Security .INC Software", - ["Author"]="Darkrising", + ["Author"]="Outraged Security .INC", ["Package"]="API", }, ["darkfile"]={ ["GitURL"]="https://raw.githubusercontent.com/rservices/darkprograms/darkprograms/api/darkfile.lua", ["Version"]=1.101, ["Type"]="api", - ["Name"]="Dark File API", + ["Name"]="Outraged Security .INC FILE API", ["Description"]="File API used for compression and other things", - ["Author"]="Darkrising", + ["Author"]="Outraged Security .INC", ["Package"]="API", }, ["screen"]={ ["GitURL"]="https://raw.githubusercontent.com/rservices/darkprograms/darkprograms/screen.lua", ["Version"]=3.361, ["Type"]="program", - ["Name"]="Dark Screen", + ["Name"]="Outraged Security .INC Screen", ["Description"]="Used to display information on monitors", ["Author"]="Outraged Security .INC", ["Package"]="Standalone", From 4a30451dcf72955ed916b6112b160e07b7b7d361 Mon Sep 17 00:00:00 2001 From: rservices Date: Thu, 26 Dec 2019 10:21:21 -0600 Subject: [PATCH 015/125] Update programVersions --- programVersions | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/programVersions b/programVersions index 043d735..c8373d5 100644 --- a/programVersions +++ b/programVersions @@ -6,7 +6,7 @@ ["Name"]="Outraged Security .INC Server", ["Description"]="Outraged Security .INC Base Security Server", ["Author"]="Outraged Security .INC", - ["Package"]="Dark Security", + ["Package"]="Outraged Security .INC", }, ["client"]={ ["GitURL"]="https://raw.githubusercontent.com/rservices/darkprograms/darkprograms/darksecurity/client.lua", @@ -15,7 +15,7 @@ ["Name"]="Dark Programs Client", ["Description"]="DOutraged Security .INC Base Security Client", ["Author"]="Outraged Security .INC", - ["Package"]="Dark Security", + ["Package"]="Outraged Security .INC", }, ["dark"]={ ["GitURL"]="https://raw.githubusercontent.com/rservices/darkprograms/darkprograms/api/dark.lua", From fa4b059c2edda4772730c58e731f1a330e43f450 Mon Sep 17 00:00:00 2001 From: rservices Date: Thu, 26 Dec 2019 10:23:23 -0600 Subject: [PATCH 016/125] Update programVersions --- programVersions | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/programVersions b/programVersions index c8373d5..088d847 100644 --- a/programVersions +++ b/programVersions @@ -12,7 +12,7 @@ ["GitURL"]="https://raw.githubusercontent.com/rservices/darkprograms/darkprograms/darksecurity/client.lua", ["Version"]=4.26, ["Type"]="program", - ["Name"]="Dark Programs Client", + ["Name"]="Outraged Security .INC Client", ["Description"]="DOutraged Security .INC Base Security Client", ["Author"]="Outraged Security .INC", ["Package"]="Outraged Security .INC", @@ -21,7 +21,7 @@ ["GitURL"]="https://raw.githubusercontent.com/rservices/darkprograms/darkprograms/api/dark.lua", ["Version"]=3.25, ["Type"]="api", - ["Name"]="Dark API", + ["Name"]="Outraged Security .INC API", ["Description"]="API that is used in most Outraged Security .INC Software", ["Author"]="Outraged Security .INC", ["Package"]="API", @@ -48,7 +48,7 @@ ["GitURL"]="https://raw.githubusercontent.com/rservices/darkprograms/darkprograms/chat.lua", ["Version"]=1.51, ["Type"]="program", - ["Name"]="OSI Chat", + ["Name"]="Outraged Security .INC Secure Chat", ["Description"]="A standalone rednet chat program", ["Author"]="Outraged Security .INC", ["Package"]="Standalone", @@ -66,7 +66,7 @@ ["GitURL"]="https://raw.githubusercontent.com/rservices/darkprograms/darkprograms/turt.lua", ["Version"]=1.121, ["Type"]="program", - ["Name"]="Inturtlpler", + ["Name"]="Outraged Security .INC Inturtlpler", ["Description"]="Short hand turtle command interpreter", ["Author"]="Outraged Security .INC", ["Package"]="Standalone", @@ -75,7 +75,7 @@ ["GitURL"]="https://raw.githubusercontent.com/rservices/darkprograms/darkprograms/stargatetouch.lua", ["Version"]=1.204, ["Type"]="program", - ["Name"]="Stargate Touch Program", + ["Name"]="Outraged Security .INC Stargate Touch Program", ["Description"]="A program that allows you to dial gates using a touch screen, stargate and terminal.", ["Author"]="Outraged Security .INC", ["Package"]="Stargate", From c43f46895214b2f8da448db189e5775351e7ea75 Mon Sep 17 00:00:00 2001 From: rservices Date: Thu, 26 Dec 2019 10:31:43 -0600 Subject: [PATCH 017/125] Update server.lua --- darksecurity/server.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/darksecurity/server.lua b/darksecurity/server.lua index fd90cba..b131e69 100644 --- a/darksecurity/server.lua +++ b/darksecurity/server.lua @@ -5,11 +5,11 @@ Version = 6.37 term.clear() term.setCursorPos(1,1) if fs.exists("dark") == false then - print("Missing DarkAPI") + print("Missing OSI API") print("Attempting to download...") status, getGit = pcall(http.get, "https://raw.githubusercontent.com/rservices/darkprograms/darkprograms/api/dark.lua") if not status then - print("\nFailed to get Dark API") + print("\nFailed to get OSI API") print("Error: ".. getGit) return exit end @@ -350,7 +350,7 @@ Co = { printR("[8] Shell", 8) end dark.printL("-", y, nil, co, co) - dark.printA("by darkrising", x-13, y, nil, "yellow", co) + dark.printA("by OutragedMetro", x-13, y, nil, "yellow", co) dark.printA(" ", 1, y, nil, co, co) dark.printA("Current Security Level: "..slevel, 1, y, nil, "white", co) end, From 8af799a9f7d6a0808bbc1ddfaf04186281edd7a8 Mon Sep 17 00:00:00 2001 From: rservices Date: Thu, 26 Dec 2019 10:33:53 -0600 Subject: [PATCH 018/125] Update server.lua --- darksecurity/server.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/darksecurity/server.lua b/darksecurity/server.lua index b131e69..759af24 100644 --- a/darksecurity/server.lua +++ b/darksecurity/server.lua @@ -238,7 +238,7 @@ end function runServerGui() term.clear() term.setCursorPos(1,1) - dark.splash(1.5, "Powered by DarkGui") + dark.splash(1.5, "Powered by Outraged Security Gui") state = "main" Page = 0 while true do From 2c27ecba6e6283101ce4e966ce4dddcfbf473211 Mon Sep 17 00:00:00 2001 From: rservices Date: Thu, 26 Dec 2019 10:36:34 -0600 Subject: [PATCH 019/125] Update chat.lua --- chat.lua | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/chat.lua b/chat.lua index fb52623..00d3044 100644 --- a/chat.lua +++ b/chat.lua @@ -33,7 +33,7 @@ function findPeripheral(Perihp) end function gitUpdate(ProgramName, Filename, ProgramVersion) if http then - status, getGit = pcall(http.get, "https://raw.github.com/darkrising/darkprograms/darkprograms/programVersions") + status, getGit = pcall(http.get, "https://raw.githubusercontent.com/rservices/darkprograms/darkprograms/programVersions") if not status then return(getGit) end @@ -166,7 +166,7 @@ function draw() cWrite(chatN[2],"black",chatN[1]) end term.setCursorPos(1, Y - 1) - cWrite(string.rep("-", X), "blue","blue", true) + cWrite(string.rep("-", X), "green","green", true) term.setTextColor(colors.white) local Text = " " term.setCursorPos(X/2 - #Text/2, Y - 1) @@ -241,10 +241,10 @@ function startUp() end modem = peripheral.wrap(Side) term.clear() term.setCursorPos(1,1) - cWrite(string.rep("-", X), "blue","blue", true) bgReset() + cWrite(string.rep("-", X), "green","green", true) bgReset() term.setCursorPos(X/2 - string.len(Header)/2,2) print(Header) - cWrite(string.rep("-", X), "blue","blue", true) bgReset() + cWrite(string.rep("-", X), "green","green", true) bgReset() end function privateMode() user = config.user @@ -458,4 +458,4 @@ modem.open(channelN) term.clear() term.setCursorPos(1,1) rSend("has joined the chat!", true, true) -parallel.waitForAny(getChat, sendChat, keyListen, getOnlineList, scrollingEventListen, tableEventListen, exitEventListen) \ No newline at end of file +parallel.waitForAny(getChat, sendChat, keyListen, getOnlineList, scrollingEventListen, tableEventListen, exitEventListen) From a5ac78eef63a2196d4df2390e79983d1a0b3c195 Mon Sep 17 00:00:00 2001 From: rservices Date: Thu, 26 Dec 2019 10:47:04 -0600 Subject: [PATCH 020/125] Update server.lua --- darksecurity/server.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/darksecurity/server.lua b/darksecurity/server.lua index 759af24..75ed5c0 100644 --- a/darksecurity/server.lua +++ b/darksecurity/server.lua @@ -323,7 +323,7 @@ Co = { term.setCursorPos(1,1) header("Help") dark.printA("Press [1] to return to the main menu", 1, y) - print("\nHelp comming soon!") + print("\nTo Navigate the settings menu and to add new users, Keycards, And passwords just follow the on screen prompts!") os.pullEvent("key") end, options = {"main"} From 0b10a6054cec4d520e6beefd37e65ea6ff725dd9 Mon Sep 17 00:00:00 2001 From: rservices Date: Thu, 26 Dec 2019 13:25:06 -0600 Subject: [PATCH 021/125] Update client.lua --- darksecurity/client.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/darksecurity/client.lua b/darksecurity/client.lua index e922710..929dd38 100644 --- a/darksecurity/client.lua +++ b/darksecurity/client.lua @@ -57,7 +57,7 @@ function header(text, lText, rText) end function footer() dark.printL("-", y, nil, "green", "green") - dark.printA("by darkrising", x-13, y, nil, "yellow", "green") + dark.printA("by OutragedMetro", x-13, y, nil, "yellow", "green") end function keycard_mainProgram() while true do From a5a2b3c6fd111aecb6a4153c9ee6bc2b9eef2658 Mon Sep 17 00:00:00 2001 From: rservices Date: Thu, 26 Dec 2019 13:25:45 -0600 Subject: [PATCH 022/125] Update client.lua --- darksecurity/client.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/darksecurity/client.lua b/darksecurity/client.lua index 929dd38..3c93d5e 100644 --- a/darksecurity/client.lua +++ b/darksecurity/client.lua @@ -9,12 +9,12 @@ x,y = term.getSize() oldEvent = os.pullEvent os.pullEvent = os.pullEventRaw if fs.exists("dark") == false then -- load darkAPI - print("Missing DarkAPI") + print("Missing OSI API") sleep(2) print("Attempting to download...") status, getGit = pcall(http.get,"https://raw.githubusercontent.com/rservices/darkprograms/darkprograms/api/dark.lua") if not status then - print("\nFailed to get Dark API") + print("\nFailed to get OSI API") print("Error: ".. getGit) return exit end From 36bb07c3d2ce15a3445d774c219fd422413f3fd4 Mon Sep 17 00:00:00 2001 From: rservices Date: Sun, 29 Dec 2019 12:11:30 -0600 Subject: [PATCH 023/125] Create securesigns --- securesigns | 1 + 1 file changed, 1 insertion(+) create mode 100644 securesigns diff --git a/securesigns b/securesigns new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/securesigns @@ -0,0 +1 @@ + From 6676c9ba8f70cd049249a367b2f5748fe1a1af3d Mon Sep 17 00:00:00 2001 From: rservices Date: Sun, 29 Dec 2019 12:12:24 -0600 Subject: [PATCH 024/125] Create securitysigns --- signs/securitysigns | 1 + 1 file changed, 1 insertion(+) create mode 100644 signs/securitysigns diff --git a/signs/securitysigns b/signs/securitysigns new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/signs/securitysigns @@ -0,0 +1 @@ + From 327ce84212a01a3bd461270a79d25a214ee8b234 Mon Sep 17 00:00:00 2001 From: rservices Date: Sun, 29 Dec 2019 12:16:38 -0600 Subject: [PATCH 025/125] Update programVersions --- programVersions | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/programVersions b/programVersions index 088d847..59066dc 100644 --- a/programVersions +++ b/programVersions @@ -89,4 +89,13 @@ ["Author"]="Outraged Security .INC", ["Package"]="Standalone", }, + ["Dark Signs"]={ + ["GitURL"]="https://raw.githubusercontent.com/rservices/darkprograms/darkprograms/signs/startup", + ["Version"]=2.12, + ["Type"]="program", + ["Name"]="Outraged Security .INC Signs", + ["Description"]="Downloads Sign Program from github", + ["Author"]="Outraged Security .INC", + ["Package"]="Standalone", + }, } From 677986f7d997ccaf37482e9f18f6128a6866ef4d Mon Sep 17 00:00:00 2001 From: rservices Date: Sun, 29 Dec 2019 12:19:34 -0600 Subject: [PATCH 026/125] Create startup --- signs/startup | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 signs/startup diff --git a/signs/startup b/signs/startup new file mode 100644 index 0000000..f4f24e8 --- /dev/null +++ b/signs/startup @@ -0,0 +1,19 @@ +local pos = 18 +mon = peripheral.wrap("top") +mon.clear() +mon.setBackgroundColor(32768) +mon.setTextColor(32) +mon.setTextScale(5) + +while true do + if pos==-26 then + pos = 18 + end + + mon.clear() + mon.setCursorPos(pos,1) + mon.write("Insert Security Text Here") + pos = pos-1 + + os.sleep(0.15) +end From f165695d666895de37dcd0518da255f2bfdc1cd8 Mon Sep 17 00:00:00 2001 From: rservices Date: Sun, 29 Dec 2019 12:30:16 -0600 Subject: [PATCH 027/125] Update programVersions --- programVersions | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/programVersions b/programVersions index 59066dc..3e2dd68 100644 --- a/programVersions +++ b/programVersions @@ -89,7 +89,7 @@ ["Author"]="Outraged Security .INC", ["Package"]="Standalone", }, - ["Dark Signs"]={ + ["signs"]={ ["GitURL"]="https://raw.githubusercontent.com/rservices/darkprograms/darkprograms/signs/startup", ["Version"]=2.12, ["Type"]="program", From 5a2e2e6bff1dfcfaa717c51cd7ee5268af0b8808 Mon Sep 17 00:00:00 2001 From: rservices Date: Sun, 29 Dec 2019 12:31:03 -0600 Subject: [PATCH 028/125] Update programVersions --- programVersions | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/programVersions b/programVersions index 3e2dd68..dbc871c 100644 --- a/programVersions +++ b/programVersions @@ -89,7 +89,7 @@ ["Author"]="Outraged Security .INC", ["Package"]="Standalone", }, - ["signs"]={ + ["startup"]={ ["GitURL"]="https://raw.githubusercontent.com/rservices/darkprograms/darkprograms/signs/startup", ["Version"]=2.12, ["Type"]="program", From 6d14495692b0415a6222748caf2c31b7ee349290 Mon Sep 17 00:00:00 2001 From: rservices Date: Sun, 29 Dec 2019 12:31:27 -0600 Subject: [PATCH 029/125] Update programVersions --- programVersions | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/programVersions b/programVersions index dbc871c..1547759 100644 --- a/programVersions +++ b/programVersions @@ -89,7 +89,7 @@ ["Author"]="Outraged Security .INC", ["Package"]="Standalone", }, - ["startup"]={ + ["sign"]={ ["GitURL"]="https://raw.githubusercontent.com/rservices/darkprograms/darkprograms/signs/startup", ["Version"]=2.12, ["Type"]="program", From 10c42b3394f8e00effb42c3033798eac7dbaed94 Mon Sep 17 00:00:00 2001 From: rservices Date: Sun, 29 Dec 2019 12:31:45 -0600 Subject: [PATCH 030/125] Update programVersions --- programVersions | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/programVersions b/programVersions index 1547759..b532a49 100644 --- a/programVersions +++ b/programVersions @@ -90,7 +90,7 @@ ["Package"]="Standalone", }, ["sign"]={ - ["GitURL"]="https://raw.githubusercontent.com/rservices/darkprograms/darkprograms/signs/startup", + ["GitURL"]="https://raw.githubusercontent.com/rservices/darkprograms/darkprograms/signs/sign", ["Version"]=2.12, ["Type"]="program", ["Name"]="Outraged Security .INC Signs", From 8e214e46c40da9b255760f71a5bd706d79ccdeff Mon Sep 17 00:00:00 2001 From: rservices Date: Sun, 29 Dec 2019 12:32:10 -0600 Subject: [PATCH 031/125] Rename startup to sign --- signs/{startup => sign} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename signs/{startup => sign} (100%) diff --git a/signs/startup b/signs/sign similarity index 100% rename from signs/startup rename to signs/sign From 608e0e294d49e190e0f83a27761bb1f701a59618 Mon Sep 17 00:00:00 2001 From: rservices Date: Sun, 29 Dec 2019 12:46:18 -0600 Subject: [PATCH 032/125] Add files via upload --- mbs-master.zip | Bin 0 -> 155894 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 mbs-master.zip diff --git a/mbs-master.zip b/mbs-master.zip new file mode 100644 index 0000000000000000000000000000000000000000..4d8c4f0a962acd1397e56a2a6d45f6d09a1cb0d6 GIT binary patch literal 155894 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 z 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
    0h{;ziOpNR6m7yN(r$JQ2x^#9!m|NmY8PjPu4 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 vHi#Q!W+93c5rK;cBSqmNqGn Date: Sun, 29 Dec 2019 12:54:10 -0600 Subject: [PATCH 033/125] fix fix --- Secure OS/apis/processes.lua | 194 +++++++++++++++++++ Secure OS/apis/ui.lua | 3 + Secure OS/programs/autostart/config.lua | 6 + Secure OS/programs/autostart/init.lua | 4 + Secure OS/programs/autostart/properties.lua | 3 + Secure OS/programs/desktop/init.lua | 5 + Secure OS/programs/desktop/properties.lua | 6 + Secure OS/programs/keyhandler/init.lua | 12 ++ Secure OS/programs/keyhandler/properties.lua | 4 + Secure OS/programs/menu/init.lua | 45 +++++ Secure OS/programs/menu/items/clock.lua | 6 + Secure OS/programs/menu/items/launcher.lua | 52 +++++ Secure OS/programs/menu/properties.lua | 5 + Secure OS/secure.lua | 27 +++ 14 files changed, 372 insertions(+) create mode 100644 Secure OS/apis/processes.lua create mode 100644 Secure OS/apis/ui.lua create mode 100644 Secure OS/programs/autostart/config.lua create mode 100644 Secure OS/programs/autostart/init.lua create mode 100644 Secure OS/programs/autostart/properties.lua create mode 100644 Secure OS/programs/desktop/init.lua create mode 100644 Secure OS/programs/desktop/properties.lua create mode 100644 Secure OS/programs/keyhandler/init.lua create mode 100644 Secure OS/programs/keyhandler/properties.lua create mode 100644 Secure OS/programs/menu/init.lua create mode 100644 Secure OS/programs/menu/items/clock.lua create mode 100644 Secure OS/programs/menu/items/launcher.lua create mode 100644 Secure OS/programs/menu/properties.lua create mode 100644 Secure OS/secure.lua diff --git a/Secure OS/apis/processes.lua b/Secure OS/apis/processes.lua new file mode 100644 index 0000000..9772cff --- /dev/null +++ b/Secure OS/apis/processes.lua @@ -0,0 +1,194 @@ +local userEvents = {"mouse_click", "mouse_up", "mouse_drag", "char", "key", "monitor_touch", "key_up", "paste", "terminate"} +local parentTerm = term.current() +local parentWidth, parentHeight = parentTerm.getSize() +local defaultProperties = { + x = math.ceil(parentWidth/2-parentWidth/4), + y = math.ceil(parentHeight/2-parentHeight/4), + w = math.ceil(parentWidth/2), h = math.ceil(parentHeight/2), + noWindow = false, noBar = false, noInteraction = false, + name = "Unnamed Window" +} + +local isUserEvent = function(event) + for userEventNum = 1, #userEvents do + local userEvent = userEvents[userEventNum] + if event == userEvent then + return true + end + end +end + +local createProcessCoroutine = function(func) + return coroutine.create(function() + local succ, mess = pcall(func) + if not succ and mess then + while true do + term.setBackgroundColor(colors.black) + term.setTextColor(colors.orange) + term.clear() + term.setCursorPos(1, 1) + print("The program crashed!") + write(mess) + coroutine.yield() + end + end + end) +end + +return { + drawWindowDecorations = function(self, processNum, event, var1, var2, var3) + local process = self[processNum] + if not process.noWindow and not process.noBar then + local pw, ph = process.term.getSize() + + paintutils.drawLine(process.x, process.y-1, process.x+pw-1, process.y-1, colors.lightGray) + term.setTextColor(colors.gray) + + term.setCursorPos(process.x+pw-1, process.y-1) + term.write("x") + term.setCursorPos(process.x, process.y-1) + term.write(process.name) + end + end, + handleInputOfProcess = function(self, processNum, event, var1, var2, var3) + local process = self[processNum] + if not process.noWindow and not process.noInteraction then + local px, py = process.term.getPosition() + local pw, ph = process.term.getSize() + local mx, my + if var3 then + mx, my = var2-px+1, var3-py+1 + end + + if event == "mouse_click" then + process.resizeX, process.resizeY = nil, nil + if mx>0 and my==0 and mx0 and my>0 and mx#self.name and h>3 then + local oldX, oldY = self.term.getPosition() + self.term.reposition(oldX, oldY, w, h) + os.queueEvent("term_resize") + end + end + } + + for k, v in pairs(defaultProperties) do + process[k] = properties[k] or v + end + + process.term = window.create( + parentTerm, process.x, process.y, process.w, process.h + ) + + table.insert(self, process) + end, + -- update all processes + update = function(self, event, var1, var2, var3) + for processNum, process in ipairs(self) do + self:updateProcess(processNum, event, var1, var2, var3) + end + end +} diff --git a/Secure OS/apis/ui.lua b/Secure OS/apis/ui.lua new file mode 100644 index 0000000..edc369a --- /dev/null +++ b/Secure OS/apis/ui.lua @@ -0,0 +1,3 @@ +local ui = {} + +return ui diff --git a/Secure OS/programs/autostart/config.lua b/Secure OS/programs/autostart/config.lua new file mode 100644 index 0000000..e7a5594 --- /dev/null +++ b/Secure OS/programs/autostart/config.lua @@ -0,0 +1,6 @@ +-- list of programs to autostart +return { + "desktop", + "menu", + "keyhandler" +} diff --git a/Secure OS/programs/autostart/init.lua b/Secure OS/programs/autostart/init.lua new file mode 100644 index 0000000..ce78aab --- /dev/null +++ b/Secure OS/programs/autostart/init.lua @@ -0,0 +1,4 @@ +local config = require "programs.autostart.config" +for _, programName in ipairs(config) do + processes:start(programName) +end diff --git a/Secure OS/programs/autostart/properties.lua b/Secure OS/programs/autostart/properties.lua new file mode 100644 index 0000000..32328e3 --- /dev/null +++ b/Secure OS/programs/autostart/properties.lua @@ -0,0 +1,3 @@ +return { + noWindow = true +} diff --git a/Secure OS/programs/desktop/init.lua b/Secure OS/programs/desktop/init.lua new file mode 100644 index 0000000..b83df36 --- /dev/null +++ b/Secure OS/programs/desktop/init.lua @@ -0,0 +1,5 @@ +while true do + term.setBackgroundColor(colors.white) + term.clear() + coroutine.yield() +end diff --git a/Secure OS/programs/desktop/properties.lua b/Secure OS/programs/desktop/properties.lua new file mode 100644 index 0000000..eec7ea5 --- /dev/null +++ b/Secure OS/programs/desktop/properties.lua @@ -0,0 +1,6 @@ +local w, h = term.native().getSize() +return { + x = 1, y = 1, + w = w, h = h, + noBar = true, noMove = true +} diff --git a/Secure OS/programs/keyhandler/init.lua b/Secure OS/programs/keyhandler/init.lua new file mode 100644 index 0000000..878bcf7 --- /dev/null +++ b/Secure OS/programs/keyhandler/init.lua @@ -0,0 +1,12 @@ +keyboard = {} + +while true do + local event, key = os.pullEvent() + if event == "char" then + keyboard.lastChar = key + elseif event == "key" then + keyboard[keys.getName(key)] = true + elseif event == "key_up" then + keyboard[keys.getName(key)] = false + end +end diff --git a/Secure OS/programs/keyhandler/properties.lua b/Secure OS/programs/keyhandler/properties.lua new file mode 100644 index 0000000..8039511 --- /dev/null +++ b/Secure OS/programs/keyhandler/properties.lua @@ -0,0 +1,4 @@ +return { + x = 1, y = 3, w = 20, h = 2, + noWindow = true +} diff --git a/Secure OS/programs/menu/init.lua b/Secure OS/programs/menu/init.lua new file mode 100644 index 0000000..9152d1b --- /dev/null +++ b/Secure OS/programs/menu/init.lua @@ -0,0 +1,45 @@ +local itemNames = fs.list("/programs/menu/items") +local items = {} +local nextX = 2 + +for _, itemName in ipairs(itemNames) do + local itemFunc = loadfile("/programs/menu/items/"..itemName, _ENV) + local item = itemFunc() + local itemWindow = window.create(term.current(), nextX, 1, item.w, 1) + + table.insert(items, { + term = itemWindow, + update = function(self, event, var1, var2, var3) + local oldTerm = term.redirect(self.term) + + term.setBackgroundColor(colors.lightGray) + term.setTextColor(colors.black) + term.clear() + term.setCursorPos(1, 1) + item.update(event, var1, var2, var3) + + term.redirect(oldTerm) + end + }) + + nextX = nextX+item.w+1 +end + +while true do + local event, var1, var2, var3 = os.pullEvent() + + + term.setBackgroundColor(colors.lightGray) + term.clear() + term.setCursorPos(1, 1) + for _, item in ipairs(items) do + local var1, var2, var3 = var1, var2, var3 + + if string.sub(event, 1, #"mouse") == "mouse" then + local x, y = item.term.getPosition() + var2 = var2-x+1 + var3 = var3-y+1 + end + item:update(event, var1, var2, var3) + end +end diff --git a/Secure OS/programs/menu/items/clock.lua b/Secure OS/programs/menu/items/clock.lua new file mode 100644 index 0000000..ff94c56 --- /dev/null +++ b/Secure OS/programs/menu/items/clock.lua @@ -0,0 +1,6 @@ +return { + w = 5, + update = function() + term.write(textutils.formatTime(os.time())) + end +} diff --git a/Secure OS/programs/menu/items/launcher.lua b/Secure OS/programs/menu/items/launcher.lua new file mode 100644 index 0000000..6440d6f --- /dev/null +++ b/Secure OS/programs/menu/items/launcher.lua @@ -0,0 +1,52 @@ +local programs = { + { + exec = "worm", + icon = {"\141\136", "de", "ff"} + }, + { + exec = "paint image", + icon = {"\152\135", "ce", "ff"} + }, + { + exec = "edit text", + icon = {"ed", "44", "ff"} + }, + { + exec = "shell", + icon = {">_", "40", "ff"} + }, + { + exec = "redirection", + icon = {"\136\133", "b8", "77"} + }, + { + exec = "adventure", + icon = {"?_", "40", "ff"} + }, + { + exec = "lua", + icon = {"lu", "00", "ff"} + }, + { + exec = "chat join lost_chat "..os.getComputerID(), + icon = {"\2\19", "00", "ff"} + }, + { + exec = "chat host lost_chat", + icon = {"\16\19", "00", "ff"} + } +} + +return { + w = #programs*3, + update = function(event, var1, var2, var3) + for programNum, program in ipairs(programs) do + + if event == "mouse_click" and var3 == 1 and (var2 == (programNum-1)*3+1 or var2 == (programNum-1)*3+2) then + processes:run(program.exec) + end + term.blit(program.icon[1], program.icon[2], program.icon[3]) + term.write(" ") + end + end +} diff --git a/Secure OS/programs/menu/properties.lua b/Secure OS/programs/menu/properties.lua new file mode 100644 index 0000000..e0c063e --- /dev/null +++ b/Secure OS/programs/menu/properties.lua @@ -0,0 +1,5 @@ +return { + x = 1, y = 1, + w = term.native().getSize(), h = 1, + noBar = true, noMove = true, noResize = true +} diff --git a/Secure OS/secure.lua b/Secure OS/secure.lua new file mode 100644 index 0000000..485cf0e --- /dev/null +++ b/Secure OS/secure.lua @@ -0,0 +1,27 @@ +local buffer = window.create(term.current(), 1, 1, term.getSize()) +local oldTerm = term.redirect(buffer) +local succ, mess = pcall(function() +-- intended global, so every program can modify the processes +processes = require "apis.processes" +processes:start("autostart") + +while true do + -- update + local timer = os.startTimer(0.001) + local event, var1, var2, var3 = os.pullEventRaw() + os.cancelTimer(timer) + + buffer.setVisible(false) + processes:update(event, var1, var2, var3) + buffer.setVisible(true) +end +end) + +term.setBackgroundColor(colors.black) +term.setTextColor(colors.white) +term.clear() +term.setCursorPos(1, 1) +if not succ and mess then + term.setTextColor(colors.orange) + print(mess) +end From 619989ad895aa7ed4ae278791814dc07bf1c0bfc Mon Sep 17 00:00:00 2001 From: rservices Date: Sun, 8 Mar 2020 09:39:00 -0600 Subject: [PATCH 034/125] Create mineos --- mineos | 1 + 1 file changed, 1 insertion(+) create mode 100644 mineos diff --git a/mineos b/mineos new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/mineos @@ -0,0 +1 @@ + From d00644f290edd697d754450a23a623f3f1a45c5b Mon Sep 17 00:00:00 2001 From: rservices Date: Sun, 8 Mar 2020 09:42:21 -0600 Subject: [PATCH 035/125] Update programVersions --- programVersions | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/programVersions b/programVersions index b532a49..2932fb2 100644 --- a/programVersions +++ b/programVersions @@ -98,4 +98,13 @@ ["Author"]="Outraged Security .INC", ["Package"]="Standalone", }, + ["sign"]={ + ["GitURL"]="https://raw.githubusercontent.com/rservices/darkprograms/darkprograms/mineos/setup", + ["Version"]=2.12, + ["Type"]="program", + ["Name"]="Outraged Security .INC MineOS Secure OS", + ["Description"]="Downloads MineOS Program from github", + ["Author"]="Outraged Security .INC", + ["Package"]="Standalone", + }, } From 933aece5eefd493a1eeda5e5b2cc54dd356da830 Mon Sep 17 00:00:00 2001 From: rservices Date: Sun, 8 Mar 2020 09:57:57 -0600 Subject: [PATCH 036/125] Mineos mineos --- mineos | 1 - mineos/setup | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 1 deletion(-) delete mode 100644 mineos create mode 100644 mineos/setup diff --git a/mineos b/mineos deleted file mode 100644 index 8b13789..0000000 --- a/mineos +++ /dev/null @@ -1 +0,0 @@ - diff --git a/mineos/setup b/mineos/setup new file mode 100644 index 0000000..aaad499 --- /dev/null +++ b/mineos/setup @@ -0,0 +1,54 @@ +Nr = "" + +shell.run("mkdir MineOS") +shell.run("cd MineOS") +term.setBackgroundColor(8) +term.setTextColor(1) +shell.run("clear") + + + +shell.run("rm kernel") +shell.run("pastebin get mbrjKBv5 kernel") + + + +shell.run("mkdir Images") +shell.run("cd Images") +shell.run("rm background.image") +shell.run("pastebin get nzuZuewk background.image") + +shell.run("cd /") +shell.run("rm startup") +shell.run("pastebin get KRGvFsqQ startup") + +term.setCursorPos(1,19) +print("Rebooting in 5") +sleep(1) +term.setCursorPos(1,19) +print("Rebooting in 4") +sleep(1) +term.setCursorPos(1,19) +print("Rebooting in 3") +sleep(1) +term.setCursorPos(1,19) +print("Rebooting in 2") +sleep(1) +term.setCursorPos(1,19) +print("Rebooting in 1") +shell.run("reboot") + + + + +--Codes +-- +--term.setCursorPos +--print("") +--textutils.slowPrint("") +--edit +--shell.run("") +--shell.run("clear") +--sleep() +--term.setBackgroundColor +--term.setTextColor \ No newline at end of file From 9938dd09f1db971d3202858f127c00c5352f2567 Mon Sep 17 00:00:00 2001 From: rservices Date: Sun, 8 Mar 2020 09:59:13 -0600 Subject: [PATCH 037/125] setup --- mineos/{setup => setup1} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename mineos/{setup => setup1} (100%) diff --git a/mineos/setup b/mineos/setup1 similarity index 100% rename from mineos/setup rename to mineos/setup1 From f5a9c97635d3d8abda6d725b0a3c137bec62ed7c Mon Sep 17 00:00:00 2001 From: rservices Date: Sun, 8 Mar 2020 09:59:39 -0600 Subject: [PATCH 038/125] Revert "setup" This reverts commit 9938dd09f1db971d3202858f127c00c5352f2567. --- mineos/{setup1 => setup} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename mineos/{setup1 => setup} (100%) diff --git a/mineos/setup1 b/mineos/setup similarity index 100% rename from mineos/setup1 rename to mineos/setup From fb22435d48c1d19cc36cb41648796b61e9bf68f5 Mon Sep 17 00:00:00 2001 From: rservices Date: Sun, 8 Mar 2020 10:02:32 -0600 Subject: [PATCH 039/125] Update programVersions --- programVersions | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/programVersions b/programVersions index 2932fb2..4132f98 100644 --- a/programVersions +++ b/programVersions @@ -98,7 +98,7 @@ ["Author"]="Outraged Security .INC", ["Package"]="Standalone", }, - ["sign"]={ + ["setup"]={ ["GitURL"]="https://raw.githubusercontent.com/rservices/darkprograms/darkprograms/mineos/setup", ["Version"]=2.12, ["Type"]="program", From 6bb5aae98e91f19ecc79e131a146e4b31df91645 Mon Sep 17 00:00:00 2001 From: rservices Date: Tue, 10 Mar 2020 07:22:33 -0600 Subject: [PATCH 040/125] Create SecureDoor --- SecurePdaDoor/SecureDoor | 1348 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 1348 insertions(+) create mode 100644 SecurePdaDoor/SecureDoor diff --git a/SecurePdaDoor/SecureDoor b/SecurePdaDoor/SecureDoor new file mode 100644 index 0000000..1387970 --- /dev/null +++ b/SecurePdaDoor/SecureDoor @@ -0,0 +1,1348 @@ +tArgs = {...} + +if OneOS then + --running under OneOS + OneOS.ToolBarColour = colours.white + OneOS.ToolBarTextColour = colours.grey +end + +local _w, _h = term.getSize() + +local round = function(num, idp) + local mult = 10^(idp or 0) + return math.floor(num * mult + 0.5) / mult +end + +InterfaceElements = {} + +Drawing = { + + Screen = { + Width = _w, + Height = _h + }, + + DrawCharacters = function (x, y, characters, textColour,bgColour) + Drawing.WriteStringToBuffer(x, y, characters, textColour, bgColour) + end, + + DrawBlankArea = function (x, y, w, h, colour) + Drawing.DrawArea (x, y, w, h, " ", 1, colour) + end, + + DrawArea = function (x, y, w, h, character, textColour, bgColour) + --width must be greater than 1, other wise we get a stack overflow + if w < 0 then + w = w * -1 + elseif w == 0 then + w = 1 + end + + for ix = 1, w do + local currX = x + ix - 1 + for iy = 1, h do + local currY = y + iy - 1 + Drawing.WriteToBuffer(currX, currY, character, textColour, bgColour) + end + end + end, + + DrawImage = function(_x,_y,tImage, w, h) + if tImage then + for y = 1, h do + if not tImage[y] then + break + end + for x = 1, w do + if not tImage[y][x] then + break + end + local bgColour = tImage[y][x] + local textColour = tImage.textcol[y][x] or colours.white + local char = tImage.text[y][x] + Drawing.WriteToBuffer(x+_x-1, y+_y-1, char, textColour, bgColour) + end + end + elseif w and h then + Drawing.DrawBlankArea(x, y, w, h, colours.green) + end + end, + --using .nft + LoadImage = function(path) + local image = { + text = {}, + textcol = {} + } + local fs = fs + if OneOS then + fs = OneOS.FS + end + if fs.exists(path) then + local _open = io.open + if OneOS then + _open = OneOS.IO.open + end + local file = _open(path, "r") + local sLine = file:read() + local num = 1 + while sLine do + table.insert(image, num, {}) + table.insert(image.text, num, {}) + table.insert(image.textcol, num, {}) + + --As we're no longer 1-1, we keep track of what index to write to + local writeIndex = 1 + --Tells us if we've hit a 30 or 31 (BG and FG respectively)- next char specifies the curr colour + local bgNext, fgNext = false, false + --The current background and foreground colours + local currBG, currFG = nil,nil + for i=1,#sLine do + local nextChar = string.sub(sLine, i, i) + if nextChar:byte() == 30 then + bgNext = true + elseif nextChar:byte() == 31 then + fgNext = true + elseif bgNext then + currBG = Drawing.GetColour(nextChar) + bgNext = false + elseif fgNext then + currFG = Drawing.GetColour(nextChar) + fgNext = false + else + if nextChar ~= " " and currFG == nil then + currFG = colours.white + end + image[num][writeIndex] = currBG + image.textcol[num][writeIndex] = currFG + image.text[num][writeIndex] = nextChar + writeIndex = writeIndex + 1 + end + end + num = num+1 + sLine = file:read() + end + file:close() + end + return image + end, + + DrawCharactersCenter = function(x, y, w, h, characters, textColour,bgColour) + w = w or Drawing.Screen.Width + h = h or Drawing.Screen.Height + x = x or 0 + y = y or 0 + x = math.ceil((w - #characters) / 2) + x + y = math.floor(h / 2) + y + + Drawing.DrawCharacters(x, y, characters, textColour, bgColour) + end, + + GetColour = function(hex) + if hex == ' ' then + return colours.transparent + end + local value = tonumber(hex, 16) + if not value then return nil end + value = math.pow(2,value) + return value + end, + + Clear = function (_colour) + _colour = _colour or colours.black + Drawing.ClearBuffer() + Drawing.DrawBlankArea(1, 1, Drawing.Screen.Width, Drawing.Screen.Height, _colour) + end, + + Buffer = {}, + BackBuffer = {}, + + DrawBuffer = function() + for y,row in pairs(Drawing.Buffer) do + for x,pixel in pairs(row) do + local shouldDraw = true + local hasBackBuffer = true + if Drawing.BackBuffer[y] == nil or Drawing.BackBuffer[y][x] == nil or #Drawing.BackBuffer[y][x] ~= 3 then + hasBackBuffer = false + end + if hasBackBuffer and Drawing.BackBuffer[y][x][1] == Drawing.Buffer[y][x][1] and Drawing.BackBuffer[y][x][2] == Drawing.Buffer[y][x][2] and Drawing.BackBuffer[y][x][3] == Drawing.Buffer[y][x][3] then + shouldDraw = false + end + if shouldDraw then + term.setBackgroundColour(pixel[3]) + term.setTextColour(pixel[2]) + term.setCursorPos(x, y) + term.write(pixel[1]) + end + end + end + Drawing.BackBuffer = Drawing.Buffer + Drawing.Buffer = {} + term.setCursorPos(1,1) + end, + + ClearBuffer = function() + Drawing.Buffer = {} + end, + + WriteStringToBuffer = function (x, y, characters, textColour,bgColour) + for i = 1, #characters do + local character = characters:sub(i,i) + Drawing.WriteToBuffer(x + i - 1, y, character, textColour, bgColour) + end + end, + + WriteToBuffer = function(x, y, character, textColour,bgColour) + x = round(x) + y = round(y) + if bgColour == colours.transparent then + Drawing.Buffer[y] = Drawing.Buffer[y] or {} + Drawing.Buffer[y][x] = Drawing.Buffer[y][x] or {"", colours.white, colours.black} + Drawing.Buffer[y][x][1] = character + Drawing.Buffer[y][x][2] = textColour + else + Drawing.Buffer[y] = Drawing.Buffer[y] or {} + Drawing.Buffer[y][x] = {character, textColour, bgColour} + end + end, +} + +Current = { + Document = nil, + TextInput = nil, + CursorPos = {1,1}, + CursorColour = colours.black, + Selection = {8, 36}, + Window = nil, + HeaderText = '', + StatusText = '', + StatusColour = colours.grey, + StatusScreen = true, + ButtonOne = nil, + ButtonTwo = nil, + Locked = false, + Page = '', + PageControls = {} +} + +isRunning = true + +Events = {} + +Button = { + X = 1, + Y = 1, + Width = 0, + Height = 0, + BackgroundColour = colours.lightGrey, + TextColour = colours.white, + ActiveBackgroundColour = colours.lightGrey, + Text = "", + Parent = nil, + _Click = nil, + Toggle = nil, + + AbsolutePosition = function(self) + return self.Parent:AbsolutePosition() + end, + + Draw = function(self) + local bg = self.BackgroundColour + local tc = self.TextColour + if type(bg) == 'function' then + bg = bg() + end + + if self.Toggle then + tc = colours.white + bg = self.ActiveBackgroundColour + end + + local pos = GetAbsolutePosition(self) + Drawing.DrawBlankArea(pos.X, pos.Y, self.Width, self.Height, bg) + Drawing.DrawCharactersCenter(pos.X, pos.Y, self.Width, self.Height, self.Text, tc, bg) + end, + + Initialise = function(self, x, y, width, height, backgroundColour, parent, click, text, textColour, toggle, activeBackgroundColour) + local new = {} -- the new instance + setmetatable( new, {__index = self} ) + height = height or 1 + new.Width = width or #text + 2 + new.Height = height + new.Y = y + new.X = x + new.Text = text or "" + new.BackgroundColour = backgroundColour or colours.lightGrey + new.TextColour = textColour or colours.white + new.ActiveBackgroundColour = activeBackgroundColour or colours.lightBlue + new.Parent = parent + new._Click = click + new.Toggle = toggle + return new + end, + + Click = function(self, side, x, y) + if self._Click then + if self:_Click(side, x, y, not self.Toggle) ~= false and self.Toggle ~= nil then + self.Toggle = not self.Toggle + Draw() + end + return true + else + return false + end + end +} + +Label = { + X = 1, + Y = 1, + Width = 0, + Height = 0, + BackgroundColour = colours.lightGrey, + TextColour = colours.white, + Text = "", + Parent = nil, + + AbsolutePosition = function(self) + return self.Parent:AbsolutePosition() + end, + + Draw = function(self) + local bg = self.BackgroundColour + local tc = self.TextColour + + if self.Toggle then + tc = UIColours.MenuBarActive + bg = self.ActiveBackgroundColour + end + + local pos = GetAbsolutePosition(self) + Drawing.DrawCharacters(pos.X, pos.Y, self.Text, self.TextColour, self.BackgroundColour) + end, + + Initialise = function(self, x, y, text, textColour, backgroundColour, parent) + local new = {} -- the new instance + setmetatable( new, {__index = self} ) + height = height or 1 + new.Width = width or #text + 2 + new.Height = height + new.Y = y + new.X = x + new.Text = text or "" + new.BackgroundColour = backgroundColour or colours.white + new.TextColour = textColour or colours.black + new.Parent = parent + return new + end, + + Click = function(self, side, x, y) + return false + end +} + +TextBox = { + X = 1, + Y = 1, + Width = 0, + Height = 0, + BackgroundColour = colours.lightGrey, + TextColour = colours.black, + Parent = nil, + TextInput = nil, + Placeholder = '', + + AbsolutePosition = function(self) + return self.Parent:AbsolutePosition() + end, + + Draw = function(self) + local pos = GetAbsolutePosition(self) + Drawing.DrawBlankArea(pos.X, pos.Y, self.Width, self.Height, self.BackgroundColour) + local text = self.TextInput.Value + if #tostring(text) > (self.Width - 2) then + text = text:sub(#text-(self.Width - 3)) + if Current.TextInput == self.TextInput then + Current.CursorPos = {pos.X + 1 + self.Width-2, pos.Y} + end + else + if Current.TextInput == self.TextInput then + Current.CursorPos = {pos.X + 1 + self.TextInput.CursorPos, pos.Y} + end + end + + if #tostring(text) == 0 then + Drawing.DrawCharacters(pos.X + 1, pos.Y, self.Placeholder, colours.lightGrey, self.BackgroundColour) + else + Drawing.DrawCharacters(pos.X + 1, pos.Y, text, self.TextColour, self.BackgroundColour) + end + + term.setCursorBlink(true) + + Current.CursorColour = self.TextColour + end, + + Initialise = function(self, x, y, width, height, parent, text, backgroundColour, textColour, done, numerical) + local new = {} -- the new instance + setmetatable( new, {__index = self} ) + height = height or 1 + new.Width = width or #text + 2 + new.Height = height + new.Y = y + new.X = x + new.TextInput = TextInput:Initialise(text or '', function(key) + if done then + done(key) + end + Draw() + end, numerical) + new.BackgroundColour = backgroundColour or colours.lightGrey + new.TextColour = textColour or colours.black + new.Parent = parent + return new + end, + + Click = function(self, side, x, y) + Current.Input = self.TextInput + self:Draw() + end +} + +TextInput = { + Value = "", + Change = nil, + CursorPos = nil, + Numerical = false, + IsDocument = nil, + + Initialise = function(self, value, change, numerical, isDocument) + local new = {} -- the new instance + setmetatable( new, {__index = self} ) + new.Value = tostring(value) + new.Change = change + new.CursorPos = #tostring(value) + new.Numerical = numerical + new.IsDocument = isDocument or false + return new + end, + + Insert = function(self, str) + if self.Numerical then + str = tostring(tonumber(str)) + end + + local selection = OrderSelection() + + if self.IsDocument and selection then + self.Value = string.sub(self.Value, 1, selection[1]-1) .. str .. string.sub( self.Value, selection[2]+2) + self.CursorPos = selection[1] + Current.Selection = nil + else + local _, newLineAdjust = string.gsub(self.Value:sub(1, self.CursorPos), '\n','') + + self.Value = string.sub(self.Value, 1, self.CursorPos + newLineAdjust) .. str .. string.sub( self.Value, self.CursorPos + 1 + newLineAdjust) + self.CursorPos = self.CursorPos + 1 + end + + self.Change(key) + end, + + Extract = function(self, remove) + local selection = OrderSelection() + if self.IsDocument and selection then + local _, newLineAdjust = string.gsub(self.Value:sub(selection[1], selection[2]), '\n','') + local str = string.sub(self.Value, selection[1], selection[2]+1+newLineAdjust) + if remove then + self.Value = string.sub(self.Value, 1, selection[1]-1) .. string.sub( self.Value, selection[2]+2+newLineAdjust) + self.CursorPos = selection[1] - 1 + Current.Selection = nil + end + return str + end + end, + + Char = function(self, char) + if char == 'nil' then + return + end + self:Insert(char) + end, + + Key = function(self, key) + if key == keys.enter then + if self.IsDocument then + self.Value = string.sub(self.Value, 1, self.CursorPos ) .. '\n' .. string.sub( self.Value, self.CursorPos + 1 ) + self.CursorPos = self.CursorPos + 1 + end + self.Change(key) + elseif key == keys.left then + -- Left + if self.CursorPos > 0 then + local colShift = FindColours(string.sub( self.Value, self.CursorPos, self.CursorPos)) + self.CursorPos = self.CursorPos - 1 - colShift + self.Change(key) + end + + elseif key == keys.right then + -- Right + if self.CursorPos < string.len(self.Value) then + local colShift = FindColours(string.sub( self.Value, self.CursorPos+1, self.CursorPos+1)) + self.CursorPos = self.CursorPos + 1 + colShift + self.Change(key) + end + + elseif key == keys.backspace then + -- Backspace + if self.IsDocument and Current.Selection then + self:Extract(true) + self.Change(key) + elseif self.CursorPos > 0 then + local colShift = FindColours(string.sub( self.Value, self.CursorPos, self.CursorPos)) + local _, newLineAdjust = string.gsub(self.Value:sub(1, self.CursorPos), '\n','') + + self.Value = string.sub( self.Value, 1, self.CursorPos - 1 - colShift + newLineAdjust) .. string.sub( self.Value, self.CursorPos + 1 - colShift + newLineAdjust) + self.CursorPos = self.CursorPos - 1 - colShift + self.Change(key) + end + elseif key == keys.home then + -- Home + self.CursorPos = 0 + self.Change(key) + elseif key == keys.delete then + if self.IsDocument and Current.Selection then + self:Extract(true) + self.Change(key) + elseif self.CursorPos < string.len(self.Value) then + self.Value = string.sub( self.Value, 1, self.CursorPos ) .. string.sub( self.Value, self.CursorPos + 2 ) + self.Change(key) + end + elseif key == keys["end"] then + -- End + self.CursorPos = string.len(self.Value) + self.Change(key) + elseif key == keys.up and self.IsDocument then + -- Up + if Current.Document.CursorPos then + local page = Current.Document.Pages[Current.Document.CursorPos.Page] + self.CursorPos = page:GetCursorPosFromPoint(Current.Document.CursorPos.Collum + page.MarginX, Current.Document.CursorPos.Line - page.MarginY - 1 + Current.Document.ScrollBar.Scroll, true) + self.Change(key) + end + elseif key == keys.down and self.IsDocument then + -- Down + if Current.Document.CursorPos then + local page = Current.Document.Pages[Current.Document.CursorPos.Page] + self.CursorPos = page:GetCursorPosFromPoint(Current.Document.CursorPos.Collum + page.MarginX, Current.Document.CursorPos.Line - page.MarginY + 1 + Current.Document.ScrollBar.Scroll, true) + self.Change(key) + end + end + end +} + +local Capitalise = function(str) + return str:sub(1, 1):upper() .. str:sub(2, -1) +end + + +local getNames = peripheral.getNames or function() + local tResults = {} + for n,sSide in ipairs( rs.getSides() ) do + if peripheral.isPresent( sSide ) then + table.insert( tResults, sSide ) + local isWireless = false + if not pcall(function()isWireless = peripheral.call(sSide, 'isWireless') end) then + isWireless = true + end + if peripheral.getType( sSide ) == "modem" and not isWireless then + local tRemote = peripheral.call( sSide, "getNamesRemote" ) + for n,sName in ipairs( tRemote ) do + table.insert( tResults, sName ) + end + end + end + end + return tResults +end + +Peripheral = { + GetPeripheral = function(_type) + for i, p in ipairs(Peripheral.GetPeripherals()) do + if p.Type == _type then + return p + end + end + end, + + Call = function(type, ...) + local tArgs = {...} + local p = Peripheral.GetPeripheral(type) + peripheral.call(p.Side, unpack(tArgs)) + end, + + GetPeripherals = function(filterType) + local peripherals = {} + for i, side in ipairs(getNames()) do + local name = peripheral.getType(side):gsub("^%l", string.upper) + local code = string.upper(side:sub(1,1)) + if side:find('_') then + code = side:sub(side:find('_')+1) + end + + local dupe = false + for i, v in ipairs(peripherals) do + if v[1] == name .. ' ' .. code then + dupe = true + end + end + + if not dupe then + local _type = peripheral.getType(side) + local isWireless = false + if _type == 'modem' then + if not pcall(function()isWireless = peripheral.call(sSide, 'isWireless') end) then + isWireless = true + end + if isWireless then + _type = 'wireless_modem' + name = 'W '..name + end + end + if not filterType or _type == filterType then + table.insert(peripherals, {Name = name:sub(1,8) .. ' '..code, Fullname = name .. ' ('..side:sub(1, 1):upper() .. side:sub(2, -1)..')', Side = side, Type = _type, Wireless = isWireless}) + end + end + end + return peripherals + end, + + PresentNamed = function(name) + return peripheral.isPresent(name) + end, + + CallType = function(type, ...) + local tArgs = {...} + local p = Peripheral.GetPeripheral(type) + return peripheral.call(p.Side, unpack(tArgs)) + end, + + CallNamed = function(name, ...) + local tArgs = {...} + return peripheral.call(name, unpack(tArgs)) + end +} + +Wireless = { + Channels = { + UltimateDoorlockPing = 4210, + UltimateDoorlockRequest = 4211, + UltimateDoorlockRequestReply = 4212, + }, + + isOpen = function(channel) + return Peripheral.CallType('wireless_modem', 'isOpen', channel) + end, + + Open = function(channel) + if not Wireless.isOpen(channel) then + Peripheral.CallType('wireless_modem', 'open', channel) + end + end, + + close = function(channel) + Peripheral.CallType('wireless_modem', 'close', channel) + end, + + closeAll = function() + Peripheral.CallType('wireless_modem', 'closeAll') + end, + + transmit = function(channel, replyChannel, message) + Peripheral.CallType('wireless_modem', 'transmit', channel, replyChannel, textutils.serialize(message)) + end, + + Present = function() + if Peripheral.GetPeripheral('wireless_modem') == nil then + return false + else + return true + end + end, + + FormatMessage = function(message, messageID, destinationID) + return { + content = textutils.serialize(message), + senderID = os.getComputerID(), + senderName = os.getComputerLabel(), + channel = channel, + replyChannel = reply, + messageID = messageID or math.random(10000), + destinationID = destinationID + } + end, + + Timeout = function(func, time) + time = time or 1 + parallel.waitForAny(func, function() + sleep(time) + --log('Timeout!'..time) + end) + end, + + RecieveMessage = function(_channel, messageID, timeout) + open(_channel) + local done = false + local event, side, channel, replyChannel, message = nil + Timeout(function() + while not done do + event, side, channel, replyChannel, message = os.pullEvent('modem_message') + if channel ~= _channel then + event, side, channel, replyChannel, message = nil + else + message = textutils.unserialize(message) + message.content = textutils.unserialize(message.content) + if messageID and messageID ~= message.messageID or (message.destinationID ~= nil and message.destinationID ~= os.getComputerID()) then + event, side, channel, replyChannel, message = nil + else + done = true + end + end + end + end, + timeout) + return event, side, channel, replyChannel, message + end, + + Initialise = function() + if Wireless.Present() then + for i, c in pairs(Wireless.Channels) do + Wireless.Open(c) + end + end + end, + + HandleMessage = function(event, side, channel, replyChannel, message, distance) + message = textutils.unserialize(message) + message.content = textutils.unserialize(message.content) + + if channel == Wireless.Channels.Ping then + if message.content == 'Ping!' then + SendMessage(replyChannel, 'Pong!', nil, message.messageID) + end + elseif message.destinationID ~= nil and message.destinationID ~= os.getComputerID() then + elseif Wireless.Responder then + Wireless.Responder(event, side, channel, replyChannel, message, distance) + end + end, + + SendMessage = function(channel, message, reply, messageID, destinationID) + reply = reply or channel + 1 + Wireless.Open(channel) + Wireless.Open(reply) + local _message = Wireless.FormatMessage(message, messageID, destinationID) + Wireless.transmit(channel, reply, _message) + return _message + end, + + Ping = function() + local message = SendMessage(Channels.Ping, 'Ping!', Channels.PingReply) + RecieveMessage(Channels.PingReply, message.messageID) + end +} + +function GetAbsolutePosition(object) + local obj = object + local i = 0 + local x = 1 + local y = 1 + while true do + x = x + obj.X - 1 + y = y + obj.Y - 1 + + if not obj.Parent then + return {X = x, Y = y} + end + + obj = obj.Parent + + if i > 32 then + return {X = 1, Y = 1} + end + + i = i + 1 + end + +end + +function Draw() + Drawing.Clear(colours.white) + + if Current.StatusScreen then + Drawing.DrawCharactersCenter(1, -2, nil, nil, Current.HeaderText, colours.blue, colours.white) + Drawing.DrawCharactersCenter(1, -1, nil, nil, 'by oeed', colours.lightGrey, colours.white) + Drawing.DrawCharactersCenter(1, 1, nil, nil, Current.StatusText, Current.StatusColour, colours.white) + end + + if Current.ButtonOne then + Current.ButtonOne:Draw() + end + + if Current.ButtonTwo then + Current.ButtonTwo:Draw() + end + + for i, v in ipairs(Current.PageControls) do + v:Draw() + end + + Drawing.DrawBuffer() + + if Current.TextInput and Current.CursorPos and not Current.Menu and not(Current.Window and Current.Document and Current.TextInput == Current.Document.TextInput) and Current.CursorPos[2] > 1 then + term.setCursorPos(Current.CursorPos[1], Current.CursorPos[2]) + term.setCursorBlink(true) + term.setTextColour(Current.CursorColour) + else + term.setCursorBlink(false) + end +end +MainDraw = Draw + +function GenerateFingerprint() + local str = "" + for _ = 1, 256 do + local char = math.random(32, 126) + --if char == 96 then char = math.random(32, 95) end + str = str .. string.char(char) + end + return str +end + +function MakeFingerprint() + local h = fs.open('.fingerprint', 'w') + if h then + h.write(GenerateFingerprint()) + end + h.close() + Current.Fingerprint = str +end + +local drawTimer = nil +function SetText(header, status, colour, isReset) + if header then + Current.HeaderText = header + end + if status then + Current.StatusText = status + end + if colour then + Current.StatusColour = colour + end + Draw() + if not isReset then + statusResetTimer = os.startTimer(2) + end +end + +function ResetStatus() + if pocket then + if Current.Locked then + SetText('Ultimate Door Lock', 'Add Wireless Modem to PDA', colours.red, true) + else + SetText('Ultimate Door Lock', 'Ready', colours.grey, true) + end + else + if Current.Locked then + SetText('Ultimate Door Lock', ' Attach a Wireless Modem then reboot', colours.red, true) + else + SetText('Ultimate Door Lock', 'Ready', colours.grey, true) + end + end +end + +function ResetPage() + Wireless.Responder = function()end + pingTimer = nil + Current.PageControls = nil + Current.StatusScreen = false + Current.ButtonOne = nil + Current.ButtonTwo = nil + Current.PageControls = {} + CloseDoor() +end + +function PocketInitialise() + Current.ButtonOne = Button:Initialise(Drawing.Screen.Width - 6, Drawing.Screen.Height - 1, nil, nil, nil, nil, Quit, 'Quit', colours.black) + if not Wireless.Present() then + Current.Locked = true + ResetStatus() + return + end + Wireless.Initialise() + ResetStatus() + if fs.exists('.fingerprint') then + local h = fs.open('.fingerprint', 'r') + if h then + Current.Fingerprint = h.readAll() + else + MakeFingerprint() + end + h.close() + else + MakeFingerprint() + end + + Wireless.Responder = function(event, side, channel, replyChannel, message, distance) + if channel == Wireless.Channels.UltimateDoorlockPing then + Wireless.SendMessage(Wireless.Channels.UltimateDoorlockRequest, Current.Fingerprint, Wireless.Channels.UltimateDoorlockRequestReply, nil, message.senderID) + elseif channel == Wireless.Channels.UltimateDoorlockRequestReply then + if message.content == true then + SetText(nil, 'Opening Door', colours.green) + else + SetText(nil, ' Access Denied', colours.red) + end + end + end +end + +function FingerprintIsOnWhitelist(fingerprint) + if Current.Settings.Whitelist then + for i, f in ipairs(Current.Settings.Whitelist) do + if f == fingerprint then + return true + end + end + end + return false +end + +function SaveSettings() + Current.Settings = Current.Settings or {} + local h = fs.open('.settings', 'w') + if h then + h.write(textutils.serialize(Current.Settings)) + end + h.close() +end + +local closeDoorTimer = nil +function OpenDoor() + if Current.Settings and Current.Settings.RedstoneSide then + SetText(nil, 'Opening Door', colours.green) + redstone.setOutput(Current.Settings.RedstoneSide, true) + closeDoorTimer = os.startTimer(0.6) + end +end + +function CloseDoor() + if Current.Settings and Current.Settings.RedstoneSide then + if redstone.getOutput(Current.Settings.RedstoneSide) then + SetText(nil, 'Closing Door', colours.orange) + redstone.setOutput(Current.Settings.RedstoneSide, false) + end + end +end + +DefaultSettings = { + Whitelist = {}, + RedstoneSide = 'back', + Distance = 10 +} + +function RegisterPDA(event, drive) + if disk.hasData(drive) then + local _fs = fs + if OneOS then + _fs = OneOS.FS + end + local path = disk.getMountPath(drive) + local addStartup = true + if _fs.exists(path..'/System/') then + path = path..'/System/' + addStartup = false + end + local fingerprint = nil + if _fs.exists(path..'/.fingerprint') then + local h = _fs.open(path..'/.fingerprint', 'r') + if h then + local str = h.readAll() + if #str == 256 then + fingerprint = str + end + end + h.close() + end + if not fingerprint then + fingerprint = GenerateFingerprint() + local h = _fs.open(path..'/.fingerprint', 'w') + h.write(fingerprint) + h.close() + if addStartup then + local h = fs.open(shell.getRunningProgram(), 'r') + local startup = h.readAll() + h.close() + local h = _fs.open(path..'/startup', 'w') + h.write(startup) + h.close() + end + end + if not FingerprintIsOnWhitelist(fingerprint) then + table.insert(Current.Settings.Whitelist, fingerprint) + SaveSettings() + end + disk.eject(drive) + SetText(nil, 'Registered Pocket Computer', colours.green) + end +end + +function HostSetup() + ResetPage() + Current.Page = 'HostSetup' + Current.ButtonTwo = Button:Initialise(Drawing.Screen.Width - 6, Drawing.Screen.Height - 1, nil, nil, nil, nil, HostStatusPage, 'Save', colours.black) + if not Current.Settings then + Current.Settings = DefaultSettings + end + + local sideButtons = {} + local function resetSideToggle(self) + for i, v in ipairs(sideButtons) do + if v.Toggle ~= nil then + v.Toggle = false + end + end + Current.Settings.RedstoneSide = self.Text:lower() + SaveSettings() + end + + table.insert(Current.PageControls, Label:Initialise(2, 2, 'Redstone Side')) + sideButtons = { + Button:Initialise(2, 4, nil, nil, nil, nil, resetSideToggle, 'Back', colours.black, false, colours.green), + Button:Initialise(9, 4, nil, nil, nil, nil, resetSideToggle, 'Front', colours.black, false, colours.green), + Button:Initialise(2, 6, nil, nil, nil, nil, resetSideToggle, 'Left', colours.black, false, colours.green), + Button:Initialise(9, 6, nil, nil, nil, nil, resetSideToggle, 'Right', colours.black, false, colours.green), + Button:Initialise(2, 8, nil, nil, nil, nil, resetSideToggle, 'Top', colours.black, false, colours.green), + Button:Initialise(8, 8, nil, nil, nil, nil, resetSideToggle, 'Bottom', colours.black, false, colours.green) + } + for i, v in ipairs(sideButtons) do + if v.Text:lower() == Current.Settings.RedstoneSide then + v.Toggle = true + end + table.insert(Current.PageControls, v) + end + + local distanceButtons = {} + local function resetDistanceToggle(self) + for i, v in ipairs(distanceButtons) do + if v.Toggle ~= nil then + v.Toggle = false + end + end + if self.Text == 'Small' then + Current.Settings.Distance = 5 + elseif self.Text == 'Normal' then + Current.Settings.Distance = 10 + elseif self.Text == 'Far' then + Current.Settings.Distance = 15 + end + SaveSettings() + end + + table.insert(Current.PageControls, Label:Initialise(23, 2, 'Opening Distance')) + distanceButtons = { + Button:Initialise(23, 4, nil, nil, nil, nil, resetDistanceToggle, 'Small', colours.black, false, colours.green), + Button:Initialise(31, 4, nil, nil, nil, nil, resetDistanceToggle, 'Normal', colours.black, false, colours.green), + Button:Initialise(40, 4, nil, nil, nil, nil, resetDistanceToggle, 'Far', colours.black, false, colours.green) + } + for i, v in ipairs(distanceButtons) do + if v.Text == 'Small' and Current.Settings.Distance == 5 then + v.Toggle = true + elseif v.Text == 'Normal' and Current.Settings.Distance == 10 then + v.Toggle = true + elseif v.Text == 'Far' and Current.Settings.Distance == 15 then + v.Toggle = true + end + table.insert(Current.PageControls, v) + end + + table.insert(Current.PageControls, Label:Initialise(2, 10, 'Registered PDAs: '..#Current.Settings.Whitelist)) + table.insert(Current.PageControls, Button:Initialise(2, 12, nil, nil, nil, nil, function()Current.Settings.Whitelist = {}HostSetup()end, 'Unregister All', colours.black)) + + + table.insert(Current.PageControls, Label:Initialise(23, 6, 'Help', colours.black)) + local helpLines = { + Label:Initialise(23, 8, 'To register a new PDA simply', colours.black), + Label:Initialise(23, 9, 'place a Disk Drive next to', colours.black), + Label:Initialise(23, 10, 'the computer, then put the', colours.black), + Label:Initialise(23, 11, 'PDA in the Drive, it will', colours.black), + Label:Initialise(23, 12, 'register automatically. If', colours.black), + Label:Initialise(23, 13, 'it worked it will eject.', colours.black), + Label:Initialise(23, 15, 'Make sure you hide this', colours.red), + Label:Initialise(23, 16, 'computer away from the', colours.red), + Label:Initialise(23, 17, 'door! (other people)', colours.red) + } + for i, v in ipairs(helpLines) do + table.insert(Current.PageControls, v) + end + + + table.insert(Current.PageControls, Button:Initialise(2, 14, nil, nil, nil, nil, function() + for i = 1, 6 do + helpLines[i].TextColour = colours.green + end + end, 'Register New PDA', colours.black)) + +end + +function HostStatusPage() + ResetPage() + Current.Page = 'HostStatus' + Current.StatusScreen = true + Current.ButtonOne = Button:Initialise(Drawing.Screen.Width - 6, Drawing.Screen.Height - 1, nil, nil, nil, nil, Quit, 'Quit', colours.black) + Current.ButtonTwo = Button:Initialise(2, Drawing.Screen.Height - 1, nil, nil, nil, nil, HostSetup, 'Settings/Help', colours.black) + + Wireless.Responder = function(event, side, channel, replyChannel, message, distance) + if channel == Wireless.Channels.UltimateDoorlockRequest and distance < Current.Settings.Distance then + if FingerprintIsOnWhitelist(message.content) then + OpenDoor() + Wireless.SendMessage(Wireless.Channels.UltimateDoorlockRequestReply, true) + else + Wireless.SendMessage(Wireless.Channels.UltimateDoorlockRequestReply, false) + end + end + end + + PingPocketComputers() +end + +function HostInitialise() + if not Wireless.Present() then + Current.Locked = true + Current.ButtonOne = Button:Initialise(Drawing.Screen.Width - 6, Drawing.Screen.Height - 1, nil, nil, nil, nil, Quit, 'Quit', colours.black) + Current.ButtonTwo = Button:Initialise(2, Drawing.Screen.Height - 1, nil, nil, nil, nil, function()os.reboot()end, 'Reboot', colours.black) + ResetStatus() + return + end + Wireless.Initialise() + ResetStatus() + if fs.exists('.settings') then + local h = fs.open('.settings', 'r') + if h then + Current.Settings = textutils.unserialize(h.readAll()) + end + h.close() + HostStatusPage() + else + HostSetup() + end + if OneOS then + OneOS.CanClose = function() + CloseDoor() + return true + end + end +end + +local pingTimer = nil +function PingPocketComputers() + Wireless.SendMessage(Wireless.Channels.UltimateDoorlockPing, 'Ping!', Wireless.Channels.UltimateDoorlockRequest) + pingTimer = os.startTimer(0.5) +end + +function Initialise(arg) + EventRegister('mouse_click', TryClick) + EventRegister('mouse_drag', function(event, side, x, y)TryClick(event, side, x, y, true)end) + EventRegister('mouse_scroll', Scroll) + EventRegister('key', HandleKey) + EventRegister('char', HandleKey) + EventRegister('timer', Timer) + EventRegister('terminate', function(event) if Close() then error( "Terminated", 0 ) end end) + EventRegister('modem_message', Wireless.HandleMessage) + EventRegister('disk', RegisterPDA) + + if OneOS then + OneOS.RequestRunAtStartup() + end + + if pocket then + PocketInitialise() + else + HostInitialise() + end + + + Draw() + + EventHandler() +end + +function Timer(event, timer) + if timer == pingTimer then + PingPocketComputers() + elseif timer == closeDoorTimer then + CloseDoor() + elseif timer == statusResetTimer then + ResetStatus() + end +end + +local ignoreNextChar = false +function HandleKey(...) + local args = {...} + local event = args[1] + local keychar = args[2] + --[[ + --Mac left command character + if event == 'key' and keychar == keys.leftCtrl or keychar == keys.rightCtrl or keychar == 219 then + isControlPushed = true + controlPushedTimer = os.startTimer(0.5) + elseif isControlPushed then + if event == 'key' then + if CheckKeyboardShortcut(keychar) then + isControlPushed = false + ignoreNextChar = true + end + end + elseif ignoreNextChar then + ignoreNextChar = false + elseif Current.TextInput then + if event == 'char' then + Current.TextInput:Char(keychar) + elseif event == 'key' then + Current.TextInput:Key(keychar) + end + end + ]]-- +end + +--[[ + Check if the given object falls under the click coordinates +]]-- +function CheckClick(object, x, y) + if object.X <= x and object.Y <= y and object.X + object.Width > x and object.Y + object.Height > y then + return true + end +end + +--[[ + Attempt to clicka given object +]]-- +function DoClick(object, side, x, y, drag) + local obj = GetAbsolutePosition(object) + obj.Width = object.Width + obj.Height = object.Height + if object and CheckClick(obj, x, y) then + return object:Click(side, x - object.X + 1, y - object.Y + 1, drag) + end +end + +--[[ + Try to click at the given coordinates +]]-- +function TryClick(event, side, x, y, drag) + if Current.ButtonOne then + if DoClick(Current.ButtonOne, side, x, y, drag) then + Draw() + return + end + end + + if Current.ButtonTwo then + if DoClick(Current.ButtonTwo, side, x, y, drag) then + Draw() + return + end + end + + for i, v in ipairs(Current.PageControls) do + if DoClick(v, side, x, y, drag) then + Draw() + return + end + end + + Draw() +end + +function Scroll(event, direction, x, y) + if Current.Window and Current.Window.OpenButton then + Current.Document.Scroll = Current.Document.Scroll + direction + if Current.Window.Scroll < 0 then + Current.Window.Scroll = 0 + elseif Current.Window.Scroll > Current.Window.MaxScroll then + Current.Window.Scroll = Current.Window.MaxScroll + end + Draw() + elseif Current.ScrollBar then + if Current.ScrollBar:DoScroll(direction*2) then + Draw() + end + end +end + +--[[ + Registers functions to run on certain events +]]-- +function EventRegister(event, func) + if not Events[event] then + Events[event] = {} + end + + table.insert(Events[event], func) +end + +--[[ + The main loop event handler, runs registered event functinos +]]-- +function EventHandler() + while isRunning do + local event, arg1, arg2, arg3, arg4, arg5, arg6 = os.pullEventRaw() + if Events[event] then + for i, e in ipairs(Events[event]) do + e(event, arg1, arg2, arg3, arg4, arg5, arg6) + end + end + end +end + +function Quit() + isRunning = false + term.setCursorPos(1,1) + term.setBackgroundColour(colours.black) + term.setTextColour(colours.white) + term.clear() + if OneOS then + OneOS.Close() + end +end + +if not term.current then -- if not 1.6 + print('Because it requires pocket computers, Ultimate Door Lock requires ComputerCraft 1.6. Please update to 1.6 to use Ultimate Door Lock.') +elseif not (OneOS and pocket) and term.isColor and term.isColor() then + -- If the program crashes close the door and reboot + local _, err = pcall(Initialise) + if err then + CloseDoor() + term.setCursorPos(1,1) + term.setBackgroundColour(colours.black) + term.setTextColour(colours.white) + term.clear() + print('Ultimate Door Lock has crashed') + print('To maintain security, the computer will reboot.') + print('If you are seeing this alot try turning off all Pocket Computers or reinstall.') + print() + print('Error:') + printError(err) + sleep(5) + os.reboot() + end +elseif OneOS and pocket then + term.setCursorPos(1,3) + term.setBackgroundColour(colours.white) + term.setTextColour(colours.blue) + term.clear() + print('OneOS already acts as a door key. Simply place your PDA in the door\'s disk drive to register it.') + print() + print('To setup a door, run this program on an advanced computer (non-pocket).') + print() + print('Click anywhere to quit') + os.pullEvent('mouse_click') + Quit() +else + print('Ultimate Door Lock requires an advanced (gold) computer or pocket computer.') +end From bcc230336b7c48c212922760b88d24dea5ce048e Mon Sep 17 00:00:00 2001 From: rservices Date: Tue, 10 Mar 2020 07:24:42 -0600 Subject: [PATCH 041/125] Update programVersions --- programVersions | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/programVersions b/programVersions index 4132f98..91f6bad 100644 --- a/programVersions +++ b/programVersions @@ -85,7 +85,7 @@ ["Version"]=2.12, ["Type"]="program", ["Name"]="Outraged Security .INC Retriever", - ["Description"]="Downloads programs from github", + ["Description"]="Downloads programs from from Outraged Security .INC", ["Author"]="Outraged Security .INC", ["Package"]="Standalone", }, @@ -94,16 +94,16 @@ ["Version"]=2.12, ["Type"]="program", ["Name"]="Outraged Security .INC Signs", - ["Description"]="Downloads Sign Program from github", + ["Description"]="Downloads Sign Program from Outraged Security .INC", ["Author"]="Outraged Security .INC", ["Package"]="Standalone", }, ["setup"]={ - ["GitURL"]="https://raw.githubusercontent.com/rservices/darkprograms/darkprograms/mineos/setup", + ["GitURL"]="https://raw.githubusercontent.com/rservices/darkprograms/darkprograms/SecurePdaDoor/SecureDoor", ["Version"]=2.12, ["Type"]="program", ["Name"]="Outraged Security .INC MineOS Secure OS", - ["Description"]="Downloads MineOS Program from github", + ["Description"]="Downloads A PDA Secure Door Program from Outraged Security .INC", ["Author"]="Outraged Security .INC", ["Package"]="Standalone", }, From 1fc44de874f910b88d9200be12c1d114c2ea7122 Mon Sep 17 00:00:00 2001 From: rservices Date: Tue, 10 Mar 2020 07:27:55 -0600 Subject: [PATCH 042/125] Update programVersions --- programVersions | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/programVersions b/programVersions index 91f6bad..28c3e38 100644 --- a/programVersions +++ b/programVersions @@ -102,7 +102,7 @@ ["GitURL"]="https://raw.githubusercontent.com/rservices/darkprograms/darkprograms/SecurePdaDoor/SecureDoor", ["Version"]=2.12, ["Type"]="program", - ["Name"]="Outraged Security .INC MineOS Secure OS", + ["Name"]="Outraged Security .INC PDA Secure Door", ["Description"]="Downloads A PDA Secure Door Program from Outraged Security .INC", ["Author"]="Outraged Security .INC", ["Package"]="Standalone", From 6c23e997d9dca26a587083cb0655e44695b7fd5b Mon Sep 17 00:00:00 2001 From: rservices Date: Tue, 10 Mar 2020 09:16:20 -0600 Subject: [PATCH 043/125] Create office --- notepad/office | 3151 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 3151 insertions(+) create mode 100644 notepad/office diff --git a/notepad/office b/notepad/office new file mode 100644 index 0000000..809e210 --- /dev/null +++ b/notepad/office @@ -0,0 +1,3151 @@ +tArgs = {...} + +if OneOS then + --running under OneOS + OneOS.ToolBarColour = colours.grey + OneOS.ToolBarTextColour = colours.white +end + +local _w, _h = term.getSize() + +local round = function(num, idp) + local mult = 10^(idp or 0) + return math.floor(num * mult + 0.5) / mult +end + +UIColours = { + Toolbar = colours.grey, + ToolbarText = colours.lightGrey, + ToolbarSelected = colours.lightBlue, + ControlText = colours.white, + ToolbarItemTitle = colours.black, + Background = colours.lightGrey, + MenuBackground = colours.white, + MenuText = colours.black, + MenuSeparatorText = colours.grey, + MenuDisabledText = colours.lightGrey, + Shadow = colours.grey, + TransparentBackgroundOne = colours.white, + TransparentBackgroundTwo = colours.lightGrey, + MenuBarActive = colours.white +} + +local getNames = peripheral.getNames or function() + local tResults = {} + for n,sSide in ipairs( rs.getSides() ) do + if peripheral.isPresent( sSide ) then + table.insert( tResults, sSide ) + local isWireless = false + if not pcall(function()isWireless = peripheral.call(sSide, 'isWireless') end) then + isWireless = true + end + if peripheral.getType( sSide ) == "modem" and not isWireless then + local tRemote = peripheral.call( sSide, "getNamesRemote" ) + for n,sName in ipairs( tRemote ) do + table.insert( tResults, sName ) + end + end + end + end + return tResults +end + +Peripheral = { + GetPeripheral = function(_type) + for i, p in ipairs(Peripheral.GetPeripherals()) do + if p.Type == _type then + return p + end + end + end, + + Call = function(type, ...) + local tArgs = {...} + local p = Peripheral.GetPeripheral(type) + peripheral.call(p.Side, unpack(tArgs)) + end, + + GetPeripherals = function(filterType) + local peripherals = {} + for i, side in ipairs(getNames()) do + local name = peripheral.getType(side):gsub("^%l", string.upper) + local code = string.upper(side:sub(1,1)) + if side:find('_') then + code = side:sub(side:find('_')+1) + end + + local dupe = false + for i, v in ipairs(peripherals) do + if v[1] == name .. ' ' .. code then + dupe = true + end + end + + if not dupe then + local _type = peripheral.getType(side) + local isWireless = false + if _type == 'modem' then + if not pcall(function()isWireless = peripheral.call(sSide, 'isWireless') end) then + isWireless = true + end + if isWireless then + _type = 'wireless_modem' + name = 'W '..name + end + end + if not filterType or _type == filterType then + table.insert(peripherals, {Name = name:sub(1,8) .. ' '..code, Fullname = name .. ' ('..side:sub(1, 1):upper() .. side:sub(2, -1)..')', Side = side, Type = _type, Wireless = isWireless}) + end + end + end + return peripherals + end, + + PresentNamed = function(name) + return peripheral.isPresent(name) + end, + + CallType = function(type, ...) + local tArgs = {...} + local p = GetPeripheral(type) + return peripheral.call(p.Side, unpack(tArgs)) + end, + + CallNamed = function(name, ...) + local tArgs = {...} + return peripheral.call(name, unpack(tArgs)) + end +} + +TextLine = { + Text = "", + Alignment = AlignmentLeft, + + Initialise = function(self, text, alignment) + local new = {} -- the new instance + setmetatable( new, {__index = self} ) + new.Text = text + new.Alignment = alignment or AlignmentLeft + return new + end +} + +local StripColours = function(str) + return str:gsub('['..string.char(14)..'-'..string.char(29)..']','') +end + +Printer = { + Name = nil, + PeripheralType = 'printer', + + paperLevel = function(self) + return Peripheral.CallNamed(self.Name, 'getPaperLevel') + end, + + newPage = function(self) + return Peripheral.CallNamed(self.Name, 'newPage') + end, + + endPage = function(self) + return Peripheral.CallNamed(self.Name, 'endPage') + end, + + pageWrite = function(self, text) + return Peripheral.CallNamed(self.Name, 'write', text) + end, + + setPageTitle = function(self, title) + return Peripheral.CallNamed(self.Name, 'setPageTitle', title) + end, + + inkLevel = function(self) + return Peripheral.CallNamed(self.Name, 'getInkLevel') + end, + + getCursorPos = function(self) + return Peripheral.CallNamed(self.Name, 'getCursorPos') + end, + + setCursorPos = function(self, x, y) + return Peripheral.CallNamed(self.Name, 'setCursorPos', x, y) + end, + + pageSize = function(self) + return Peripheral.CallNamed(self.Name, 'getPageSize') + end, + + Present = function() + if Peripheral.GetPeripheral(Printer.PeripheralType) == nil then + return false + else + return true + end + end, + + PrintLines = function(self, lines, title, copies) + local pages = {} + local pageLines = {} + for i, line in ipairs(lines) do + table.insert(pageLines, TextLine:Initialise(StripColours(line))) + if i % 25 == 0 then + table.insert(pages, pageLines) + pageLines = {} + end + end + if #pageLines ~= 0 then + table.insert(pages, pageLines) + end + return self:PrintPages(pages, title, copies) + end, + + PrintPages = function(self, pages, title, copies) + copies = copies or 1 + for c = 1, copies do + for p, page in ipairs(pages) do + if self:paperLevel() < #pages * copies then + return 'Add more paper to the printer' + end + if self:inkLevel() < #pages * copies then + return 'Add more ink to the printer' + end + self:newPage() + for i, line in ipairs(page) do + self:setCursorPos(1, i) + self:pageWrite(StripColours(line.Text)) + end + if title then + self:setPageTitle(title) + end + self:endPage() + end + end + end, + + Initialise = function(self, name) + if Printer.Present() then --fix + local new = {} -- the new instance + setmetatable( new, {__index = self} ) + if name and Peripheral.PresentNamed(name) then + new.Name = name + else + new.Name = Peripheral.GetPeripheral(Printer.PeripheralType).Side + end + return new + end + end +} + +Clipboard = { + Content = nil, + Type = nil, + IsCut = false, + + Empty = function() + Clipboard.Content = nil + Clipboard.Type = nil + Clipboard.IsCut = false + end, + + isEmpty = function() + return Clipboard.Content == nil + end, + + Copy = function(content, _type) + Clipboard.Content = content + Clipboard.Type = _type or 'generic' + Clipboard.IsCut = false + end, + + Cut = function(content, _type) + Clipboard.Content = content + Clipboard.Type = _type or 'generic' + Clipboard.IsCut = true + end, + + Paste = function() + local c, t = Clipboard.Content, Clipboard.Type + if Clipboard.IsCut then + Clipboard.Empty() + end + return c, t + end +} + +if OneOS and OneOS.Clipboard then + Clipboard = OneOS.Clipboard +end + +Drawing = { + + Screen = { + Width = _w, + Height = _h + }, + + DrawCharacters = function (x, y, characters, textColour,bgColour) + Drawing.WriteStringToBuffer(x, y, characters, textColour, bgColour) + end, + + DrawBlankArea = function (x, y, w, h, colour) + Drawing.DrawArea (x, y, w, h, " ", 1, colour) + end, + + DrawArea = function (x, y, w, h, character, textColour, bgColour) + --width must be greater than 1, other wise we get a stack overflow + if w < 0 then + w = w * -1 + elseif w == 0 then + w = 1 + end + + for ix = 1, w do + local currX = x + ix - 1 + for iy = 1, h do + local currY = y + iy - 1 + Drawing.WriteToBuffer(currX, currY, character, textColour, bgColour) + end + end + end, + + DrawImage = function(_x,_y,tImage, w, h) + if tImage then + for y = 1, h do + if not tImage[y] then + break + end + for x = 1, w do + if not tImage[y][x] then + break + end + local bgColour = tImage[y][x] + local textColour = tImage.textcol[y][x] or colours.white + local char = tImage.text[y][x] + Drawing.WriteToBuffer(x+_x-1, y+_y-1, char, textColour, bgColour) + end + end + elseif w and h then + Drawing.DrawBlankArea(x, y, w, h, colours.green) + end + end, + --using .nft + LoadImage = function(path) + local image = { + text = {}, + textcol = {} + } + local fs = fs + if OneOS then + fs = OneOS.FS + end + if fs.exists(path) then + local _open = io.open + if OneOS then + _open = OneOS.IO.open + end + local file = _open(path, "r") + local sLine = file:read() + local num = 1 + while sLine do + table.insert(image, num, {}) + table.insert(image.text, num, {}) + table.insert(image.textcol, num, {}) + + --As we're no longer 1-1, we keep track of what index to write to + local writeIndex = 1 + --Tells us if we've hit a 30 or 31 (BG and FG respectively)- next char specifies the curr colour + local bgNext, fgNext = false, false + --The current background and foreground colours + local currBG, currFG = nil,nil + for i=1,#sLine do + local nextChar = string.sub(sLine, i, i) + if nextChar:byte() == 30 then + bgNext = true + elseif nextChar:byte() == 31 then + fgNext = true + elseif bgNext then + currBG = Drawing.GetColour(nextChar) + bgNext = false + elseif fgNext then + currFG = Drawing.GetColour(nextChar) + fgNext = false + else + if nextChar ~= " " and currFG == nil then + currFG = colours.white + end + image[num][writeIndex] = currBG + image.textcol[num][writeIndex] = currFG + image.text[num][writeIndex] = nextChar + writeIndex = writeIndex + 1 + end + end + num = num+1 + sLine = file:read() + end + file:close() + end + return image + end, + + DrawCharactersCenter = function(x, y, w, h, characters, textColour,bgColour) + w = w or Drawing.Screen.Width + h = h or Drawing.Screen.Height + x = x or 0 + y = y or 0 + x = math.ceil((w - #characters) / 2) + x + y = math.floor(h / 2) + y + + Drawing.DrawCharacters(x, y, characters, textColour, bgColour) + end, + + GetColour = function(hex) + if hex == ' ' then + return colours.transparent + end + local value = tonumber(hex, 16) + if not value then return nil end + value = math.pow(2,value) + return value + end, + + Clear = function (_colour) + _colour = _colour or colours.black + Drawing.ClearBuffer() + Drawing.DrawBlankArea(1, 1, Drawing.Screen.Width, Drawing.Screen.Height, _colour) + end, + + Buffer = {}, + BackBuffer = {}, + + DrawBuffer = function() + for y,row in pairs(Drawing.Buffer) do + for x,pixel in pairs(row) do + local shouldDraw = true + local hasBackBuffer = true + if Drawing.BackBuffer[y] == nil or Drawing.BackBuffer[y][x] == nil or #Drawing.BackBuffer[y][x] ~= 3 then + hasBackBuffer = false + end + if hasBackBuffer and Drawing.BackBuffer[y][x][1] == Drawing.Buffer[y][x][1] and Drawing.BackBuffer[y][x][2] == Drawing.Buffer[y][x][2] and Drawing.BackBuffer[y][x][3] == Drawing.Buffer[y][x][3] then + shouldDraw = false + end + if shouldDraw then + term.setBackgroundColour(pixel[3]) + term.setTextColour(pixel[2]) + term.setCursorPos(x, y) + term.write(pixel[1]) + end + end + end + Drawing.BackBuffer = Drawing.Buffer + Drawing.Buffer = {} + term.setCursorPos(1,1) + end, + + ClearBuffer = function() + Drawing.Buffer = {} + end, + + WriteStringToBuffer = function (x, y, characters, textColour,bgColour) + for i = 1, #characters do + local character = characters:sub(i,i) + Drawing.WriteToBuffer(x + i - 1, y, character, textColour, bgColour) + end + end, + + WriteToBuffer = function(x, y, character, textColour,bgColour) + x = round(x) + y = round(y) + if bgColour == colours.transparent then + Drawing.Buffer[y] = Drawing.Buffer[y] or {} + Drawing.Buffer[y][x] = Drawing.Buffer[y][x] or {"", colours.white, colours.black} + Drawing.Buffer[y][x][1] = character + Drawing.Buffer[y][x][2] = textColour + else + Drawing.Buffer[y] = Drawing.Buffer[y] or {} + Drawing.Buffer[y][x] = {character, textColour, bgColour} + end + end, +} + +Current = { + Document = nil, + TextInput = nil, + CursorPos = {1,1}, + CursorColour = colours.black, + Selection = {8, 36}, + Window = nil, + Modified = false, +} + +local isQuitting = false + +function OrderSelection() + if Current.Selection then + if Current.Selection[1] <= Current.Selection[2] then + return Current.Selection + else + return {Current.Selection[2], Current.Selection[1]} + end + end +end + +function StripColours(str) + return str:gsub('['..string.char(14)..'-'..string.char(29)..']','') +end + +function FindColours(str) + local _, count = str:gsub('['..string.char(14)..'-'..string.char(29)..']','') + return count +end + +ColourFromCharacter = function(character) + local n = character:byte() - 14 + if n > 16 then + return nil + else + return 2^n + end +end + +CharacterFromColour = function(colour) + return string.char(math.floor(math.log(colour)/math.log(2))+14) +end + +Events = {} + +Button = { + X = 1, + Y = 1, + Width = 0, + Height = 0, + BackgroundColour = colours.lightGrey, + TextColour = colours.white, + ActiveBackgroundColour = colours.lightGrey, + Text = "", + Parent = nil, + _Click = nil, + Toggle = nil, + + AbsolutePosition = function(self) + return self.Parent:AbsolutePosition() + end, + + Draw = function(self) + local bg = self.BackgroundColour + local tc = self.TextColour + if type(bg) == 'function' then + bg = bg() + end + + if self.Toggle then + tc = UIColours.MenuBarActive + bg = self.ActiveBackgroundColour + end + + local pos = GetAbsolutePosition(self) + Drawing.DrawBlankArea(pos.X, pos.Y, self.Width, self.Height, bg) + Drawing.DrawCharactersCenter(pos.X, pos.Y, self.Width, self.Height, self.Text, tc, bg) + end, + + Initialise = function(self, x, y, width, height, backgroundColour, parent, click, text, textColour, toggle, activeBackgroundColour) + local new = {} -- the new instance + setmetatable( new, {__index = self} ) + height = height or 1 + new.Width = width or #text + 2 + new.Height = height + new.Y = y + new.X = x + new.Text = text or "" + new.BackgroundColour = backgroundColour or colours.lightGrey + new.TextColour = textColour or colours.white + new.ActiveBackgroundColour = activeBackgroundColour or colours.lightGrey + new.Parent = parent + new._Click = click + new.Toggle = toggle + return new + end, + + Click = function(self, side, x, y) + if self._Click then + if self:_Click(side, x, y, not self.Toggle) ~= false and self.Toggle ~= nil then + self.Toggle = not self.Toggle + Draw() + end + return true + else + return false + end + end +} + +TextBox = { + X = 1, + Y = 1, + Width = 0, + Height = 0, + BackgroundColour = colours.lightGrey, + TextColour = colours.black, + Parent = nil, + TextInput = nil, + Placeholder = '', + + AbsolutePosition = function(self) + return self.Parent:AbsolutePosition() + end, + + Draw = function(self) + local pos = GetAbsolutePosition(self) + Drawing.DrawBlankArea(pos.X, pos.Y, self.Width, self.Height, self.BackgroundColour) + local text = self.TextInput.Value + if #tostring(text) > (self.Width - 2) then + text = text:sub(#text-(self.Width - 3)) + if Current.TextInput == self.TextInput then + Current.CursorPos = {pos.X + 1 + self.Width-2, pos.Y} + end + else + if Current.TextInput == self.TextInput then + Current.CursorPos = {pos.X + 1 + self.TextInput.CursorPos, pos.Y} + end + end + + if #tostring(text) == 0 then + Drawing.DrawCharacters(pos.X + 1, pos.Y, self.Placeholder, colours.lightGrey, self.BackgroundColour) + else + Drawing.DrawCharacters(pos.X + 1, pos.Y, text, self.TextColour, self.BackgroundColour) + end + + term.setCursorBlink(true) + + Current.CursorColour = self.TextColour + end, + + Initialise = function(self, x, y, width, height, parent, text, backgroundColour, textColour, done, numerical) + local new = {} -- the new instance + setmetatable( new, {__index = self} ) + height = height or 1 + new.Width = width or #text + 2 + new.Height = height + new.Y = y + new.X = x + new.TextInput = TextInput:Initialise(text or '', function(key) + if done then + done(key) + end + Draw() + end, numerical) + new.BackgroundColour = backgroundColour or colours.lightGrey + new.TextColour = textColour or colours.black + new.Parent = parent + return new + end, + + Click = function(self, side, x, y) + Current.Input = self.TextInput + self:Draw() + end +} + +TextInput = { + Value = "", + Change = nil, + CursorPos = nil, + Numerical = false, + IsDocument = nil, + + Initialise = function(self, value, change, numerical, isDocument) + local new = {} -- the new instance + setmetatable( new, {__index = self} ) + new.Value = tostring(value) + new.Change = change + new.CursorPos = #tostring(value) + new.Numerical = numerical + new.IsDocument = isDocument or false + return new + end, + + Insert = function(self, str) + if self.Numerical then + str = tostring(tonumber(str)) + end + + local selection = OrderSelection() + + if self.IsDocument and selection then + self.Value = string.sub(self.Value, 1, selection[1]-1) .. str .. string.sub( self.Value, selection[2]+2) + self.CursorPos = selection[1] + Current.Selection = nil + else + local _, newLineAdjust = string.gsub(self.Value:sub(1, self.CursorPos), '\n','') + + self.Value = string.sub(self.Value, 1, self.CursorPos + newLineAdjust) .. str .. string.sub( self.Value, self.CursorPos + 1 + newLineAdjust) + self.CursorPos = self.CursorPos + 1 + end + + self.Change(key) + end, + + Extract = function(self, remove) + local selection = OrderSelection() + if self.IsDocument and selection then + local _, newLineAdjust = string.gsub(self.Value:sub(selection[1], selection[2]), '\n','') + local str = string.sub(self.Value, selection[1], selection[2]+1+newLineAdjust) + if remove then + self.Value = string.sub(self.Value, 1, selection[1]-1) .. string.sub( self.Value, selection[2]+2+newLineAdjust) + self.CursorPos = selection[1] - 1 + Current.Selection = nil + end + return str + end + end, + + Char = function(self, char) + if char == 'nil' then + return + end + self:Insert(char) + end, + + Key = function(self, key) + if key == keys.enter then + if self.IsDocument then + self.Value = string.sub(self.Value, 1, self.CursorPos ) .. '\n' .. string.sub( self.Value, self.CursorPos + 1 ) + self.CursorPos = self.CursorPos + 1 + end + self.Change(key) + elseif key == keys.left then + -- Left + if self.CursorPos > 0 then + local colShift = FindColours(string.sub( self.Value, self.CursorPos, self.CursorPos)) + self.CursorPos = self.CursorPos - 1 - colShift + self.Change(key) + end + + elseif key == keys.right then + -- Right + if self.CursorPos < string.len(self.Value) then + local colShift = FindColours(string.sub( self.Value, self.CursorPos+1, self.CursorPos+1)) + self.CursorPos = self.CursorPos + 1 + colShift + self.Change(key) + end + + elseif key == keys.backspace then + -- Backspace + if self.IsDocument and Current.Selection then + self:Extract(true) + self.Change(key) + elseif self.CursorPos > 0 then + local colShift = FindColours(string.sub( self.Value, self.CursorPos, self.CursorPos)) + local _, newLineAdjust = string.gsub(self.Value:sub(1, self.CursorPos), '\n','') + + self.Value = string.sub( self.Value, 1, self.CursorPos - 1 - colShift + newLineAdjust) .. string.sub( self.Value, self.CursorPos + 1 - colShift + newLineAdjust) + self.CursorPos = self.CursorPos - 1 - colShift + self.Change(key) + end + elseif key == keys.home then + -- Home + self.CursorPos = 0 + self.Change(key) + elseif key == keys.delete then + if self.IsDocument and Current.Selection then + self:Extract(true) + self.Change(key) + elseif self.CursorPos < string.len(self.Value) then + self.Value = string.sub( self.Value, 1, self.CursorPos ) .. string.sub( self.Value, self.CursorPos + 2 ) + self.Change(key) + end + elseif key == keys["end"] then + -- End + self.CursorPos = string.len(self.Value) + self.Change(key) + elseif key == keys.up and self.IsDocument then + -- Up + if Current.Document.CursorPos then + local page = Current.Document.Pages[Current.Document.CursorPos.Page] + self.CursorPos = page:GetCursorPosFromPoint(Current.Document.CursorPos.Collum + page.MarginX, Current.Document.CursorPos.Line - page.MarginY - 1 + Current.Document.ScrollBar.Scroll, true) + self.Change(key) + end + elseif key == keys.down and self.IsDocument then + -- Down + if Current.Document.CursorPos then + local page = Current.Document.Pages[Current.Document.CursorPos.Page] + self.CursorPos = page:GetCursorPosFromPoint(Current.Document.CursorPos.Collum + page.MarginX, Current.Document.CursorPos.Line - page.MarginY + 1 + Current.Document.ScrollBar.Scroll, true) + self.Change(key) + end + end + end +} + +Menu = { + X = 0, + Y = 0, + Width = 0, + Height = 0, + Owner = nil, + Items = {}, + RemoveTop = false, + + Draw = function(self) + Drawing.DrawBlankArea(self.X + 1, self.Y + 1, self.Width, self.Height, UIColours.Shadow) + if not self.RemoveTop then + Drawing.DrawBlankArea(self.X, self.Y, self.Width, self.Height, UIColours.MenuBackground) + for i, item in ipairs(self.Items) do + if item.Separator then + Drawing.DrawArea(self.X, self.Y + i, self.Width, 1, '-', colours.grey, UIColours.MenuBackground) + else + local textColour = item.Colour or UIColours.MenuText + if (item.Enabled and type(item.Enabled) == 'function' and item.Enabled() == false) or item.Enabled == false then + textColour = UIColours.MenuDisabledText + end + Drawing.DrawCharacters(self.X + 1, self.Y + i, item.Title, textColour, UIColours.MenuBackground) + end + end + else + Drawing.DrawBlankArea(self.X, self.Y, self.Width, self.Height, UIColours.MenuBackground) + for i, item in ipairs(self.Items) do + if item.Separator then + Drawing.DrawArea(self.X, self.Y + i - 1, self.Width, 1, '-', colours.grey, UIColours.MenuBackground) + else + local textColour = item.Colour or UIColours.MenuText + if (item.Enabled and type(item.Enabled) == 'function' and item.Enabled() == false) or item.Enabled == false then + textColour = UIColours.MenuDisabledText + end + Drawing.DrawCharacters(self.X + 1, self.Y + i - 1, item.Title, textColour, UIColours.MenuBackground) + + Drawing.DrawCharacters(self.X - 1 + self.Width-#item.KeyName, self.Y + i - 1, item.KeyName, textColour, UIColours.MenuBackground) + end + end + end + end, + + NameForKey = function(self, key) + if key == keys.leftCtrl then + return '^' + elseif key == keys.tab then + return 'Tab' + elseif key == keys.delete then + return 'Delete' + elseif key == keys.n then + return 'N' + elseif key == keys.a then + return 'A' + elseif key == keys.s then + return 'S' + elseif key == keys.o then + return 'O' + elseif key == keys.z then + return 'Z' + elseif key == keys.y then + return 'Y' + elseif key == keys.c then + return 'C' + elseif key == keys.x then + return 'X' + elseif key == keys.v then + return 'V' + elseif key == keys.r then + return 'R' + elseif key == keys.l then + return 'L' + elseif key == keys.t then + return 'T' + elseif key == keys.h then + return 'H' + elseif key == keys.e then + return 'E' + elseif key == keys.p then + return 'P' + elseif key == keys.f then + return 'F' + elseif key == keys.m then + return 'M' + elseif key == keys.q then + return 'Q' + else + return '?' + end + end, + + Initialise = function(self, x, y, items, owner, removeTop) + local new = {} -- the new instance + setmetatable( new, {__index = self} ) + if not owner then + return + end + + local keyNames = {} + + for i, v in ipairs(items) do + items[i].KeyName = '' + if v.Keys then + for _i, key in ipairs(v.Keys) do + items[i].KeyName = items[i].KeyName .. self:NameForKey(key) + end + end + if items[i].KeyName ~= '' then + table.insert(keyNames, items[i].KeyName) + end + end + local keysLength = LongestString(keyNames) + if keysLength > 0 then + keysLength = keysLength + 2 + end + + new.Width = LongestString(items, 'Title') + 2 + keysLength + if new.Width < 10 then + new.Width = 10 + end + new.Height = #items + 2 + new.RemoveTop = removeTop or false + if removeTop then + new.Height = new.Height - 1 + end + + if y < 1 then + y = 1 + end + if x < 1 then + x = 1 + end + + if y + new.Height > Drawing.Screen.Height + 1 then + y = Drawing.Screen.Height - new.Height + end + if x + new.Width > Drawing.Screen.Width + 1 then + x = Drawing.Screen.Width - new.Width + end + + + new.Y = y + new.X = x + new.Items = items + new.Owner = owner + return new + end, + + New = function(self, x, y, items, owner, removeTop) + if Current.Menu and Current.Menu.Owner == owner then + Current.Menu = nil + return + end + + local new = self:Initialise(x, y, items, owner, removeTop) + Current.Menu = new + return new + end, + + Click = function(self, side, x, y) + local i = y-1 + if self.RemoveTop then + i = y + end + if i >= 1 and y < self.Height then + if not ((self.Items[i].Enabled and type(self.Items[i].Enabled) == 'function' and self.Items[i].Enabled() == false) or self.Items[i].Enabled == false) and self.Items[i].Click then + self.Items[i]:Click() + if Current.Menu.Owner and Current.Menu.Owner.Toggle then + Current.Menu.Owner.Toggle = false + end + Current.Menu = nil + self = nil + end + return true + end + end +} + +MenuBar = { + X = 1, + Y = 1, + Width = Drawing.Screen.Width, + Height = 1, + MenuBarItems = {}, + + AbsolutePosition = function(self) + return {X = self.X, Y = self.Y} + end, + + Draw = function(self) + --Drawing.DrawArea(self.X - 1, self.Y, 1, self.Height, "|", UIColours.ToolbarText, UIColours.Background) + + Drawing.DrawBlankArea(self.X, self.Y, self.Width, self.Height, colours.grey) + for i, button in ipairs(self.MenuBarItems) do + button:Draw() + end + end, + + Initialise = function(self, items) + local new = {} -- the new instance + setmetatable( new, {__index = self} ) + new.X = 1 + new.Y = 1 + new.MenuBarItems = items + return new + end, + + AddToolbarItem = function(self, item) + table.insert(self.ToolbarItems, item) + self:CalculateToolbarItemPositions() + end, + + CalculateToolbarItemPositions = function(self) + local currY = 1 + for i, toolbarItem in ipairs(self.ToolbarItems) do + toolbarItem.Y = currY + currY = currY + toolbarItem.Height + end + end, + + Click = function(self, side, x, y) + for i, item in ipairs(self.MenuBarItems) do + if item.X <= x and item.X + item.Width > x then + if item:Click(item, side, x - item.X + 1, 1) then + return true + end + end + end + return false + end +} + +TextFormatPlainText = 1 +TextFormatInkText = 2 + +Document = { + X = 1, + Y = 1, + PageSize = {Width = 25, Height = 21}, + TextInput = nil, + Pages = {}, + Format = TextFormatPlainText, + Title = '', + Path = nil, + ScrollBar = nil, + Lines = {}, + CursorPos = nil, + + CalculateLineWrapping = function(self) + local limit = self.PageSize.Width + local text = self.TextInput.Value + local lines = {''} + local words = {} + + for word, space in text:gmatch('(%S+)(%s*)') do + for i = 1, math.ceil(#word/limit) do + local _space = '' + if i == math.ceil(#word/limit) then + _space = space + end + table.insert(words, {word:sub(1+limit*(i-1), limit*i), _space}) + end + end + + for i, ws in ipairs(words) do + local word = ws[1] + local space = ws[2] + local temp = lines[#lines] .. word .. space:gsub('\n','') + if #temp > limit then + table.insert(lines, '') + end + if space:find('\n') then + lines[#lines] = lines[#lines] .. word + + space = space:gsub('\n', function() + table.insert(lines, '') + return '' + end) + else + lines[#lines] = lines[#lines] .. word .. space + end + end + return lines + end, + + CalculateCursorPos = function(self) + local passedCharacters = 0 + Current.CursorPos = nil + for p, page in ipairs(self.Pages) do + page:Draw() + if not Current.CursorPos then + for i, line in ipairs(page.Lines) do + local relCursor = self.TextInput.CursorPos - FindColours(self.TextInput.Value:sub(1,self.TextInput.CursorPos)) + if passedCharacters + #StripColours(line.Text:gsub('\n','')) >= relCursor then + Current.CursorPos = {self.X + page.MarginX + (relCursor - passedCharacters), page.Y + 1 + i} + self.CursorPos = {Page = p, Line = i, Collum = relCursor - passedCharacters - FindColours(self.TextInput.Value:sub(1,self.TextInput.CursorPos-1))} + break + end + passedCharacters = passedCharacters + #StripColours(line.Text:gsub('\n','')) + end + end + end + end, + + Draw = function(self) + self:CalculatePages() + self:CalculateCursorPos() + self.ScrollBar:Draw() + end, + + CalculatePages = function(self) + self.Pages = {} + local lines = self:CalculateLineWrapping() + self.Lines = lines + local pageLines = {} + local totalPageHeight = (3 + self.PageSize.Height + 2 * Page.MarginY) + for i, line in ipairs(lines) do + table.insert(pageLines, TextLine:Initialise(line)) + if i % self.PageSize.Height == 0 then + table.insert(self.Pages, Page:Initialise(self, pageLines, 3 - self.ScrollBar.Scroll + totalPageHeight*(#self.Pages))) + pageLines = {} + end + end + if #pageLines ~= 0 then + table.insert(self.Pages, Page:Initialise(self, pageLines, 3 - self.ScrollBar.Scroll + totalPageHeight*(#self.Pages))) + end + + self.ScrollBar.MaxScroll = totalPageHeight*(#self.Pages) - Drawing.Screen.Height + 1 + end, + + ScrollToCursor = function(self) + self:CalculateCursorPos() + if Current.CursorPos and + (Current.CursorPos[2] > Drawing.Screen.Height + or Current.CursorPos[2] < 2) then + self.ScrollBar:DoScroll(Current.CursorPos[2] - Drawing.Screen.Height) + end + end, + + SetSelectionColour = function(self, colour) + local selection = OrderSelection() + local text = self.TextInput:Extract(true) + local colChar = CharacterFromColour(colour) + local precedingColour = '' + if FindColours(self.TextInput.Value:sub(self.TextInput.CursorPos+1, self.TextInput.CursorPos+1)) == 0 then + for i = 1, self.TextInput.CursorPos do + local c = self.TextInput.Value:sub(self.TextInput.CursorPos - i,self.TextInput.CursorPos - i) + if FindColours(c) == 1 then + precedingColour = c + break + end + end + if precedingColour == '' then + precedingColour = CharacterFromColour(colours.black) + end + end + + self.TextInput:Insert(colChar..StripColours(text)..precedingColour) + --text = text:gsub('['..string.char(14)..'-'..string.char(29)..']','') + end, + + Initialise = function(self, text, title, path) + local new = {} -- the new instance + setmetatable( new, {__index = self} ) + new.Title = title or 'New Document' + new.Path = path + new.X = (Drawing.Screen.Width - (new.PageSize.Width + 2*(Page.MarginX)))/2 + new.Y = 2 + new.TextInput = TextInput:Initialise(text, function() + new:ScrollToCursor() + Current.Modified = true + Draw() + end, false, true) + new.ScrollBar = ScrollBar:Initialise(Drawing.Screen.Width, new.Y, Drawing.Screen.Height-1, 0, nil, nil, nil, function()end) + Current.TextInput = new.TextInput + Current.ScrollBar = new.ScrollBar + return new + end +} + +ScrollBar = { + X = 1, + Y = 1, + Width = 1, + Height = 1, + BackgroundColour = colours.grey, + BarColour = colours.lightBlue, + Parent = nil, + Change = nil, + Scroll = 0, + MaxScroll = 0, + ClickPoint = nil, + + AbsolutePosition = function(self) + return self.Parent:AbsolutePosition() + end, + + Draw = function(self) + local pos = GetAbsolutePosition(self) + local barHeight = self.Height - self.MaxScroll + if barHeight < 3 then + barHeight = 3 + end + local percentage = (self.Scroll/self.MaxScroll) + + Drawing.DrawBlankArea(pos.X, pos.Y, self.Width, self.Height, self.BackgroundColour) + Drawing.DrawBlankArea(pos.X, pos.Y + round(self.Height*percentage - barHeight*percentage), self.Width, barHeight, self.BarColour) + end, + + Initialise = function(self, x, y, height, maxScroll, backgroundColour, barColour, parent, change) + local new = {} -- the new instance + setmetatable( new, {__index = self} ) + new.Width = 1 + new.Height = height + new.Y = y + new.X = x + new.BackgroundColour = backgroundColour or colours.grey + new.BarColour = barColour or colours.lightBlue + new.Parent = parent + new.Change = change or function()end + new.MaxScroll = maxScroll + new.Scroll = 0 + return new + end, + + DoScroll = function(self, amount) + amount = round(amount) + if self.Scroll < 0 or self.Scroll > self.MaxScroll then + return false + end + self.Scroll = self.Scroll + amount + if self.Scroll < 0 then + self.Scroll = 0 + elseif self.Scroll > self.MaxScroll then + self.Scroll = self.MaxScroll + end + self.Change() + return true + end, + + Click = function(self, side, x, y, drag) + local percentage = (self.Scroll/self.MaxScroll) + local barHeight = (self.Height - self.MaxScroll) + if barHeight < 3 then + barHeight = 3 + end + local relScroll = (self.MaxScroll*(y + barHeight*percentage)/self.Height) + if not drag then + self.ClickPoint = self.Scroll - relScroll + 1 + end + + if self.Scroll-1 ~= relScroll then + self:DoScroll(relScroll-self.Scroll-1 + self.ClickPoint) + end + return true + end +} + +AlignmentLeft = 1 +AlignmentCentre = 2 +AlignmentRight = 3 + +TextLine = { + Text = "", + Alignment = AlignmentLeft, + + Initialise = function(self, text, alignment) + local new = {} -- the new instance + setmetatable( new, {__index = self} ) + new.Text = text + new.Alignment = alignment or AlignmentLeft + return new + end +} + +local clickPos = 1 +Page = { + X = 1, + Y = 1, + Width = 1, + Height = 1, + MarginX = 3, + MarginY = 2, + BackgroundColour = colours.white, + TextColour = colours.white, + ActiveBackgroundColour = colours.lightGrey, + Lines = {}, + Parent = nil, + + AbsolutePosition = function(self) + return self.Parent:AbsolutePosition() + end, + + Draw = function(self) + local pos = GetAbsolutePosition(self) + + if pos.Y > Drawing.Screen.Height or pos.Y + self.Height < 1 then + return + end + + Drawing.DrawBlankArea(pos.X+self.Width,pos.Y -1 + 1, 1, self.Height, UIColours.Shadow) + Drawing.DrawBlankArea(pos.X+1, pos.Y -1 + self.Height, self.Width, 1, UIColours.Shadow) + Drawing.DrawBlankArea(pos.X, pos.Y -1, self.Width, self.Height, self.BackgroundColour) + + local textColour = self.TextColour + if not Current.Selection then + for i, line in ipairs(self.Lines) do + local _c = 1 + for c = 1, #line.Text do + local col = ColourFromCharacter(line.Text:sub(c,c)) + if col then + textColour = col + else + Drawing.WriteToBuffer(pos.X + self.MarginX - 1 + _c, pos.Y -2 + i + self.MarginY, line.Text:sub(c,c), textColour, self.BackgroundColour) + _c = _c + 1 + end + end + end + else + local selection = OrderSelection() + local char = 1 + local textColour = self.TextColour + for i, line in ipairs(self.Lines) do + local _c = 1 + for c = 1, #line.Text do + local col = ColourFromCharacter(line.Text:sub(c,c)) + if col then + textColour = col + else + local tc = textColour + local colour = colours.white + if char >= selection[1] and char <= selection[2] then + colour = colours.lightBlue + tc = colours.white + end + + Drawing.WriteToBuffer(pos.X + self.MarginX - 1 + _c, pos.Y -2 + i + self.MarginY, line.Text:sub(c,c), tc, colour) + _c = _c + 1 + end + char = char + 1 + end + end + end + end, + + Initialise = function(self, parent, lines, y) + local new = {} -- the new instanc + setmetatable( new, {__index = self} ) + new.Height = parent.PageSize.Height + 2 * self.MarginY + new.Width = parent.PageSize.Width + 2 * self.MarginX + new.X = 1 + new.Y = y or 1 + new.Lines = lines or {} + new.BackgroundColour = colours.white + new.TextColour = colours.black + new.Parent = parent + new.ClickPos = 1 + return new + end, + + GetCursorPosFromPoint = function(self, x, y, rel) + local pos = GetAbsolutePosition(self) + if rel then + pos = {Y = 0, X = 0} + end + local row = y - pos.Y + self.MarginY - self.Parent.ScrollBar.Scroll + local col = x - self.MarginX - pos.X + 1 + local cursorPos = 0 + if row <= 0 or col <= 0 then + return 0 + end + + if row > #self.Lines then + for i, v in ipairs(self.Lines) do + cursorPos = cursorPos + #v.Text-- - FindColours(v.Text) + end + return cursorPos + end + + --term.setCursorPos(1,3) + local prevLineCount = 0 + for i, v in ipairs(self.Lines) do + if i == row then + if col > #v.Text then + col = #v.Text-- + FindColours(v.Text) + else + col = col + FindColours(v.Text:sub(1, col)) + end + --term.setCursorPos(1,2) + --print(prevLineCount) + cursorPos = cursorPos + col + 2 - i - prevLineCount + break + else + prevLineCount = FindColours(v.Text) + if prevLineCount ~= 0 then + prevLineCount = prevLineCount + end + cursorPos = cursorPos + #v.Text + 2 - i + FindColours(v.Text) + end + end + + return cursorPos - 2 + end, + + Click = function(self, side, x, y, drag) + local cursorPos = self:GetCursorPosFromPoint(x, y) + self.Parent.TextInput.CursorPos = cursorPos + if drag == nil then + Current.Selection = nil + clickPos = x + else + local relCursor = cursorPos-- - FindColours(self.Parent.TextInput.Value:sub(1,cursorPos)) + 1 + if not Current.Selection then + local adder = 1 + if clickPos and clickPos < x then + adder = 0 + end + Current.Selection = {relCursor + adder, relCursor + 1 + adder} + else + Current.Selection[2] = relCursor + 1 + end + end + Draw() + return true + end +} + +function GetAbsolutePosition(object) + local obj = object + local i = 0 + local x = 1 + local y = 1 + while true do + x = x + obj.X - 1 + y = y + obj.Y - 1 + + if not obj.Parent then + return {X = x, Y = y} + end + + obj = obj.Parent + + if i > 32 then + return {X = 1, Y = 1} + end + + i = i + 1 + end + +end + +function Draw() + if not Current.Window then + Drawing.Clear(colours.lightGrey) + else + Drawing.DrawArea(1, 2, Drawing.Screen.Width, Drawing.Screen.Height, '|', colours.black, colours.lightGrey) + end + + if Current.Document then + Current.Document:Draw() + end + + Current.MenuBar:Draw() + + if Current.Window then + Current.Window:Draw() + end + + if Current.Menu then + Current.Menu:Draw() + end + + Drawing.DrawBuffer() + + if Current.TextInput and Current.CursorPos and not Current.Menu and not(Current.Window and Current.Document and Current.TextInput == Current.Document.TextInput) and Current.CursorPos[2] > 1 then + term.setCursorPos(Current.CursorPos[1], Current.CursorPos[2]) + term.setCursorBlink(true) + term.setTextColour(Current.CursorColour) + else + term.setCursorBlink(false) + end +end +MainDraw = Draw + +LongestString = function(input, key) + local length = 0 + for i = 1, #input do + local value = input[i] + if key then + if value[key] then + value = value[key] + else + value = '' + end + end + local titleLength = string.len(value) + if titleLength > length then + length = titleLength + end + end + return length +end + +function LoadMenuBar() + Current.MenuBar = MenuBar:Initialise({ + Button:Initialise(1, 1, nil, nil, colours.grey, Current.MenuBar, function(self, side, x, y, toggle) + if toggle then + Menu:New(1, 2, { + { + Title = "New...", + Click = function() + Current.Document = Document:Initialise('') + end, + Keys = { + keys.leftCtrl, + keys.n + } + }, + { + Title = 'Open...', + Click = function() + DisplayOpenDocumentWindow() + end, + Keys = { + keys.leftCtrl, + keys.o + } + }, + { + Separator = true + }, + { + Title = 'Save...', + Click = function() + SaveDocument() + end, + Keys = { + keys.leftCtrl, + keys.s + }, + Enabled = function() + return true + end + }, + { + Separator = true + }, + { + Title = 'Print...', + Click = function() + PrintDocument() + end, + Keys = { + keys.leftCtrl, + keys.p + }, + Enabled = function() + return true + end + }, + { + Separator = true + }, + { + Title = 'Quit', + Click = function() + Close() + end + }, + --[[ + { + Title = 'Save As...', + Click = function() + + end + } + ]]-- + }, self, true) + else + Current.Menu = nil + end + return true + end, 'File', colours.lightGrey, false), + Button:Initialise(7, 1, nil, nil, colours.grey, Current.MenuBar, function(self, side, x, y, toggle) + if not self.Toggle then + Menu:New(7, 2, { + --[[ + { + Title = "Undo", + Click = function() + end, + Keys = { + keys.leftCtrl, + keys.z + }, + Enabled = function() + return false + end + }, + { + Title = 'Redo', + Click = function() + + end, + Keys = { + keys.leftCtrl, + keys.y + }, + Enabled = function() + return false + end + }, + { + Separator = true + }, + ]]-- + { + Title = 'Cut', + Click = function() + Clipboard.Cut(Current.Document.TextInput:Extract(true), 'text') + end, + Keys = { + keys.leftCtrl, + keys.x + }, + Enabled = function() + return Current.Document ~= nil and Current.Selection and Current.Selection[1] and Current.Selection[2] ~= nil + end + }, + { + Title = 'Copy', + Click = function() + Clipboard.Copy(Current.Document.TextInput:Extract(), 'text') + end, + Keys = { + keys.leftCtrl, + keys.c + }, + Enabled = function() + return Current.Document ~= nil and Current.Selection and Current.Selection[1] and Current.Selection[2] ~= nil + end + }, + { + Title = 'Paste', + Click = function() + local paste = Clipboard.Paste() + Current.Document.TextInput:Insert(paste) + Current.Document.TextInput.CursorPos = Current.Document.TextInput.CursorPos + #paste - 1 + end, + Keys = { + keys.leftCtrl, + keys.v + }, + Enabled = function() + return Current.Document ~= nil and (not Clipboard.isEmpty()) and Clipboard.Type == 'text' + end + }, + { + Separator = true, + }, + { + Title = 'Select All', + Click = function() + Current.Selection = {1, #Current.Document.TextInput.Value:gsub('\n','')} + end, + Keys = { + keys.leftCtrl, + keys.a + }, + Enabled = function() + return Current.Document ~= nil + end + } + }, self, true) + else + Current.Menu = nil + end + return true + end, 'Edit', colours.lightGrey, false) + }) +end + +function LoadMenuBar() + Current.MenuBar = MenuBar:Initialise({ + Button:Initialise(1, 1, nil, nil, colours.grey, Current.MenuBar, function(self, side, x, y, toggle) + if toggle then + Menu:New(1, 2, { + { + Title = "New...", + Click = function() + Current.Document = Document:Initialise('') + end, + Keys = { + keys.leftCtrl, + keys.n + } + }, + { + Title = 'Open...', + Click = function() + DisplayOpenDocumentWindow() + end, + Keys = { + keys.leftCtrl, + keys.o + } + }, + { + Separator = true + }, + { + Title = 'Save...', + Click = function() + SaveDocument() + end, + Keys = { + keys.leftCtrl, + keys.s + }, + Enabled = function() + return Current.Document ~= nil + end + }, + { + Separator = true + }, + { + Title = 'Print...', + Click = function() + PrintDocument() + end, + Keys = { + keys.leftCtrl, + keys.p + }, + Enabled = function() + return true + end + }, + { + Separator = true + }, + { + Title = 'Quit', + Click = function() + if Close() and OneOS then + OneOS.Close() + end + end, + Keys = { + keys.leftCtrl, + keys.q + } + }, + --[[ + { + Title = 'Save As...', + Click = function() + + end + } + ]]-- + }, self, true) + else + Current.Menu = nil + end + return true + end, 'File', colours.lightGrey, false), + Button:Initialise(7, 1, nil, nil, colours.grey, Current.MenuBar, function(self, side, x, y, toggle) + if not self.Toggle then + Menu:New(7, 2, { + --[[ + { + Title = "Undo", + Click = function() + end, + Keys = { + keys.leftCtrl, + keys.z + }, + Enabled = function() + return false + end + }, + { + Title = 'Redo', + Click = function() + + end, + Keys = { + keys.leftCtrl, + keys.y + }, + Enabled = function() + return false + end + }, + { + Separator = true + }, + ]]-- + { + Title = 'Cut', + Click = function() + Clipboard.Cut(Current.Document.TextInput:Extract(true), 'text') + end, + Keys = { + keys.leftCtrl, + keys.x + }, + Enabled = function() + return Current.Document ~= nil and Current.Selection and Current.Selection[1] and Current.Selection[2] ~= nil + end + }, + { + Title = 'Copy', + Click = function() + Clipboard.Copy(Current.Document.TextInput:Extract(), 'text') + end, + Keys = { + keys.leftCtrl, + keys.c + }, + Enabled = function() + return Current.Document ~= nil and Current.Selection and Current.Selection[1] and Current.Selection[2] ~= nil + end + }, + { + Title = 'Paste', + Click = function() + local paste = Clipboard.Paste() + Current.Document.TextInput:Insert(paste) + Current.Document.TextInput.CursorPos = Current.Document.TextInput.CursorPos + #paste - 1 + end, + Keys = { + keys.leftCtrl, + keys.v + }, + Enabled = function() + return Current.Document ~= nil and (not Clipboard.isEmpty()) and Clipboard.Type == 'text' + end + }, + { + Separator = true, + }, + { + Title = 'Select All', + Click = function() + Current.Selection = {1, #Current.Document.TextInput.Value:gsub('\n','')} + end, + Keys = { + keys.leftCtrl, + keys.a + }, + Enabled = function() + return Current.Document ~= nil + end + } + }, self, true) + else + Current.Menu = nil + end + return true + end, 'Edit', colours.lightGrey, false), + Button:Initialise(13, 1, nil, nil, colours.grey, Current.MenuBar, function(self, side, x, y, toggle) + if not self.Toggle then + Menu:New(13, 2, { + { + Title = 'Red', + Click = function(item) + Current.Document:SetSelectionColour(item.Colour) + end, + Colour = colours.red, + Enabled = function() + return (Current.Document ~= nil and Current.Selection ~= nil and Current.Selection[1] ~= nil and Current.Selection[2] ~= nil) + end + }, + { + Title = 'Orange', + Click = function(item) + Current.Document:SetSelectionColour(item.Colour) + end, + Colour = colours.orange, + Enabled = function() + return (Current.Document ~= nil and Current.Selection ~= nil and Current.Selection[1] ~= nil and Current.Selection[2] ~= nil) + end + }, + { + Title = 'Yellow', + Click = function(item) + Current.Document:SetSelectionColour(item.Colour) + end, + Colour = colours.yellow, + Enabled = function() + return (Current.Document ~= nil and Current.Selection ~= nil and Current.Selection[1] ~= nil and Current.Selection[2] ~= nil) + end + }, + { + Title = 'Pink', + Click = function(item) + Current.Document:SetSelectionColour(item.Colour) + end, + Colour = colours.pink, + Enabled = function() + return (Current.Document ~= nil and Current.Selection ~= nil and Current.Selection[1] ~= nil and Current.Selection[2] ~= nil) + end + }, + { + Title = 'Magenta', + Click = function(item) + Current.Document:SetSelectionColour(item.Colour) + end, + Colour = colours.magenta, + Enabled = function() + return (Current.Document ~= nil and Current.Selection ~= nil and Current.Selection[1] ~= nil and Current.Selection[2] ~= nil) + end + }, + { + Title = 'Purple', + Click = function(item) + Current.Document:SetSelectionColour(item.Colour) + end, + Colour = colours.purple, + Enabled = function() + return (Current.Document ~= nil and Current.Selection ~= nil and Current.Selection[1] ~= nil and Current.Selection[2] ~= nil) + end + }, + { + Title = 'Light Blue', + Click = function(item) + Current.Document:SetSelectionColour(item.Colour) + end, + Colour = colours.lightBlue, + Enabled = function() + return (Current.Document ~= nil and Current.Selection ~= nil and Current.Selection[1] ~= nil and Current.Selection[2] ~= nil) + end + }, + { + Title = 'Cyan', + Click = function(item) + Current.Document:SetSelectionColour(item.Colour) + end, + Colour = colours.cyan, + Enabled = function() + return (Current.Document ~= nil and Current.Selection ~= nil and Current.Selection[1] ~= nil and Current.Selection[2] ~= nil) + end + }, + { + Title = 'Blue', + Click = function(item) + Current.Document:SetSelectionColour(item.Colour) + end, + Colour = colours.blue, + Enabled = function() + return (Current.Document ~= nil and Current.Selection ~= nil and Current.Selection[1] ~= nil and Current.Selection[2] ~= nil) + end + }, + { + Title = 'Green', + Click = function(item) + Current.Document:SetSelectionColour(item.Colour) + end, + Colour = colours.green, + Enabled = function() + return (Current.Document ~= nil and Current.Selection ~= nil and Current.Selection[1] ~= nil and Current.Selection[2] ~= nil) + end + }, + { + Title = 'Light Grey', + Click = function(item) + Current.Document:SetSelectionColour(item.Colour) + end, + Colour = colours.lightGrey, + Enabled = function() + return (Current.Document ~= nil and Current.Selection ~= nil and Current.Selection[1] ~= nil and Current.Selection[2] ~= nil) + end + }, + { + Title = 'Grey', + Click = function(item) + Current.Document:SetSelectionColour(item.Colour) + end, + Colour = colours.grey, + Enabled = function() + return (Current.Document ~= nil and Current.Selection ~= nil and Current.Selection[1] ~= nil and Current.Selection[2] ~= nil) + end + }, + { + Title = 'Black', + Click = function(item) + Current.Document:SetSelectionColour(item.Colour) + end, + Colour = colours.black, + Enabled = function() + return (Current.Document ~= nil and Current.Selection ~= nil and Current.Selection[1] ~= nil and Current.Selection[2] ~= nil) + end + }, + { + Title = 'Brown', + Click = function(item) + Current.Document:SetSelectionColour(item.Colour) + end, + Colour = colours.brown, + Enabled = function() + return (Current.Document ~= nil and Current.Selection ~= nil and Current.Selection[1] ~= nil and Current.Selection[2] ~= nil) + end + } + }, self, true) + else + Current.Menu = nil + end + return true + end, 'Colour', colours.lightGrey, false) + }) +end + +function SplashScreen() + local w = colours.white + local b = colours.black + local u = colours.blue + local lb = colours.lightBlue + local splashIcon = {{w,w,w,b,w,w,w,b,w,w,w,},{w,w,w,b,w,w,w,b,w,w,w,},{w,w,w,b,u,u,u,b,w,w,w,},{w,b,b,u,u,u,u,u,b,b,w,},{b,u,u,lb,lb,u,u,u,u,u,b,},{b,u,lb,lb,u,u,u,u,u,u,b,},{b,u,lb,lb,u,u,u,u,u,u,b,},{b,u,u,u,u,u,u,u,u,u,b,},{w,b,b,b,b,b,b,b,b,b,w,}, + ["text"]={{" "," "," "," "," "," "," "," "," "," "," ",},{" "," "," "," "," "," "," "," "," "," "," ",},{" "," "," "," "," "," "," "," "," "," "," ",},{" "," "," "," "," "," "," "," "," "," "," ",},{" "," "," "," "," "," "," "," "," "," "," ",},{" "," "," "," ","I","n","k"," "," "," "," ",},{" "," "," "," "," "," "," "," "," "," "," ",},{" "," ","b","y"," ","o","e","e","d"," "," "},{" "," "," "," "," "," "," "," "," "," "," ",},}, + ["textcol"]={{w,w,w,w,w,w,w,w,w,w,w,},{w,w,w,w,w,w,w,w,w,w,w,},{w,w,w,w,w,w,w,w,w,w,w,},{w,w,w,w,w,w,w,w,w,w,w,},{w,w,w,w,w,w,w,w,w,w,w,},{w,w,w,w,w,w,w,w,w,w,w,},{w,w,w,w,w,w,w,w,w,w,w,},{lb,lb,lb,lb,lb,lb,lb,lb,lb,lb,lb,},{w,w,w,w,w,w,w,w,w,w,w,},},} + Drawing.Clear(colours.white) + Drawing.DrawImage((Drawing.Screen.Width - 11)/2, (Drawing.Screen.Height - 9)/2, splashIcon, 11, 9) + Drawing.DrawBuffer() + Drawing.Clear(colours.black) + parallel.waitForAny(function()sleep(1)end, function()os.pullEvent('mouse_click')end) +end + +function Initialise(arg) + if OneOS then + fs = OneOS.FS + end + + if not OneOS then + SplashScreen() + end + EventRegister('mouse_click', TryClick) + EventRegister('mouse_drag', function(event, side, x, y)TryClick(event, side, x, y, true)end) + EventRegister('mouse_scroll', Scroll) + EventRegister('key', HandleKey) + EventRegister('char', HandleKey) + EventRegister('timer', Timer) + EventRegister('terminate', function(event) if Close() then error( "Terminated", 0 ) end end) + + LoadMenuBar() + + --Current.Document = Document:Initialise('abcdefghijklmnopqrtuvwxy')--'Hello everybody!') + if tArgs[1] then + if fs.exists(tArgs[1]) then + OpenDocument(tArgs[1]) + else + --new + end + else + Current.Document = Document:Initialise('')--'Hello everybody!') + end + + --[[ + if arg and fs.exists(arg) then + OpenDocument(arg) + else + DisplayNewDocumentWindow() + end + ]]-- + Draw() + + EventHandler() +end + +local isControlPushed = false +controlPushedTimer = nil +closeWindowTimer = nil +function Timer(event, timer) + if timer == closeWindowTimer then + if Current.Window then + Current.Window:Close() + end + Draw() + elseif timer == controlPushedTimer then + isControlPushed = false + end +end + +local ignoreNextChar = false +function HandleKey(...) + local args = {...} + local event = args[1] + local keychar = args[2] + --Mac left command character + if event == 'key' and keychar == keys.leftCtrl or keychar == keys.rightCtrl or keychar == 219 then + isControlPushed = true + controlPushedTimer = os.startTimer(0.5) + elseif isControlPushed then + if event == 'key' then + if CheckKeyboardShortcut(keychar) then + isControlPushed = false + ignoreNextChar = true + end + end + elseif ignoreNextChar then + ignoreNextChar = false + elseif Current.TextInput then + if event == 'char' then + Current.TextInput:Char(keychar) + elseif event == 'key' then + Current.TextInput:Key(keychar) + end + end +end + +function CheckKeyboardShortcut(key) + local shortcuts = {} + shortcuts[keys.n] = function() Current.Document = Document:Initialise('') end + shortcuts[keys.o] = function() DisplayOpenDocumentWindow() end + shortcuts[keys.s] = function() if Current.Document ~= nil then SaveDocument() end end + shortcuts[keys.left] = function() if Current.TextInput then Current.TextInput:Key(keys.home) end end + shortcuts[keys.right] = function() if Current.TextInput then Current.TextInput:Key(keys["end"]) end end +-- shortcuts[keys.q] = function() DisplayOpenDocumentWindow() end + if Current.Document ~= nil then + shortcuts[keys.s] = function() SaveDocument() end + shortcuts[keys.p] = function() PrintDocument() end + if Current.Selection and Current.Selection[1] and Current.Selection[2] ~= nil then + shortcuts[keys.x] = function() Clipboard.Cut(Current.Document.TextInput:Extract(true), 'text') end + shortcuts[keys.c] = function() Clipboard.Copy(Current.Document.TextInput:Extract(), 'text') end + end + if (not Clipboard.isEmpty()) and Clipboard.Type == 'text' then + shortcuts[keys.v] = function() local paste = Clipboard.Paste() + Current.Document.TextInput:Insert(paste) + Current.Document.TextInput.CursorPos = Current.Document.TextInput.CursorPos + #paste - 1 + end + end + shortcuts[keys.a] = function() Current.Selection = {1, #Current.Document.TextInput.Value:gsub('\n','')} end + end + + if shortcuts[key] then + shortcuts[key]() + Draw() + return true + else + return false + end +end + +--[[ + Check if the given object falls under the click coordinates +]]-- +function CheckClick(object, x, y) + if object.X <= x and object.Y <= y and object.X + object.Width > x and object.Y + object.Height > y then + return true + end +end + +--[[ + Attempt to clicka given object +]]-- +function DoClick(object, side, x, y, drag) + local obj = GetAbsolutePosition(object) + obj.Width = object.Width + obj.Height = object.Height + if object and CheckClick(obj, x, y) then + return object:Click(side, x - object.X + 1, y - object.Y + 1, drag) + end +end + +--[[ + Try to click at the given coordinates +]]-- +function TryClick(event, side, x, y, drag) + if Current.Menu then + if DoClick(Current.Menu, side, x, y, drag) then + Draw() + return + else + if Current.Menu.Owner and Current.Menu.Owner.Toggle then + Current.Menu.Owner.Toggle = false + end + Current.Menu = nil + Draw() + return + end + elseif Current.Window then + if DoClick(Current.Window, side, x, y, drag) then + Draw() + return + else + Current.Window:Flash() + return + end + end + local interfaceElements = {} + + table.insert(interfaceElements, Current.MenuBar) + table.insert(interfaceElements, Current.ScrollBar) + for i, page in ipairs(Current.Document.Pages) do + table.insert(interfaceElements, page) + end + + for i, object in ipairs(interfaceElements) do + if DoClick(object, side, x, y, drag) then + Draw() + return + end + end + Draw() +end + +function Scroll(event, direction, x, y) + if Current.Window and Current.Window.OpenButton then + Current.Document.Scroll = Current.Document.Scroll + direction + if Current.Window.Scroll < 0 then + Current.Window.Scroll = 0 + elseif Current.Window.Scroll > Current.Window.MaxScroll then + Current.Window.Scroll = Current.Window.MaxScroll + end + Draw() + elseif Current.ScrollBar then + if Current.ScrollBar:DoScroll(direction*2) then + Draw() + end + end +end + + +--[[ + Registers functions to run on certain events +]]-- +function EventRegister(event, func) + if not Events[event] then + Events[event] = {} + end + + table.insert(Events[event], func) +end + +--[[ + The main loop event handler, runs registered event functinos +]]-- +function EventHandler() + while true do + local event, arg1, arg2, arg3, arg4 = os.pullEventRaw() + if Events[event] then + for i, e in ipairs(Events[event]) do + e(event, arg1, arg2, arg3, arg4) + end + end + end +end + + +local function Extension(path, addDot) + if not path then + return nil + elseif not string.find(fs.getName(path), '%.') then + if not addDot then + return fs.getName(path) + else + return '' + end + else + local _path = path + if path:sub(#path) == '/' then + _path = path:sub(1,#path-1) + end + local extension = _path:gmatch('%.[0-9a-z]+$')() + if extension then + extension = extension:sub(2) + else + --extension = nil + return '' + end + if addDot then + extension = '.'..extension + end + return extension:lower() + end +end + +local RemoveExtension = function(path) + if path:sub(1,1) == '.' then + return path + end + local extension = Extension(path) + if extension == path then + return fs.getName(path) + end + return string.gsub(path, extension, ''):sub(1, -2) +end + +local acknowledgedColour = false +function PrintDocument() + if OneOS then + OneOS.LoadAPI('/System/API/Helpers.lua') + OneOS.LoadAPI('/System/API/Peripheral.lua') + OneOS.LoadAPI('/System/API/Printer.lua') + end + + local doPrint = function() + local window = PrintDocumentWindow:Initialise():Show() + end + + if Peripheral.GetPeripheral('printer') == nil then + ButtonDialougeWindow:Initialise('No Printer Found', 'Please place a printer next to your computer. Ensure you also insert dye (left slot) and paper (top slots)', 'Ok', nil, function(window, ok) + window:Close() + end):Show() + elseif not acknowledgedColour and FindColours(Current.Document.TextInput.Value) ~= 0 then + ButtonDialougeWindow:Initialise('Important', 'Due to the way printers work, you can\'t print in more than one colour. The dye you use will be the colour of the text.', 'Ok', nil, function(window, ok) + acknowledgedColour = true + window:Close() + doPrint() + end):Show() + else + doPrint() + end +end + +function SaveDocument() + local function save() + local h = fs.open(Current.Document.Path, 'w') + if h then + if Current.Document.Format == TextFormatPlainText then + h.write(Current.Document.TextInput.Value) + else + local lines = {} + for p, page in ipairs(Current.Document.Pages) do + for i, line in ipairs(page.Lines) do + table.insert(lines, line) + end + end + h.write(textutils.serialize(lines)) + end + Current.Modified = false + else + ButtonDialougeWindow:Initialise('Error', 'An error occured while saving the file, try again.', 'Ok', nil, function(window, ok) + window:Close() + end):Show() + end + h.close() + end + + if not Current.Document.Path then + SaveDocumentWindow:Initialise(function(self, success, path) + self:Close() + if success then + local extension = '' + if Current.Document.Format == TextFormatPlainText then + extension = '.txt' + elseif Current.Document.Format == TextFormatInkText then + extension = '.ink' + end + + if path:sub(-4) ~= extension then + path = path .. extension + end + + Current.Document.Path = path + Current.Document.Title = fs.getName(path) + save() + end + if Current.Document then + Current.TextInput = Current.Document.TextInput + end + end):Show() + else + save() + end +end + +function DisplayOpenDocumentWindow() + OpenDocumentWindow:Initialise(function(self, success, path) + self:Close() + if success then + OpenDocument(path) + end + end):Show() +end + +function OpenDocument(path) + Current.Selection = nil + local h = fs.open(path, 'r') + if h then + Current.Document = Document:Initialise(h.readAll(), RemoveExtension(fs.getName(path)), path) + else + ButtonDialougeWindow:Initialise('Error', 'An error occured while opening the file, try again.', 'Ok', nil, function(window, ok) + window:Close() + if Current.Document then + Current.TextInput = Current.Document.TextInput + end + end):Show() + end + h.close() +end + +local TidyPath = function(path) + path = '/'..path + local fs = fs + if OneOS then + fs = OneOS.FS + end + if fs.isDir(path) then + path = path .. '/' + end + + path, n = path:gsub("//", "/") + while n > 0 do + path, n = path:gsub("//", "/") + end + return path +end + +OpenDocumentWindow = { + X = 1, + Y = 1, + Width = 0, + Height = 0, + CursorPos = 1, + Visible = true, + Return = nil, + OpenButton = nil, + PathTextBox = nil, + CurrentDirectory = '/', + Scroll = 0, + MaxScroll = 0, + GoUpButton = nil, + SelectedFile = '', + Files = {}, + Typed = false, + + AbsolutePosition = function(self) + return {X = self.X, Y = self.Y} + end, + + Draw = function(self) + if not self.Visible then + return + end + Drawing.DrawBlankArea(self.X + 1, self.Y+1, self.Width, self.Height, colours.grey) + Drawing.DrawBlankArea(self.X, self.Y, self.Width, 3, colours.lightGrey) + Drawing.DrawBlankArea(self.X, self.Y+1, self.Width, self.Height-6, colours.white) + Drawing.DrawCharactersCenter(self.X, self.Y, self.Width, 1, self.Title, colours.black, colours.lightGrey) + Drawing.DrawBlankArea(self.X, self.Y + self.Height - 5, self.Width, 5, colours.lightGrey) + self:DrawFiles() + + if (fs.exists(self.PathTextBox.TextInput.Value)) or (self.SelectedFile and #self.SelectedFile > 0 and fs.exists(self.CurrentDirectory .. self.SelectedFile)) then + self.OpenButton.TextColour = colours.black + else + self.OpenButton.TextColour = colours.lightGrey + end + + self.PathTextBox:Draw() + self.OpenButton:Draw() + self.CancelButton:Draw() + self.GoUpButton:Draw() + end, + + DrawFiles = function(self) + for i, file in ipairs(self.Files) do + if i > self.Scroll and i - self.Scroll <= 11 then + if file == self.SelectedFile then + Drawing.DrawCharacters(self.X + 1, self.Y + i - self.Scroll, file, colours.white, colours.lightBlue) + elseif string.find(file, '%.txt') or string.find(file, '%.text') or string.find(file, '%.ink') or fs.isDir(self.CurrentDirectory .. file) then + Drawing.DrawCharacters(self.X + 1, self.Y + i - self.Scroll, file, colours.black, colours.white) + else + Drawing.DrawCharacters(self.X + 1, self.Y + i - self.Scroll, file, colours.grey, colours.white) + end + end + end + self.MaxScroll = #self.Files - 11 + if self.MaxScroll < 0 then + self.MaxScroll = 0 + end + end, + + Initialise = function(self, returnFunc) + local new = {} -- the new instance + setmetatable( new, {__index = self} ) + new.Width = 32 + new.Height = 17 + new.Return = returnFunc + new.X = math.ceil((Drawing.Screen.Width - new.Width) / 2) + new.Y = math.ceil((Drawing.Screen.Height - new.Height) / 2) + new.Title = 'Open Document' + new.Visible = true + new.CurrentDirectory = '/' + new.SelectedFile = nil + if OneOS and fs.exists('/Desktop/Documents/') then + new.CurrentDirectory = '/Desktop/Documents/' + end + new.OpenButton = Button:Initialise(new.Width - 6, new.Height - 1, nil, nil, colours.white, new, function(self, side, x, y, toggle) + if fs.exists(new.PathTextBox.TextInput.Value) and self.TextColour == colours.black and not fs.isDir(new.PathTextBox.TextInput.Value) then + returnFunc(new, true, TidyPath(new.PathTextBox.TextInput.Value)) + elseif new.SelectedFile and self.TextColour == colours.black and fs.isDir(new.CurrentDirectory .. new.SelectedFile) then + new:GoToDirectory(new.CurrentDirectory .. new.SelectedFile) + elseif new.SelectedFile and self.TextColour == colours.black then + returnFunc(new, true, TidyPath(new.CurrentDirectory .. '/' .. new.SelectedFile)) + end + end, 'Open', colours.black) + new.CancelButton = Button:Initialise(new.Width - 15, new.Height - 1, nil, nil, colours.white, new, function(self, side, x, y, toggle) + returnFunc(new, false) + end, 'Cancel', colours.black) + new.GoUpButton = Button:Initialise(2, new.Height - 1, nil, nil, colours.white, new, function(self, side, x, y, toggle) + local folderName = fs.getName(new.CurrentDirectory) + local parentDirectory = new.CurrentDirectory:sub(1, #new.CurrentDirectory-#folderName-1) + new:GoToDirectory(parentDirectory) + end, 'Go Up', colours.black) + new.PathTextBox = TextBox:Initialise(2, new.Height - 3, new.Width - 2, 1, new, new.CurrentDirectory, colours.white, colours.black) + new:GoToDirectory(new.CurrentDirectory) + return new + end, + + Show = function(self) + Current.Window = self + return self + end, + + Close = function(self) + Current.Input = nil + Current.Window = nil + self = nil + end, + + GoToDirectory = function(self, path) + path = TidyPath(path) + self.CurrentDirectory = path + self.Scroll = 0 + self.SelectedFile = nil + self.Typed = false + self.PathTextBox.TextInput.Value = path + local fs = fs + if OneOS then + fs = OneOS.FS + end + self.Files = fs.list(self.CurrentDirectory) + Draw() + end, + + Flash = function(self) + self.Visible = false + Draw() + sleep(0.15) + self.Visible = true + Draw() + sleep(0.15) + self.Visible = false + Draw() + sleep(0.15) + self.Visible = true + Draw() + end, + + Click = function(self, side, x, y) + local items = {self.OpenButton, self.CancelButton, self.PathTextBox, self.GoUpButton} + local found = false + for i, v in ipairs(items) do + if CheckClick(v, x, y) then + v:Click(side, x, y) + found = true + end + end + + if not found then + if y <= 12 then + local fs = fs + if OneOS then + fs = OneOS.FS + end + self.SelectedFile = fs.list(self.CurrentDirectory)[y-1] + self.PathTextBox.TextInput.Value = TidyPath(self.CurrentDirectory .. '/' .. self.SelectedFile) + Draw() + end + end + return true + end +} + +PrintDocumentWindow = { + X = 1, + Y = 1, + Width = 0, + Height = 0, + CursorPos = 1, + Visible = true, + Return = nil, + PrintButton = nil, + CopiesTextBox = nil, + Scroll = 0, + MaxScroll = 0, + PrinterSelectButton = nil, + Title = '', + Status = 0, --0 = neutral, 1 = good, -1 = error + StatusText = '', + SelectedPrinter = nil, + + AbsolutePosition = function(self) + return {X = self.X, Y = self.Y} + end, + + Draw = function(self) + if not self.Visible then + return + end + Drawing.DrawBlankArea(self.X + 1, self.Y+1, self.Width, self.Height, colours.grey) + Drawing.DrawBlankArea(self.X, self.Y, self.Width, 1, colours.lightGrey) + Drawing.DrawBlankArea(self.X, self.Y+1, self.Width, self.Height-1, colours.white) + Drawing.DrawCharactersCenter(self.X, self.Y, self.Width, 1, self.Title, colours.black, colours.lightGrey) + + self.PrinterSelectButton:Draw() + Drawing.DrawCharactersCenter(self.X, self.Y + self.PrinterSelectButton.Y - 2, self.Width, 1, 'Printer', colours.black, colours.white) + Drawing.DrawCharacters(self.X + self.Width - 3, self.Y + self.PrinterSelectButton.Y - 1, '\\/', colours.black, colours.lightGrey) + Drawing.DrawCharacters(self.X + 1, self.Y + self.CopiesTextBox.Y - 1, 'Copies', colours.black, colours.white) + local statusColour = colours.grey + if self.Status == -1 then + statusColour = colours.red + elseif self.Status == 1 then + statusColour = colours.green + end + Drawing.DrawCharacters(self.X + 1, self.Y + self.CopiesTextBox.Y + 1, self.StatusText, statusColour, colours.white) + + self.CopiesTextBox:Draw() + self.PrintButton:Draw() + self.CancelButton:Draw() + end, + + Initialise = function(self) + local new = {} -- the new instance + setmetatable( new, {__index = self} ) + new.Width = 32 + new.Height = 11 + new.Return = returnFunc + new.X = math.ceil((Drawing.Screen.Width - new.Width) / 2) + new.Y = math.ceil((Drawing.Screen.Height - new.Height) / 2) + new.Title = 'Print Document' + new.Visible = true + new.PrintButton = Button:Initialise(new.Width - 7, new.Height - 1, nil, nil, colours.lightGrey, new, function(self, side, x, y, toggle) + local doPrint = true + if new.SelectedPrinter == nil then + local p = Peripheral.GetPeripheral('printer') + if p then + new.SelectedPrinter = p.Side + new.PrinterSelectButton.Text = p.Fullname + else + new.StatusText = 'No Connected Printer' + new.Status = -1 + doPrint = false + end + end + if doPrint then + local printer = Printer:Initialise(new.SelectedPrinter) + local err = printer:PrintLines(Current.Document.Lines, Current.Document.Title, tonumber(new.CopiesTextBox.TextInput.Value)) + if not err then + new.StatusText = 'Document Printed!' + new.Status = 1 + closeWindowTimer = os.startTimer(1) + else + new.StatusText = err + new.Status = -1 + end + end + end, 'Print', colours.black) + new.CancelButton = Button:Initialise(new.Width - 15, new.Height - 1, nil, nil, colours.lightGrey, new, function(self, side, x, y, toggle) + new:Close() + Draw() + end, 'Close', colours.black) + new.PrinterSelectButton = Button:Initialise(2, 4, new.Width - 2, nil, colours.lightGrey, new, function(self, side, x, y, toggle) + local printers = { + { + Title = "Automatic", + Click = function() + new.SelectedPrinter = nil + new.PrinterSelectButton.Text = 'Automatic' + end + }, + { + Separator = true + } + } + for i, p in ipairs(Peripheral.GetPeripherals('printer')) do + table.insert(printers, { + Title = p.Fullname, + Click = function(self) + new.SelectedPrinter = p.Side + new.PrinterSelectButton.Text = p.Fullname + end + }) + end + Current.Menu = Menu:New(x, y+4, printers, self, true) + end, 'Automatic', colours.black) + new.CopiesTextBox = TextBox:Initialise(9, 6, 4, 1, new, 1, colours.lightGrey, colours.black, nil, true) + Current.TextInput = new.CopiesTextBox.TextInput + new.StatusText = 'Waiting...' + new.Status = 0 + return new + end, + + Show = function(self) + Current.Window = self + return self + end, + + Close = function(self) + Current.Input = nil + Current.Window = nil + self = nil + end, + + Flash = function(self) + self.Visible = false + Draw() + sleep(0.15) + self.Visible = true + Draw() + sleep(0.15) + self.Visible = false + Draw() + sleep(0.15) + self.Visible = true + Draw() + end, + + Click = function(self, side, x, y) + local items = {self.PrintButton, self.CancelButton, self.CopiesTextBox, self.PrinterSelectButton} + for i, v in ipairs(items) do + if CheckClick(v, x, y) then + v:Click(side, x, y) + end + end + return true + end +} + +SaveDocumentWindow = { + X = 1, + Y = 1, + Width = 0, + Height = 0, + CursorPos = 1, + Visible = true, + Return = nil, + SaveButton = nil, + PathTextBox = nil, + CurrentDirectory = '/', + Scroll = 0, + MaxScroll = 0, + ScrollBar = nil, + GoUpButton = nil, + Files = {}, + Typed = false, + + AbsolutePosition = function(self) + return {X = self.X, Y = self.Y} + end, + + Draw = function(self) + if not self.Visible then + return + end + Drawing.DrawBlankArea(self.X + 1, self.Y+1, self.Width, self.Height, colours.grey) + Drawing.DrawBlankArea(self.X, self.Y, self.Width, 3, colours.lightGrey) + Drawing.DrawBlankArea(self.X, self.Y+1, self.Width, self.Height-6, colours.white) + Drawing.DrawCharactersCenter(self.X, self.Y, self.Width, 1, self.Title, colours.black, colours.lightGrey) + Drawing.DrawBlankArea(self.X, self.Y + self.Height - 5, self.Width, 5, colours.lightGrey) + Drawing.DrawCharacters(self.X + 1, self.Y + self.Height - 5, self.CurrentDirectory, colours.grey, colours.lightGrey) + self:DrawFiles() + + if (self.PathTextBox.TextInput.Value) then + self.SaveButton.TextColour = colours.black + else + self.SaveButton.TextColour = colours.lightGrey + end + + self.PathTextBox:Draw() + self.SaveButton:Draw() + self.CancelButton:Draw() + self.GoUpButton:Draw() + end, + + DrawFiles = function(self) + for i, file in ipairs(self.Files) do + if i > self.Scroll and i - self.Scroll <= 10 then + if file == self.SelectedFile then + Drawing.DrawCharacters(self.X + 1, self.Y + i - self.Scroll, file, colours.white, colours.lightBlue) + elseif fs.isDir(self.CurrentDirectory .. file) then + Drawing.DrawCharacters(self.X + 1, self.Y + i - self.Scroll, file, colours.black, colours.white) + else + Drawing.DrawCharacters(self.X + 1, self.Y + i - self.Scroll, file, colours.lightGrey, colours.white) + end + end + end + self.MaxScroll = #self.Files - 11 + if self.MaxScroll < 0 then + self.MaxScroll = 0 + end + end, + + Initialise = function(self, returnFunc) + local new = {} -- the new instance + setmetatable( new, {__index = self} ) + new.Width = 32 + new.Height = 16 + new.Return = returnFunc + new.X = math.ceil((Drawing.Screen.Width - new.Width) / 2) + new.Y = math.ceil((Drawing.Screen.Height - new.Height) / 2) + new.Title = 'Save Document' + new.Visible = true + new.CurrentDirectory = '/' + if OneOS and fs.exists('/Desktop/Documents/') then + new.CurrentDirectory = '/Desktop/Documents/' + end + new.SaveButton = Button:Initialise(new.Width - 6, new.Height - 1, nil, nil, colours.white, new, function(self, side, x, y, toggle) + if self.TextColour == colours.black and not fs.isDir(new.CurrentDirectory ..'/' .. new.PathTextBox.TextInput.Value) then + returnFunc(new, true, TidyPath(new.CurrentDirectory ..'/' .. new.PathTextBox.TextInput.Value)) + elseif new.SelectedFile and self.TextColour == colours.black and fs.isDir(new.CurrentDirectory .. new.SelectedFile) then + new:GoToDirectory(new.CurrentDirectory .. new.SelectedFile) + end + end, 'Save', colours.black) + new.CancelButton = Button:Initialise(new.Width - 15, new.Height - 1, nil, nil, colours.white, new, function(self, side, x, y, toggle) + returnFunc(new, false) + end, 'Cancel', colours.black) + new.GoUpButton = Button:Initialise(2, new.Height - 1, nil, nil, colours.white, new, function(self, side, x, y, toggle) + local folderName = fs.getName(new.CurrentDirectory) + local parentDirectory = new.CurrentDirectory:sub(1, #new.CurrentDirectory-#folderName-1) + new:GoToDirectory(parentDirectory) + end, 'Go Up', colours.black) + new.PathTextBox = TextBox:Initialise(2, new.Height - 3, new.Width - 2, 1, new, '', colours.white, colours.black, function(key) + if key == keys.enter then + new.SaveButton:Click() + end + end) + new.PathTextBox.Placeholder = 'Document Name' + Current.TextInput = new.PathTextBox.TextInput + new:GoToDirectory(new.CurrentDirectory) + return new + end, + + Show = function(self) + Current.Window = self + return self + end, + + Close = function(self) + Current.Input = nil + Current.Window = nil + self = nil + end, + + GoToDirectory = function(self, path) + path = TidyPath(path) + self.CurrentDirectory = path + self.Scroll = 0 + self.Typed = false + local fs = fs + if OneOS then + fs = OneOS.FS + end + self.Files = fs.list(self.CurrentDirectory) + Draw() + end, + + Flash = function(self) + self.Visible = false + Draw() + sleep(0.15) + self.Visible = true + Draw() + sleep(0.15) + self.Visible = false + Draw() + sleep(0.15) + self.Visible = true + Draw() + end, + + Click = function(self, side, x, y) + local items = {self.SaveButton, self.CancelButton, self.PathTextBox, self.GoUpButton} + local found = false + for i, v in ipairs(items) do + if CheckClick(v, x, y) then + v:Click(side, x, y) + found = true + end + end + + if not found then + if y <= 11 then + local files = fs.list(self.CurrentDirectory) + if files[y-1] then + self:GoToDirectory(self.CurrentDirectory..files[y-1]) + Draw() + end + end + end + return true + end +} + +local WrapText = function(text, maxWidth) + local lines = {''} + for word, space in text:gmatch('(%S+)(%s*)') do + local temp = lines[#lines] .. word .. space:gsub('\n','') + if #temp > maxWidth then + table.insert(lines, '') + end + if space:find('\n') then + lines[#lines] = lines[#lines] .. word + + space = space:gsub('\n', function() + table.insert(lines, '') + return '' + end) + else + lines[#lines] = lines[#lines] .. word .. space + end + end + return lines +end + +ButtonDialougeWindow = { + X = 1, + Y = 1, + Width = 0, + Height = 0, + CursorPos = 1, + Visible = true, + CancelButton = nil, + OkButton = nil, + Lines = {}, + + AbsolutePosition = function(self) + return {X = self.X, Y = self.Y} + end, + + Draw = function(self) + if not self.Visible then + return + end + Drawing.DrawBlankArea(self.X + 1, self.Y+1, self.Width, self.Height, colours.grey) + Drawing.DrawBlankArea(self.X, self.Y, self.Width, 1, colours.lightGrey) + Drawing.DrawBlankArea(self.X, self.Y+1, self.Width, self.Height-1, colours.white) + Drawing.DrawCharactersCenter(self.X, self.Y, self.Width, 1, self.Title, colours.black, colours.lightGrey) + + for i, text in ipairs(self.Lines) do + Drawing.DrawCharacters(self.X + 1, self.Y + 1 + i, text, colours.black, colours.white) + end + + self.OkButton:Draw() + if self.CancelButton then + self.CancelButton:Draw() + end + end, + + Initialise = function(self, title, message, okText, cancelText, returnFunc) + local new = {} -- the new instance + setmetatable( new, {__index = self} ) + new.Width = 28 + new.Lines = WrapText(message, new.Width - 2) + new.Height = 5 + #new.Lines + new.Return = returnFunc + new.X = math.ceil((Drawing.Screen.Width - new.Width) / 2) + new.Y = math.ceil((Drawing.Screen.Height - new.Height) / 2) + new.Title = title + new.Visible = true + new.Visible = true + new.OkButton = Button:Initialise(new.Width - #okText - 2, new.Height - 1, nil, 1, nil, new, function() + returnFunc(new, true) + end, okText) + if cancelText then + new.CancelButton = Button:Initialise(new.Width - #okText - 2 - 1 - #cancelText - 2, new.Height - 1, nil, 1, nil, new, function() + returnFunc(new, false) + end, cancelText) + end + + return new + end, + + Show = function(self) + Current.Window = self + return self + end, + + Close = function(self) + Current.Window = nil + self = nil + end, + + Flash = function(self) + self.Visible = false + Draw() + sleep(0.15) + self.Visible = true + Draw() + sleep(0.15) + self.Visible = false + Draw() + sleep(0.15) + self.Visible = true + Draw() + end, + + Click = function(self, side, x, y) + local items = {self.OkButton, self.CancelButton} + local found = false + for i, v in ipairs(items) do + if CheckClick(v, x, y) then + v:Click(side, x, y) + found = true + end + end + return true + end +} + +TextDialougeWindow = { + X = 1, + Y = 1, + Width = 0, + Height = 0, + CursorPos = 1, + Visible = true, + CancelButton = nil, + OkButton = nil, + Lines = {}, + TextInput = nil, + + AbsolutePosition = function(self) + return {X = self.X, Y = self.Y} + end, + + Draw = function(self) + if not self.Visible then + return + end + Drawing.DrawBlankArea(self.X + 1, self.Y+1, self.Width, self.Height, colours.grey) + Drawing.DrawBlankArea(self.X, self.Y, self.Width, 1, colours.lightGrey) + Drawing.DrawBlankArea(self.X, self.Y+1, self.Width, self.Height-1, colours.white) + Drawing.DrawCharactersCenter(self.X, self.Y, self.Width, 1, self.Title, colours.black, colours.lightGrey) + + for i, text in ipairs(self.Lines) do + Drawing.DrawCharacters(self.X + 1, self.Y + 1 + i, text, colours.black, colours.white) + end + + + Drawing.DrawBlankArea(self.X + 1, self.Y + self.Height - 4, self.Width - 2, 1, colours.lightGrey) + Drawing.DrawCharacters(self.X + 2, self.Y + self.Height - 4, self.TextInput.Value, colours.black, colours.lightGrey) + Current.CursorPos = {self.X + 2 + self.TextInput.CursorPos, self.Y + self.Height - 4} + Current.CursorColour = colours.black + + self.OkButton:Draw() + if self.CancelButton then + self.CancelButton:Draw() + end + end, + + Initialise = function(self, title, message, okText, cancelText, returnFunc, numerical) + local new = {} -- the new instance + setmetatable( new, {__index = self} ) + new.Width = 28 + new.Lines = WrapText(message, new.Width - 2) + new.Height = 7 + #new.Lines + new.Return = returnFunc + new.X = math.ceil((Drawing.Screen.Width - new.Width) / 2) + new.Y = math.ceil((Drawing.Screen.Height - new.Height) / 2) + new.Title = title + new.Visible = true + new.Visible = true + new.OkButton = Button:Initialise(new.Width - #okText - 2, new.Height - 1, nil, 1, nil, new, function() + if #new.TextInput.Value > 0 then + returnFunc(new, true, new.TextInput.Value) + end + end, okText) + if cancelText then + new.CancelButton = Button:Initialise(new.Width - #okText - 2 - 1 - #cancelText - 2, new.Height - 1, nil, 1, nil, new, function() + returnFunc(new, false) + end, cancelText) + end + new.TextInput = TextInput:Initialise('', function(enter) + if enter then + new.OkButton:Click() + end + Draw() + end, numerical) + + Current.Input = new.TextInput + + return new + end, + + Show = function(self) + Current.Window = self + return self + end, + + Close = function(self) + Current.Window = nil + Current.Input = nil + self = nil + end, + + Flash = function(self) + self.Visible = false + Draw() + sleep(0.15) + self.Visible = true + Draw() + sleep(0.15) + self.Visible = false + Draw() + sleep(0.15) + self.Visible = true + Draw() + end, + + Click = function(self, side, x, y) + local items = {self.OkButton, self.CancelButton} + local found = false + for i, v in ipairs(items) do + if CheckClick(v, x, y) then + v:Click(side, x, y) + found = true + end + end + return true + end +} + +function PrintCentered(text, y) + local w, h = term.getSize() + x = math.ceil(math.ceil((w / 2) - (#text / 2)), 0)+1 + term.setCursorPos(x, y) + print(text) +end + +function DoVanillaClose() + term.setBackgroundColour(colours.black) + term.setTextColour(colours.white) + term.clear() + term.setCursorPos(1, 1) + PrintCentered("Thanks for using Ink!", (Drawing.Screen.Height/2)-1) + term.setTextColour(colours.lightGrey) + PrintCentered("Word Proccessor for ComputerCraft", (Drawing.Screen.Height/2)) + term.setTextColour(colours.white) + PrintCentered("(c) oeed 2014", (Drawing.Screen.Height/2)+3) + term.setCursorPos(1, Drawing.Screen.Height) + error('', 0) +end + +function Close() + if isQuitting or not Current.Document or not Current.Modified then + if not OneOS then + DoVanillaClose() + end + return true + else + local _w = ButtonDialougeWindow:Initialise('Quit Ink?', 'You have unsaved changes, do you want to quit anyway?', 'Quit', 'Cancel', function(window, success) + if success then + if OneOS then + OneOS.Close(true) + else + DoVanillaClose() + end + end + window:Close() + Draw() + end):Show() + --it's hacky but it works + os.queueEvent('mouse_click', 1, _w.X, _w.Y) + return false + end +end + +if OneOS then + OneOS.CanClose = function() + return Close() + end +end From cce0dbb8de76599f69e4a176631872f49373a9a8 Mon Sep 17 00:00:00 2001 From: rservices Date: Tue, 10 Mar 2020 09:19:01 -0600 Subject: [PATCH 044/125] Update programVersions --- programVersions | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/programVersions b/programVersions index 28c3e38..b20f61c 100644 --- a/programVersions +++ b/programVersions @@ -106,5 +106,14 @@ ["Description"]="Downloads A PDA Secure Door Program from Outraged Security .INC", ["Author"]="Outraged Security .INC", ["Package"]="Standalone", + }, + ["Office"]={ + ["GitURL"]="https://raw.githubusercontent.com/rservices/darkprograms/darkprograms/notepad/office", + ["Version"]=2.16, + ["Type"]="program", + ["Name"]="Outraged Security .INC Office Word Software", + ["Description"]="Downloads A Word writing office Program from Outraged Security .INC", + ["Author"]="Outraged Security .INC", + ["Package"]="Standalone", }, } From 072ffea2633c62750407954c0daa5eaf1be7b0a8 Mon Sep 17 00:00:00 2001 From: rservices Date: Tue, 10 Mar 2020 09:27:57 -0600 Subject: [PATCH 045/125] Rename office to ink --- notepad/{office => ink} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename notepad/{office => ink} (100%) diff --git a/notepad/office b/notepad/ink similarity index 100% rename from notepad/office rename to notepad/ink From db7d9baa3b94dd300a0750b73503493743b5fbbc Mon Sep 17 00:00:00 2001 From: rservices Date: Tue, 10 Mar 2020 09:33:21 -0600 Subject: [PATCH 046/125] Update ink --- notepad/ink | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/notepad/ink b/notepad/ink index 809e210..e32eb01 100644 --- a/notepad/ink +++ b/notepad/ink @@ -3115,7 +3115,7 @@ function DoVanillaClose() term.setTextColour(colours.lightGrey) PrintCentered("Word Proccessor for ComputerCraft", (Drawing.Screen.Height/2)) term.setTextColour(colours.white) - PrintCentered("(c) oeed 2014", (Drawing.Screen.Height/2)+3) + PrintCentered("(c) Outraged Security .INC 2020", (Drawing.Screen.Height/2)+3) term.setCursorPos(1, Drawing.Screen.Height) error('', 0) end From eaf90e7df8240789329f743fd7a3f98b4c5fbfe6 Mon Sep 17 00:00:00 2001 From: rservices Date: Tue, 10 Mar 2020 09:33:53 -0600 Subject: [PATCH 047/125] Update programVersions --- programVersions | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/programVersions b/programVersions index b20f61c..5a61242 100644 --- a/programVersions +++ b/programVersions @@ -107,8 +107,8 @@ ["Author"]="Outraged Security .INC", ["Package"]="Standalone", }, - ["Office"]={ - ["GitURL"]="https://raw.githubusercontent.com/rservices/darkprograms/darkprograms/notepad/office", + ["ink"]={ + ["GitURL"]="https://raw.githubusercontent.com/rservices/darkprograms/darkprograms/notepad/ink", ["Version"]=2.16, ["Type"]="program", ["Name"]="Outraged Security .INC Office Word Software", From eeb60d65e4e90df9a1674cd10445c7571c752251 Mon Sep 17 00:00:00 2001 From: rservices Date: Tue, 10 Mar 2020 09:41:20 -0600 Subject: [PATCH 048/125] Update ink --- notepad/ink | 5912 ++++++++++++++++++++++++++------------------------- 1 file changed, 2957 insertions(+), 2955 deletions(-) diff --git a/notepad/ink b/notepad/ink index e32eb01..9b1ec70 100644 --- a/notepad/ink +++ b/notepad/ink @@ -1,374 +1,374 @@ tArgs = {...} - + if OneOS then - --running under OneOS - OneOS.ToolBarColour = colours.grey - OneOS.ToolBarTextColour = colours.white + --running under OneOS + OneOS.ToolBarColour = colours.grey + OneOS.ToolBarTextColour = colours.white end - + local _w, _h = term.getSize() - + local round = function(num, idp) - local mult = 10^(idp or 0) - return math.floor(num * mult + 0.5) / mult + local mult = 10^(idp or 0) + return math.floor(num * mult + 0.5) / mult end - + UIColours = { - Toolbar = colours.grey, - ToolbarText = colours.lightGrey, - ToolbarSelected = colours.lightBlue, - ControlText = colours.white, - ToolbarItemTitle = colours.black, - Background = colours.lightGrey, - MenuBackground = colours.white, - MenuText = colours.black, - MenuSeparatorText = colours.grey, - MenuDisabledText = colours.lightGrey, - Shadow = colours.grey, - TransparentBackgroundOne = colours.white, - TransparentBackgroundTwo = colours.lightGrey, - MenuBarActive = colours.white + Toolbar = colours.grey, + ToolbarText = colours.lightGrey, + ToolbarSelected = colours.lightBlue, + ControlText = colours.white, + ToolbarItemTitle = colours.black, + Background = colours.lightGrey, + MenuBackground = colours.white, + MenuText = colours.black, + MenuSeparatorText = colours.grey, + MenuDisabledText = colours.lightGrey, + Shadow = colours.grey, + TransparentBackgroundOne = colours.white, + TransparentBackgroundTwo = colours.lightGrey, + MenuBarActive = colours.white } - + local getNames = peripheral.getNames or function() - local tResults = {} - for n,sSide in ipairs( rs.getSides() ) do - if peripheral.isPresent( sSide ) then - table.insert( tResults, sSide ) - local isWireless = false - if not pcall(function()isWireless = peripheral.call(sSide, 'isWireless') end) then - isWireless = true - end - if peripheral.getType( sSide ) == "modem" and not isWireless then - local tRemote = peripheral.call( sSide, "getNamesRemote" ) - for n,sName in ipairs( tRemote ) do - table.insert( tResults, sName ) - end - end - end - end - return tResults + local tResults = {} + for n,sSide in ipairs( rs.getSides() ) do + if peripheral.isPresent( sSide ) then + table.insert( tResults, sSide ) + local isWireless = false + if not pcall(function()isWireless = peripheral.call(sSide, 'isWireless') end) then + isWireless = true + end + if peripheral.getType( sSide ) == "modem" and not isWireless then + local tRemote = peripheral.call( sSide, "getNamesRemote" ) + for n,sName in ipairs( tRemote ) do + table.insert( tResults, sName ) + end + end + end + end + return tResults end - + Peripheral = { - GetPeripheral = function(_type) - for i, p in ipairs(Peripheral.GetPeripherals()) do - if p.Type == _type then - return p - end - end - end, - - Call = function(type, ...) - local tArgs = {...} - local p = Peripheral.GetPeripheral(type) - peripheral.call(p.Side, unpack(tArgs)) - end, - - GetPeripherals = function(filterType) - local peripherals = {} - for i, side in ipairs(getNames()) do - local name = peripheral.getType(side):gsub("^%l", string.upper) - local code = string.upper(side:sub(1,1)) - if side:find('_') then - code = side:sub(side:find('_')+1) - end - - local dupe = false - for i, v in ipairs(peripherals) do - if v[1] == name .. ' ' .. code then - dupe = true - end - end - - if not dupe then - local _type = peripheral.getType(side) - local isWireless = false - if _type == 'modem' then - if not pcall(function()isWireless = peripheral.call(sSide, 'isWireless') end) then - isWireless = true - end - if isWireless then - _type = 'wireless_modem' - name = 'W '..name - end - end - if not filterType or _type == filterType then - table.insert(peripherals, {Name = name:sub(1,8) .. ' '..code, Fullname = name .. ' ('..side:sub(1, 1):upper() .. side:sub(2, -1)..')', Side = side, Type = _type, Wireless = isWireless}) - end - end - end - return peripherals - end, - - PresentNamed = function(name) - return peripheral.isPresent(name) - end, - - CallType = function(type, ...) - local tArgs = {...} - local p = GetPeripheral(type) - return peripheral.call(p.Side, unpack(tArgs)) - end, - - CallNamed = function(name, ...) - local tArgs = {...} - return peripheral.call(name, unpack(tArgs)) - end + GetPeripheral = function(_type) + for i, p in ipairs(Peripheral.GetPeripherals()) do + if p.Type == _type then + return p + end + end + end, + + Call = function(type, ...) + local tArgs = {...} + local p = Peripheral.GetPeripheral(type) + peripheral.call(p.Side, unpack(tArgs)) + end, + + GetPeripherals = function(filterType) + local peripherals = {} + for i, side in ipairs(getNames()) do + local name = peripheral.getType(side):gsub("^%l", string.upper) + local code = string.upper(side:sub(1,1)) + if side:find('_') then + code = side:sub(side:find('_')+1) + end + + local dupe = false + for i, v in ipairs(peripherals) do + if v[1] == name .. ' ' .. code then + dupe = true + end + end + + if not dupe then + local _type = peripheral.getType(side) + local isWireless = false + if _type == 'modem' then + if not pcall(function()isWireless = peripheral.call(sSide, 'isWireless') end) then + isWireless = true + end + if isWireless then + _type = 'wireless_modem' + name = 'W '..name + end + end + if not filterType or _type == filterType then + table.insert(peripherals, {Name = name:sub(1,8) .. ' '..code, Fullname = name .. ' ('..side:sub(1, 1):upper() .. side:sub(2, -1)..')', Side = side, Type = _type, Wireless = isWireless}) + end + end + end + return peripherals + end, + + PresentNamed = function(name) + return peripheral.isPresent(name) + end, + + CallType = function(type, ...) + local tArgs = {...} + local p = GetPeripheral(type) + return peripheral.call(p.Side, unpack(tArgs)) + end, + + CallNamed = function(name, ...) + local tArgs = {...} + return peripheral.call(name, unpack(tArgs)) + end } - + TextLine = { - Text = "", - Alignment = AlignmentLeft, - - Initialise = function(self, text, alignment) - local new = {} -- the new instance - setmetatable( new, {__index = self} ) - new.Text = text - new.Alignment = alignment or AlignmentLeft - return new - end + Text = "", + Alignment = AlignmentLeft, + + Initialise = function(self, text, alignment) + local new = {} -- the new instance + setmetatable( new, {__index = self} ) + new.Text = text + new.Alignment = alignment or AlignmentLeft + return new + end } - + local StripColours = function(str) - return str:gsub('['..string.char(14)..'-'..string.char(29)..']','') + return str:gsub('['..string.char(14)..'-'..string.char(29)..']','') end - + Printer = { - Name = nil, - PeripheralType = 'printer', - - paperLevel = function(self) - return Peripheral.CallNamed(self.Name, 'getPaperLevel') - end, - - newPage = function(self) - return Peripheral.CallNamed(self.Name, 'newPage') - end, - - endPage = function(self) - return Peripheral.CallNamed(self.Name, 'endPage') - end, - - pageWrite = function(self, text) - return Peripheral.CallNamed(self.Name, 'write', text) - end, - - setPageTitle = function(self, title) - return Peripheral.CallNamed(self.Name, 'setPageTitle', title) - end, - - inkLevel = function(self) - return Peripheral.CallNamed(self.Name, 'getInkLevel') - end, - - getCursorPos = function(self) - return Peripheral.CallNamed(self.Name, 'getCursorPos') - end, - - setCursorPos = function(self, x, y) - return Peripheral.CallNamed(self.Name, 'setCursorPos', x, y) - end, - - pageSize = function(self) - return Peripheral.CallNamed(self.Name, 'getPageSize') - end, - - Present = function() - if Peripheral.GetPeripheral(Printer.PeripheralType) == nil then - return false - else - return true - end - end, - - PrintLines = function(self, lines, title, copies) - local pages = {} - local pageLines = {} - for i, line in ipairs(lines) do - table.insert(pageLines, TextLine:Initialise(StripColours(line))) - if i % 25 == 0 then - table.insert(pages, pageLines) - pageLines = {} - end - end - if #pageLines ~= 0 then - table.insert(pages, pageLines) - end - return self:PrintPages(pages, title, copies) - end, - - PrintPages = function(self, pages, title, copies) - copies = copies or 1 - for c = 1, copies do - for p, page in ipairs(pages) do - if self:paperLevel() < #pages * copies then - return 'Add more paper to the printer' - end - if self:inkLevel() < #pages * copies then - return 'Add more ink to the printer' - end - self:newPage() - for i, line in ipairs(page) do - self:setCursorPos(1, i) - self:pageWrite(StripColours(line.Text)) - end - if title then - self:setPageTitle(title) - end - self:endPage() - end - end - end, - - Initialise = function(self, name) - if Printer.Present() then --fix - local new = {} -- the new instance - setmetatable( new, {__index = self} ) - if name and Peripheral.PresentNamed(name) then - new.Name = name - else - new.Name = Peripheral.GetPeripheral(Printer.PeripheralType).Side - end - return new - end - end + Name = nil, + PeripheralType = 'printer', + + paperLevel = function(self) + return Peripheral.CallNamed(self.Name, 'getPaperLevel') + end, + + newPage = function(self) + return Peripheral.CallNamed(self.Name, 'newPage') + end, + + endPage = function(self) + return Peripheral.CallNamed(self.Name, 'endPage') + end, + + pageWrite = function(self, text) + return Peripheral.CallNamed(self.Name, 'write', text) + end, + + setPageTitle = function(self, title) + return Peripheral.CallNamed(self.Name, 'setPageTitle', title) + end, + + inkLevel = function(self) + return Peripheral.CallNamed(self.Name, 'getInkLevel') + end, + + getCursorPos = function(self) + return Peripheral.CallNamed(self.Name, 'getCursorPos') + end, + + setCursorPos = function(self, x, y) + return Peripheral.CallNamed(self.Name, 'setCursorPos', x, y) + end, + + pageSize = function(self) + return Peripheral.CallNamed(self.Name, 'getPageSize') + end, + + Present = function() + if Peripheral.GetPeripheral(Printer.PeripheralType) == nil then + return false + else + return true + end + end, + + PrintLines = function(self, lines, title, copies) + local pages = {} + local pageLines = {} + for i, line in ipairs(lines) do + table.insert(pageLines, TextLine:Initialise(StripColours(line))) + if i % 25 == 0 then + table.insert(pages, pageLines) + pageLines = {} + end + end + if #pageLines ~= 0 then + table.insert(pages, pageLines) + end + return self:PrintPages(pages, title, copies) + end, + + PrintPages = function(self, pages, title, copies) + copies = copies or 1 + for c = 1, copies do + for p, page in ipairs(pages) do + if self:paperLevel() < #pages * copies then + return 'Add more paper to the printer' + end + if self:inkLevel() < #pages * copies then + return 'Add more ink to the printer' + end + self:newPage() + for i, line in ipairs(page) do + self:setCursorPos(1, i) + self:pageWrite(StripColours(line.Text)) + end + if title then + self:setPageTitle(title) + end + self:endPage() + end + end + end, + + Initialise = function(self, name) + if Printer.Present() then --fix + local new = {} -- the new instance + setmetatable( new, {__index = self} ) + if name and Peripheral.PresentNamed(name) then + new.Name = name + else + new.Name = Peripheral.GetPeripheral(Printer.PeripheralType).Side + end + return new + end + end } - + Clipboard = { - Content = nil, - Type = nil, - IsCut = false, - - Empty = function() - Clipboard.Content = nil - Clipboard.Type = nil - Clipboard.IsCut = false - end, - - isEmpty = function() - return Clipboard.Content == nil - end, - - Copy = function(content, _type) - Clipboard.Content = content - Clipboard.Type = _type or 'generic' - Clipboard.IsCut = false - end, - - Cut = function(content, _type) - Clipboard.Content = content - Clipboard.Type = _type or 'generic' - Clipboard.IsCut = true - end, - - Paste = function() - local c, t = Clipboard.Content, Clipboard.Type - if Clipboard.IsCut then - Clipboard.Empty() - end - return c, t - end + Content = nil, + Type = nil, + IsCut = false, + + Empty = function() + Clipboard.Content = nil + Clipboard.Type = nil + Clipboard.IsCut = false + end, + + isEmpty = function() + return Clipboard.Content == nil + end, + + Copy = function(content, _type) + Clipboard.Content = content + Clipboard.Type = _type or 'generic' + Clipboard.IsCut = false + end, + + Cut = function(content, _type) + Clipboard.Content = content + Clipboard.Type = _type or 'generic' + Clipboard.IsCut = true + end, + + Paste = function() + local c, t = Clipboard.Content, Clipboard.Type + if Clipboard.IsCut then + Clipboard.Empty() + end + return c, t + end } - + if OneOS and OneOS.Clipboard then - Clipboard = OneOS.Clipboard + Clipboard = OneOS.Clipboard end - + Drawing = { - - Screen = { - Width = _w, - Height = _h - }, - - DrawCharacters = function (x, y, characters, textColour,bgColour) - Drawing.WriteStringToBuffer(x, y, characters, textColour, bgColour) - end, - - DrawBlankArea = function (x, y, w, h, colour) - Drawing.DrawArea (x, y, w, h, " ", 1, colour) - end, - - DrawArea = function (x, y, w, h, character, textColour, bgColour) - --width must be greater than 1, other wise we get a stack overflow - if w < 0 then - w = w * -1 - elseif w == 0 then - w = 1 - end - - for ix = 1, w do - local currX = x + ix - 1 - for iy = 1, h do - local currY = y + iy - 1 - Drawing.WriteToBuffer(currX, currY, character, textColour, bgColour) - end - end - end, - - DrawImage = function(_x,_y,tImage, w, h) - if tImage then - for y = 1, h do - if not tImage[y] then - break - end - for x = 1, w do - if not tImage[y][x] then - break - end - local bgColour = tImage[y][x] - local textColour = tImage.textcol[y][x] or colours.white - local char = tImage.text[y][x] - Drawing.WriteToBuffer(x+_x-1, y+_y-1, char, textColour, bgColour) - end - end - elseif w and h then - Drawing.DrawBlankArea(x, y, w, h, colours.green) - end - end, - --using .nft - LoadImage = function(path) - local image = { - text = {}, - textcol = {} - } - local fs = fs - if OneOS then - fs = OneOS.FS - end - if fs.exists(path) then - local _open = io.open - if OneOS then - _open = OneOS.IO.open - end - local file = _open(path, "r") - local sLine = file:read() - local num = 1 - while sLine do - table.insert(image, num, {}) - table.insert(image.text, num, {}) - table.insert(image.textcol, num, {}) - - --As we're no longer 1-1, we keep track of what index to write to - local writeIndex = 1 - --Tells us if we've hit a 30 or 31 (BG and FG respectively)- next char specifies the curr colour - local bgNext, fgNext = false, false - --The current background and foreground colours - local currBG, currFG = nil,nil - for i=1,#sLine do - local nextChar = string.sub(sLine, i, i) - if nextChar:byte() == 30 then + + Screen = { + Width = _w, + Height = _h + }, + + DrawCharacters = function (x, y, characters, textColour,bgColour) + Drawing.WriteStringToBuffer(x, y, characters, textColour, bgColour) + end, + + DrawBlankArea = function (x, y, w, h, colour) + Drawing.DrawArea (x, y, w, h, " ", 1, colour) + end, + + DrawArea = function (x, y, w, h, character, textColour, bgColour) + --width must be greater than 1, other wise we get a stack overflow + if w < 0 then + w = w * -1 + elseif w == 0 then + w = 1 + end + + for ix = 1, w do + local currX = x + ix - 1 + for iy = 1, h do + local currY = y + iy - 1 + Drawing.WriteToBuffer(currX, currY, character, textColour, bgColour) + end + end + end, + + DrawImage = function(_x,_y,tImage, w, h) + if tImage then + for y = 1, h do + if not tImage[y] then + break + end + for x = 1, w do + if not tImage[y][x] then + break + end + local bgColour = tImage[y][x] + local textColour = tImage.textcol[y][x] or colours.white + local char = tImage.text[y][x] + Drawing.WriteToBuffer(x+_x-1, y+_y-1, char, textColour, bgColour) + end + end + elseif w and h then + Drawing.DrawBlankArea(x, y, w, h, colours.green) + end + end, + --using .nft + LoadImage = function(path) + local image = { + text = {}, + textcol = {} + } + local fs = fs + if OneOS then + fs = OneOS.FS + end + if fs.exists(path) then + local _open = io.open + if OneOS then + _open = OneOS.IO.open + end + local file = _open(path, "r") + local sLine = file:read() + local num = 1 + while sLine do + table.insert(image, num, {}) + table.insert(image.text, num, {}) + table.insert(image.textcol, num, {}) + + --As we're no longer 1-1, we keep track of what index to write to + local writeIndex = 1 + --Tells us if we've hit a 30 or 31 (BG and FG respectively)- next char specifies the curr colour + local bgNext, fgNext = false, false + --The current background and foreground colours + local currBG, currFG = nil,nil + for i=1,#sLine do + local nextChar = string.sub(sLine, i, i) + if nextChar:byte() == 30 then bgNext = true - elseif nextChar:byte() == 31 then + elseif nextChar:byte() == 31 then fgNext = true - elseif bgNext then + elseif bgNext then currBG = Drawing.GetColour(nextChar) bgNext = false - elseif fgNext then + elseif fgNext then currFG = Drawing.GetColour(nextChar) fgNext = false - else + else if nextChar ~= " " and currFG == nil then currFG = colours.white end @@ -376,677 +376,677 @@ Drawing = { image.textcol[num][writeIndex] = currFG image.text[num][writeIndex] = nextChar writeIndex = writeIndex + 1 - end - end - num = num+1 - sLine = file:read() - end - file:close() - end - return image - end, - - DrawCharactersCenter = function(x, y, w, h, characters, textColour,bgColour) - w = w or Drawing.Screen.Width - h = h or Drawing.Screen.Height - x = x or 0 - y = y or 0 - x = math.ceil((w - #characters) / 2) + x - y = math.floor(h / 2) + y - - Drawing.DrawCharacters(x, y, characters, textColour, bgColour) - end, - - GetColour = function(hex) - if hex == ' ' then - return colours.transparent - end - local value = tonumber(hex, 16) - if not value then return nil end - value = math.pow(2,value) - return value - end, - - Clear = function (_colour) - _colour = _colour or colours.black - Drawing.ClearBuffer() - Drawing.DrawBlankArea(1, 1, Drawing.Screen.Width, Drawing.Screen.Height, _colour) - end, - - Buffer = {}, - BackBuffer = {}, - - DrawBuffer = function() - for y,row in pairs(Drawing.Buffer) do - for x,pixel in pairs(row) do - local shouldDraw = true - local hasBackBuffer = true - if Drawing.BackBuffer[y] == nil or Drawing.BackBuffer[y][x] == nil or #Drawing.BackBuffer[y][x] ~= 3 then - hasBackBuffer = false - end - if hasBackBuffer and Drawing.BackBuffer[y][x][1] == Drawing.Buffer[y][x][1] and Drawing.BackBuffer[y][x][2] == Drawing.Buffer[y][x][2] and Drawing.BackBuffer[y][x][3] == Drawing.Buffer[y][x][3] then - shouldDraw = false - end - if shouldDraw then - term.setBackgroundColour(pixel[3]) - term.setTextColour(pixel[2]) - term.setCursorPos(x, y) - term.write(pixel[1]) - end - end - end - Drawing.BackBuffer = Drawing.Buffer - Drawing.Buffer = {} - term.setCursorPos(1,1) - end, - - ClearBuffer = function() - Drawing.Buffer = {} - end, - - WriteStringToBuffer = function (x, y, characters, textColour,bgColour) - for i = 1, #characters do - local character = characters:sub(i,i) - Drawing.WriteToBuffer(x + i - 1, y, character, textColour, bgColour) - end - end, - - WriteToBuffer = function(x, y, character, textColour,bgColour) - x = round(x) - y = round(y) - if bgColour == colours.transparent then - Drawing.Buffer[y] = Drawing.Buffer[y] or {} - Drawing.Buffer[y][x] = Drawing.Buffer[y][x] or {"", colours.white, colours.black} - Drawing.Buffer[y][x][1] = character - Drawing.Buffer[y][x][2] = textColour - else - Drawing.Buffer[y] = Drawing.Buffer[y] or {} - Drawing.Buffer[y][x] = {character, textColour, bgColour} - end - end, + end + end + num = num+1 + sLine = file:read() + end + file:close() + end + return image + end, + + DrawCharactersCenter = function(x, y, w, h, characters, textColour,bgColour) + w = w or Drawing.Screen.Width + h = h or Drawing.Screen.Height + x = x or 0 + y = y or 0 + x = math.ceil((w - #characters) / 2) + x + y = math.floor(h / 2) + y + + Drawing.DrawCharacters(x, y, characters, textColour, bgColour) + end, + + GetColour = function(hex) + if hex == ' ' then + return colours.transparent + end + local value = tonumber(hex, 16) + if not value then return nil end + value = math.pow(2,value) + return value + end, + + Clear = function (_colour) + _colour = _colour or colours.black + Drawing.ClearBuffer() + Drawing.DrawBlankArea(1, 1, Drawing.Screen.Width, Drawing.Screen.Height, _colour) + end, + + Buffer = {}, + BackBuffer = {}, + + DrawBuffer = function() + for y,row in pairs(Drawing.Buffer) do + for x,pixel in pairs(row) do + local shouldDraw = true + local hasBackBuffer = true + if Drawing.BackBuffer[y] == nil or Drawing.BackBuffer[y][x] == nil or #Drawing.BackBuffer[y][x] ~= 3 then + hasBackBuffer = false + end + if hasBackBuffer and Drawing.BackBuffer[y][x][1] == Drawing.Buffer[y][x][1] and Drawing.BackBuffer[y][x][2] == Drawing.Buffer[y][x][2] and Drawing.BackBuffer[y][x][3] == Drawing.Buffer[y][x][3] then + shouldDraw = false + end + if shouldDraw then + term.setBackgroundColour(pixel[3]) + term.setTextColour(pixel[2]) + term.setCursorPos(x, y) + term.write(pixel[1]) + end + end + end + Drawing.BackBuffer = Drawing.Buffer + Drawing.Buffer = {} + term.setCursorPos(1,1) + end, + + ClearBuffer = function() + Drawing.Buffer = {} + end, + + WriteStringToBuffer = function (x, y, characters, textColour,bgColour) + for i = 1, #characters do + local character = characters:sub(i,i) + Drawing.WriteToBuffer(x + i - 1, y, character, textColour, bgColour) + end + end, + + WriteToBuffer = function(x, y, character, textColour,bgColour) + x = round(x) + y = round(y) + if bgColour == colours.transparent then + Drawing.Buffer[y] = Drawing.Buffer[y] or {} + Drawing.Buffer[y][x] = Drawing.Buffer[y][x] or {"", colours.white, colours.black} + Drawing.Buffer[y][x][1] = character + Drawing.Buffer[y][x][2] = textColour + else + Drawing.Buffer[y] = Drawing.Buffer[y] or {} + Drawing.Buffer[y][x] = {character, textColour, bgColour} + end + end, } - + Current = { - Document = nil, - TextInput = nil, - CursorPos = {1,1}, - CursorColour = colours.black, - Selection = {8, 36}, - Window = nil, - Modified = false, + Document = nil, + TextInput = nil, + CursorPos = {1,1}, + CursorColour = colours.black, + Selection = {8, 36}, + Window = nil, + Modified = false, } - + local isQuitting = false - + function OrderSelection() - if Current.Selection then - if Current.Selection[1] <= Current.Selection[2] then - return Current.Selection - else - return {Current.Selection[2], Current.Selection[1]} - end - end + if Current.Selection then + if Current.Selection[1] <= Current.Selection[2] then + return Current.Selection + else + return {Current.Selection[2], Current.Selection[1]} + end + end end - + function StripColours(str) - return str:gsub('['..string.char(14)..'-'..string.char(29)..']','') + return str:gsub('['..string.char(14)..'-'..string.char(29)..']','') end - + function FindColours(str) - local _, count = str:gsub('['..string.char(14)..'-'..string.char(29)..']','') - return count + local _, count = str:gsub('['..string.char(14)..'-'..string.char(29)..']','') + return count end - + ColourFromCharacter = function(character) - local n = character:byte() - 14 - if n > 16 then - return nil - else - return 2^n - end + local n = character:byte() - 14 + if n > 16 then + return nil + else + return 2^n + end end - + CharacterFromColour = function(colour) - return string.char(math.floor(math.log(colour)/math.log(2))+14) + return string.char(math.floor(math.log(colour)/math.log(2))+14) end - + Events = {} - + Button = { - X = 1, - Y = 1, - Width = 0, - Height = 0, - BackgroundColour = colours.lightGrey, - TextColour = colours.white, - ActiveBackgroundColour = colours.lightGrey, - Text = "", - Parent = nil, - _Click = nil, - Toggle = nil, - - AbsolutePosition = function(self) - return self.Parent:AbsolutePosition() - end, - - Draw = function(self) - local bg = self.BackgroundColour - local tc = self.TextColour - if type(bg) == 'function' then - bg = bg() - end - - if self.Toggle then - tc = UIColours.MenuBarActive - bg = self.ActiveBackgroundColour - end - - local pos = GetAbsolutePosition(self) - Drawing.DrawBlankArea(pos.X, pos.Y, self.Width, self.Height, bg) - Drawing.DrawCharactersCenter(pos.X, pos.Y, self.Width, self.Height, self.Text, tc, bg) - end, - - Initialise = function(self, x, y, width, height, backgroundColour, parent, click, text, textColour, toggle, activeBackgroundColour) - local new = {} -- the new instance - setmetatable( new, {__index = self} ) - height = height or 1 - new.Width = width or #text + 2 - new.Height = height - new.Y = y - new.X = x - new.Text = text or "" - new.BackgroundColour = backgroundColour or colours.lightGrey - new.TextColour = textColour or colours.white - new.ActiveBackgroundColour = activeBackgroundColour or colours.lightGrey - new.Parent = parent - new._Click = click - new.Toggle = toggle - return new - end, - - Click = function(self, side, x, y) - if self._Click then - if self:_Click(side, x, y, not self.Toggle) ~= false and self.Toggle ~= nil then - self.Toggle = not self.Toggle - Draw() - end - return true - else - return false - end - end + X = 1, + Y = 1, + Width = 0, + Height = 0, + BackgroundColour = colours.lightGrey, + TextColour = colours.white, + ActiveBackgroundColour = colours.lightGrey, + Text = "", + Parent = nil, + _Click = nil, + Toggle = nil, + + AbsolutePosition = function(self) + return self.Parent:AbsolutePosition() + end, + + Draw = function(self) + local bg = self.BackgroundColour + local tc = self.TextColour + if type(bg) == 'function' then + bg = bg() + end + + if self.Toggle then + tc = UIColours.MenuBarActive + bg = self.ActiveBackgroundColour + end + + local pos = GetAbsolutePosition(self) + Drawing.DrawBlankArea(pos.X, pos.Y, self.Width, self.Height, bg) + Drawing.DrawCharactersCenter(pos.X, pos.Y, self.Width, self.Height, self.Text, tc, bg) + end, + + Initialise = function(self, x, y, width, height, backgroundColour, parent, click, text, textColour, toggle, activeBackgroundColour) + local new = {} -- the new instance + setmetatable( new, {__index = self} ) + height = height or 1 + new.Width = width or #text + 2 + new.Height = height + new.Y = y + new.X = x + new.Text = text or "" + new.BackgroundColour = backgroundColour or colours.lightGrey + new.TextColour = textColour or colours.white + new.ActiveBackgroundColour = activeBackgroundColour or colours.lightGrey + new.Parent = parent + new._Click = click + new.Toggle = toggle + return new + end, + + Click = function(self, side, x, y) + if self._Click then + if self:_Click(side, x, y, not self.Toggle) ~= false and self.Toggle ~= nil then + self.Toggle = not self.Toggle + Draw() + end + return true + else + return false + end + end } - + TextBox = { - X = 1, - Y = 1, - Width = 0, - Height = 0, - BackgroundColour = colours.lightGrey, - TextColour = colours.black, - Parent = nil, - TextInput = nil, - Placeholder = '', - - AbsolutePosition = function(self) - return self.Parent:AbsolutePosition() - end, - - Draw = function(self) - local pos = GetAbsolutePosition(self) - Drawing.DrawBlankArea(pos.X, pos.Y, self.Width, self.Height, self.BackgroundColour) - local text = self.TextInput.Value - if #tostring(text) > (self.Width - 2) then - text = text:sub(#text-(self.Width - 3)) - if Current.TextInput == self.TextInput then - Current.CursorPos = {pos.X + 1 + self.Width-2, pos.Y} - end - else - if Current.TextInput == self.TextInput then - Current.CursorPos = {pos.X + 1 + self.TextInput.CursorPos, pos.Y} - end - end - - if #tostring(text) == 0 then - Drawing.DrawCharacters(pos.X + 1, pos.Y, self.Placeholder, colours.lightGrey, self.BackgroundColour) - else - Drawing.DrawCharacters(pos.X + 1, pos.Y, text, self.TextColour, self.BackgroundColour) - end - - term.setCursorBlink(true) - - Current.CursorColour = self.TextColour - end, - - Initialise = function(self, x, y, width, height, parent, text, backgroundColour, textColour, done, numerical) - local new = {} -- the new instance - setmetatable( new, {__index = self} ) - height = height or 1 - new.Width = width or #text + 2 - new.Height = height - new.Y = y - new.X = x - new.TextInput = TextInput:Initialise(text or '', function(key) - if done then - done(key) - end - Draw() - end, numerical) - new.BackgroundColour = backgroundColour or colours.lightGrey - new.TextColour = textColour or colours.black - new.Parent = parent - return new - end, - - Click = function(self, side, x, y) - Current.Input = self.TextInput - self:Draw() - end + X = 1, + Y = 1, + Width = 0, + Height = 0, + BackgroundColour = colours.lightGrey, + TextColour = colours.black, + Parent = nil, + TextInput = nil, + Placeholder = '', + + AbsolutePosition = function(self) + return self.Parent:AbsolutePosition() + end, + + Draw = function(self) + local pos = GetAbsolutePosition(self) + Drawing.DrawBlankArea(pos.X, pos.Y, self.Width, self.Height, self.BackgroundColour) + local text = self.TextInput.Value + if #tostring(text) > (self.Width - 2) then + text = text:sub(#text-(self.Width - 3)) + if Current.TextInput == self.TextInput then + Current.CursorPos = {pos.X + 1 + self.Width-2, pos.Y} + end + else + if Current.TextInput == self.TextInput then + Current.CursorPos = {pos.X + 1 + self.TextInput.CursorPos, pos.Y} + end + end + + if #tostring(text) == 0 then + Drawing.DrawCharacters(pos.X + 1, pos.Y, self.Placeholder, colours.lightGrey, self.BackgroundColour) + else + Drawing.DrawCharacters(pos.X + 1, pos.Y, text, self.TextColour, self.BackgroundColour) + end + + term.setCursorBlink(true) + + Current.CursorColour = self.TextColour + end, + + Initialise = function(self, x, y, width, height, parent, text, backgroundColour, textColour, done, numerical) + local new = {} -- the new instance + setmetatable( new, {__index = self} ) + height = height or 1 + new.Width = width or #text + 2 + new.Height = height + new.Y = y + new.X = x + new.TextInput = TextInput:Initialise(text or '', function(key) + if done then + done(key) + end + Draw() + end, numerical) + new.BackgroundColour = backgroundColour or colours.lightGrey + new.TextColour = textColour or colours.black + new.Parent = parent + return new + end, + + Click = function(self, side, x, y) + Current.Input = self.TextInput + self:Draw() + end } - + TextInput = { - Value = "", - Change = nil, - CursorPos = nil, - Numerical = false, - IsDocument = nil, - - Initialise = function(self, value, change, numerical, isDocument) - local new = {} -- the new instance - setmetatable( new, {__index = self} ) - new.Value = tostring(value) - new.Change = change - new.CursorPos = #tostring(value) - new.Numerical = numerical - new.IsDocument = isDocument or false - return new - end, - - Insert = function(self, str) - if self.Numerical then - str = tostring(tonumber(str)) - end - - local selection = OrderSelection() - - if self.IsDocument and selection then - self.Value = string.sub(self.Value, 1, selection[1]-1) .. str .. string.sub( self.Value, selection[2]+2) - self.CursorPos = selection[1] - Current.Selection = nil - else - local _, newLineAdjust = string.gsub(self.Value:sub(1, self.CursorPos), '\n','') - - self.Value = string.sub(self.Value, 1, self.CursorPos + newLineAdjust) .. str .. string.sub( self.Value, self.CursorPos + 1 + newLineAdjust) - self.CursorPos = self.CursorPos + 1 - end - - self.Change(key) - end, - - Extract = function(self, remove) - local selection = OrderSelection() - if self.IsDocument and selection then - local _, newLineAdjust = string.gsub(self.Value:sub(selection[1], selection[2]), '\n','') - local str = string.sub(self.Value, selection[1], selection[2]+1+newLineAdjust) - if remove then - self.Value = string.sub(self.Value, 1, selection[1]-1) .. string.sub( self.Value, selection[2]+2+newLineAdjust) - self.CursorPos = selection[1] - 1 - Current.Selection = nil - end - return str - end - end, - - Char = function(self, char) - if char == 'nil' then - return - end - self:Insert(char) - end, - - Key = function(self, key) - if key == keys.enter then - if self.IsDocument then - self.Value = string.sub(self.Value, 1, self.CursorPos ) .. '\n' .. string.sub( self.Value, self.CursorPos + 1 ) - self.CursorPos = self.CursorPos + 1 - end - self.Change(key) - elseif key == keys.left then - -- Left - if self.CursorPos > 0 then - local colShift = FindColours(string.sub( self.Value, self.CursorPos, self.CursorPos)) - self.CursorPos = self.CursorPos - 1 - colShift - self.Change(key) - end - - elseif key == keys.right then - -- Right - if self.CursorPos < string.len(self.Value) then - local colShift = FindColours(string.sub( self.Value, self.CursorPos+1, self.CursorPos+1)) - self.CursorPos = self.CursorPos + 1 + colShift - self.Change(key) - end - - elseif key == keys.backspace then - -- Backspace - if self.IsDocument and Current.Selection then - self:Extract(true) - self.Change(key) - elseif self.CursorPos > 0 then - local colShift = FindColours(string.sub( self.Value, self.CursorPos, self.CursorPos)) - local _, newLineAdjust = string.gsub(self.Value:sub(1, self.CursorPos), '\n','') - - self.Value = string.sub( self.Value, 1, self.CursorPos - 1 - colShift + newLineAdjust) .. string.sub( self.Value, self.CursorPos + 1 - colShift + newLineAdjust) - self.CursorPos = self.CursorPos - 1 - colShift - self.Change(key) - end - elseif key == keys.home then - -- Home - self.CursorPos = 0 - self.Change(key) - elseif key == keys.delete then - if self.IsDocument and Current.Selection then - self:Extract(true) - self.Change(key) - elseif self.CursorPos < string.len(self.Value) then - self.Value = string.sub( self.Value, 1, self.CursorPos ) .. string.sub( self.Value, self.CursorPos + 2 ) - self.Change(key) - end - elseif key == keys["end"] then - -- End - self.CursorPos = string.len(self.Value) - self.Change(key) - elseif key == keys.up and self.IsDocument then - -- Up - if Current.Document.CursorPos then - local page = Current.Document.Pages[Current.Document.CursorPos.Page] - self.CursorPos = page:GetCursorPosFromPoint(Current.Document.CursorPos.Collum + page.MarginX, Current.Document.CursorPos.Line - page.MarginY - 1 + Current.Document.ScrollBar.Scroll, true) - self.Change(key) - end - elseif key == keys.down and self.IsDocument then - -- Down - if Current.Document.CursorPos then - local page = Current.Document.Pages[Current.Document.CursorPos.Page] - self.CursorPos = page:GetCursorPosFromPoint(Current.Document.CursorPos.Collum + page.MarginX, Current.Document.CursorPos.Line - page.MarginY + 1 + Current.Document.ScrollBar.Scroll, true) - self.Change(key) - end - end - end + Value = "", + Change = nil, + CursorPos = nil, + Numerical = false, + IsDocument = nil, + + Initialise = function(self, value, change, numerical, isDocument) + local new = {} -- the new instance + setmetatable( new, {__index = self} ) + new.Value = tostring(value) + new.Change = change + new.CursorPos = #tostring(value) + new.Numerical = numerical + new.IsDocument = isDocument or false + return new + end, + + Insert = function(self, str) + if self.Numerical then + str = tostring(tonumber(str)) + end + + local selection = OrderSelection() + + if self.IsDocument and selection then + self.Value = string.sub(self.Value, 1, selection[1]-1) .. str .. string.sub( self.Value, selection[2]+2) + self.CursorPos = selection[1] + Current.Selection = nil + else + local _, newLineAdjust = string.gsub(self.Value:sub(1, self.CursorPos), '\n','') + + self.Value = string.sub(self.Value, 1, self.CursorPos + newLineAdjust) .. str .. string.sub( self.Value, self.CursorPos + 1 + newLineAdjust) + self.CursorPos = self.CursorPos + 1 + end + + self.Change(key) + end, + + Extract = function(self, remove) + local selection = OrderSelection() + if self.IsDocument and selection then + local _, newLineAdjust = string.gsub(self.Value:sub(selection[1], selection[2]), '\n','') + local str = string.sub(self.Value, selection[1], selection[2]+1+newLineAdjust) + if remove then + self.Value = string.sub(self.Value, 1, selection[1]-1) .. string.sub( self.Value, selection[2]+2+newLineAdjust) + self.CursorPos = selection[1] - 1 + Current.Selection = nil + end + return str + end + end, + + Char = function(self, char) + if char == 'nil' then + return + end + self:Insert(char) + end, + + Key = function(self, key) + if key == keys.enter then + if self.IsDocument then + self.Value = string.sub(self.Value, 1, self.CursorPos ) .. '\n' .. string.sub( self.Value, self.CursorPos + 1 ) + self.CursorPos = self.CursorPos + 1 + end + self.Change(key) + elseif key == keys.left then + -- Left + if self.CursorPos > 0 then + local colShift = FindColours(string.sub( self.Value, self.CursorPos, self.CursorPos)) + self.CursorPos = self.CursorPos - 1 - colShift + self.Change(key) + end + + elseif key == keys.right then + -- Right + if self.CursorPos < string.len(self.Value) then + local colShift = FindColours(string.sub( self.Value, self.CursorPos+1, self.CursorPos+1)) + self.CursorPos = self.CursorPos + 1 + colShift + self.Change(key) + end + + elseif key == keys.backspace then + -- Backspace + if self.IsDocument and Current.Selection then + self:Extract(true) + self.Change(key) + elseif self.CursorPos > 0 then + local colShift = FindColours(string.sub( self.Value, self.CursorPos, self.CursorPos)) + local _, newLineAdjust = string.gsub(self.Value:sub(1, self.CursorPos), '\n','') + + self.Value = string.sub( self.Value, 1, self.CursorPos - 1 - colShift + newLineAdjust) .. string.sub( self.Value, self.CursorPos + 1 - colShift + newLineAdjust) + self.CursorPos = self.CursorPos - 1 - colShift + self.Change(key) + end + elseif key == keys.home then + -- Home + self.CursorPos = 0 + self.Change(key) + elseif key == keys.delete then + if self.IsDocument and Current.Selection then + self:Extract(true) + self.Change(key) + elseif self.CursorPos < string.len(self.Value) then + self.Value = string.sub( self.Value, 1, self.CursorPos ) .. string.sub( self.Value, self.CursorPos + 2 ) + self.Change(key) + end + elseif key == keys["end"] then + -- End + self.CursorPos = string.len(self.Value) + self.Change(key) + elseif key == keys.up and self.IsDocument then + -- Up + if Current.Document.CursorPos then + local page = Current.Document.Pages[Current.Document.CursorPos.Page] + self.CursorPos = page:GetCursorPosFromPoint(Current.Document.CursorPos.Collum + page.MarginX, Current.Document.CursorPos.Line - page.MarginY - 1 + Current.Document.ScrollBar.Scroll, true) + self.Change(key) + end + elseif key == keys.down and self.IsDocument then + -- Down + if Current.Document.CursorPos then + local page = Current.Document.Pages[Current.Document.CursorPos.Page] + self.CursorPos = page:GetCursorPosFromPoint(Current.Document.CursorPos.Collum + page.MarginX, Current.Document.CursorPos.Line - page.MarginY + 1 + Current.Document.ScrollBar.Scroll, true) + self.Change(key) + end + end + end } - + Menu = { - X = 0, - Y = 0, - Width = 0, - Height = 0, - Owner = nil, - Items = {}, - RemoveTop = false, - - Draw = function(self) - Drawing.DrawBlankArea(self.X + 1, self.Y + 1, self.Width, self.Height, UIColours.Shadow) - if not self.RemoveTop then - Drawing.DrawBlankArea(self.X, self.Y, self.Width, self.Height, UIColours.MenuBackground) - for i, item in ipairs(self.Items) do - if item.Separator then - Drawing.DrawArea(self.X, self.Y + i, self.Width, 1, '-', colours.grey, UIColours.MenuBackground) - else - local textColour = item.Colour or UIColours.MenuText - if (item.Enabled and type(item.Enabled) == 'function' and item.Enabled() == false) or item.Enabled == false then - textColour = UIColours.MenuDisabledText - end - Drawing.DrawCharacters(self.X + 1, self.Y + i, item.Title, textColour, UIColours.MenuBackground) - end - end - else - Drawing.DrawBlankArea(self.X, self.Y, self.Width, self.Height, UIColours.MenuBackground) - for i, item in ipairs(self.Items) do - if item.Separator then - Drawing.DrawArea(self.X, self.Y + i - 1, self.Width, 1, '-', colours.grey, UIColours.MenuBackground) - else - local textColour = item.Colour or UIColours.MenuText - if (item.Enabled and type(item.Enabled) == 'function' and item.Enabled() == false) or item.Enabled == false then - textColour = UIColours.MenuDisabledText - end - Drawing.DrawCharacters(self.X + 1, self.Y + i - 1, item.Title, textColour, UIColours.MenuBackground) - - Drawing.DrawCharacters(self.X - 1 + self.Width-#item.KeyName, self.Y + i - 1, item.KeyName, textColour, UIColours.MenuBackground) - end - end - end - end, - - NameForKey = function(self, key) - if key == keys.leftCtrl then - return '^' - elseif key == keys.tab then - return 'Tab' - elseif key == keys.delete then - return 'Delete' - elseif key == keys.n then - return 'N' - elseif key == keys.a then - return 'A' - elseif key == keys.s then - return 'S' - elseif key == keys.o then - return 'O' - elseif key == keys.z then - return 'Z' - elseif key == keys.y then - return 'Y' - elseif key == keys.c then - return 'C' - elseif key == keys.x then - return 'X' - elseif key == keys.v then - return 'V' - elseif key == keys.r then - return 'R' - elseif key == keys.l then - return 'L' - elseif key == keys.t then - return 'T' - elseif key == keys.h then - return 'H' - elseif key == keys.e then - return 'E' - elseif key == keys.p then - return 'P' - elseif key == keys.f then - return 'F' - elseif key == keys.m then - return 'M' - elseif key == keys.q then - return 'Q' - else - return '?' - end - end, - - Initialise = function(self, x, y, items, owner, removeTop) - local new = {} -- the new instance - setmetatable( new, {__index = self} ) - if not owner then - return - end - - local keyNames = {} - - for i, v in ipairs(items) do - items[i].KeyName = '' - if v.Keys then - for _i, key in ipairs(v.Keys) do - items[i].KeyName = items[i].KeyName .. self:NameForKey(key) - end - end - if items[i].KeyName ~= '' then - table.insert(keyNames, items[i].KeyName) - end - end - local keysLength = LongestString(keyNames) - if keysLength > 0 then - keysLength = keysLength + 2 - end - - new.Width = LongestString(items, 'Title') + 2 + keysLength - if new.Width < 10 then - new.Width = 10 - end - new.Height = #items + 2 - new.RemoveTop = removeTop or false - if removeTop then - new.Height = new.Height - 1 - end - - if y < 1 then - y = 1 - end - if x < 1 then - x = 1 - end - - if y + new.Height > Drawing.Screen.Height + 1 then - y = Drawing.Screen.Height - new.Height - end - if x + new.Width > Drawing.Screen.Width + 1 then - x = Drawing.Screen.Width - new.Width - end - - - new.Y = y - new.X = x - new.Items = items - new.Owner = owner - return new - end, - - New = function(self, x, y, items, owner, removeTop) - if Current.Menu and Current.Menu.Owner == owner then - Current.Menu = nil - return - end - - local new = self:Initialise(x, y, items, owner, removeTop) - Current.Menu = new - return new - end, - - Click = function(self, side, x, y) - local i = y-1 - if self.RemoveTop then - i = y - end - if i >= 1 and y < self.Height then - if not ((self.Items[i].Enabled and type(self.Items[i].Enabled) == 'function' and self.Items[i].Enabled() == false) or self.Items[i].Enabled == false) and self.Items[i].Click then - self.Items[i]:Click() - if Current.Menu.Owner and Current.Menu.Owner.Toggle then - Current.Menu.Owner.Toggle = false - end - Current.Menu = nil - self = nil - end - return true - end - end + X = 0, + Y = 0, + Width = 0, + Height = 0, + Owner = nil, + Items = {}, + RemoveTop = false, + + Draw = function(self) + Drawing.DrawBlankArea(self.X + 1, self.Y + 1, self.Width, self.Height, UIColours.Shadow) + if not self.RemoveTop then + Drawing.DrawBlankArea(self.X, self.Y, self.Width, self.Height, UIColours.MenuBackground) + for i, item in ipairs(self.Items) do + if item.Separator then + Drawing.DrawArea(self.X, self.Y + i, self.Width, 1, '-', colours.grey, UIColours.MenuBackground) + else + local textColour = item.Colour or UIColours.MenuText + if (item.Enabled and type(item.Enabled) == 'function' and item.Enabled() == false) or item.Enabled == false then + textColour = UIColours.MenuDisabledText + end + Drawing.DrawCharacters(self.X + 1, self.Y + i, item.Title, textColour, UIColours.MenuBackground) + end + end + else + Drawing.DrawBlankArea(self.X, self.Y, self.Width, self.Height, UIColours.MenuBackground) + for i, item in ipairs(self.Items) do + if item.Separator then + Drawing.DrawArea(self.X, self.Y + i - 1, self.Width, 1, '-', colours.grey, UIColours.MenuBackground) + else + local textColour = item.Colour or UIColours.MenuText + if (item.Enabled and type(item.Enabled) == 'function' and item.Enabled() == false) or item.Enabled == false then + textColour = UIColours.MenuDisabledText + end + Drawing.DrawCharacters(self.X + 1, self.Y + i - 1, item.Title, textColour, UIColours.MenuBackground) + + Drawing.DrawCharacters(self.X - 1 + self.Width-#item.KeyName, self.Y + i - 1, item.KeyName, textColour, UIColours.MenuBackground) + end + end + end + end, + + NameForKey = function(self, key) + if key == keys.leftCtrl then + return '^' + elseif key == keys.tab then + return 'Tab' + elseif key == keys.delete then + return 'Delete' + elseif key == keys.n then + return 'N' + elseif key == keys.a then + return 'A' + elseif key == keys.s then + return 'S' + elseif key == keys.o then + return 'O' + elseif key == keys.z then + return 'Z' + elseif key == keys.y then + return 'Y' + elseif key == keys.c then + return 'C' + elseif key == keys.x then + return 'X' + elseif key == keys.v then + return 'V' + elseif key == keys.r then + return 'R' + elseif key == keys.l then + return 'L' + elseif key == keys.t then + return 'T' + elseif key == keys.h then + return 'H' + elseif key == keys.e then + return 'E' + elseif key == keys.p then + return 'P' + elseif key == keys.f then + return 'F' + elseif key == keys.m then + return 'M' + elseif key == keys.q then + return 'Q' + else + return '?' + end + end, + + Initialise = function(self, x, y, items, owner, removeTop) + local new = {} -- the new instance + setmetatable( new, {__index = self} ) + if not owner then + return + end + + local keyNames = {} + + for i, v in ipairs(items) do + items[i].KeyName = '' + if v.Keys then + for _i, key in ipairs(v.Keys) do + items[i].KeyName = items[i].KeyName .. self:NameForKey(key) + end + end + if items[i].KeyName ~= '' then + table.insert(keyNames, items[i].KeyName) + end + end + local keysLength = LongestString(keyNames) + if keysLength > 0 then + keysLength = keysLength + 2 + end + + new.Width = LongestString(items, 'Title') + 2 + keysLength + if new.Width < 10 then + new.Width = 10 + end + new.Height = #items + 2 + new.RemoveTop = removeTop or false + if removeTop then + new.Height = new.Height - 1 + end + + if y < 1 then + y = 1 + end + if x < 1 then + x = 1 + end + + if y + new.Height > Drawing.Screen.Height + 1 then + y = Drawing.Screen.Height - new.Height + end + if x + new.Width > Drawing.Screen.Width + 1 then + x = Drawing.Screen.Width - new.Width + end + + + new.Y = y + new.X = x + new.Items = items + new.Owner = owner + return new + end, + + New = function(self, x, y, items, owner, removeTop) + if Current.Menu and Current.Menu.Owner == owner then + Current.Menu = nil + return + end + + local new = self:Initialise(x, y, items, owner, removeTop) + Current.Menu = new + return new + end, + + Click = function(self, side, x, y) + local i = y-1 + if self.RemoveTop then + i = y + end + if i >= 1 and y < self.Height then + if not ((self.Items[i].Enabled and type(self.Items[i].Enabled) == 'function' and self.Items[i].Enabled() == false) or self.Items[i].Enabled == false) and self.Items[i].Click then + self.Items[i]:Click() + if Current.Menu.Owner and Current.Menu.Owner.Toggle then + Current.Menu.Owner.Toggle = false + end + Current.Menu = nil + self = nil + end + return true + end + end } - + MenuBar = { - X = 1, - Y = 1, - Width = Drawing.Screen.Width, - Height = 1, - MenuBarItems = {}, - - AbsolutePosition = function(self) - return {X = self.X, Y = self.Y} - end, - - Draw = function(self) - --Drawing.DrawArea(self.X - 1, self.Y, 1, self.Height, "|", UIColours.ToolbarText, UIColours.Background) - - Drawing.DrawBlankArea(self.X, self.Y, self.Width, self.Height, colours.grey) - for i, button in ipairs(self.MenuBarItems) do - button:Draw() - end - end, - - Initialise = function(self, items) - local new = {} -- the new instance - setmetatable( new, {__index = self} ) - new.X = 1 - new.Y = 1 - new.MenuBarItems = items - return new - end, - - AddToolbarItem = function(self, item) - table.insert(self.ToolbarItems, item) - self:CalculateToolbarItemPositions() - end, - - CalculateToolbarItemPositions = function(self) - local currY = 1 - for i, toolbarItem in ipairs(self.ToolbarItems) do - toolbarItem.Y = currY - currY = currY + toolbarItem.Height - end - end, - - Click = function(self, side, x, y) - for i, item in ipairs(self.MenuBarItems) do - if item.X <= x and item.X + item.Width > x then - if item:Click(item, side, x - item.X + 1, 1) then - return true - end - end - end - return false - end + X = 1, + Y = 1, + Width = Drawing.Screen.Width, + Height = 1, + MenuBarItems = {}, + + AbsolutePosition = function(self) + return {X = self.X, Y = self.Y} + end, + + Draw = function(self) + --Drawing.DrawArea(self.X - 1, self.Y, 1, self.Height, "|", UIColours.ToolbarText, UIColours.Background) + + Drawing.DrawBlankArea(self.X, self.Y, self.Width, self.Height, colours.grey) + for i, button in ipairs(self.MenuBarItems) do + button:Draw() + end + end, + + Initialise = function(self, items) + local new = {} -- the new instance + setmetatable( new, {__index = self} ) + new.X = 1 + new.Y = 1 + new.MenuBarItems = items + return new + end, + + AddToolbarItem = function(self, item) + table.insert(self.ToolbarItems, item) + self:CalculateToolbarItemPositions() + end, + + CalculateToolbarItemPositions = function(self) + local currY = 1 + for i, toolbarItem in ipairs(self.ToolbarItems) do + toolbarItem.Y = currY + currY = currY + toolbarItem.Height + end + end, + + Click = function(self, side, x, y) + for i, item in ipairs(self.MenuBarItems) do + if item.X <= x and item.X + item.Width > x then + if item:Click(item, side, x - item.X + 1, 1) then + return true + end + end + end + return false + end } - + TextFormatPlainText = 1 TextFormatInkText = 2 - + Document = { - X = 1, - Y = 1, - PageSize = {Width = 25, Height = 21}, - TextInput = nil, - Pages = {}, - Format = TextFormatPlainText, - Title = '', - Path = nil, - ScrollBar = nil, - Lines = {}, - CursorPos = nil, - - CalculateLineWrapping = function(self) - local limit = self.PageSize.Width - local text = self.TextInput.Value + X = 1, + Y = 1, + PageSize = {Width = 25, Height = 21}, + TextInput = nil, + Pages = {}, + Format = TextFormatPlainText, + Title = '', + Path = nil, + ScrollBar = nil, + Lines = {}, + CursorPos = nil, + + CalculateLineWrapping = function(self) + local limit = self.PageSize.Width + local text = self.TextInput.Value local lines = {''} local words = {} - + for word, space in text:gmatch('(%S+)(%s*)') do - for i = 1, math.ceil(#word/limit) do - local _space = '' - if i == math.ceil(#word/limit) then - _space = space - end - table.insert(words, {word:sub(1+limit*(i-1), limit*i), _space}) - end + for i = 1, math.ceil(#word/limit) do + local _space = '' + if i == math.ceil(#word/limit) then + _space = space + end + table.insert(words, {word:sub(1+limit*(i-1), limit*i), _space}) + end end - + for i, ws in ipairs(words) do - local word = ws[1] - local space = ws[2] + local word = ws[1] + local space = ws[2] local temp = lines[#lines] .. word .. space:gsub('\n','') if #temp > limit then table.insert(lines, '') end if space:find('\n') then lines[#lines] = lines[#lines] .. word - + space = space:gsub('\n', function() table.insert(lines, '') return '' @@ -1056,1822 +1056,1822 @@ Document = { end end return lines - end, - - CalculateCursorPos = function(self) - local passedCharacters = 0 - Current.CursorPos = nil - for p, page in ipairs(self.Pages) do - page:Draw() - if not Current.CursorPos then - for i, line in ipairs(page.Lines) do - local relCursor = self.TextInput.CursorPos - FindColours(self.TextInput.Value:sub(1,self.TextInput.CursorPos)) - if passedCharacters + #StripColours(line.Text:gsub('\n','')) >= relCursor then - Current.CursorPos = {self.X + page.MarginX + (relCursor - passedCharacters), page.Y + 1 + i} - self.CursorPos = {Page = p, Line = i, Collum = relCursor - passedCharacters - FindColours(self.TextInput.Value:sub(1,self.TextInput.CursorPos-1))} - break - end - passedCharacters = passedCharacters + #StripColours(line.Text:gsub('\n','')) - end - end - end - end, - - Draw = function(self) - self:CalculatePages() - self:CalculateCursorPos() - self.ScrollBar:Draw() - end, - - CalculatePages = function(self) - self.Pages = {} - local lines = self:CalculateLineWrapping() - self.Lines = lines - local pageLines = {} - local totalPageHeight = (3 + self.PageSize.Height + 2 * Page.MarginY) - for i, line in ipairs(lines) do - table.insert(pageLines, TextLine:Initialise(line)) - if i % self.PageSize.Height == 0 then - table.insert(self.Pages, Page:Initialise(self, pageLines, 3 - self.ScrollBar.Scroll + totalPageHeight*(#self.Pages))) - pageLines = {} - end - end - if #pageLines ~= 0 then - table.insert(self.Pages, Page:Initialise(self, pageLines, 3 - self.ScrollBar.Scroll + totalPageHeight*(#self.Pages))) - end - - self.ScrollBar.MaxScroll = totalPageHeight*(#self.Pages) - Drawing.Screen.Height + 1 - end, - - ScrollToCursor = function(self) - self:CalculateCursorPos() - if Current.CursorPos and - (Current.CursorPos[2] > Drawing.Screen.Height - or Current.CursorPos[2] < 2) then - self.ScrollBar:DoScroll(Current.CursorPos[2] - Drawing.Screen.Height) - end - end, - - SetSelectionColour = function(self, colour) - local selection = OrderSelection() - local text = self.TextInput:Extract(true) - local colChar = CharacterFromColour(colour) - local precedingColour = '' - if FindColours(self.TextInput.Value:sub(self.TextInput.CursorPos+1, self.TextInput.CursorPos+1)) == 0 then - for i = 1, self.TextInput.CursorPos do - local c = self.TextInput.Value:sub(self.TextInput.CursorPos - i,self.TextInput.CursorPos - i) - if FindColours(c) == 1 then - precedingColour = c - break - end - end - if precedingColour == '' then - precedingColour = CharacterFromColour(colours.black) - end - end - - self.TextInput:Insert(colChar..StripColours(text)..precedingColour) - --text = text:gsub('['..string.char(14)..'-'..string.char(29)..']','') - end, - - Initialise = function(self, text, title, path) - local new = {} -- the new instance - setmetatable( new, {__index = self} ) - new.Title = title or 'New Document' - new.Path = path - new.X = (Drawing.Screen.Width - (new.PageSize.Width + 2*(Page.MarginX)))/2 - new.Y = 2 - new.TextInput = TextInput:Initialise(text, function() - new:ScrollToCursor() - Current.Modified = true - Draw() - end, false, true) - new.ScrollBar = ScrollBar:Initialise(Drawing.Screen.Width, new.Y, Drawing.Screen.Height-1, 0, nil, nil, nil, function()end) - Current.TextInput = new.TextInput - Current.ScrollBar = new.ScrollBar - return new - end + end, + + CalculateCursorPos = function(self) + local passedCharacters = 0 + Current.CursorPos = nil + for p, page in ipairs(self.Pages) do + page:Draw() + if not Current.CursorPos then + for i, line in ipairs(page.Lines) do + local relCursor = self.TextInput.CursorPos - FindColours(self.TextInput.Value:sub(1,self.TextInput.CursorPos)) + if passedCharacters + #StripColours(line.Text:gsub('\n','')) >= relCursor then + Current.CursorPos = {self.X + page.MarginX + (relCursor - passedCharacters), page.Y + 1 + i} + self.CursorPos = {Page = p, Line = i, Collum = relCursor - passedCharacters - FindColours(self.TextInput.Value:sub(1,self.TextInput.CursorPos-1))} + break + end + passedCharacters = passedCharacters + #StripColours(line.Text:gsub('\n','')) + end + end + end + end, + + Draw = function(self) + self:CalculatePages() + self:CalculateCursorPos() + self.ScrollBar:Draw() + end, + + CalculatePages = function(self) + self.Pages = {} + local lines = self:CalculateLineWrapping() + self.Lines = lines + local pageLines = {} + local totalPageHeight = (3 + self.PageSize.Height + 2 * Page.MarginY) + for i, line in ipairs(lines) do + table.insert(pageLines, TextLine:Initialise(line)) + if i % self.PageSize.Height == 0 then + table.insert(self.Pages, Page:Initialise(self, pageLines, 3 - self.ScrollBar.Scroll + totalPageHeight*(#self.Pages))) + pageLines = {} + end + end + if #pageLines ~= 0 then + table.insert(self.Pages, Page:Initialise(self, pageLines, 3 - self.ScrollBar.Scroll + totalPageHeight*(#self.Pages))) + end + + self.ScrollBar.MaxScroll = totalPageHeight*(#self.Pages) - Drawing.Screen.Height + 1 + end, + + ScrollToCursor = function(self) + self:CalculateCursorPos() + if Current.CursorPos and + (Current.CursorPos[2] > Drawing.Screen.Height + or Current.CursorPos[2] < 2) then + self.ScrollBar:DoScroll(Current.CursorPos[2] - Drawing.Screen.Height) + end + end, + + SetSelectionColour = function(self, colour) + local selection = OrderSelection() + local text = self.TextInput:Extract(true) + local colChar = CharacterFromColour(colour) + local precedingColour = '' + if FindColours(self.TextInput.Value:sub(self.TextInput.CursorPos+1, self.TextInput.CursorPos+1)) == 0 then + for i = 1, self.TextInput.CursorPos do + local c = self.TextInput.Value:sub(self.TextInput.CursorPos - i,self.TextInput.CursorPos - i) + if FindColours(c) == 1 then + precedingColour = c + break + end + end + if precedingColour == '' then + precedingColour = CharacterFromColour(colours.black) + end + end + + self.TextInput:Insert(colChar..StripColours(text)..precedingColour) + --text = text:gsub('['..string.char(14)..'-'..string.char(29)..']','') + end, + + Initialise = function(self, text, title, path) + local new = {} -- the new instance + setmetatable( new, {__index = self} ) + new.Title = title or 'New Document' + new.Path = path + new.X = (Drawing.Screen.Width - (new.PageSize.Width + 2*(Page.MarginX)))/2 + new.Y = 2 + new.TextInput = TextInput:Initialise(text, function() + new:ScrollToCursor() + Current.Modified = true + Draw() + end, false, true) + new.ScrollBar = ScrollBar:Initialise(Drawing.Screen.Width, new.Y, Drawing.Screen.Height-1, 0, nil, nil, nil, function()end) + Current.TextInput = new.TextInput + Current.ScrollBar = new.ScrollBar + return new + end } - + ScrollBar = { - X = 1, - Y = 1, - Width = 1, - Height = 1, - BackgroundColour = colours.grey, - BarColour = colours.lightBlue, - Parent = nil, - Change = nil, - Scroll = 0, - MaxScroll = 0, - ClickPoint = nil, - - AbsolutePosition = function(self) - return self.Parent:AbsolutePosition() - end, - - Draw = function(self) - local pos = GetAbsolutePosition(self) - local barHeight = self.Height - self.MaxScroll - if barHeight < 3 then - barHeight = 3 - end - local percentage = (self.Scroll/self.MaxScroll) - - Drawing.DrawBlankArea(pos.X, pos.Y, self.Width, self.Height, self.BackgroundColour) - Drawing.DrawBlankArea(pos.X, pos.Y + round(self.Height*percentage - barHeight*percentage), self.Width, barHeight, self.BarColour) - end, - - Initialise = function(self, x, y, height, maxScroll, backgroundColour, barColour, parent, change) - local new = {} -- the new instance - setmetatable( new, {__index = self} ) - new.Width = 1 - new.Height = height - new.Y = y - new.X = x - new.BackgroundColour = backgroundColour or colours.grey - new.BarColour = barColour or colours.lightBlue - new.Parent = parent - new.Change = change or function()end - new.MaxScroll = maxScroll - new.Scroll = 0 - return new - end, - - DoScroll = function(self, amount) - amount = round(amount) - if self.Scroll < 0 or self.Scroll > self.MaxScroll then - return false - end - self.Scroll = self.Scroll + amount - if self.Scroll < 0 then - self.Scroll = 0 - elseif self.Scroll > self.MaxScroll then - self.Scroll = self.MaxScroll - end - self.Change() - return true - end, - - Click = function(self, side, x, y, drag) - local percentage = (self.Scroll/self.MaxScroll) - local barHeight = (self.Height - self.MaxScroll) - if barHeight < 3 then - barHeight = 3 - end - local relScroll = (self.MaxScroll*(y + barHeight*percentage)/self.Height) - if not drag then - self.ClickPoint = self.Scroll - relScroll + 1 - end - - if self.Scroll-1 ~= relScroll then - self:DoScroll(relScroll-self.Scroll-1 + self.ClickPoint) - end - return true - end + X = 1, + Y = 1, + Width = 1, + Height = 1, + BackgroundColour = colours.grey, + BarColour = colours.lightBlue, + Parent = nil, + Change = nil, + Scroll = 0, + MaxScroll = 0, + ClickPoint = nil, + + AbsolutePosition = function(self) + return self.Parent:AbsolutePosition() + end, + + Draw = function(self) + local pos = GetAbsolutePosition(self) + local barHeight = self.Height - self.MaxScroll + if barHeight < 3 then + barHeight = 3 + end + local percentage = (self.Scroll/self.MaxScroll) + + Drawing.DrawBlankArea(pos.X, pos.Y, self.Width, self.Height, self.BackgroundColour) + Drawing.DrawBlankArea(pos.X, pos.Y + round(self.Height*percentage - barHeight*percentage), self.Width, barHeight, self.BarColour) + end, + + Initialise = function(self, x, y, height, maxScroll, backgroundColour, barColour, parent, change) + local new = {} -- the new instance + setmetatable( new, {__index = self} ) + new.Width = 1 + new.Height = height + new.Y = y + new.X = x + new.BackgroundColour = backgroundColour or colours.grey + new.BarColour = barColour or colours.lightBlue + new.Parent = parent + new.Change = change or function()end + new.MaxScroll = maxScroll + new.Scroll = 0 + return new + end, + + DoScroll = function(self, amount) + amount = round(amount) + if self.Scroll < 0 or self.Scroll > self.MaxScroll then + return false + end + self.Scroll = self.Scroll + amount + if self.Scroll < 0 then + self.Scroll = 0 + elseif self.Scroll > self.MaxScroll then + self.Scroll = self.MaxScroll + end + self.Change() + return true + end, + + Click = function(self, side, x, y, drag) + local percentage = (self.Scroll/self.MaxScroll) + local barHeight = (self.Height - self.MaxScroll) + if barHeight < 3 then + barHeight = 3 + end + local relScroll = (self.MaxScroll*(y + barHeight*percentage)/self.Height) + if not drag then + self.ClickPoint = self.Scroll - relScroll + 1 + end + + if self.Scroll-1 ~= relScroll then + self:DoScroll(relScroll-self.Scroll-1 + self.ClickPoint) + end + return true + end } - + AlignmentLeft = 1 AlignmentCentre = 2 AlignmentRight = 3 - + TextLine = { - Text = "", - Alignment = AlignmentLeft, - - Initialise = function(self, text, alignment) - local new = {} -- the new instance - setmetatable( new, {__index = self} ) - new.Text = text - new.Alignment = alignment or AlignmentLeft - return new - end + Text = "", + Alignment = AlignmentLeft, + + Initialise = function(self, text, alignment) + local new = {} -- the new instance + setmetatable( new, {__index = self} ) + new.Text = text + new.Alignment = alignment or AlignmentLeft + return new + end } - + local clickPos = 1 Page = { - X = 1, - Y = 1, - Width = 1, - Height = 1, - MarginX = 3, - MarginY = 2, - BackgroundColour = colours.white, - TextColour = colours.white, - ActiveBackgroundColour = colours.lightGrey, - Lines = {}, - Parent = nil, - - AbsolutePosition = function(self) - return self.Parent:AbsolutePosition() - end, - - Draw = function(self) - local pos = GetAbsolutePosition(self) - - if pos.Y > Drawing.Screen.Height or pos.Y + self.Height < 1 then - return - end - - Drawing.DrawBlankArea(pos.X+self.Width,pos.Y -1 + 1, 1, self.Height, UIColours.Shadow) - Drawing.DrawBlankArea(pos.X+1, pos.Y -1 + self.Height, self.Width, 1, UIColours.Shadow) - Drawing.DrawBlankArea(pos.X, pos.Y -1, self.Width, self.Height, self.BackgroundColour) - - local textColour = self.TextColour - if not Current.Selection then - for i, line in ipairs(self.Lines) do - local _c = 1 - for c = 1, #line.Text do - local col = ColourFromCharacter(line.Text:sub(c,c)) - if col then - textColour = col - else - Drawing.WriteToBuffer(pos.X + self.MarginX - 1 + _c, pos.Y -2 + i + self.MarginY, line.Text:sub(c,c), textColour, self.BackgroundColour) - _c = _c + 1 - end - end - end - else - local selection = OrderSelection() - local char = 1 - local textColour = self.TextColour - for i, line in ipairs(self.Lines) do - local _c = 1 - for c = 1, #line.Text do - local col = ColourFromCharacter(line.Text:sub(c,c)) - if col then - textColour = col - else - local tc = textColour - local colour = colours.white - if char >= selection[1] and char <= selection[2] then - colour = colours.lightBlue - tc = colours.white - end - - Drawing.WriteToBuffer(pos.X + self.MarginX - 1 + _c, pos.Y -2 + i + self.MarginY, line.Text:sub(c,c), tc, colour) - _c = _c + 1 - end - char = char + 1 - end - end - end - end, - - Initialise = function(self, parent, lines, y) - local new = {} -- the new instanc - setmetatable( new, {__index = self} ) - new.Height = parent.PageSize.Height + 2 * self.MarginY - new.Width = parent.PageSize.Width + 2 * self.MarginX - new.X = 1 - new.Y = y or 1 - new.Lines = lines or {} - new.BackgroundColour = colours.white - new.TextColour = colours.black - new.Parent = parent - new.ClickPos = 1 - return new - end, - - GetCursorPosFromPoint = function(self, x, y, rel) - local pos = GetAbsolutePosition(self) - if rel then - pos = {Y = 0, X = 0} - end - local row = y - pos.Y + self.MarginY - self.Parent.ScrollBar.Scroll - local col = x - self.MarginX - pos.X + 1 - local cursorPos = 0 - if row <= 0 or col <= 0 then - return 0 - end - - if row > #self.Lines then - for i, v in ipairs(self.Lines) do - cursorPos = cursorPos + #v.Text-- - FindColours(v.Text) - end - return cursorPos - end - - --term.setCursorPos(1,3) - local prevLineCount = 0 - for i, v in ipairs(self.Lines) do - if i == row then - if col > #v.Text then - col = #v.Text-- + FindColours(v.Text) - else - col = col + FindColours(v.Text:sub(1, col)) - end - --term.setCursorPos(1,2) - --print(prevLineCount) - cursorPos = cursorPos + col + 2 - i - prevLineCount - break - else - prevLineCount = FindColours(v.Text) - if prevLineCount ~= 0 then - prevLineCount = prevLineCount - end - cursorPos = cursorPos + #v.Text + 2 - i + FindColours(v.Text) - end - end - - return cursorPos - 2 - end, - - Click = function(self, side, x, y, drag) - local cursorPos = self:GetCursorPosFromPoint(x, y) - self.Parent.TextInput.CursorPos = cursorPos - if drag == nil then - Current.Selection = nil - clickPos = x - else - local relCursor = cursorPos-- - FindColours(self.Parent.TextInput.Value:sub(1,cursorPos)) + 1 - if not Current.Selection then - local adder = 1 - if clickPos and clickPos < x then - adder = 0 - end - Current.Selection = {relCursor + adder, relCursor + 1 + adder} - else - Current.Selection[2] = relCursor + 1 - end - end - Draw() - return true - end + X = 1, + Y = 1, + Width = 1, + Height = 1, + MarginX = 3, + MarginY = 2, + BackgroundColour = colours.white, + TextColour = colours.white, + ActiveBackgroundColour = colours.lightGrey, + Lines = {}, + Parent = nil, + + AbsolutePosition = function(self) + return self.Parent:AbsolutePosition() + end, + + Draw = function(self) + local pos = GetAbsolutePosition(self) + + if pos.Y > Drawing.Screen.Height or pos.Y + self.Height < 1 then + return + end + + Drawing.DrawBlankArea(pos.X+self.Width,pos.Y -1 + 1, 1, self.Height, UIColours.Shadow) + Drawing.DrawBlankArea(pos.X+1, pos.Y -1 + self.Height, self.Width, 1, UIColours.Shadow) + Drawing.DrawBlankArea(pos.X, pos.Y -1, self.Width, self.Height, self.BackgroundColour) + + local textColour = self.TextColour + if not Current.Selection then + for i, line in ipairs(self.Lines) do + local _c = 1 + for c = 1, #line.Text do + local col = ColourFromCharacter(line.Text:sub(c,c)) + if col then + textColour = col + else + Drawing.WriteToBuffer(pos.X + self.MarginX - 1 + _c, pos.Y -2 + i + self.MarginY, line.Text:sub(c,c), textColour, self.BackgroundColour) + _c = _c + 1 + end + end + end + else + local selection = OrderSelection() + local char = 1 + local textColour = self.TextColour + for i, line in ipairs(self.Lines) do + local _c = 1 + for c = 1, #line.Text do + local col = ColourFromCharacter(line.Text:sub(c,c)) + if col then + textColour = col + else + local tc = textColour + local colour = colours.white + if char >= selection[1] and char <= selection[2] then + colour = colours.lightBlue + tc = colours.white + end + + Drawing.WriteToBuffer(pos.X + self.MarginX - 1 + _c, pos.Y -2 + i + self.MarginY, line.Text:sub(c,c), tc, colour) + _c = _c + 1 + end + char = char + 1 + end + end + end + end, + + Initialise = function(self, parent, lines, y) + local new = {} -- the new instanc + setmetatable( new, {__index = self} ) + new.Height = parent.PageSize.Height + 2 * self.MarginY + new.Width = parent.PageSize.Width + 2 * self.MarginX + new.X = 1 + new.Y = y or 1 + new.Lines = lines or {} + new.BackgroundColour = colours.white + new.TextColour = colours.black + new.Parent = parent + new.ClickPos = 1 + return new + end, + + GetCursorPosFromPoint = function(self, x, y, rel) + local pos = GetAbsolutePosition(self) + if rel then + pos = {Y = 0, X = 0} + end + local row = y - pos.Y + self.MarginY - self.Parent.ScrollBar.Scroll + local col = x - self.MarginX - pos.X + 1 + local cursorPos = 0 + if row <= 0 or col <= 0 then + return 0 + end + + if row > #self.Lines then + for i, v in ipairs(self.Lines) do + cursorPos = cursorPos + #v.Text-- - FindColours(v.Text) + end + return cursorPos + end + + --term.setCursorPos(1,3) + local prevLineCount = 0 + for i, v in ipairs(self.Lines) do + if i == row then + if col > #v.Text then + col = #v.Text-- + FindColours(v.Text) + else + col = col + FindColours(v.Text:sub(1, col)) + end + --term.setCursorPos(1,2) + --print(prevLineCount) + cursorPos = cursorPos + col + 2 - i - prevLineCount + break + else + prevLineCount = FindColours(v.Text) + if prevLineCount ~= 0 then + prevLineCount = prevLineCount + end + cursorPos = cursorPos + #v.Text + 2 - i + FindColours(v.Text) + end + end + + return cursorPos - 2 + end, + + Click = function(self, side, x, y, drag) + local cursorPos = self:GetCursorPosFromPoint(x, y) + self.Parent.TextInput.CursorPos = cursorPos + if drag == nil then + Current.Selection = nil + clickPos = x + else + local relCursor = cursorPos-- - FindColours(self.Parent.TextInput.Value:sub(1,cursorPos)) + 1 + if not Current.Selection then + local adder = 1 + if clickPos and clickPos < x then + adder = 0 + end + Current.Selection = {relCursor + adder, relCursor + 1 + adder} + else + Current.Selection[2] = relCursor + 1 + end + end + Draw() + return true + end } - + function GetAbsolutePosition(object) - local obj = object - local i = 0 - local x = 1 - local y = 1 - while true do - x = x + obj.X - 1 - y = y + obj.Y - 1 - - if not obj.Parent then - return {X = x, Y = y} - end - - obj = obj.Parent - - if i > 32 then - return {X = 1, Y = 1} - end - - i = i + 1 - end - + local obj = object + local i = 0 + local x = 1 + local y = 1 + while true do + x = x + obj.X - 1 + y = y + obj.Y - 1 + + if not obj.Parent then + return {X = x, Y = y} + end + + obj = obj.Parent + + if i > 32 then + return {X = 1, Y = 1} + end + + i = i + 1 + end + end - + function Draw() - if not Current.Window then - Drawing.Clear(colours.lightGrey) - else - Drawing.DrawArea(1, 2, Drawing.Screen.Width, Drawing.Screen.Height, '|', colours.black, colours.lightGrey) - end - - if Current.Document then - Current.Document:Draw() - end - - Current.MenuBar:Draw() - - if Current.Window then - Current.Window:Draw() - end - - if Current.Menu then - Current.Menu:Draw() - end - - Drawing.DrawBuffer() - - if Current.TextInput and Current.CursorPos and not Current.Menu and not(Current.Window and Current.Document and Current.TextInput == Current.Document.TextInput) and Current.CursorPos[2] > 1 then - term.setCursorPos(Current.CursorPos[1], Current.CursorPos[2]) - term.setCursorBlink(true) - term.setTextColour(Current.CursorColour) - else - term.setCursorBlink(false) - end + if not Current.Window then + Drawing.Clear(colours.lightGrey) + else + Drawing.DrawArea(1, 2, Drawing.Screen.Width, Drawing.Screen.Height, '|', colours.black, colours.lightGrey) + end + + if Current.Document then + Current.Document:Draw() + end + + Current.MenuBar:Draw() + + if Current.Window then + Current.Window:Draw() + end + + if Current.Menu then + Current.Menu:Draw() + end + + Drawing.DrawBuffer() + + if Current.TextInput and Current.CursorPos and not Current.Menu and not(Current.Window and Current.Document and Current.TextInput == Current.Document.TextInput) and Current.CursorPos[2] > 1 then + term.setCursorPos(Current.CursorPos[1], Current.CursorPos[2]) + term.setCursorBlink(true) + term.setTextColour(Current.CursorColour) + else + term.setCursorBlink(false) + end end MainDraw = Draw - + LongestString = function(input, key) - local length = 0 - for i = 1, #input do - local value = input[i] - if key then - if value[key] then - value = value[key] - else - value = '' - end - end - local titleLength = string.len(value) - if titleLength > length then - length = titleLength - end - end - return length + local length = 0 + for i = 1, #input do + local value = input[i] + if key then + if value[key] then + value = value[key] + else + value = '' + end + end + local titleLength = string.len(value) + if titleLength > length then + length = titleLength + end + end + return length end - + function LoadMenuBar() - Current.MenuBar = MenuBar:Initialise({ - Button:Initialise(1, 1, nil, nil, colours.grey, Current.MenuBar, function(self, side, x, y, toggle) - if toggle then - Menu:New(1, 2, { - { - Title = "New...", - Click = function() - Current.Document = Document:Initialise('') - end, - Keys = { - keys.leftCtrl, - keys.n - } - }, - { - Title = 'Open...', - Click = function() - DisplayOpenDocumentWindow() - end, - Keys = { - keys.leftCtrl, - keys.o - } - }, - { - Separator = true - }, - { - Title = 'Save...', - Click = function() - SaveDocument() - end, - Keys = { - keys.leftCtrl, - keys.s - }, - Enabled = function() - return true - end - }, - { - Separator = true - }, - { - Title = 'Print...', - Click = function() - PrintDocument() - end, - Keys = { - keys.leftCtrl, - keys.p - }, - Enabled = function() - return true - end - }, - { - Separator = true - }, - { - Title = 'Quit', - Click = function() - Close() - end - }, - --[[ - { - Title = 'Save As...', - Click = function() - - end - } - ]]-- - }, self, true) - else - Current.Menu = nil - end - return true - end, 'File', colours.lightGrey, false), - Button:Initialise(7, 1, nil, nil, colours.grey, Current.MenuBar, function(self, side, x, y, toggle) - if not self.Toggle then - Menu:New(7, 2, { - --[[ - { - Title = "Undo", - Click = function() - end, - Keys = { - keys.leftCtrl, - keys.z - }, - Enabled = function() - return false - end - }, - { - Title = 'Redo', - Click = function() - - end, - Keys = { - keys.leftCtrl, - keys.y - }, - Enabled = function() - return false - end - }, - { - Separator = true - }, - ]]-- - { - Title = 'Cut', - Click = function() - Clipboard.Cut(Current.Document.TextInput:Extract(true), 'text') - end, - Keys = { - keys.leftCtrl, - keys.x - }, - Enabled = function() - return Current.Document ~= nil and Current.Selection and Current.Selection[1] and Current.Selection[2] ~= nil - end - }, - { - Title = 'Copy', - Click = function() - Clipboard.Copy(Current.Document.TextInput:Extract(), 'text') - end, - Keys = { - keys.leftCtrl, - keys.c - }, - Enabled = function() - return Current.Document ~= nil and Current.Selection and Current.Selection[1] and Current.Selection[2] ~= nil - end - }, - { - Title = 'Paste', - Click = function() - local paste = Clipboard.Paste() - Current.Document.TextInput:Insert(paste) - Current.Document.TextInput.CursorPos = Current.Document.TextInput.CursorPos + #paste - 1 - end, - Keys = { - keys.leftCtrl, - keys.v - }, - Enabled = function() - return Current.Document ~= nil and (not Clipboard.isEmpty()) and Clipboard.Type == 'text' - end - }, - { - Separator = true, - }, - { - Title = 'Select All', - Click = function() - Current.Selection = {1, #Current.Document.TextInput.Value:gsub('\n','')} - end, - Keys = { - keys.leftCtrl, - keys.a - }, - Enabled = function() - return Current.Document ~= nil - end - } - }, self, true) - else - Current.Menu = nil - end - return true - end, 'Edit', colours.lightGrey, false) - }) + Current.MenuBar = MenuBar:Initialise({ + Button:Initialise(1, 1, nil, nil, colours.grey, Current.MenuBar, function(self, side, x, y, toggle) + if toggle then + Menu:New(1, 2, { + { + Title = "New...", + Click = function() + Current.Document = Document:Initialise('') + end, + Keys = { + keys.leftCtrl, + keys.n + } + }, + { + Title = 'Open...', + Click = function() + DisplayOpenDocumentWindow() + end, + Keys = { + keys.leftCtrl, + keys.o + } + }, + { + Separator = true + }, + { + Title = 'Save...', + Click = function() + SaveDocument() + end, + Keys = { + keys.leftCtrl, + keys.s + }, + Enabled = function() + return true + end + }, + { + Separator = true + }, + { + Title = 'Print...', + Click = function() + PrintDocument() + end, + Keys = { + keys.leftCtrl, + keys.p + }, + Enabled = function() + return true + end + }, + { + Separator = true + }, + { + Title = 'Quit', + Click = function() + Close() + end + }, + --[[ + { + Title = 'Save As...', + Click = function() + + end + } + ]]-- + }, self, true) + else + Current.Menu = nil + end + return true + end, 'File', colours.lightGrey, false), + Button:Initialise(7, 1, nil, nil, colours.grey, Current.MenuBar, function(self, side, x, y, toggle) + if not self.Toggle then + Menu:New(7, 2, { + --[[ + { + Title = "Undo", + Click = function() + end, + Keys = { + keys.leftCtrl, + keys.z + }, + Enabled = function() + return false + end + }, + { + Title = 'Redo', + Click = function() + + end, + Keys = { + keys.leftCtrl, + keys.y + }, + Enabled = function() + return false + end + }, + { + Separator = true + }, + ]]-- + { + Title = 'Cut', + Click = function() + Clipboard.Cut(Current.Document.TextInput:Extract(true), 'text') + end, + Keys = { + keys.leftCtrl, + keys.x + }, + Enabled = function() + return Current.Document ~= nil and Current.Selection and Current.Selection[1] and Current.Selection[2] ~= nil + end + }, + { + Title = 'Copy', + Click = function() + Clipboard.Copy(Current.Document.TextInput:Extract(), 'text') + end, + Keys = { + keys.leftCtrl, + keys.c + }, + Enabled = function() + return Current.Document ~= nil and Current.Selection and Current.Selection[1] and Current.Selection[2] ~= nil + end + }, + { + Title = 'Paste', + Click = function() + local paste = Clipboard.Paste() + Current.Document.TextInput:Insert(paste) + Current.Document.TextInput.CursorPos = Current.Document.TextInput.CursorPos + #paste - 1 + end, + Keys = { + keys.leftCtrl, + keys.v + }, + Enabled = function() + return Current.Document ~= nil and (not Clipboard.isEmpty()) and Clipboard.Type == 'text' + end + }, + { + Separator = true, + }, + { + Title = 'Select All', + Click = function() + Current.Selection = {1, #Current.Document.TextInput.Value:gsub('\n','')} + end, + Keys = { + keys.leftCtrl, + keys.a + }, + Enabled = function() + return Current.Document ~= nil + end + } + }, self, true) + else + Current.Menu = nil + end + return true + end, 'Edit', colours.lightGrey, false) + }) end - + function LoadMenuBar() - Current.MenuBar = MenuBar:Initialise({ - Button:Initialise(1, 1, nil, nil, colours.grey, Current.MenuBar, function(self, side, x, y, toggle) - if toggle then - Menu:New(1, 2, { - { - Title = "New...", - Click = function() - Current.Document = Document:Initialise('') - end, - Keys = { - keys.leftCtrl, - keys.n - } - }, - { - Title = 'Open...', - Click = function() - DisplayOpenDocumentWindow() - end, - Keys = { - keys.leftCtrl, - keys.o - } - }, - { - Separator = true - }, - { - Title = 'Save...', - Click = function() - SaveDocument() - end, - Keys = { - keys.leftCtrl, - keys.s - }, - Enabled = function() - return Current.Document ~= nil - end - }, - { - Separator = true - }, - { - Title = 'Print...', - Click = function() - PrintDocument() - end, - Keys = { - keys.leftCtrl, - keys.p - }, - Enabled = function() - return true - end - }, - { - Separator = true - }, - { - Title = 'Quit', - Click = function() - if Close() and OneOS then - OneOS.Close() - end - end, - Keys = { - keys.leftCtrl, - keys.q - } - }, - --[[ - { - Title = 'Save As...', - Click = function() - - end - } - ]]-- - }, self, true) - else - Current.Menu = nil - end - return true - end, 'File', colours.lightGrey, false), - Button:Initialise(7, 1, nil, nil, colours.grey, Current.MenuBar, function(self, side, x, y, toggle) - if not self.Toggle then - Menu:New(7, 2, { - --[[ - { - Title = "Undo", - Click = function() - end, - Keys = { - keys.leftCtrl, - keys.z - }, - Enabled = function() - return false - end - }, - { - Title = 'Redo', - Click = function() - - end, - Keys = { - keys.leftCtrl, - keys.y - }, - Enabled = function() - return false - end - }, - { - Separator = true - }, - ]]-- - { - Title = 'Cut', - Click = function() - Clipboard.Cut(Current.Document.TextInput:Extract(true), 'text') - end, - Keys = { - keys.leftCtrl, - keys.x - }, - Enabled = function() - return Current.Document ~= nil and Current.Selection and Current.Selection[1] and Current.Selection[2] ~= nil - end - }, - { - Title = 'Copy', - Click = function() - Clipboard.Copy(Current.Document.TextInput:Extract(), 'text') - end, - Keys = { - keys.leftCtrl, - keys.c - }, - Enabled = function() - return Current.Document ~= nil and Current.Selection and Current.Selection[1] and Current.Selection[2] ~= nil - end - }, - { - Title = 'Paste', - Click = function() - local paste = Clipboard.Paste() - Current.Document.TextInput:Insert(paste) - Current.Document.TextInput.CursorPos = Current.Document.TextInput.CursorPos + #paste - 1 - end, - Keys = { - keys.leftCtrl, - keys.v - }, - Enabled = function() - return Current.Document ~= nil and (not Clipboard.isEmpty()) and Clipboard.Type == 'text' - end - }, - { - Separator = true, - }, - { - Title = 'Select All', - Click = function() - Current.Selection = {1, #Current.Document.TextInput.Value:gsub('\n','')} - end, - Keys = { - keys.leftCtrl, - keys.a - }, - Enabled = function() - return Current.Document ~= nil - end - } - }, self, true) - else - Current.Menu = nil - end - return true - end, 'Edit', colours.lightGrey, false), - Button:Initialise(13, 1, nil, nil, colours.grey, Current.MenuBar, function(self, side, x, y, toggle) - if not self.Toggle then - Menu:New(13, 2, { - { - Title = 'Red', - Click = function(item) - Current.Document:SetSelectionColour(item.Colour) - end, - Colour = colours.red, - Enabled = function() - return (Current.Document ~= nil and Current.Selection ~= nil and Current.Selection[1] ~= nil and Current.Selection[2] ~= nil) - end - }, - { - Title = 'Orange', - Click = function(item) - Current.Document:SetSelectionColour(item.Colour) - end, - Colour = colours.orange, - Enabled = function() - return (Current.Document ~= nil and Current.Selection ~= nil and Current.Selection[1] ~= nil and Current.Selection[2] ~= nil) - end - }, - { - Title = 'Yellow', - Click = function(item) - Current.Document:SetSelectionColour(item.Colour) - end, - Colour = colours.yellow, - Enabled = function() - return (Current.Document ~= nil and Current.Selection ~= nil and Current.Selection[1] ~= nil and Current.Selection[2] ~= nil) - end - }, - { - Title = 'Pink', - Click = function(item) - Current.Document:SetSelectionColour(item.Colour) - end, - Colour = colours.pink, - Enabled = function() - return (Current.Document ~= nil and Current.Selection ~= nil and Current.Selection[1] ~= nil and Current.Selection[2] ~= nil) - end - }, - { - Title = 'Magenta', - Click = function(item) - Current.Document:SetSelectionColour(item.Colour) - end, - Colour = colours.magenta, - Enabled = function() - return (Current.Document ~= nil and Current.Selection ~= nil and Current.Selection[1] ~= nil and Current.Selection[2] ~= nil) - end - }, - { - Title = 'Purple', - Click = function(item) - Current.Document:SetSelectionColour(item.Colour) - end, - Colour = colours.purple, - Enabled = function() - return (Current.Document ~= nil and Current.Selection ~= nil and Current.Selection[1] ~= nil and Current.Selection[2] ~= nil) - end - }, - { - Title = 'Light Blue', - Click = function(item) - Current.Document:SetSelectionColour(item.Colour) - end, - Colour = colours.lightBlue, - Enabled = function() - return (Current.Document ~= nil and Current.Selection ~= nil and Current.Selection[1] ~= nil and Current.Selection[2] ~= nil) - end - }, - { - Title = 'Cyan', - Click = function(item) - Current.Document:SetSelectionColour(item.Colour) - end, - Colour = colours.cyan, - Enabled = function() - return (Current.Document ~= nil and Current.Selection ~= nil and Current.Selection[1] ~= nil and Current.Selection[2] ~= nil) - end - }, - { - Title = 'Blue', - Click = function(item) - Current.Document:SetSelectionColour(item.Colour) - end, - Colour = colours.blue, - Enabled = function() - return (Current.Document ~= nil and Current.Selection ~= nil and Current.Selection[1] ~= nil and Current.Selection[2] ~= nil) - end - }, - { - Title = 'Green', - Click = function(item) - Current.Document:SetSelectionColour(item.Colour) - end, - Colour = colours.green, - Enabled = function() - return (Current.Document ~= nil and Current.Selection ~= nil and Current.Selection[1] ~= nil and Current.Selection[2] ~= nil) - end - }, - { - Title = 'Light Grey', - Click = function(item) - Current.Document:SetSelectionColour(item.Colour) - end, - Colour = colours.lightGrey, - Enabled = function() - return (Current.Document ~= nil and Current.Selection ~= nil and Current.Selection[1] ~= nil and Current.Selection[2] ~= nil) - end - }, - { - Title = 'Grey', - Click = function(item) - Current.Document:SetSelectionColour(item.Colour) - end, - Colour = colours.grey, - Enabled = function() - return (Current.Document ~= nil and Current.Selection ~= nil and Current.Selection[1] ~= nil and Current.Selection[2] ~= nil) - end - }, - { - Title = 'Black', - Click = function(item) - Current.Document:SetSelectionColour(item.Colour) - end, - Colour = colours.black, - Enabled = function() - return (Current.Document ~= nil and Current.Selection ~= nil and Current.Selection[1] ~= nil and Current.Selection[2] ~= nil) - end - }, - { - Title = 'Brown', - Click = function(item) - Current.Document:SetSelectionColour(item.Colour) - end, - Colour = colours.brown, - Enabled = function() - return (Current.Document ~= nil and Current.Selection ~= nil and Current.Selection[1] ~= nil and Current.Selection[2] ~= nil) - end - } - }, self, true) - else - Current.Menu = nil - end - return true - end, 'Colour', colours.lightGrey, false) - }) + Current.MenuBar = MenuBar:Initialise({ + Button:Initialise(1, 1, nil, nil, colours.grey, Current.MenuBar, function(self, side, x, y, toggle) + if toggle then + Menu:New(1, 2, { + { + Title = "New...", + Click = function() + Current.Document = Document:Initialise('') + end, + Keys = { + keys.leftCtrl, + keys.n + } + }, + { + Title = 'Open...', + Click = function() + DisplayOpenDocumentWindow() + end, + Keys = { + keys.leftCtrl, + keys.o + } + }, + { + Separator = true + }, + { + Title = 'Save...', + Click = function() + SaveDocument() + end, + Keys = { + keys.leftCtrl, + keys.s + }, + Enabled = function() + return Current.Document ~= nil + end + }, + { + Separator = true + }, + { + Title = 'Print...', + Click = function() + PrintDocument() + end, + Keys = { + keys.leftCtrl, + keys.p + }, + Enabled = function() + return true + end + }, + { + Separator = true + }, + { + Title = 'Quit', + Click = function() + if Close() and OneOS then + OneOS.Close() + end + end, + Keys = { + keys.leftCtrl, + keys.q + } + }, + --[[ + { + Title = 'Save As...', + Click = function() + + end + } + ]]-- + }, self, true) + else + Current.Menu = nil + end + return true + end, 'File', colours.lightGrey, false), + Button:Initialise(7, 1, nil, nil, colours.grey, Current.MenuBar, function(self, side, x, y, toggle) + if not self.Toggle then + Menu:New(7, 2, { + --[[ + { + Title = "Undo", + Click = function() + end, + Keys = { + keys.leftCtrl, + keys.z + }, + Enabled = function() + return false + end + }, + { + Title = 'Redo', + Click = function() + + end, + Keys = { + keys.leftCtrl, + keys.y + }, + Enabled = function() + return false + end + }, + { + Separator = true + }, + ]]-- + { + Title = 'Cut', + Click = function() + Clipboard.Cut(Current.Document.TextInput:Extract(true), 'text') + end, + Keys = { + keys.leftCtrl, + keys.x + }, + Enabled = function() + return Current.Document ~= nil and Current.Selection and Current.Selection[1] and Current.Selection[2] ~= nil + end + }, + { + Title = 'Copy', + Click = function() + Clipboard.Copy(Current.Document.TextInput:Extract(), 'text') + end, + Keys = { + keys.leftCtrl, + keys.c + }, + Enabled = function() + return Current.Document ~= nil and Current.Selection and Current.Selection[1] and Current.Selection[2] ~= nil + end + }, + { + Title = 'Paste', + Click = function() + local paste = Clipboard.Paste() + Current.Document.TextInput:Insert(paste) + Current.Document.TextInput.CursorPos = Current.Document.TextInput.CursorPos + #paste - 1 + end, + Keys = { + keys.leftCtrl, + keys.v + }, + Enabled = function() + return Current.Document ~= nil and (not Clipboard.isEmpty()) and Clipboard.Type == 'text' + end + }, + { + Separator = true, + }, + { + Title = 'Select All', + Click = function() + Current.Selection = {1, #Current.Document.TextInput.Value:gsub('\n','')} + end, + Keys = { + keys.leftCtrl, + keys.a + }, + Enabled = function() + return Current.Document ~= nil + end + } + }, self, true) + else + Current.Menu = nil + end + return true + end, 'Edit', colours.lightGrey, false), + Button:Initialise(13, 1, nil, nil, colours.grey, Current.MenuBar, function(self, side, x, y, toggle) + if not self.Toggle then + Menu:New(13, 2, { + { + Title = 'Red', + Click = function(item) + Current.Document:SetSelectionColour(item.Colour) + end, + Colour = colours.red, + Enabled = function() + return (Current.Document ~= nil and Current.Selection ~= nil and Current.Selection[1] ~= nil and Current.Selection[2] ~= nil) + end + }, + { + Title = 'Orange', + Click = function(item) + Current.Document:SetSelectionColour(item.Colour) + end, + Colour = colours.orange, + Enabled = function() + return (Current.Document ~= nil and Current.Selection ~= nil and Current.Selection[1] ~= nil and Current.Selection[2] ~= nil) + end + }, + { + Title = 'Yellow', + Click = function(item) + Current.Document:SetSelectionColour(item.Colour) + end, + Colour = colours.yellow, + Enabled = function() + return (Current.Document ~= nil and Current.Selection ~= nil and Current.Selection[1] ~= nil and Current.Selection[2] ~= nil) + end + }, + { + Title = 'Pink', + Click = function(item) + Current.Document:SetSelectionColour(item.Colour) + end, + Colour = colours.pink, + Enabled = function() + return (Current.Document ~= nil and Current.Selection ~= nil and Current.Selection[1] ~= nil and Current.Selection[2] ~= nil) + end + }, + { + Title = 'Magenta', + Click = function(item) + Current.Document:SetSelectionColour(item.Colour) + end, + Colour = colours.magenta, + Enabled = function() + return (Current.Document ~= nil and Current.Selection ~= nil and Current.Selection[1] ~= nil and Current.Selection[2] ~= nil) + end + }, + { + Title = 'Purple', + Click = function(item) + Current.Document:SetSelectionColour(item.Colour) + end, + Colour = colours.purple, + Enabled = function() + return (Current.Document ~= nil and Current.Selection ~= nil and Current.Selection[1] ~= nil and Current.Selection[2] ~= nil) + end + }, + { + Title = 'Light Blue', + Click = function(item) + Current.Document:SetSelectionColour(item.Colour) + end, + Colour = colours.lightBlue, + Enabled = function() + return (Current.Document ~= nil and Current.Selection ~= nil and Current.Selection[1] ~= nil and Current.Selection[2] ~= nil) + end + }, + { + Title = 'Cyan', + Click = function(item) + Current.Document:SetSelectionColour(item.Colour) + end, + Colour = colours.cyan, + Enabled = function() + return (Current.Document ~= nil and Current.Selection ~= nil and Current.Selection[1] ~= nil and Current.Selection[2] ~= nil) + end + }, + { + Title = 'Blue', + Click = function(item) + Current.Document:SetSelectionColour(item.Colour) + end, + Colour = colours.blue, + Enabled = function() + return (Current.Document ~= nil and Current.Selection ~= nil and Current.Selection[1] ~= nil and Current.Selection[2] ~= nil) + end + }, + { + Title = 'Green', + Click = function(item) + Current.Document:SetSelectionColour(item.Colour) + end, + Colour = colours.green, + Enabled = function() + return (Current.Document ~= nil and Current.Selection ~= nil and Current.Selection[1] ~= nil and Current.Selection[2] ~= nil) + end + }, + { + Title = 'Light Grey', + Click = function(item) + Current.Document:SetSelectionColour(item.Colour) + end, + Colour = colours.lightGrey, + Enabled = function() + return (Current.Document ~= nil and Current.Selection ~= nil and Current.Selection[1] ~= nil and Current.Selection[2] ~= nil) + end + }, + { + Title = 'Grey', + Click = function(item) + Current.Document:SetSelectionColour(item.Colour) + end, + Colour = colours.grey, + Enabled = function() + return (Current.Document ~= nil and Current.Selection ~= nil and Current.Selection[1] ~= nil and Current.Selection[2] ~= nil) + end + }, + { + Title = 'Black', + Click = function(item) + Current.Document:SetSelectionColour(item.Colour) + end, + Colour = colours.black, + Enabled = function() + return (Current.Document ~= nil and Current.Selection ~= nil and Current.Selection[1] ~= nil and Current.Selection[2] ~= nil) + end + }, + { + Title = 'Brown', + Click = function(item) + Current.Document:SetSelectionColour(item.Colour) + end, + Colour = colours.brown, + Enabled = function() + return (Current.Document ~= nil and Current.Selection ~= nil and Current.Selection[1] ~= nil and Current.Selection[2] ~= nil) + end + } + }, self, true) + else + Current.Menu = nil + end + return true + end, 'Colour', colours.lightGrey, false) + }) end - + function SplashScreen() - local w = colours.white - local b = colours.black - local u = colours.blue - local lb = colours.lightBlue - local splashIcon = {{w,w,w,b,w,w,w,b,w,w,w,},{w,w,w,b,w,w,w,b,w,w,w,},{w,w,w,b,u,u,u,b,w,w,w,},{w,b,b,u,u,u,u,u,b,b,w,},{b,u,u,lb,lb,u,u,u,u,u,b,},{b,u,lb,lb,u,u,u,u,u,u,b,},{b,u,lb,lb,u,u,u,u,u,u,b,},{b,u,u,u,u,u,u,u,u,u,b,},{w,b,b,b,b,b,b,b,b,b,w,}, - ["text"]={{" "," "," "," "," "," "," "," "," "," "," ",},{" "," "," "," "," "," "," "," "," "," "," ",},{" "," "," "," "," "," "," "," "," "," "," ",},{" "," "," "," "," "," "," "," "," "," "," ",},{" "," "," "," "," "," "," "," "," "," "," ",},{" "," "," "," ","I","n","k"," "," "," "," ",},{" "," "," "," "," "," "," "," "," "," "," ",},{" "," ","b","y"," ","o","e","e","d"," "," "},{" "," "," "," "," "," "," "," "," "," "," ",},}, - ["textcol"]={{w,w,w,w,w,w,w,w,w,w,w,},{w,w,w,w,w,w,w,w,w,w,w,},{w,w,w,w,w,w,w,w,w,w,w,},{w,w,w,w,w,w,w,w,w,w,w,},{w,w,w,w,w,w,w,w,w,w,w,},{w,w,w,w,w,w,w,w,w,w,w,},{w,w,w,w,w,w,w,w,w,w,w,},{lb,lb,lb,lb,lb,lb,lb,lb,lb,lb,lb,},{w,w,w,w,w,w,w,w,w,w,w,},},} - Drawing.Clear(colours.white) - Drawing.DrawImage((Drawing.Screen.Width - 11)/2, (Drawing.Screen.Height - 9)/2, splashIcon, 11, 9) - Drawing.DrawBuffer() - Drawing.Clear(colours.black) - parallel.waitForAny(function()sleep(1)end, function()os.pullEvent('mouse_click')end) + local w = colours.white + local b = colours.black + local u = colours.blue + local lb = colours.lightBlue + local splashIcon = {{w,w,w,b,w,w,w,b,w,w,w,},{w,w,w,b,w,w,w,b,w,w,w,},{w,w,w,b,u,u,u,b,w,w,w,},{w,b,b,u,u,u,u,u,b,b,w,},{b,u,u,lb,lb,u,u,u,u,u,b,},{b,u,lb,lb,u,u,u,u,u,u,b,},{b,u,lb,lb,u,u,u,u,u,u,b,},{b,u,u,u,u,u,u,u,u,u,b,},{w,b,b,b,b,b,b,b,b,b,w,}, + ["text"]={{" "," "," "," "," "," "," "," "," "," "," ",},{" "," "," "," "," "," "," "," "," "," "," ",},{" "," "," "," "," "," "," "," "," "," "," ",},{" "," "," "," "," "," "," "," "," "," "," ",},{" "," "," "," "," "," "," "," "," "," "," ",},{" "," "," "," ","I","n","k"," "," "," "," ",},{" "," "," "," "," "," "," "," "," "," "," ",},{" "," ","b","y"," ","o","e","e","d"," "," "},{" "," "," "," "," "," "," "," "," "," "," ",},}, + ["textcol"]={{w,w,w,w,w,w,w,w,w,w,w,},{w,w,w,w,w,w,w,w,w,w,w,},{w,w,w,w,w,w,w,w,w,w,w,},{w,w,w,w,w,w,w,w,w,w,w,},{w,w,w,w,w,w,w,w,w,w,w,},{w,w,w,w,w,w,w,w,w,w,w,},{w,w,w,w,w,w,w,w,w,w,w,},{lb,lb,lb,lb,lb,lb,lb,lb,lb,lb,lb,},{w,w,w,w,w,w,w,w,w,w,w,},},} + Drawing.Clear(colours.white) + Drawing.DrawImage((Drawing.Screen.Width - 11)/2, (Drawing.Screen.Height - 9)/2, splashIcon, 11, 9) + Drawing.DrawBuffer() + Drawing.Clear(colours.black) + parallel.waitForAny(function()sleep(1)end, function()os.pullEvent('mouse_click')end) end - + function Initialise(arg) - if OneOS then - fs = OneOS.FS - end - - if not OneOS then - SplashScreen() - end - EventRegister('mouse_click', TryClick) - EventRegister('mouse_drag', function(event, side, x, y)TryClick(event, side, x, y, true)end) - EventRegister('mouse_scroll', Scroll) - EventRegister('key', HandleKey) - EventRegister('char', HandleKey) - EventRegister('timer', Timer) - EventRegister('terminate', function(event) if Close() then error( "Terminated", 0 ) end end) - - LoadMenuBar() - - --Current.Document = Document:Initialise('abcdefghijklmnopqrtuvwxy')--'Hello everybody!') - if tArgs[1] then - if fs.exists(tArgs[1]) then - OpenDocument(tArgs[1]) - else - --new - end - else - Current.Document = Document:Initialise('')--'Hello everybody!') - end - - --[[ - if arg and fs.exists(arg) then - OpenDocument(arg) - else - DisplayNewDocumentWindow() - end - ]]-- - Draw() - - EventHandler() + if OneOS then + fs = OneOS.FS + end + + if not OneOS then + SplashScreen() + end + EventRegister('mouse_click', TryClick) + EventRegister('mouse_drag', function(event, side, x, y)TryClick(event, side, x, y, true)end) + EventRegister('mouse_scroll', Scroll) + EventRegister('key', HandleKey) + EventRegister('char', HandleKey) + EventRegister('timer', Timer) + EventRegister('terminate', function(event) if Close() then error( "Terminated", 0 ) end end) + + LoadMenuBar() + + --Current.Document = Document:Initialise('abcdefghijklmnopqrtuvwxy')--'Hello everybody!') + if tArgs[1] then + if fs.exists(tArgs[1]) then + OpenDocument(tArgs[1]) + else + --new + end + else + Current.Document = Document:Initialise('')--'Hello everybody!') + end + + --[[ + if arg and fs.exists(arg) then + OpenDocument(arg) + else + DisplayNewDocumentWindow() + end + ]]-- + Draw() + + EventHandler() end - + local isControlPushed = false controlPushedTimer = nil closeWindowTimer = nil function Timer(event, timer) - if timer == closeWindowTimer then - if Current.Window then - Current.Window:Close() - end - Draw() - elseif timer == controlPushedTimer then - isControlPushed = false - end + if timer == closeWindowTimer then + if Current.Window then + Current.Window:Close() + end + Draw() + elseif timer == controlPushedTimer then + isControlPushed = false + end end - + local ignoreNextChar = false function HandleKey(...) - local args = {...} - local event = args[1] - local keychar = args[2] - --Mac left command character - if event == 'key' and keychar == keys.leftCtrl or keychar == keys.rightCtrl or keychar == 219 then - isControlPushed = true - controlPushedTimer = os.startTimer(0.5) - elseif isControlPushed then - if event == 'key' then - if CheckKeyboardShortcut(keychar) then - isControlPushed = false - ignoreNextChar = true - end - end - elseif ignoreNextChar then - ignoreNextChar = false - elseif Current.TextInput then - if event == 'char' then - Current.TextInput:Char(keychar) - elseif event == 'key' then - Current.TextInput:Key(keychar) - end - end + local args = {...} + local event = args[1] + local keychar = args[2] + --Mac left command character + if event == 'key' and keychar == keys.leftCtrl or keychar == keys.rightCtrl or keychar == 219 then + isControlPushed = true + controlPushedTimer = os.startTimer(0.5) + elseif isControlPushed then + if event == 'key' then + if CheckKeyboardShortcut(keychar) then + isControlPushed = false + ignoreNextChar = true + end + end + elseif ignoreNextChar then + ignoreNextChar = false + elseif Current.TextInput then + if event == 'char' then + Current.TextInput:Char(keychar) + elseif event == 'key' then + Current.TextInput:Key(keychar) + end + end end - + function CheckKeyboardShortcut(key) - local shortcuts = {} - shortcuts[keys.n] = function() Current.Document = Document:Initialise('') end - shortcuts[keys.o] = function() DisplayOpenDocumentWindow() end - shortcuts[keys.s] = function() if Current.Document ~= nil then SaveDocument() end end - shortcuts[keys.left] = function() if Current.TextInput then Current.TextInput:Key(keys.home) end end - shortcuts[keys.right] = function() if Current.TextInput then Current.TextInput:Key(keys["end"]) end end --- shortcuts[keys.q] = function() DisplayOpenDocumentWindow() end - if Current.Document ~= nil then - shortcuts[keys.s] = function() SaveDocument() end - shortcuts[keys.p] = function() PrintDocument() end - if Current.Selection and Current.Selection[1] and Current.Selection[2] ~= nil then - shortcuts[keys.x] = function() Clipboard.Cut(Current.Document.TextInput:Extract(true), 'text') end - shortcuts[keys.c] = function() Clipboard.Copy(Current.Document.TextInput:Extract(), 'text') end - end - if (not Clipboard.isEmpty()) and Clipboard.Type == 'text' then - shortcuts[keys.v] = function() local paste = Clipboard.Paste() - Current.Document.TextInput:Insert(paste) - Current.Document.TextInput.CursorPos = Current.Document.TextInput.CursorPos + #paste - 1 - end - end - shortcuts[keys.a] = function() Current.Selection = {1, #Current.Document.TextInput.Value:gsub('\n','')} end - end - - if shortcuts[key] then - shortcuts[key]() - Draw() - return true - else - return false - end + local shortcuts = {} + shortcuts[keys.n] = function() Current.Document = Document:Initialise('') end + shortcuts[keys.o] = function() DisplayOpenDocumentWindow() end + shortcuts[keys.s] = function() if Current.Document ~= nil then SaveDocument() end end + shortcuts[keys.left] = function() if Current.TextInput then Current.TextInput:Key(keys.home) end end + shortcuts[keys.right] = function() if Current.TextInput then Current.TextInput:Key(keys["end"]) end end +-- shortcuts[keys.q] = function() DisplayOpenDocumentWindow() end + if Current.Document ~= nil then + shortcuts[keys.s] = function() SaveDocument() end + shortcuts[keys.p] = function() PrintDocument() end + if Current.Selection and Current.Selection[1] and Current.Selection[2] ~= nil then + shortcuts[keys.x] = function() Clipboard.Cut(Current.Document.TextInput:Extract(true), 'text') end + shortcuts[keys.c] = function() Clipboard.Copy(Current.Document.TextInput:Extract(), 'text') end + end + if (not Clipboard.isEmpty()) and Clipboard.Type == 'text' then + shortcuts[keys.v] = function() local paste = Clipboard.Paste() + Current.Document.TextInput:Insert(paste) + Current.Document.TextInput.CursorPos = Current.Document.TextInput.CursorPos + #paste - 1 + end + end + shortcuts[keys.a] = function() Current.Selection = {1, #Current.Document.TextInput.Value:gsub('\n','')} end + end + + if shortcuts[key] then + shortcuts[key]() + Draw() + return true + else + return false + end end - + --[[ - Check if the given object falls under the click coordinates + Check if the given object falls under the click coordinates ]]-- function CheckClick(object, x, y) - if object.X <= x and object.Y <= y and object.X + object.Width > x and object.Y + object.Height > y then - return true - end + if object.X <= x and object.Y <= y and object.X + object.Width > x and object.Y + object.Height > y then + return true + end end - + --[[ - Attempt to clicka given object + Attempt to clicka given object ]]-- function DoClick(object, side, x, y, drag) - local obj = GetAbsolutePosition(object) - obj.Width = object.Width - obj.Height = object.Height - if object and CheckClick(obj, x, y) then - return object:Click(side, x - object.X + 1, y - object.Y + 1, drag) - end + local obj = GetAbsolutePosition(object) + obj.Width = object.Width + obj.Height = object.Height + if object and CheckClick(obj, x, y) then + return object:Click(side, x - object.X + 1, y - object.Y + 1, drag) + end end - + --[[ - Try to click at the given coordinates + Try to click at the given coordinates ]]-- function TryClick(event, side, x, y, drag) - if Current.Menu then - if DoClick(Current.Menu, side, x, y, drag) then - Draw() - return - else - if Current.Menu.Owner and Current.Menu.Owner.Toggle then - Current.Menu.Owner.Toggle = false - end - Current.Menu = nil - Draw() - return - end - elseif Current.Window then - if DoClick(Current.Window, side, x, y, drag) then - Draw() - return - else - Current.Window:Flash() - return - end - end - local interfaceElements = {} - - table.insert(interfaceElements, Current.MenuBar) - table.insert(interfaceElements, Current.ScrollBar) - for i, page in ipairs(Current.Document.Pages) do - table.insert(interfaceElements, page) - end - - for i, object in ipairs(interfaceElements) do - if DoClick(object, side, x, y, drag) then - Draw() - return - end - end - Draw() + if Current.Menu then + if DoClick(Current.Menu, side, x, y, drag) then + Draw() + return + else + if Current.Menu.Owner and Current.Menu.Owner.Toggle then + Current.Menu.Owner.Toggle = false + end + Current.Menu = nil + Draw() + return + end + elseif Current.Window then + if DoClick(Current.Window, side, x, y, drag) then + Draw() + return + else + Current.Window:Flash() + return + end + end + local interfaceElements = {} + + table.insert(interfaceElements, Current.MenuBar) + table.insert(interfaceElements, Current.ScrollBar) + for i, page in ipairs(Current.Document.Pages) do + table.insert(interfaceElements, page) + end + + for i, object in ipairs(interfaceElements) do + if DoClick(object, side, x, y, drag) then + Draw() + return + end + end + Draw() end - + function Scroll(event, direction, x, y) - if Current.Window and Current.Window.OpenButton then - Current.Document.Scroll = Current.Document.Scroll + direction - if Current.Window.Scroll < 0 then - Current.Window.Scroll = 0 - elseif Current.Window.Scroll > Current.Window.MaxScroll then - Current.Window.Scroll = Current.Window.MaxScroll - end - Draw() - elseif Current.ScrollBar then - if Current.ScrollBar:DoScroll(direction*2) then - Draw() - end - end + if Current.Window and Current.Window.OpenButton then + Current.Document.Scroll = Current.Document.Scroll + direction + if Current.Window.Scroll < 0 then + Current.Window.Scroll = 0 + elseif Current.Window.Scroll > Current.Window.MaxScroll then + Current.Window.Scroll = Current.Window.MaxScroll + end + Draw() + elseif Current.ScrollBar then + if Current.ScrollBar:DoScroll(direction*2) then + Draw() + end + end end - - + + --[[ - Registers functions to run on certain events + Registers functions to run on certain events ]]-- function EventRegister(event, func) - if not Events[event] then - Events[event] = {} - end - - table.insert(Events[event], func) + if not Events[event] then + Events[event] = {} + end + + table.insert(Events[event], func) end - + --[[ - The main loop event handler, runs registered event functinos + The main loop event handler, runs registered event functinos ]]-- function EventHandler() - while true do - local event, arg1, arg2, arg3, arg4 = os.pullEventRaw() - if Events[event] then - for i, e in ipairs(Events[event]) do - e(event, arg1, arg2, arg3, arg4) - end - end - end + while true do + local event, arg1, arg2, arg3, arg4 = os.pullEventRaw() + if Events[event] then + for i, e in ipairs(Events[event]) do + e(event, arg1, arg2, arg3, arg4) + end + end + end end - - + + local function Extension(path, addDot) - if not path then - return nil - elseif not string.find(fs.getName(path), '%.') then - if not addDot then - return fs.getName(path) - else - return '' - end - else - local _path = path - if path:sub(#path) == '/' then - _path = path:sub(1,#path-1) - end - local extension = _path:gmatch('%.[0-9a-z]+$')() - if extension then - extension = extension:sub(2) - else - --extension = nil - return '' - end - if addDot then - extension = '.'..extension - end - return extension:lower() - end + if not path then + return nil + elseif not string.find(fs.getName(path), '%.') then + if not addDot then + return fs.getName(path) + else + return '' + end + else + local _path = path + if path:sub(#path) == '/' then + _path = path:sub(1,#path-1) + end + local extension = _path:gmatch('%.[0-9a-z]+$')() + if extension then + extension = extension:sub(2) + else + --extension = nil + return '' + end + if addDot then + extension = '.'..extension + end + return extension:lower() + end end - + local RemoveExtension = function(path) - if path:sub(1,1) == '.' then - return path - end - local extension = Extension(path) - if extension == path then - return fs.getName(path) - end - return string.gsub(path, extension, ''):sub(1, -2) + if path:sub(1,1) == '.' then + return path + end + local extension = Extension(path) + if extension == path then + return fs.getName(path) + end + return string.gsub(path, extension, ''):sub(1, -2) end - + local acknowledgedColour = false function PrintDocument() - if OneOS then - OneOS.LoadAPI('/System/API/Helpers.lua') - OneOS.LoadAPI('/System/API/Peripheral.lua') - OneOS.LoadAPI('/System/API/Printer.lua') - end - - local doPrint = function() - local window = PrintDocumentWindow:Initialise():Show() - end - - if Peripheral.GetPeripheral('printer') == nil then - ButtonDialougeWindow:Initialise('No Printer Found', 'Please place a printer next to your computer. Ensure you also insert dye (left slot) and paper (top slots)', 'Ok', nil, function(window, ok) - window:Close() - end):Show() - elseif not acknowledgedColour and FindColours(Current.Document.TextInput.Value) ~= 0 then - ButtonDialougeWindow:Initialise('Important', 'Due to the way printers work, you can\'t print in more than one colour. The dye you use will be the colour of the text.', 'Ok', nil, function(window, ok) - acknowledgedColour = true - window:Close() - doPrint() - end):Show() - else - doPrint() - end + if OneOS then + OneOS.LoadAPI('/System/API/Helpers.lua') + OneOS.LoadAPI('/System/API/Peripheral.lua') + OneOS.LoadAPI('/System/API/Printer.lua') + end + + local doPrint = function() + local window = PrintDocumentWindow:Initialise():Show() + end + + if Peripheral.GetPeripheral('printer') == nil then + ButtonDialougeWindow:Initialise('No Printer Found', 'Please place a printer next to your computer. Ensure you also insert dye (left slot) and paper (top slots)', 'Ok', nil, function(window, ok) + window:Close() + end):Show() + elseif not acknowledgedColour and FindColours(Current.Document.TextInput.Value) ~= 0 then + ButtonDialougeWindow:Initialise('Important', 'Due to the way printers work, you can\'t print in more than one colour. The dye you use will be the colour of the text.', 'Ok', nil, function(window, ok) + acknowledgedColour = true + window:Close() + doPrint() + end):Show() + else + doPrint() + end end - + function SaveDocument() - local function save() - local h = fs.open(Current.Document.Path, 'w') - if h then - if Current.Document.Format == TextFormatPlainText then - h.write(Current.Document.TextInput.Value) - else - local lines = {} - for p, page in ipairs(Current.Document.Pages) do - for i, line in ipairs(page.Lines) do - table.insert(lines, line) - end - end - h.write(textutils.serialize(lines)) - end - Current.Modified = false - else - ButtonDialougeWindow:Initialise('Error', 'An error occured while saving the file, try again.', 'Ok', nil, function(window, ok) - window:Close() - end):Show() - end - h.close() - end - - if not Current.Document.Path then - SaveDocumentWindow:Initialise(function(self, success, path) - self:Close() - if success then - local extension = '' - if Current.Document.Format == TextFormatPlainText then - extension = '.txt' - elseif Current.Document.Format == TextFormatInkText then - extension = '.ink' - end - - if path:sub(-4) ~= extension then - path = path .. extension - end - - Current.Document.Path = path - Current.Document.Title = fs.getName(path) - save() - end - if Current.Document then - Current.TextInput = Current.Document.TextInput - end - end):Show() - else - save() - end + local function save() + local h = fs.open(Current.Document.Path, 'w') + if h then + if Current.Document.Format == TextFormatPlainText then + h.write(Current.Document.TextInput.Value) + else + local lines = {} + for p, page in ipairs(Current.Document.Pages) do + for i, line in ipairs(page.Lines) do + table.insert(lines, line) + end + end + h.write(textutils.serialize(lines)) + end + Current.Modified = false + else + ButtonDialougeWindow:Initialise('Error', 'An error occured while saving the file, try again.', 'Ok', nil, function(window, ok) + window:Close() + end):Show() + end + h.close() + end + + if not Current.Document.Path then + SaveDocumentWindow:Initialise(function(self, success, path) + self:Close() + if success then + local extension = '' + if Current.Document.Format == TextFormatPlainText then + extension = '.txt' + elseif Current.Document.Format == TextFormatInkText then + extension = '.ink' + end + + if path:sub(-4) ~= extension then + path = path .. extension + end + + Current.Document.Path = path + Current.Document.Title = fs.getName(path) + save() + end + if Current.Document then + Current.TextInput = Current.Document.TextInput + end + end):Show() + else + save() + end end - + function DisplayOpenDocumentWindow() - OpenDocumentWindow:Initialise(function(self, success, path) - self:Close() - if success then - OpenDocument(path) - end - end):Show() + OpenDocumentWindow:Initialise(function(self, success, path) + self:Close() + if success then + OpenDocument(path) + end + end):Show() end - + function OpenDocument(path) - Current.Selection = nil - local h = fs.open(path, 'r') - if h then - Current.Document = Document:Initialise(h.readAll(), RemoveExtension(fs.getName(path)), path) - else - ButtonDialougeWindow:Initialise('Error', 'An error occured while opening the file, try again.', 'Ok', nil, function(window, ok) - window:Close() - if Current.Document then - Current.TextInput = Current.Document.TextInput - end - end):Show() - end - h.close() + Current.Selection = nil + local h = fs.open(path, 'r') + if h then + Current.Document = Document:Initialise(h.readAll(), RemoveExtension(fs.getName(path)), path) + else + ButtonDialougeWindow:Initialise('Error', 'An error occured while opening the file, try again.', 'Ok', nil, function(window, ok) + window:Close() + if Current.Document then + Current.TextInput = Current.Document.TextInput + end + end):Show() + end + h.close() end - + local TidyPath = function(path) - path = '/'..path - local fs = fs - if OneOS then - fs = OneOS.FS - end - if fs.isDir(path) then - path = path .. '/' - end - - path, n = path:gsub("//", "/") - while n > 0 do - path, n = path:gsub("//", "/") - end - return path + path = '/'..path + local fs = fs + if OneOS then + fs = OneOS.FS + end + if fs.isDir(path) then + path = path .. '/' + end + + path, n = path:gsub("//", "/") + while n > 0 do + path, n = path:gsub("//", "/") + end + return path end - + OpenDocumentWindow = { - X = 1, - Y = 1, - Width = 0, - Height = 0, - CursorPos = 1, - Visible = true, - Return = nil, - OpenButton = nil, - PathTextBox = nil, - CurrentDirectory = '/', - Scroll = 0, - MaxScroll = 0, - GoUpButton = nil, - SelectedFile = '', - Files = {}, - Typed = false, - - AbsolutePosition = function(self) - return {X = self.X, Y = self.Y} - end, - - Draw = function(self) - if not self.Visible then - return - end - Drawing.DrawBlankArea(self.X + 1, self.Y+1, self.Width, self.Height, colours.grey) - Drawing.DrawBlankArea(self.X, self.Y, self.Width, 3, colours.lightGrey) - Drawing.DrawBlankArea(self.X, self.Y+1, self.Width, self.Height-6, colours.white) - Drawing.DrawCharactersCenter(self.X, self.Y, self.Width, 1, self.Title, colours.black, colours.lightGrey) - Drawing.DrawBlankArea(self.X, self.Y + self.Height - 5, self.Width, 5, colours.lightGrey) - self:DrawFiles() - - if (fs.exists(self.PathTextBox.TextInput.Value)) or (self.SelectedFile and #self.SelectedFile > 0 and fs.exists(self.CurrentDirectory .. self.SelectedFile)) then - self.OpenButton.TextColour = colours.black - else - self.OpenButton.TextColour = colours.lightGrey - end - - self.PathTextBox:Draw() - self.OpenButton:Draw() - self.CancelButton:Draw() - self.GoUpButton:Draw() - end, - - DrawFiles = function(self) - for i, file in ipairs(self.Files) do - if i > self.Scroll and i - self.Scroll <= 11 then - if file == self.SelectedFile then - Drawing.DrawCharacters(self.X + 1, self.Y + i - self.Scroll, file, colours.white, colours.lightBlue) - elseif string.find(file, '%.txt') or string.find(file, '%.text') or string.find(file, '%.ink') or fs.isDir(self.CurrentDirectory .. file) then - Drawing.DrawCharacters(self.X + 1, self.Y + i - self.Scroll, file, colours.black, colours.white) - else - Drawing.DrawCharacters(self.X + 1, self.Y + i - self.Scroll, file, colours.grey, colours.white) - end - end - end - self.MaxScroll = #self.Files - 11 - if self.MaxScroll < 0 then - self.MaxScroll = 0 - end - end, - - Initialise = function(self, returnFunc) - local new = {} -- the new instance - setmetatable( new, {__index = self} ) - new.Width = 32 - new.Height = 17 - new.Return = returnFunc - new.X = math.ceil((Drawing.Screen.Width - new.Width) / 2) - new.Y = math.ceil((Drawing.Screen.Height - new.Height) / 2) - new.Title = 'Open Document' - new.Visible = true - new.CurrentDirectory = '/' - new.SelectedFile = nil - if OneOS and fs.exists('/Desktop/Documents/') then - new.CurrentDirectory = '/Desktop/Documents/' - end - new.OpenButton = Button:Initialise(new.Width - 6, new.Height - 1, nil, nil, colours.white, new, function(self, side, x, y, toggle) - if fs.exists(new.PathTextBox.TextInput.Value) and self.TextColour == colours.black and not fs.isDir(new.PathTextBox.TextInput.Value) then - returnFunc(new, true, TidyPath(new.PathTextBox.TextInput.Value)) - elseif new.SelectedFile and self.TextColour == colours.black and fs.isDir(new.CurrentDirectory .. new.SelectedFile) then - new:GoToDirectory(new.CurrentDirectory .. new.SelectedFile) - elseif new.SelectedFile and self.TextColour == colours.black then - returnFunc(new, true, TidyPath(new.CurrentDirectory .. '/' .. new.SelectedFile)) - end - end, 'Open', colours.black) - new.CancelButton = Button:Initialise(new.Width - 15, new.Height - 1, nil, nil, colours.white, new, function(self, side, x, y, toggle) - returnFunc(new, false) - end, 'Cancel', colours.black) - new.GoUpButton = Button:Initialise(2, new.Height - 1, nil, nil, colours.white, new, function(self, side, x, y, toggle) - local folderName = fs.getName(new.CurrentDirectory) - local parentDirectory = new.CurrentDirectory:sub(1, #new.CurrentDirectory-#folderName-1) - new:GoToDirectory(parentDirectory) - end, 'Go Up', colours.black) - new.PathTextBox = TextBox:Initialise(2, new.Height - 3, new.Width - 2, 1, new, new.CurrentDirectory, colours.white, colours.black) - new:GoToDirectory(new.CurrentDirectory) - return new - end, - - Show = function(self) - Current.Window = self - return self - end, - - Close = function(self) - Current.Input = nil - Current.Window = nil - self = nil - end, - - GoToDirectory = function(self, path) - path = TidyPath(path) - self.CurrentDirectory = path - self.Scroll = 0 - self.SelectedFile = nil - self.Typed = false - self.PathTextBox.TextInput.Value = path - local fs = fs - if OneOS then - fs = OneOS.FS - end - self.Files = fs.list(self.CurrentDirectory) - Draw() - end, - - Flash = function(self) - self.Visible = false - Draw() - sleep(0.15) - self.Visible = true - Draw() - sleep(0.15) - self.Visible = false - Draw() - sleep(0.15) - self.Visible = true - Draw() - end, - - Click = function(self, side, x, y) - local items = {self.OpenButton, self.CancelButton, self.PathTextBox, self.GoUpButton} - local found = false - for i, v in ipairs(items) do - if CheckClick(v, x, y) then - v:Click(side, x, y) - found = true - end - end - - if not found then - if y <= 12 then - local fs = fs - if OneOS then - fs = OneOS.FS - end - self.SelectedFile = fs.list(self.CurrentDirectory)[y-1] - self.PathTextBox.TextInput.Value = TidyPath(self.CurrentDirectory .. '/' .. self.SelectedFile) - Draw() - end - end - return true - end + X = 1, + Y = 1, + Width = 0, + Height = 0, + CursorPos = 1, + Visible = true, + Return = nil, + OpenButton = nil, + PathTextBox = nil, + CurrentDirectory = '/', + Scroll = 0, + MaxScroll = 0, + GoUpButton = nil, + SelectedFile = '', + Files = {}, + Typed = false, + + AbsolutePosition = function(self) + return {X = self.X, Y = self.Y} + end, + + Draw = function(self) + if not self.Visible then + return + end + Drawing.DrawBlankArea(self.X + 1, self.Y+1, self.Width, self.Height, colours.grey) + Drawing.DrawBlankArea(self.X, self.Y, self.Width, 3, colours.lightGrey) + Drawing.DrawBlankArea(self.X, self.Y+1, self.Width, self.Height-6, colours.white) + Drawing.DrawCharactersCenter(self.X, self.Y, self.Width, 1, self.Title, colours.black, colours.lightGrey) + Drawing.DrawBlankArea(self.X, self.Y + self.Height - 5, self.Width, 5, colours.lightGrey) + self:DrawFiles() + + if (fs.exists(self.PathTextBox.TextInput.Value)) or (self.SelectedFile and #self.SelectedFile > 0 and fs.exists(self.CurrentDirectory .. self.SelectedFile)) then + self.OpenButton.TextColour = colours.black + else + self.OpenButton.TextColour = colours.lightGrey + end + + self.PathTextBox:Draw() + self.OpenButton:Draw() + self.CancelButton:Draw() + self.GoUpButton:Draw() + end, + + DrawFiles = function(self) + for i, file in ipairs(self.Files) do + if i > self.Scroll and i - self.Scroll <= 11 then + if file == self.SelectedFile then + Drawing.DrawCharacters(self.X + 1, self.Y + i - self.Scroll, file, colours.white, colours.lightBlue) + elseif string.find(file, '%.txt') or string.find(file, '%.text') or string.find(file, '%.ink') or fs.isDir(self.CurrentDirectory .. file) then + Drawing.DrawCharacters(self.X + 1, self.Y + i - self.Scroll, file, colours.black, colours.white) + else + Drawing.DrawCharacters(self.X + 1, self.Y + i - self.Scroll, file, colours.grey, colours.white) + end + end + end + self.MaxScroll = #self.Files - 11 + if self.MaxScroll < 0 then + self.MaxScroll = 0 + end + end, + + Initialise = function(self, returnFunc) + local new = {} -- the new instance + setmetatable( new, {__index = self} ) + new.Width = 32 + new.Height = 17 + new.Return = returnFunc + new.X = math.ceil((Drawing.Screen.Width - new.Width) / 2) + new.Y = math.ceil((Drawing.Screen.Height - new.Height) / 2) + new.Title = 'Open Document' + new.Visible = true + new.CurrentDirectory = '/' + new.SelectedFile = nil + if OneOS and fs.exists('/Desktop/Documents/') then + new.CurrentDirectory = '/Desktop/Documents/' + end + new.OpenButton = Button:Initialise(new.Width - 6, new.Height - 1, nil, nil, colours.white, new, function(self, side, x, y, toggle) + if fs.exists(new.PathTextBox.TextInput.Value) and self.TextColour == colours.black and not fs.isDir(new.PathTextBox.TextInput.Value) then + returnFunc(new, true, TidyPath(new.PathTextBox.TextInput.Value)) + elseif new.SelectedFile and self.TextColour == colours.black and fs.isDir(new.CurrentDirectory .. new.SelectedFile) then + new:GoToDirectory(new.CurrentDirectory .. new.SelectedFile) + elseif new.SelectedFile and self.TextColour == colours.black then + returnFunc(new, true, TidyPath(new.CurrentDirectory .. '/' .. new.SelectedFile)) + end + end, 'Open', colours.black) + new.CancelButton = Button:Initialise(new.Width - 15, new.Height - 1, nil, nil, colours.white, new, function(self, side, x, y, toggle) + returnFunc(new, false) + end, 'Cancel', colours.black) + new.GoUpButton = Button:Initialise(2, new.Height - 1, nil, nil, colours.white, new, function(self, side, x, y, toggle) + local folderName = fs.getName(new.CurrentDirectory) + local parentDirectory = new.CurrentDirectory:sub(1, #new.CurrentDirectory-#folderName-1) + new:GoToDirectory(parentDirectory) + end, 'Go Up', colours.black) + new.PathTextBox = TextBox:Initialise(2, new.Height - 3, new.Width - 2, 1, new, new.CurrentDirectory, colours.white, colours.black) + new:GoToDirectory(new.CurrentDirectory) + return new + end, + + Show = function(self) + Current.Window = self + return self + end, + + Close = function(self) + Current.Input = nil + Current.Window = nil + self = nil + end, + + GoToDirectory = function(self, path) + path = TidyPath(path) + self.CurrentDirectory = path + self.Scroll = 0 + self.SelectedFile = nil + self.Typed = false + self.PathTextBox.TextInput.Value = path + local fs = fs + if OneOS then + fs = OneOS.FS + end + self.Files = fs.list(self.CurrentDirectory) + Draw() + end, + + Flash = function(self) + self.Visible = false + Draw() + sleep(0.15) + self.Visible = true + Draw() + sleep(0.15) + self.Visible = false + Draw() + sleep(0.15) + self.Visible = true + Draw() + end, + + Click = function(self, side, x, y) + local items = {self.OpenButton, self.CancelButton, self.PathTextBox, self.GoUpButton} + local found = false + for i, v in ipairs(items) do + if CheckClick(v, x, y) then + v:Click(side, x, y) + found = true + end + end + + if not found then + if y <= 12 then + local fs = fs + if OneOS then + fs = OneOS.FS + end + self.SelectedFile = fs.list(self.CurrentDirectory)[y-1] + self.PathTextBox.TextInput.Value = TidyPath(self.CurrentDirectory .. '/' .. self.SelectedFile) + Draw() + end + end + return true + end } - + PrintDocumentWindow = { - X = 1, - Y = 1, - Width = 0, - Height = 0, - CursorPos = 1, - Visible = true, - Return = nil, - PrintButton = nil, - CopiesTextBox = nil, - Scroll = 0, - MaxScroll = 0, - PrinterSelectButton = nil, - Title = '', - Status = 0, --0 = neutral, 1 = good, -1 = error - StatusText = '', - SelectedPrinter = nil, - - AbsolutePosition = function(self) - return {X = self.X, Y = self.Y} - end, - - Draw = function(self) - if not self.Visible then - return - end - Drawing.DrawBlankArea(self.X + 1, self.Y+1, self.Width, self.Height, colours.grey) - Drawing.DrawBlankArea(self.X, self.Y, self.Width, 1, colours.lightGrey) - Drawing.DrawBlankArea(self.X, self.Y+1, self.Width, self.Height-1, colours.white) - Drawing.DrawCharactersCenter(self.X, self.Y, self.Width, 1, self.Title, colours.black, colours.lightGrey) - - self.PrinterSelectButton:Draw() - Drawing.DrawCharactersCenter(self.X, self.Y + self.PrinterSelectButton.Y - 2, self.Width, 1, 'Printer', colours.black, colours.white) - Drawing.DrawCharacters(self.X + self.Width - 3, self.Y + self.PrinterSelectButton.Y - 1, '\\/', colours.black, colours.lightGrey) - Drawing.DrawCharacters(self.X + 1, self.Y + self.CopiesTextBox.Y - 1, 'Copies', colours.black, colours.white) - local statusColour = colours.grey - if self.Status == -1 then - statusColour = colours.red - elseif self.Status == 1 then - statusColour = colours.green - end - Drawing.DrawCharacters(self.X + 1, self.Y + self.CopiesTextBox.Y + 1, self.StatusText, statusColour, colours.white) - - self.CopiesTextBox:Draw() - self.PrintButton:Draw() - self.CancelButton:Draw() - end, - - Initialise = function(self) - local new = {} -- the new instance - setmetatable( new, {__index = self} ) - new.Width = 32 - new.Height = 11 - new.Return = returnFunc - new.X = math.ceil((Drawing.Screen.Width - new.Width) / 2) - new.Y = math.ceil((Drawing.Screen.Height - new.Height) / 2) - new.Title = 'Print Document' - new.Visible = true - new.PrintButton = Button:Initialise(new.Width - 7, new.Height - 1, nil, nil, colours.lightGrey, new, function(self, side, x, y, toggle) - local doPrint = true - if new.SelectedPrinter == nil then - local p = Peripheral.GetPeripheral('printer') - if p then - new.SelectedPrinter = p.Side - new.PrinterSelectButton.Text = p.Fullname - else - new.StatusText = 'No Connected Printer' - new.Status = -1 - doPrint = false - end - end - if doPrint then - local printer = Printer:Initialise(new.SelectedPrinter) - local err = printer:PrintLines(Current.Document.Lines, Current.Document.Title, tonumber(new.CopiesTextBox.TextInput.Value)) - if not err then - new.StatusText = 'Document Printed!' - new.Status = 1 - closeWindowTimer = os.startTimer(1) - else - new.StatusText = err - new.Status = -1 - end - end - end, 'Print', colours.black) - new.CancelButton = Button:Initialise(new.Width - 15, new.Height - 1, nil, nil, colours.lightGrey, new, function(self, side, x, y, toggle) - new:Close() - Draw() - end, 'Close', colours.black) - new.PrinterSelectButton = Button:Initialise(2, 4, new.Width - 2, nil, colours.lightGrey, new, function(self, side, x, y, toggle) - local printers = { - { - Title = "Automatic", - Click = function() - new.SelectedPrinter = nil - new.PrinterSelectButton.Text = 'Automatic' - end - }, - { - Separator = true - } - } - for i, p in ipairs(Peripheral.GetPeripherals('printer')) do - table.insert(printers, { - Title = p.Fullname, - Click = function(self) - new.SelectedPrinter = p.Side - new.PrinterSelectButton.Text = p.Fullname - end - }) - end - Current.Menu = Menu:New(x, y+4, printers, self, true) - end, 'Automatic', colours.black) - new.CopiesTextBox = TextBox:Initialise(9, 6, 4, 1, new, 1, colours.lightGrey, colours.black, nil, true) - Current.TextInput = new.CopiesTextBox.TextInput - new.StatusText = 'Waiting...' - new.Status = 0 - return new - end, - - Show = function(self) - Current.Window = self - return self - end, - - Close = function(self) - Current.Input = nil - Current.Window = nil - self = nil - end, - - Flash = function(self) - self.Visible = false - Draw() - sleep(0.15) - self.Visible = true - Draw() - sleep(0.15) - self.Visible = false - Draw() - sleep(0.15) - self.Visible = true - Draw() - end, - - Click = function(self, side, x, y) - local items = {self.PrintButton, self.CancelButton, self.CopiesTextBox, self.PrinterSelectButton} - for i, v in ipairs(items) do - if CheckClick(v, x, y) then - v:Click(side, x, y) - end - end - return true - end + X = 1, + Y = 1, + Width = 0, + Height = 0, + CursorPos = 1, + Visible = true, + Return = nil, + PrintButton = nil, + CopiesTextBox = nil, + Scroll = 0, + MaxScroll = 0, + PrinterSelectButton = nil, + Title = '', + Status = 0, --0 = neutral, 1 = good, -1 = error + StatusText = '', + SelectedPrinter = nil, + + AbsolutePosition = function(self) + return {X = self.X, Y = self.Y} + end, + + Draw = function(self) + if not self.Visible then + return + end + Drawing.DrawBlankArea(self.X + 1, self.Y+1, self.Width, self.Height, colours.grey) + Drawing.DrawBlankArea(self.X, self.Y, self.Width, 1, colours.lightGrey) + Drawing.DrawBlankArea(self.X, self.Y+1, self.Width, self.Height-1, colours.white) + Drawing.DrawCharactersCenter(self.X, self.Y, self.Width, 1, self.Title, colours.black, colours.lightGrey) + + self.PrinterSelectButton:Draw() + Drawing.DrawCharactersCenter(self.X, self.Y + self.PrinterSelectButton.Y - 2, self.Width, 1, 'Printer', colours.black, colours.white) + Drawing.DrawCharacters(self.X + self.Width - 3, self.Y + self.PrinterSelectButton.Y - 1, '\\/', colours.black, colours.lightGrey) + Drawing.DrawCharacters(self.X + 1, self.Y + self.CopiesTextBox.Y - 1, 'Copies', colours.black, colours.white) + local statusColour = colours.grey + if self.Status == -1 then + statusColour = colours.red + elseif self.Status == 1 then + statusColour = colours.green + end + Drawing.DrawCharacters(self.X + 1, self.Y + self.CopiesTextBox.Y + 1, self.StatusText, statusColour, colours.white) + + self.CopiesTextBox:Draw() + self.PrintButton:Draw() + self.CancelButton:Draw() + end, + + Initialise = function(self) + local new = {} -- the new instance + setmetatable( new, {__index = self} ) + new.Width = 32 + new.Height = 11 + new.Return = returnFunc + new.X = math.ceil((Drawing.Screen.Width - new.Width) / 2) + new.Y = math.ceil((Drawing.Screen.Height - new.Height) / 2) + new.Title = 'Print Document' + new.Visible = true + new.PrintButton = Button:Initialise(new.Width - 7, new.Height - 1, nil, nil, colours.lightGrey, new, function(self, side, x, y, toggle) + local doPrint = true + if new.SelectedPrinter == nil then + local p = Peripheral.GetPeripheral('printer') + if p then + new.SelectedPrinter = p.Side + new.PrinterSelectButton.Text = p.Fullname + else + new.StatusText = 'No Connected Printer' + new.Status = -1 + doPrint = false + end + end + if doPrint then + local printer = Printer:Initialise(new.SelectedPrinter) + local err = printer:PrintLines(Current.Document.Lines, Current.Document.Title, tonumber(new.CopiesTextBox.TextInput.Value)) + if not err then + new.StatusText = 'Document Printed!' + new.Status = 1 + closeWindowTimer = os.startTimer(1) + else + new.StatusText = err + new.Status = -1 + end + end + end, 'Print', colours.black) + new.CancelButton = Button:Initialise(new.Width - 15, new.Height - 1, nil, nil, colours.lightGrey, new, function(self, side, x, y, toggle) + new:Close() + Draw() + end, 'Close', colours.black) + new.PrinterSelectButton = Button:Initialise(2, 4, new.Width - 2, nil, colours.lightGrey, new, function(self, side, x, y, toggle) + local printers = { + { + Title = "Automatic", + Click = function() + new.SelectedPrinter = nil + new.PrinterSelectButton.Text = 'Automatic' + end + }, + { + Separator = true + } + } + for i, p in ipairs(Peripheral.GetPeripherals('printer')) do + table.insert(printers, { + Title = p.Fullname, + Click = function(self) + new.SelectedPrinter = p.Side + new.PrinterSelectButton.Text = p.Fullname + end + }) + end + Current.Menu = Menu:New(x, y+4, printers, self, true) + end, 'Automatic', colours.black) + new.CopiesTextBox = TextBox:Initialise(9, 6, 4, 1, new, 1, colours.lightGrey, colours.black, nil, true) + Current.TextInput = new.CopiesTextBox.TextInput + new.StatusText = 'Waiting...' + new.Status = 0 + return new + end, + + Show = function(self) + Current.Window = self + return self + end, + + Close = function(self) + Current.Input = nil + Current.Window = nil + self = nil + end, + + Flash = function(self) + self.Visible = false + Draw() + sleep(0.15) + self.Visible = true + Draw() + sleep(0.15) + self.Visible = false + Draw() + sleep(0.15) + self.Visible = true + Draw() + end, + + Click = function(self, side, x, y) + local items = {self.PrintButton, self.CancelButton, self.CopiesTextBox, self.PrinterSelectButton} + for i, v in ipairs(items) do + if CheckClick(v, x, y) then + v:Click(side, x, y) + end + end + return true + end } - + SaveDocumentWindow = { - X = 1, - Y = 1, - Width = 0, - Height = 0, - CursorPos = 1, - Visible = true, - Return = nil, - SaveButton = nil, - PathTextBox = nil, - CurrentDirectory = '/', - Scroll = 0, - MaxScroll = 0, - ScrollBar = nil, - GoUpButton = nil, - Files = {}, - Typed = false, - - AbsolutePosition = function(self) - return {X = self.X, Y = self.Y} - end, - - Draw = function(self) - if not self.Visible then - return - end - Drawing.DrawBlankArea(self.X + 1, self.Y+1, self.Width, self.Height, colours.grey) - Drawing.DrawBlankArea(self.X, self.Y, self.Width, 3, colours.lightGrey) - Drawing.DrawBlankArea(self.X, self.Y+1, self.Width, self.Height-6, colours.white) - Drawing.DrawCharactersCenter(self.X, self.Y, self.Width, 1, self.Title, colours.black, colours.lightGrey) - Drawing.DrawBlankArea(self.X, self.Y + self.Height - 5, self.Width, 5, colours.lightGrey) - Drawing.DrawCharacters(self.X + 1, self.Y + self.Height - 5, self.CurrentDirectory, colours.grey, colours.lightGrey) - self:DrawFiles() - - if (self.PathTextBox.TextInput.Value) then - self.SaveButton.TextColour = colours.black - else - self.SaveButton.TextColour = colours.lightGrey - end - - self.PathTextBox:Draw() - self.SaveButton:Draw() - self.CancelButton:Draw() - self.GoUpButton:Draw() - end, - - DrawFiles = function(self) - for i, file in ipairs(self.Files) do - if i > self.Scroll and i - self.Scroll <= 10 then - if file == self.SelectedFile then - Drawing.DrawCharacters(self.X + 1, self.Y + i - self.Scroll, file, colours.white, colours.lightBlue) - elseif fs.isDir(self.CurrentDirectory .. file) then - Drawing.DrawCharacters(self.X + 1, self.Y + i - self.Scroll, file, colours.black, colours.white) - else - Drawing.DrawCharacters(self.X + 1, self.Y + i - self.Scroll, file, colours.lightGrey, colours.white) - end - end - end - self.MaxScroll = #self.Files - 11 - if self.MaxScroll < 0 then - self.MaxScroll = 0 - end - end, - - Initialise = function(self, returnFunc) - local new = {} -- the new instance - setmetatable( new, {__index = self} ) - new.Width = 32 - new.Height = 16 - new.Return = returnFunc - new.X = math.ceil((Drawing.Screen.Width - new.Width) / 2) - new.Y = math.ceil((Drawing.Screen.Height - new.Height) / 2) - new.Title = 'Save Document' - new.Visible = true - new.CurrentDirectory = '/' - if OneOS and fs.exists('/Desktop/Documents/') then - new.CurrentDirectory = '/Desktop/Documents/' - end - new.SaveButton = Button:Initialise(new.Width - 6, new.Height - 1, nil, nil, colours.white, new, function(self, side, x, y, toggle) - if self.TextColour == colours.black and not fs.isDir(new.CurrentDirectory ..'/' .. new.PathTextBox.TextInput.Value) then - returnFunc(new, true, TidyPath(new.CurrentDirectory ..'/' .. new.PathTextBox.TextInput.Value)) - elseif new.SelectedFile and self.TextColour == colours.black and fs.isDir(new.CurrentDirectory .. new.SelectedFile) then - new:GoToDirectory(new.CurrentDirectory .. new.SelectedFile) - end - end, 'Save', colours.black) - new.CancelButton = Button:Initialise(new.Width - 15, new.Height - 1, nil, nil, colours.white, new, function(self, side, x, y, toggle) - returnFunc(new, false) - end, 'Cancel', colours.black) - new.GoUpButton = Button:Initialise(2, new.Height - 1, nil, nil, colours.white, new, function(self, side, x, y, toggle) - local folderName = fs.getName(new.CurrentDirectory) - local parentDirectory = new.CurrentDirectory:sub(1, #new.CurrentDirectory-#folderName-1) - new:GoToDirectory(parentDirectory) - end, 'Go Up', colours.black) - new.PathTextBox = TextBox:Initialise(2, new.Height - 3, new.Width - 2, 1, new, '', colours.white, colours.black, function(key) - if key == keys.enter then - new.SaveButton:Click() - end - end) - new.PathTextBox.Placeholder = 'Document Name' - Current.TextInput = new.PathTextBox.TextInput - new:GoToDirectory(new.CurrentDirectory) - return new - end, - - Show = function(self) - Current.Window = self - return self - end, - - Close = function(self) - Current.Input = nil - Current.Window = nil - self = nil - end, - - GoToDirectory = function(self, path) - path = TidyPath(path) - self.CurrentDirectory = path - self.Scroll = 0 - self.Typed = false - local fs = fs - if OneOS then - fs = OneOS.FS - end - self.Files = fs.list(self.CurrentDirectory) - Draw() - end, - - Flash = function(self) - self.Visible = false - Draw() - sleep(0.15) - self.Visible = true - Draw() - sleep(0.15) - self.Visible = false - Draw() - sleep(0.15) - self.Visible = true - Draw() - end, - - Click = function(self, side, x, y) - local items = {self.SaveButton, self.CancelButton, self.PathTextBox, self.GoUpButton} - local found = false - for i, v in ipairs(items) do - if CheckClick(v, x, y) then - v:Click(side, x, y) - found = true - end - end - - if not found then - if y <= 11 then - local files = fs.list(self.CurrentDirectory) - if files[y-1] then - self:GoToDirectory(self.CurrentDirectory..files[y-1]) - Draw() - end - end - end - return true - end + X = 1, + Y = 1, + Width = 0, + Height = 0, + CursorPos = 1, + Visible = true, + Return = nil, + SaveButton = nil, + PathTextBox = nil, + CurrentDirectory = '/', + Scroll = 0, + MaxScroll = 0, + ScrollBar = nil, + GoUpButton = nil, + Files = {}, + Typed = false, + + AbsolutePosition = function(self) + return {X = self.X, Y = self.Y} + end, + + Draw = function(self) + if not self.Visible then + return + end + Drawing.DrawBlankArea(self.X + 1, self.Y+1, self.Width, self.Height, colours.grey) + Drawing.DrawBlankArea(self.X, self.Y, self.Width, 3, colours.lightGrey) + Drawing.DrawBlankArea(self.X, self.Y+1, self.Width, self.Height-6, colours.white) + Drawing.DrawCharactersCenter(self.X, self.Y, self.Width, 1, self.Title, colours.black, colours.lightGrey) + Drawing.DrawBlankArea(self.X, self.Y + self.Height - 5, self.Width, 5, colours.lightGrey) + Drawing.DrawCharacters(self.X + 1, self.Y + self.Height - 5, self.CurrentDirectory, colours.grey, colours.lightGrey) + self:DrawFiles() + + if (self.PathTextBox.TextInput.Value) then + self.SaveButton.TextColour = colours.black + else + self.SaveButton.TextColour = colours.lightGrey + end + + self.PathTextBox:Draw() + self.SaveButton:Draw() + self.CancelButton:Draw() + self.GoUpButton:Draw() + end, + + DrawFiles = function(self) + for i, file in ipairs(self.Files) do + if i > self.Scroll and i - self.Scroll <= 10 then + if file == self.SelectedFile then + Drawing.DrawCharacters(self.X + 1, self.Y + i - self.Scroll, file, colours.white, colours.lightBlue) + elseif fs.isDir(self.CurrentDirectory .. file) then + Drawing.DrawCharacters(self.X + 1, self.Y + i - self.Scroll, file, colours.black, colours.white) + else + Drawing.DrawCharacters(self.X + 1, self.Y + i - self.Scroll, file, colours.lightGrey, colours.white) + end + end + end + self.MaxScroll = #self.Files - 11 + if self.MaxScroll < 0 then + self.MaxScroll = 0 + end + end, + + Initialise = function(self, returnFunc) + local new = {} -- the new instance + setmetatable( new, {__index = self} ) + new.Width = 32 + new.Height = 16 + new.Return = returnFunc + new.X = math.ceil((Drawing.Screen.Width - new.Width) / 2) + new.Y = math.ceil((Drawing.Screen.Height - new.Height) / 2) + new.Title = 'Save Document' + new.Visible = true + new.CurrentDirectory = '/' + if OneOS and fs.exists('/Desktop/Documents/') then + new.CurrentDirectory = '/Desktop/Documents/' + end + new.SaveButton = Button:Initialise(new.Width - 6, new.Height - 1, nil, nil, colours.white, new, function(self, side, x, y, toggle) + if self.TextColour == colours.black and not fs.isDir(new.CurrentDirectory ..'/' .. new.PathTextBox.TextInput.Value) then + returnFunc(new, true, TidyPath(new.CurrentDirectory ..'/' .. new.PathTextBox.TextInput.Value)) + elseif new.SelectedFile and self.TextColour == colours.black and fs.isDir(new.CurrentDirectory .. new.SelectedFile) then + new:GoToDirectory(new.CurrentDirectory .. new.SelectedFile) + end + end, 'Save', colours.black) + new.CancelButton = Button:Initialise(new.Width - 15, new.Height - 1, nil, nil, colours.white, new, function(self, side, x, y, toggle) + returnFunc(new, false) + end, 'Cancel', colours.black) + new.GoUpButton = Button:Initialise(2, new.Height - 1, nil, nil, colours.white, new, function(self, side, x, y, toggle) + local folderName = fs.getName(new.CurrentDirectory) + local parentDirectory = new.CurrentDirectory:sub(1, #new.CurrentDirectory-#folderName-1) + new:GoToDirectory(parentDirectory) + end, 'Go Up', colours.black) + new.PathTextBox = TextBox:Initialise(2, new.Height - 3, new.Width - 2, 1, new, '', colours.white, colours.black, function(key) + if key == keys.enter then + new.SaveButton:Click() + end + end) + new.PathTextBox.Placeholder = 'Document Name' + Current.TextInput = new.PathTextBox.TextInput + new:GoToDirectory(new.CurrentDirectory) + return new + end, + + Show = function(self) + Current.Window = self + return self + end, + + Close = function(self) + Current.Input = nil + Current.Window = nil + self = nil + end, + + GoToDirectory = function(self, path) + path = TidyPath(path) + self.CurrentDirectory = path + self.Scroll = 0 + self.Typed = false + local fs = fs + if OneOS then + fs = OneOS.FS + end + self.Files = fs.list(self.CurrentDirectory) + Draw() + end, + + Flash = function(self) + self.Visible = false + Draw() + sleep(0.15) + self.Visible = true + Draw() + sleep(0.15) + self.Visible = false + Draw() + sleep(0.15) + self.Visible = true + Draw() + end, + + Click = function(self, side, x, y) + local items = {self.SaveButton, self.CancelButton, self.PathTextBox, self.GoUpButton} + local found = false + for i, v in ipairs(items) do + if CheckClick(v, x, y) then + v:Click(side, x, y) + found = true + end + end + + if not found then + if y <= 11 then + local files = fs.list(self.CurrentDirectory) + if files[y-1] then + self:GoToDirectory(self.CurrentDirectory..files[y-1]) + Draw() + end + end + end + return true + end } - + local WrapText = function(text, maxWidth) - local lines = {''} + local lines = {''} for word, space in text:gmatch('(%S+)(%s*)') do local temp = lines[#lines] .. word .. space:gsub('\n','') if #temp > maxWidth then @@ -2879,7 +2879,7 @@ local WrapText = function(text, maxWidth) end if space:find('\n') then lines[#lines] = lines[#lines] .. word - + space = space:gsub('\n', function() table.insert(lines, '') return '' @@ -2888,264 +2888,266 @@ local WrapText = function(text, maxWidth) lines[#lines] = lines[#lines] .. word .. space end end - return lines + return lines end - + ButtonDialougeWindow = { - X = 1, - Y = 1, - Width = 0, - Height = 0, - CursorPos = 1, - Visible = true, - CancelButton = nil, - OkButton = nil, - Lines = {}, - - AbsolutePosition = function(self) - return {X = self.X, Y = self.Y} - end, - - Draw = function(self) - if not self.Visible then - return - end - Drawing.DrawBlankArea(self.X + 1, self.Y+1, self.Width, self.Height, colours.grey) - Drawing.DrawBlankArea(self.X, self.Y, self.Width, 1, colours.lightGrey) - Drawing.DrawBlankArea(self.X, self.Y+1, self.Width, self.Height-1, colours.white) - Drawing.DrawCharactersCenter(self.X, self.Y, self.Width, 1, self.Title, colours.black, colours.lightGrey) - - for i, text in ipairs(self.Lines) do - Drawing.DrawCharacters(self.X + 1, self.Y + 1 + i, text, colours.black, colours.white) - end - - self.OkButton:Draw() - if self.CancelButton then - self.CancelButton:Draw() - end - end, - - Initialise = function(self, title, message, okText, cancelText, returnFunc) - local new = {} -- the new instance - setmetatable( new, {__index = self} ) - new.Width = 28 - new.Lines = WrapText(message, new.Width - 2) - new.Height = 5 + #new.Lines - new.Return = returnFunc - new.X = math.ceil((Drawing.Screen.Width - new.Width) / 2) - new.Y = math.ceil((Drawing.Screen.Height - new.Height) / 2) - new.Title = title - new.Visible = true - new.Visible = true - new.OkButton = Button:Initialise(new.Width - #okText - 2, new.Height - 1, nil, 1, nil, new, function() - returnFunc(new, true) - end, okText) - if cancelText then - new.CancelButton = Button:Initialise(new.Width - #okText - 2 - 1 - #cancelText - 2, new.Height - 1, nil, 1, nil, new, function() - returnFunc(new, false) - end, cancelText) - end - - return new - end, - - Show = function(self) - Current.Window = self - return self - end, - - Close = function(self) - Current.Window = nil - self = nil - end, - - Flash = function(self) - self.Visible = false - Draw() - sleep(0.15) - self.Visible = true - Draw() - sleep(0.15) - self.Visible = false - Draw() - sleep(0.15) - self.Visible = true - Draw() - end, - - Click = function(self, side, x, y) - local items = {self.OkButton, self.CancelButton} - local found = false - for i, v in ipairs(items) do - if CheckClick(v, x, y) then - v:Click(side, x, y) - found = true - end - end - return true - end + X = 1, + Y = 1, + Width = 0, + Height = 0, + CursorPos = 1, + Visible = true, + CancelButton = nil, + OkButton = nil, + Lines = {}, + + AbsolutePosition = function(self) + return {X = self.X, Y = self.Y} + end, + + Draw = function(self) + if not self.Visible then + return + end + Drawing.DrawBlankArea(self.X + 1, self.Y+1, self.Width, self.Height, colours.grey) + Drawing.DrawBlankArea(self.X, self.Y, self.Width, 1, colours.lightGrey) + Drawing.DrawBlankArea(self.X, self.Y+1, self.Width, self.Height-1, colours.white) + Drawing.DrawCharactersCenter(self.X, self.Y, self.Width, 1, self.Title, colours.black, colours.lightGrey) + + for i, text in ipairs(self.Lines) do + Drawing.DrawCharacters(self.X + 1, self.Y + 1 + i, text, colours.black, colours.white) + end + + self.OkButton:Draw() + if self.CancelButton then + self.CancelButton:Draw() + end + end, + + Initialise = function(self, title, message, okText, cancelText, returnFunc) + local new = {} -- the new instance + setmetatable( new, {__index = self} ) + new.Width = 28 + new.Lines = WrapText(message, new.Width - 2) + new.Height = 5 + #new.Lines + new.Return = returnFunc + new.X = math.ceil((Drawing.Screen.Width - new.Width) / 2) + new.Y = math.ceil((Drawing.Screen.Height - new.Height) / 2) + new.Title = title + new.Visible = true + new.Visible = true + new.OkButton = Button:Initialise(new.Width - #okText - 2, new.Height - 1, nil, 1, nil, new, function() + returnFunc(new, true) + end, okText) + if cancelText then + new.CancelButton = Button:Initialise(new.Width - #okText - 2 - 1 - #cancelText - 2, new.Height - 1, nil, 1, nil, new, function() + returnFunc(new, false) + end, cancelText) + end + + return new + end, + + Show = function(self) + Current.Window = self + return self + end, + + Close = function(self) + Current.Window = nil + self = nil + end, + + Flash = function(self) + self.Visible = false + Draw() + sleep(0.15) + self.Visible = true + Draw() + sleep(0.15) + self.Visible = false + Draw() + sleep(0.15) + self.Visible = true + Draw() + end, + + Click = function(self, side, x, y) + local items = {self.OkButton, self.CancelButton} + local found = false + for i, v in ipairs(items) do + if CheckClick(v, x, y) then + v:Click(side, x, y) + found = true + end + end + return true + end } - + TextDialougeWindow = { - X = 1, - Y = 1, - Width = 0, - Height = 0, - CursorPos = 1, - Visible = true, - CancelButton = nil, - OkButton = nil, - Lines = {}, - TextInput = nil, - - AbsolutePosition = function(self) - return {X = self.X, Y = self.Y} - end, - - Draw = function(self) - if not self.Visible then - return - end - Drawing.DrawBlankArea(self.X + 1, self.Y+1, self.Width, self.Height, colours.grey) - Drawing.DrawBlankArea(self.X, self.Y, self.Width, 1, colours.lightGrey) - Drawing.DrawBlankArea(self.X, self.Y+1, self.Width, self.Height-1, colours.white) - Drawing.DrawCharactersCenter(self.X, self.Y, self.Width, 1, self.Title, colours.black, colours.lightGrey) - - for i, text in ipairs(self.Lines) do - Drawing.DrawCharacters(self.X + 1, self.Y + 1 + i, text, colours.black, colours.white) - end - - - Drawing.DrawBlankArea(self.X + 1, self.Y + self.Height - 4, self.Width - 2, 1, colours.lightGrey) - Drawing.DrawCharacters(self.X + 2, self.Y + self.Height - 4, self.TextInput.Value, colours.black, colours.lightGrey) - Current.CursorPos = {self.X + 2 + self.TextInput.CursorPos, self.Y + self.Height - 4} - Current.CursorColour = colours.black - - self.OkButton:Draw() - if self.CancelButton then - self.CancelButton:Draw() - end - end, - - Initialise = function(self, title, message, okText, cancelText, returnFunc, numerical) - local new = {} -- the new instance - setmetatable( new, {__index = self} ) - new.Width = 28 - new.Lines = WrapText(message, new.Width - 2) - new.Height = 7 + #new.Lines - new.Return = returnFunc - new.X = math.ceil((Drawing.Screen.Width - new.Width) / 2) - new.Y = math.ceil((Drawing.Screen.Height - new.Height) / 2) - new.Title = title - new.Visible = true - new.Visible = true - new.OkButton = Button:Initialise(new.Width - #okText - 2, new.Height - 1, nil, 1, nil, new, function() - if #new.TextInput.Value > 0 then - returnFunc(new, true, new.TextInput.Value) - end - end, okText) - if cancelText then - new.CancelButton = Button:Initialise(new.Width - #okText - 2 - 1 - #cancelText - 2, new.Height - 1, nil, 1, nil, new, function() - returnFunc(new, false) - end, cancelText) - end - new.TextInput = TextInput:Initialise('', function(enter) - if enter then - new.OkButton:Click() - end - Draw() - end, numerical) - - Current.Input = new.TextInput - - return new - end, - - Show = function(self) - Current.Window = self - return self - end, - - Close = function(self) - Current.Window = nil - Current.Input = nil - self = nil - end, - - Flash = function(self) - self.Visible = false - Draw() - sleep(0.15) - self.Visible = true - Draw() - sleep(0.15) - self.Visible = false - Draw() - sleep(0.15) - self.Visible = true - Draw() - end, - - Click = function(self, side, x, y) - local items = {self.OkButton, self.CancelButton} - local found = false - for i, v in ipairs(items) do - if CheckClick(v, x, y) then - v:Click(side, x, y) - found = true - end - end - return true - end + X = 1, + Y = 1, + Width = 0, + Height = 0, + CursorPos = 1, + Visible = true, + CancelButton = nil, + OkButton = nil, + Lines = {}, + TextInput = nil, + + AbsolutePosition = function(self) + return {X = self.X, Y = self.Y} + end, + + Draw = function(self) + if not self.Visible then + return + end + Drawing.DrawBlankArea(self.X + 1, self.Y+1, self.Width, self.Height, colours.grey) + Drawing.DrawBlankArea(self.X, self.Y, self.Width, 1, colours.lightGrey) + Drawing.DrawBlankArea(self.X, self.Y+1, self.Width, self.Height-1, colours.white) + Drawing.DrawCharactersCenter(self.X, self.Y, self.Width, 1, self.Title, colours.black, colours.lightGrey) + + for i, text in ipairs(self.Lines) do + Drawing.DrawCharacters(self.X + 1, self.Y + 1 + i, text, colours.black, colours.white) + end + + + Drawing.DrawBlankArea(self.X + 1, self.Y + self.Height - 4, self.Width - 2, 1, colours.lightGrey) + Drawing.DrawCharacters(self.X + 2, self.Y + self.Height - 4, self.TextInput.Value, colours.black, colours.lightGrey) + Current.CursorPos = {self.X + 2 + self.TextInput.CursorPos, self.Y + self.Height - 4} + Current.CursorColour = colours.black + + self.OkButton:Draw() + if self.CancelButton then + self.CancelButton:Draw() + end + end, + + Initialise = function(self, title, message, okText, cancelText, returnFunc, numerical) + local new = {} -- the new instance + setmetatable( new, {__index = self} ) + new.Width = 28 + new.Lines = WrapText(message, new.Width - 2) + new.Height = 7 + #new.Lines + new.Return = returnFunc + new.X = math.ceil((Drawing.Screen.Width - new.Width) / 2) + new.Y = math.ceil((Drawing.Screen.Height - new.Height) / 2) + new.Title = title + new.Visible = true + new.Visible = true + new.OkButton = Button:Initialise(new.Width - #okText - 2, new.Height - 1, nil, 1, nil, new, function() + if #new.TextInput.Value > 0 then + returnFunc(new, true, new.TextInput.Value) + end + end, okText) + if cancelText then + new.CancelButton = Button:Initialise(new.Width - #okText - 2 - 1 - #cancelText - 2, new.Height - 1, nil, 1, nil, new, function() + returnFunc(new, false) + end, cancelText) + end + new.TextInput = TextInput:Initialise('', function(enter) + if enter then + new.OkButton:Click() + end + Draw() + end, numerical) + + Current.Input = new.TextInput + + return new + end, + + Show = function(self) + Current.Window = self + return self + end, + + Close = function(self) + Current.Window = nil + Current.Input = nil + self = nil + end, + + Flash = function(self) + self.Visible = false + Draw() + sleep(0.15) + self.Visible = true + Draw() + sleep(0.15) + self.Visible = false + Draw() + sleep(0.15) + self.Visible = true + Draw() + end, + + Click = function(self, side, x, y) + local items = {self.OkButton, self.CancelButton} + local found = false + for i, v in ipairs(items) do + if CheckClick(v, x, y) then + v:Click(side, x, y) + found = true + end + end + return true + end } - + function PrintCentered(text, y) local w, h = term.getSize() x = math.ceil(math.ceil((w / 2) - (#text / 2)), 0)+1 term.setCursorPos(x, y) print(text) end - + function DoVanillaClose() - term.setBackgroundColour(colours.black) - term.setTextColour(colours.white) - term.clear() - term.setCursorPos(1, 1) - PrintCentered("Thanks for using Ink!", (Drawing.Screen.Height/2)-1) - term.setTextColour(colours.lightGrey) - PrintCentered("Word Proccessor for ComputerCraft", (Drawing.Screen.Height/2)) - term.setTextColour(colours.white) - PrintCentered("(c) Outraged Security .INC 2020", (Drawing.Screen.Height/2)+3) - term.setCursorPos(1, Drawing.Screen.Height) - error('', 0) + term.setBackgroundColour(colours.black) + term.setTextColour(colours.white) + term.clear() + term.setCursorPos(1, 1) + PrintCentered("Thanks for using Ink!", (Drawing.Screen.Height/2)-1) + term.setTextColour(colours.lightGrey) + PrintCentered("Word Proccessor for ComputerCraft", (Drawing.Screen.Height/2)) + term.setTextColour(colours.white) + PrintCentered("(c) oeed 2014", (Drawing.Screen.Height/2)+3) + term.setCursorPos(1, Drawing.Screen.Height) + error('', 0) end - + function Close() - if isQuitting or not Current.Document or not Current.Modified then - if not OneOS then - DoVanillaClose() - end - return true - else - local _w = ButtonDialougeWindow:Initialise('Quit Ink?', 'You have unsaved changes, do you want to quit anyway?', 'Quit', 'Cancel', function(window, success) - if success then - if OneOS then - OneOS.Close(true) - else - DoVanillaClose() - end - end - window:Close() - Draw() - end):Show() - --it's hacky but it works - os.queueEvent('mouse_click', 1, _w.X, _w.Y) - return false - end + if isQuitting or not Current.Document or not Current.Modified then + if not OneOS then + DoVanillaClose() + end + return true + else + local _w = ButtonDialougeWindow:Initialise('Quit Ink?', 'You have unsaved changes, do you want to quit anyway?', 'Quit', 'Cancel', function(window, success) + if success then + if OneOS then + OneOS.Close(true) + else + DoVanillaClose() + end + end + window:Close() + Draw() + end):Show() + --it's hacky but it works + os.queueEvent('mouse_click', 1, _w.X, _w.Y) + return false + end end - + if OneOS then - OneOS.CanClose = function() - return Close() - end + OneOS.CanClose = function() + return Close() + end end + +Initialise() From 4a181753eedd2aec7a1bbc98fc1c0c8078d2794b Mon Sep 17 00:00:00 2001 From: rservices Date: Tue, 10 Mar 2020 09:44:29 -0600 Subject: [PATCH 049/125] Update programVersions --- programVersions | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/programVersions b/programVersions index 5a61242..353011c 100644 --- a/programVersions +++ b/programVersions @@ -112,8 +112,8 @@ ["Version"]=2.16, ["Type"]="program", ["Name"]="Outraged Security .INC Office Word Software", - ["Description"]="Downloads A Word writing office Program from Outraged Security .INC", - ["Author"]="Outraged Security .INC", + ["Description"]="Downloads A Word writing office Program from Outraged Security .INC Software Centre", + ["Author"]="Oeed 2014 Computercraft Forum!", ["Package"]="Standalone", }, } From 91bba255bed724130bcaecae51a50bec6dce63c3 Mon Sep 17 00:00:00 2001 From: rservices Date: Tue, 10 Mar 2020 19:18:40 -0600 Subject: [PATCH 050/125] Update programVersions --- programVersions | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/programVersions b/programVersions index 353011c..297e674 100644 --- a/programVersions +++ b/programVersions @@ -17,6 +17,15 @@ ["Author"]="Outraged Security .INC", ["Package"]="Outraged Security .INC", }, + ["MineOS"]={ + ["GitURL"]="https://raw.githubusercontent.com/rservices/darkprograms/darkprograms/mineos/setup", + ["Version"]=2.93, + ["Type"]="program", + ["Name"]="Outraged Security .INC Secure OS Downloader", + ["Description"]="Downloads A Secure OS Program from Outraged Security .INC Software Centre", + ["Author"]="Outraged Security .INC", + ["Package"]="Outraged Security .INC", + }, ["dark"]={ ["GitURL"]="https://raw.githubusercontent.com/rservices/darkprograms/darkprograms/api/dark.lua", ["Version"]=3.25, From f8defec3b5ca3348e3f7986f911d8185400973ed Mon Sep 17 00:00:00 2001 From: rservices Date: Sat, 25 Apr 2020 04:36:11 -0600 Subject: [PATCH 051/125] MetroOS Metro Secure OS coming soon to a program near you! --- MetroOS/Desktop/LuaIDE | 1 + MetroOS/Desktop/Sketch | 1 + MetroOS/LICENSE | 674 ++++++ MetroOS/Programs/LuaIDE/program | 2224 +++++++++++++++++ MetroOS/Programs/Sketch/program | 4014 +++++++++++++++++++++++++++++++ MetroOS/README.md | 10 + MetroOS/System/.version | 1 + MetroOS/System/APIs/crasher | 12 + MetroOS/System/APIs/sha256 | 197 ++ MetroOS/System/Images/boot | 12 + MetroOS/System/Images/desktop | 18 + MetroOS/System/autoupdater | 87 + MetroOS/System/latestversion | 1 + MetroOS/System/settings | 9 + MetroOS/os | 39 + MetroOS/setup | 101 + MetroOS/startup | 1 + 17 files changed, 7402 insertions(+) create mode 100644 MetroOS/Desktop/LuaIDE create mode 100644 MetroOS/Desktop/Sketch create mode 100644 MetroOS/LICENSE create mode 100644 MetroOS/Programs/LuaIDE/program create mode 100644 MetroOS/Programs/Sketch/program create mode 100644 MetroOS/README.md create mode 100644 MetroOS/System/.version create mode 100644 MetroOS/System/APIs/crasher create mode 100644 MetroOS/System/APIs/sha256 create mode 100644 MetroOS/System/Images/boot create mode 100644 MetroOS/System/Images/desktop create mode 100644 MetroOS/System/autoupdater create mode 100644 MetroOS/System/latestversion create mode 100644 MetroOS/System/settings create mode 100644 MetroOS/os create mode 100644 MetroOS/setup create mode 100644 MetroOS/startup diff --git a/MetroOS/Desktop/LuaIDE b/MetroOS/Desktop/LuaIDE new file mode 100644 index 0000000..8ab8299 --- /dev/null +++ b/MetroOS/Desktop/LuaIDE @@ -0,0 +1 @@ +/Programs/LuaIDE/program diff --git a/MetroOS/Desktop/Sketch b/MetroOS/Desktop/Sketch new file mode 100644 index 0000000..5bdad26 --- /dev/null +++ b/MetroOS/Desktop/Sketch @@ -0,0 +1 @@ +/Programs/Sketch/program diff --git a/MetroOS/LICENSE b/MetroOS/LICENSE new file mode 100644 index 0000000..9cecc1d --- /dev/null +++ b/MetroOS/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + {one line to give the program's name and a brief idea of what it does.} + Copyright (C) {year} {name of author} + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + {project} Copyright (C) {year} {fullname} + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/MetroOS/Programs/LuaIDE/program b/MetroOS/Programs/LuaIDE/program new file mode 100644 index 0000000..f0bb742 --- /dev/null +++ b/MetroOS/Programs/LuaIDE/program @@ -0,0 +1,2224 @@ + +-- +-- Lua IDE +-- Made by GravityScore +-- + + + + +-- Variables + +local version = "1.1" +local arguments = {...} + + +local w, h = term.getSize() +local tabWidth = 2 + + +local autosaveInterval = 20 +local allowEditorEvent = true +local keyboardShortcutTimeout = 0.4 + + +local clipboard = nil + + +local theme = { + background = colors.gray, + titleBar = colors.lightGray, + + top = colors.lightBlue, + bottom = colors.cyan, + + button = colors.cyan, + buttonHighlighted = colors.lightBlue, + + dangerButton = colors.red, + dangerButtonHighlighted = colors.pink, + + text = colors.white, + folder = colors.lime, + readOnly = colors.red, +} + + +local languages = {} +local currentLanguage = {} + + +local updateURL = "https://raw.github.com/GravityScore/LuaIDE/master/computercraft/ide.lua" +local ideLocation = "/" .. shell.getRunningProgram() +local themeLocation = "/.luaide_theme" + +local function isAdvanced() + return term.isColor and term.isColor() +end + + + + +-- -------- Utilities + +local function modRead(properties) + local w, h = term.getSize() + local defaults = {replaceChar = nil, history = nil, visibleLength = nil, textLength = nil, + liveUpdates = nil, exitOnKey = nil} + if not properties then properties = {} end + for k, v in pairs(defaults) do if not properties[k] then properties[k] = v end end + if properties.replaceChar then properties.replaceChar = properties.replaceChar:sub(1, 1) end + if not properties.visibleLength then properties.visibleLength = w end + + local sx, sy = term.getCursorPos() + local line = "" + local pos = 0 + local historyPos = nil + + local function redraw(repl) + local scroll = 0 + if properties.visibleLength and sx + pos > properties.visibleLength + 1 then + scroll = (sx + pos) - (properties.visibleLength + 1) + end + + term.setCursorPos(sx, sy) + local a = repl or properties.replaceChar + if a then term.write(string.rep(a, line:len() - scroll)) + else term.write(line:sub(scroll + 1, -1)) end + term.setCursorPos(sx + pos - scroll, sy) + end + + local function sendLiveUpdates(event, ...) + if type(properties.liveUpdates) == "function" then + local ox, oy = term.getCursorPos() + local a, data = properties.liveUpdates(line, event, ...) + if a == true and data == nil then + term.setCursorBlink(false) + return line + elseif a == true and data ~= nil then + term.setCursorBlink(false) + return data + end + term.setCursorPos(ox, oy) + end + end + + term.setCursorBlink(true) + while true do + local e, but, x, y, p4, p5 = os.pullEvent() + + if e == "char" then + local s = false + if properties.textLength and line:len() < properties.textLength then s = true + elseif not properties.textLength then s = true end + + local canType = true + if not properties.grantPrint and properties.refusePrint then + local canTypeKeys = {} + if type(properties.refusePrint) == "table" then + for _, v in pairs(properties.refusePrint) do + table.insert(canTypeKeys, tostring(v):sub(1, 1)) + end + elseif type(properties.refusePrint) == "string" then + for char in properties.refusePrint:gmatch(".") do + table.insert(canTypeKeys, char) + end + end + for _, v in pairs(canTypeKeys) do if but == v then canType = false end end + elseif properties.grantPrint then + canType = false + local canTypeKeys = {} + if type(properties.grantPrint) == "table" then + for _, v in pairs(properties.grantPrint) do + table.insert(canTypeKeys, tostring(v):sub(1, 1)) + end + elseif type(properties.grantPrint) == "string" then + for char in properties.grantPrint:gmatch(".") do + table.insert(canTypeKeys, char) + end + end + for _, v in pairs(canTypeKeys) do if but == v then canType = true end end + end + + if s and canType then + line = line:sub(1, pos) .. but .. line:sub(pos + 1, -1) + pos = pos + 1 + redraw() + end + elseif e == "key" then + if but == keys.enter then break + elseif but == keys.left then if pos > 0 then pos = pos - 1 redraw() end + elseif but == keys.right then if pos < line:len() then pos = pos + 1 redraw() end + elseif (but == keys.up or but == keys.down) and properties.history then + redraw(" ") + if but == keys.up then + if historyPos == nil and #properties.history > 0 then + historyPos = #properties.history + elseif historyPos > 1 then + historyPos = historyPos - 1 + end + elseif but == keys.down then + if historyPos == #properties.history then historyPos = nil + elseif historyPos ~= nil then historyPos = historyPos + 1 end + end + + if properties.history and historyPos then + line = properties.history[historyPos] + pos = line:len() + else + line = "" + pos = 0 + end + + redraw() + local a = sendLiveUpdates("history") + if a then return a end + elseif but == keys.backspace and pos > 0 then + redraw(" ") + line = line:sub(1, pos - 1) .. line:sub(pos + 1, -1) + pos = pos - 1 + redraw() + local a = sendLiveUpdates("delete") + if a then return a end + elseif but == keys.home then + pos = 0 + redraw() + elseif but == keys.delete and pos < line:len() then + redraw(" ") + line = line:sub(1, pos) .. line:sub(pos + 2, -1) + redraw() + local a = sendLiveUpdates("delete") + if a then return a end + elseif but == keys["end"] then + pos = line:len() + redraw() + elseif properties.exitOnKey then + if but == properties.exitOnKey or (properties.exitOnKey == "control" and + (but == 29 or but == 157)) then + term.setCursorBlink(false) + return nil + end + end + end + local a = sendLiveUpdates(e, but, x, y, p4, p5) + if a then return a end + end + + term.setCursorBlink(false) + if line ~= nil then line = line:gsub("^%s*(.-)%s*$", "%1") end + return line +end + + +-- -------- Themes + +local defaultTheme = { + background = "gray", + backgroundHighlight = "lightGray", + prompt = "cyan", + promptHighlight = "lightBlue", + err = "red", + errHighlight = "pink", + + editorBackground = "gray", + editorLineHightlight = "lightBlue", + editorLineNumbers = "gray", + editorLineNumbersHighlight = "lightGray", + editorError = "pink", + editorErrorHighlight = "red", + + textColor = "white", + conditional = "yellow", + constant = "orange", + ["function"] = "magenta", + string = "red", + comment = "lime" +} + +local normalTheme = { + background = "black", + backgroundHighlight = "black", + prompt = "black", + promptHighlight = "black", + err = "black", + errHighlight = "black", + + editorBackground = "black", + editorLineHightlight = "black", + editorLineNumbers = "black", + editorLineNumbersHighlight = "white", + editorError = "black", + editorErrorHighlight = "black", + + textColor = "white", + conditional = "white", + constant = "white", + ["function"] = "white", + string = "white", + comment = "white" +} + +local availableThemes = { + {"Water (Default)", "https://raw.github.com/GravityScore/LuaIDE/master/themes/default.txt"}, + {"Fire", "https://raw.github.com/GravityScore/LuaIDE/master/themes/fire.txt"}, + {"Sublime Text 2", "https://raw.github.com/GravityScore/LuaIDE/master/themes/st2.txt"}, + {"Midnight", "https://raw.github.com/GravityScore/LuaIDE/master/themes/midnight.txt"}, + {"TheOriginalBIT", "https://raw.github.com/GravityScore/LuaIDE/master/themes/bit.txt"}, + {"Superaxander", "https://raw.github.com/GravityScore/LuaIDE/master/themes/superaxander.txt"}, + {"Forest", "https://raw.github.com/GravityScore/LuaIDE/master/themes/forest.txt"}, + {"Night", "https://raw.github.com/GravityScore/LuaIDE/master/themes/night.txt"}, + {"Original", "https://raw.github.com/GravityScore/LuaIDE/master/themes/original.txt"}, +} + +local function loadTheme(path) + local f = io.open(path) + local l = f:read("*l") + local config = {} + while l ~= nil do + local k, v = string.match(l, "^(%a+)=(%a+)") + if k and v then config[k] = v end + l = f:read("*l") + end + f:close() + return config +end + +-- Load Theme +if isAdvanced() then theme = defaultTheme +else theme = normalTheme end + + +-- -------- Drawing + +local function centerPrint(text, ny) + if type(text) == "table" then for _, v in pairs(text) do centerPrint(v) end + else + local x, y = term.getCursorPos() + local w, h = term.getSize() + term.setCursorPos(w/2 - text:len()/2 + (#text % 2 == 0 and 1 or 0), ny or y) + print(text) + end +end + +local function title(t) + term.setTextColor(colors[theme.textColor]) + term.setBackgroundColor(colors[theme.background]) + term.clear() + + term.setBackgroundColor(colors[theme.backgroundHighlight]) + for i = 2, 4 do term.setCursorPos(1, i) term.clearLine() end + term.setCursorPos(3, 3) + term.write(t) +end + +local function centerRead(wid, begt) + local function liveUpdate(line, e, but, x, y, p4, p5) + if isAdvanced() and e == "mouse_click" and x >= w/2 - wid/2 and x <= w/2 - wid/2 + 10 + and y >= 13 and y <= 15 then + return true, "" + end + end + + if not begt then begt = "" end + term.setTextColor(colors[theme.textColor]) + term.setBackgroundColor(colors[theme.promptHighlight]) + for i = 8, 10 do + term.setCursorPos(w/2 - wid/2, i) + term.write(string.rep(" ", wid)) + end + + if isAdvanced() then + term.setBackgroundColor(colors[theme.errHighlight]) + for i = 13, 15 do + term.setCursorPos(w/2 - wid/2 + 1, i) + term.write(string.rep(" ", 10)) + end + term.setCursorPos(w/2 - wid/2 + 2, 14) + term.write("> Cancel") + end + + term.setBackgroundColor(colors[theme.promptHighlight]) + term.setCursorPos(w/2 - wid/2 + 1, 9) + term.write("> " .. begt) + return modRead({visibleLength = w/2 + wid/2, liveUpdates = liveUpdate}) +end + + +-- -------- Prompt + +local function prompt(list, dir, isGrid) + local function draw(sel) + for i, v in ipairs(list) do + if i == sel then term.setBackgroundColor(v.highlight or colors[theme.promptHighlight]) + else term.setBackgroundColor(v.bg or colors[theme.prompt]) end + term.setTextColor(v.tc or colors[theme.textColor]) + for i = -1, 1 do + term.setCursorPos(v[2], v[3] + i) + term.write(string.rep(" ", v[1]:len() + 4)) + end + + term.setCursorPos(v[2], v[3]) + if i == sel then + term.setBackgroundColor(v.highlight or colors[theme.promptHighlight]) + term.write(" > ") + else term.write(" - ") end + term.write(v[1] .. " ") + end + end + + local key1 = dir == "horizontal" and 203 or 200 + local key2 = dir == "horizontal" and 205 or 208 + local sel = 1 + draw(sel) + + while true do + local e, but, x, y = os.pullEvent() + if e == "key" and but == 28 then + return list[sel][1] + elseif e == "key" and but == key1 and sel > 1 then + sel = sel - 1 + draw(sel) + elseif e == "key" and but == key2 and ((err == true and sel < #list - 1) or (sel < #list)) then + sel = sel + 1 + draw(sel) + elseif isGrid and e == "key" and but == 203 and sel > 2 then + sel = sel - 2 + draw(sel) + elseif isGrid and e == "key" and but == 205 and sel < 3 then + sel = sel + 2 + draw(sel) + elseif e == "mouse_click" then + for i, v in ipairs(list) do + if x >= v[2] - 1 and x <= v[2] + v[1]:len() + 3 and y >= v[3] - 1 and y <= v[3] + 1 then + return list[i][1] + end + end + end + end +end + +local function scrollingPrompt(list) + local function draw(items, sel, loc) + for i, v in ipairs(items) do + local bg = colors[theme.prompt] + local bghigh = colors[theme.promptHighlight] + if v:find("Back") or v:find("Return") then + bg = colors[theme.err] + bghigh = colors[theme.errHighlight] + end + + if i == sel then term.setBackgroundColor(bghigh) + else term.setBackgroundColor(bg) end + term.setTextColor(colors[theme.textColor]) + for x = -1, 1 do + term.setCursorPos(3, (i * 4) + x + 4) + term.write(string.rep(" ", w - 13)) + end + + term.setCursorPos(3, i * 4 + 4) + if i == sel then + term.setBackgroundColor(bghigh) + term.write(" > ") + else term.write(" - ") end + term.write(v .. " ") + end + end + + local function updateDisplayList(items, loc, len) + local ret = {} + for i = 1, len do + local item = items[i + loc - 1] + if item then table.insert(ret, item) end + end + return ret + end + + -- Variables + local sel = 1 + local loc = 1 + local len = 3 + local disList = updateDisplayList(list, loc, len) + draw(disList, sel, loc) + + -- Loop + while true do + local e, key, x, y = os.pullEvent() + + if e == "mouse_click" then + for i, v in ipairs(disList) do + if x >= 3 and x <= w - 11 and y >= i * 4 + 3 and y <= i * 4 + 5 then return v end + end + elseif e == "key" and key == 200 then + if sel > 1 then + sel = sel - 1 + draw(disList, sel, loc) + elseif loc > 1 then + loc = loc - 1 + disList = updateDisplayList(list, loc, len) + draw(disList, sel, loc) + end + elseif e == "key" and key == 208 then + if sel < len then + sel = sel + 1 + draw(disList, sel, loc) + elseif loc + len - 1 < #list then + loc = loc + 1 + disList = updateDisplayList(list, loc, len) + draw(disList, sel, loc) + end + elseif e == "mouse_scroll" then + os.queueEvent("key", key == -1 and 200 or 208) + elseif e == "key" and key == 28 then + return disList[sel] + end + end +end + +function monitorKeyboardShortcuts() + local ta, tb = nil, nil + local allowChar = false + local shiftPressed = false + while true do + local event, char = os.pullEvent() + if event == "key" and (char == 42 or char == 52) then + shiftPressed = true + tb = os.startTimer(keyboardShortcutTimeout) + elseif event == "key" and (char == 29 or char == 157 or char == 219 or char == 220) then + allowEditorEvent = false + allowChar = true + ta = os.startTimer(keyboardShortcutTimeout) + elseif event == "key" and allowChar then + local name = nil + for k, v in pairs(keys) do + if v == char then + if shiftPressed then os.queueEvent("shortcut", "ctrl shift", k:lower()) + else os.queueEvent("shortcut", "ctrl", k:lower()) end + sleep(0.005) + allowEditorEvent = true + end + end + if shiftPressed then os.queueEvent("shortcut", "ctrl shift", char) + else os.queueEvent("shortcut", "ctrl", char) end + elseif event == "timer" and char == ta then + allowEditorEvent = true + allowChar = false + elseif event == "timer" and char == tb then + shiftPressed = false + end + end +end + + +-- -------- Saving and Loading + +local function download(url, path) + for i = 1, 3 do + local response = http.get(url) + if response then + local data = response.readAll() + response.close() + if path then + local f = io.open(path, "w") + f:write(data) + f:close() + end + return true + end + end + + return false +end + +local function saveFile(path, lines) + local dir = path:sub(1, path:len() - fs.getName(path):len()) + if not fs.exists(dir) then fs.makeDir(dir) end + if not fs.isDir(path) and not fs.isReadOnly(path) then + local a = "" + for _, v in pairs(lines) do a = a .. v .. "\n" end + + local f = io.open(path, "w") + f:write(a) + f:close() + return true + else return false end +end + +local function loadFile(path) + if not fs.exists(path) then + local dir = path:sub(1, path:len() - fs.getName(path):len()) + if not fs.exists(dir) then fs.makeDir(dir) end + local f = io.open(path, "w") + f:write("") + f:close() + end + + local l = {} + if fs.exists(path) and not fs.isDir(path) then + local f = io.open(path, "r") + if f then + local a = f:read("*l") + while a do + table.insert(l, a) + a = f:read("*l") + end + f:close() + end + else return nil end + + if #l < 1 then table.insert(l, "") end + return l +end + + +-- -------- Languages + +languages.lua = {} +languages.brainfuck = {} +languages.none = {} + +-- Lua + +languages.lua.helpTips = { + "A function you tried to call doesn't exist.", + "You made a typo.", + "The index of an array is nil.", + "The wrong variable type was passed.", + "A function/variable doesn't exist.", + "You missed an 'end'.", + "You missed a 'then'.", + "You declared a variable incorrectly.", + "One of your variables is mysteriously nil." +} + +languages.lua.defaultHelpTips = { + 2, 5 +} + +languages.lua.errors = { + ["Attempt to call nil."] = {1, 2}, + ["Attempt to index nil."] = {3, 2}, + [".+ expected, got .+"] = {4, 2, 9}, + ["'end' expected"] = {6, 2}, + ["'then' expected"] = {7, 2}, + ["'=' expected"] = {8, 2} +} + +languages.lua.keywords = { + ["and"] = "conditional", + ["break"] = "conditional", + ["do"] = "conditional", + ["else"] = "conditional", + ["elseif"] = "conditional", + ["end"] = "conditional", + ["for"] = "conditional", + ["function"] = "conditional", + ["if"] = "conditional", + ["in"] = "conditional", + ["local"] = "conditional", + ["not"] = "conditional", + ["or"] = "conditional", + ["repeat"] = "conditional", + ["return"] = "conditional", + ["then"] = "conditional", + ["until"] = "conditional", + ["while"] = "conditional", + + ["true"] = "constant", + ["false"] = "constant", + ["nil"] = "constant", + + ["print"] = "function", + ["write"] = "function", + ["sleep"] = "function", + ["pairs"] = "function", + ["ipairs"] = "function", + ["loadstring"] = "function", + ["loadfile"] = "function", + ["dofile"] = "function", + ["rawset"] = "function", + ["rawget"] = "function", + ["setfenv"] = "function", + ["getfenv"] = "function", +} + +languages.lua.parseError = function(e) + local ret = {filename = "unknown", line = -1, display = "Unknown!", err = ""} + if e and e ~= "" then + ret.err = e + if e:find(":") then + ret.filename = e:sub(1, e:find(":") - 1):gsub("^%s*(.-)%s*$", "%1") + -- The "" is needed to circumvent a CC bug + e = (e:sub(e:find(":") + 1) .. ""):gsub("^%s*(.-)%s*$", "%1") + if e:find(":") then + ret.line = e:sub(1, e:find(":") - 1) + e = e:sub(e:find(":") + 2):gsub("^%s*(.-)%s*$", "%1") .. "" + end + end + ret.display = e:sub(1, 1):upper() .. e:sub(2, -1) .. "." + end + + return ret +end + +languages.lua.getCompilerErrors = function(code) + code = "local function ee65da6af1cb6f63fee9a081246f2fd92b36ef2(...)\n\n" .. code .. "\n\nend" + local fn, err = loadstring(code) + if not err then + local _, e = pcall(fn) + if e then err = e end + end + + if err then + local a = err:find("]", 1, true) + if a then err = "string" .. err:sub(a + 1, -1) end + local ret = languages.lua.parseError(err) + if tonumber(ret.line) then ret.line = tonumber(ret.line) end + return ret + else return languages.lua.parseError(nil) end +end + +languages.lua.run = function(path, ar) + local fn, err = loadfile(path) + setfenv(fn, getfenv()) + if not err then + _, err = pcall(function() fn(unpack(ar)) end) + end + return err +end + + +-- Brainfuck + +languages.brainfuck.helpTips = { + "Well idk...", + "Isn't this the whole point of the language?", + "Ya know... Not being able to debug it?", + "You made a typo." +} + +languages.brainfuck.defaultHelpTips = { + 1, 2, 3 +} + +languages.brainfuck.errors = { + ["No matching '['"] = {1, 2, 3, 4} +} + +languages.brainfuck.keywords = {} + +languages.brainfuck.parseError = function(e) + local ret = {filename = "unknown", line = -1, display = "Unknown!", err = ""} + if e and e ~= "" then + ret.err = e + ret.line = e:sub(1, e:find(":") - 1) + e = e:sub(e:find(":") + 2):gsub("^%s*(.-)%s*$", "%1") .. "" + ret.display = e:sub(1, 1):upper() .. e:sub(2, -1) .. "." + end + + return ret +end + +languages.brainfuck.mapLoops = function(code) + -- Map loops + local loopLocations = {} + local loc = 1 + local line = 1 + for let in string.gmatch(code, ".") do + if let == "[" then + loopLocations[loc] = true + elseif let == "]" then + local found = false + for i = loc, 1, -1 do + if loopLocations[i] == true then + loopLocations[i] = loc + found = true + end + end + + if not found then + return line .. ": No matching '['" + end + end + + if let == "\n" then line = line + 1 end + loc = loc + 1 + end + return loopLocations +end + +languages.brainfuck.getCompilerErrors = function(code) + local a = languages.brainfuck.mapLoops(code) + if type(a) == "string" then return languages.brainfuck.parseError(a) + else return languages.brainfuck.parseError(nil) end +end + +languages.brainfuck.run = function(path) + -- Read from file + local f = io.open(path, "r") + local content = f:read("*a") + f:close() + + -- Define environment + local dataCells = {} + local dataPointer = 1 + local instructionPointer = 1 + + -- Map loops + local loopLocations = languages.brainfuck.mapLoops(content) + if type(loopLocations) == "string" then return loopLocations end + + -- Execute code + while true do + local let = content:sub(instructionPointer, instructionPointer) + + if let == ">" then + dataPointer = dataPointer + 1 + if not dataCells[tostring(dataPointer)] then dataCells[tostring(dataPointer)] = 0 end + elseif let == "<" then + if not dataCells[tostring(dataPointer)] then dataCells[tostring(dataPointer)] = 0 end + dataPointer = dataPointer - 1 + if not dataCells[tostring(dataPointer)] then dataCells[tostring(dataPointer)] = 0 end + elseif let == "+" then + if not dataCells[tostring(dataPointer)] then dataCells[tostring(dataPointer)] = 0 end + dataCells[tostring(dataPointer)] = dataCells[tostring(dataPointer)] + 1 + elseif let == "-" then + if not dataCells[tostring(dataPointer)] then dataCells[tostring(dataPointer)] = 0 end + dataCells[tostring(dataPointer)] = dataCells[tostring(dataPointer)] - 1 + elseif let == "." then + if not dataCells[tostring(dataPointer)] then dataCells[tostring(dataPointer)] = 0 end + if term.getCursorPos() >= w then print("") end + write(string.char(math.max(1, dataCells[tostring(dataPointer)]))) + elseif let == "," then + if not dataCells[tostring(dataPointer)] then dataCells[tostring(dataPointer)] = 0 end + term.setCursorBlink(true) + local e, but = os.pullEvent("char") + term.setCursorBlink(false) + dataCells[tostring(dataPointer)] = string.byte(but) + if term.getCursorPos() >= w then print("") end + write(but) + elseif let == "/" then + if not dataCells[tostring(dataPointer)] then dataCells[tostring(dataPointer)] = 0 end + if term.getCursorPos() >= w then print("") end + write(dataCells[tostring(dataPointer)]) + elseif let == "[" then + if dataCells[tostring(dataPointer)] == 0 then + for k, v in pairs(loopLocations) do + if k == instructionPointer then instructionPointer = v end + end + end + elseif let == "]" then + for k, v in pairs(loopLocations) do + if v == instructionPointer then instructionPointer = k - 1 end + end + end + + instructionPointer = instructionPointer + 1 + if instructionPointer > content:len() then print("") break end + end +end + +-- None + +languages.none.helpTips = {} +languages.none.defaultHelpTips = {} +languages.none.errors = {} +languages.none.keywords = {} + +languages.none.parseError = function(err) + return {filename = "", line = -1, display = "", err = ""} +end + +languages.none.getCompilerErrors = function(code) + return languages.none.parseError(nil) +end + +languages.none.run = function(path) end + + +-- Load language +currentLanguage = languages.lua + + +-- -------- Run GUI + +local function viewErrorHelp(e) + title("LuaIDE - Error Help") + + local tips = nil + for k, v in pairs(currentLanguage.errors) do + if e.display:find(k) then tips = v break end + end + + term.setBackgroundColor(colors[theme.err]) + for i = 6, 8 do + term.setCursorPos(5, i) + term.write(string.rep(" ", 35)) + end + + term.setBackgroundColor(colors[theme.prompt]) + for i = 10, 18 do + term.setCursorPos(5, i) + term.write(string.rep(" ", 46)) + end + + if tips then + term.setBackgroundColor(colors[theme.err]) + term.setCursorPos(6, 7) + term.write("Error Help") + + term.setBackgroundColor(colors[theme.prompt]) + for i, v in ipairs(tips) do + term.setCursorPos(7, i + 10) + term.write("- " .. currentLanguage.helpTips[v]) + end + else + term.setBackgroundColor(colors[theme.err]) + term.setCursorPos(6, 7) + term.write("No Error Tips Available!") + + term.setBackgroundColor(colors[theme.prompt]) + term.setCursorPos(6, 11) + term.write("There are no error tips available, but") + term.setCursorPos(6, 12) + term.write("you could see if it was any of these:") + + for i, v in ipairs(currentLanguage.defaultHelpTips) do + term.setCursorPos(7, i + 12) + term.write("- " .. currentLanguage.helpTips[v]) + end + end + + prompt({{"Back", w - 8, 7}}, "horizontal") +end + +local function run(path, lines, useArgs) + local ar = {} + if useArgs then + title("LuaIDE - Run " .. fs.getName(path)) + local s = centerRead(w - 13, fs.getName(path) .. " ") + for m in string.gmatch(s, "[^ \t]+") do ar[#ar + 1] = m:gsub("^%s*(.-)%s*$", "%1") end + end + + saveFile(path, lines) + term.setCursorBlink(false) + term.setBackgroundColor(colors.black) + term.setTextColor(colors.white) + term.clear() + term.setCursorPos(1, 1) + local err = currentLanguage.run(path, ar) + + term.setBackgroundColor(colors.black) + print("\n") + if err then + if isAdvanced() then term.setTextColor(colors.red) end + centerPrint("The program has crashed!") + end + term.setTextColor(colors.white) + centerPrint("Press any key to return to LuaIDE...") + while true do + local e = os.pullEvent() + if e == "key" then break end + end + + -- To prevent key from showing up in editor + os.queueEvent("") + os.pullEvent() + + if err then + if currentLanguage == languages.lua and err:find("]") then + err = fs.getName(path) .. err:sub(err:find("]", 1, true) + 1, -1) + end + + while true do + title("LuaIDE - Error!") + + term.setBackgroundColor(colors[theme.err]) + for i = 6, 8 do + term.setCursorPos(3, i) + term.write(string.rep(" ", w - 5)) + end + term.setCursorPos(4, 7) + term.write("The program has crashed!") + + term.setBackgroundColor(colors[theme.prompt]) + for i = 10, 14 do + term.setCursorPos(3, i) + term.write(string.rep(" ", w - 5)) + end + + local formattedErr = currentLanguage.parseError(err) + term.setCursorPos(4, 11) + term.write("Line: " .. formattedErr.line) + term.setCursorPos(4, 12) + term.write("Error:") + term.setCursorPos(5, 13) + + local a = formattedErr.display + local b = nil + if a:len() > w - 8 then + for i = a:len(), 1, -1 do + if a:sub(i, i) == " " then + b = a:sub(i + 1, -1) + a = a:sub(1, i) + break + end + end + end + + term.write(a) + if b then + term.setCursorPos(5, 14) + term.write(b) + end + + local opt = prompt({{"Error Help", w/2 - 15, 17}, {"Go To Line", w/2 + 2, 17}}, + "horizontal") + if opt == "Error Help" then + viewErrorHelp(formattedErr) + elseif opt == "Go To Line" then + -- To prevent key from showing up in editor + os.queueEvent("") + os.pullEvent() + + return "go to", tonumber(formattedErr.line) + end + end + end +end + + +-- -------- Functions + +local function goto() + term.setBackgroundColor(colors[theme.backgroundHighlight]) + term.setCursorPos(2, 1) + term.clearLine() + term.write("Line: ") + local line = modRead({visibleLength = w - 2}) + + local num = tonumber(line) + if num and num > 0 then return num + else + term.setCursorPos(2, 1) + term.clearLine() + term.write("Not a line number!") + sleep(1.6) + return nil + end +end + +local function setsyntax() + local opts = { + "[Lua] Brainfuck None ", + " Lua [Brainfuck] None ", + " Lua Brainfuck [None]" + } + local sel = 1 + + term.setCursorBlink(false) + term.setBackgroundColor(colors[theme.backgroundHighlight]) + term.setCursorPos(2, 1) + term.clearLine() + term.write(opts[sel]) + while true do + local e, but, x, y = os.pullEvent("key") + if but == 203 then + sel = math.max(1, sel - 1) + term.setCursorPos(2, 1) + term.clearLine() + term.write(opts[sel]) + elseif but == 205 then + sel = math.min(#opts, sel + 1) + term.setCursorPos(2, 1) + term.clearLine() + term.write(opts[sel]) + elseif but == 28 then + if sel == 1 then currentLanguage = languages.lua + elseif sel == 2 then currentLanguage = languages.brainfuck + elseif sel == 3 then currentLanguage = languages.none end + term.setCursorBlink(true) + return + end + end +end + + +-- -------- Re-Indenting + +local tabWidth = 2 + +local comments = {} +local strings = {} + +local increment = { + "if%s+.+%s+then%s*$", + "for%s+.+%s+do%s*$", + "while%s+.+%s+do%s*$", + "repeat%s*$", + "function%s+[a-zA-Z_0-9]\(.*\)%s*$" +} + +local decrement = { + "end", + "until%s+.+" +} + +local special = { + "else%s*$", + "elseif%s+.+%s+then%s*$" +} + +local function check(func) + for _, v in pairs(func) do + local cLineStart = v["lineStart"] + local cLineEnd = v["lineEnd"] + local cCharStart = v["charStart"] + local cCharEnd = v["charEnd"] + + if line >= cLineStart and line <= cLineEnd then + if line == cLineStart then return cCharStart < charNumb + elseif line == cLineEnd then return cCharEnd > charNumb + else return true end + end + end +end + +local function isIn(line, loc) + if check(comments) then return true end + if check(strings) then return true end + return false +end + +local function setComment(ls, le, cs, ce) + comments[#comments + 1] = {} + comments[#comments].lineStart = ls + comments[#comments].lineEnd = le + comments[#comments].charStart = cs + comments[#comments].charEnd = ce +end + +local function setString(ls, le, cs, ce) + strings[#strings + 1] = {} + strings[#strings].lineStart = ls + strings[#strings].lineEnd = le + strings[#strings].charStart = cs + strings[#strings].charEnd = ce +end + +local function map(contents) + local inCom = false + local inStr = false + + for i = 1, #contents do + if content[i]:find("%-%-%[%[") and not inStr and not inCom then + local cStart = content[i]:find("%-%-%[%[") + setComment(i, nil, cStart, nil) + inCom = true + elseif content[i]:find("%-%-%[=%[") and not inStr and not inCom then + local cStart = content[i]:find("%-%-%[=%[") + setComment(i, nil, cStart, nil) + inCom = true + elseif content[i]:find("%[%[") and not inStr and not inCom then + local cStart = content[i]:find("%[%[") + setString(i, nil, cStart, nil) + inStr = true + elseif content[i]:find("%[=%[") and not inStr and not inCom then + local cStart = content[i]:find("%[=%[") + setString(i, nil, cStart, nil) + inStr = true + end + + if content[i]:find("%]%]") and inStr and not inCom then + local cStart, cEnd = content[i]:find("%]%]") + strings[#strings].lineEnd = i + strings[#strings].charEnd = cEnd + inStr = false + elseif content[i]:find("%]=%]") and inStr and not inCom then + local cStart, cEnd = content[i]:find("%]=%]") + strings[#strings].lineEnd = i + strings[#strings].charEnd = cEnd + inStr = false + end + + if content[i]:find("%]%]") and not inStr and inCom then + local cStart, cEnd = content[i]:find("%]%]") + comments[#comments].lineEnd = i + comments[#comments].charEnd = cEnd + inCom = false + elseif content[i]:find("%]=%]") and not inStr and inCom then + local cStart, cEnd = content[i]:find("%]=%]") + comments[#comments].lineEnd = i + comments[#comments].charEnd = cEnd + inCom = false + end + + if content[i]:find("%-%-") and not inStr and not inCom then + local cStart = content[i]:find("%-%-") + setComment(i, i, cStart, -1) + elseif content[i]:find("'") and not inStr and not inCom then + local cStart, cEnd = content[i]:find("'") + local nextChar = content[i]:sub(cEnd + 1, string.len(content[i])) + local _, cEnd = nextChar:find("'") + setString(i, i, cStart, cEnd) + elseif content[i]:find('"') and not inStr and not inCom then + local cStart, cEnd = content[i]:find('"') + local nextChar = content[i]:sub(cEnd + 1, string.len(content[i])) + local _, cEnd = nextChar:find('"') + setString(i, i, cStart, cEnd) + end + end +end + +local function reindent(contents) + local err = nil + if currentLanguage ~= languages.lua then + err = "Cannot indent languages other than Lua!" + elseif currentLanguage.getCompilerErrors(table.concat(contents, "\n")).line ~= -1 then + err = "Cannot indent a program with errors!" + end + + if err then + term.setCursorBlink(false) + term.setCursorPos(2, 1) + term.setBackgroundColor(colors[theme.backgroundHighlight]) + term.clearLine() + term.write(err) + sleep(1.6) + return contents + end + + local new = {} + local level = 0 + for k, v in pairs(contents) do + local incrLevel = false + local foundIncr = false + for _, incr in pairs(increment) do + if v:find(incr) and not isIn(k, v:find(incr)) then + incrLevel = true + end + if v:find(incr:sub(1, -2)) and not isIn(k, v:find(incr)) then + foundIncr = true + end + end + + local decrLevel = false + if not incrLevel then + for _, decr in pairs(decrement) do + if v:find(decr) and not isIn(k, v:find(decr)) and not foundIncr then + level = math.max(0, level - 1) + decrLevel = true + end + end + end + + if not decrLevel then + for _, sp in pairs(special) do + if v:find(sp) and not isIn(k, v:find(sp)) then + incrLevel = true + level = math.max(0, level - 1) + end + end + end + + new[k] = string.rep(" ", level * tabWidth) .. v + if incrLevel then level = level + 1 end + end + + return new +end + + +-- -------- Menu + +local menu = { + [1] = {"File", +-- "About", +-- "Settings", +-- "", + "New File ^+N", + "Open File ^+O", + "Save File ^+S", + "Close ^+W", + "Print ^+P", + "Quit ^+Q" + }, [2] = {"Edit", + "Cut Line ^+X", + "Copy Line ^+C", + "Paste Line ^+V", + "Delete Line", + "Clear Line" + }, [3] = {"Functions", + "Go To Line ^+G", + "Re-Indent ^+I", + "Set Syntax ^+E", + "Start of Line ^+<", + "End of Line ^+>" + }, [4] = {"Run", + "Run Program ^+R", + "Run w/ Args ^+Shift+R" + } +} + +local shortcuts = { + -- File + ["ctrl n"] = "New File ^+N", + ["ctrl o"] = "Open File ^+O", + ["ctrl s"] = "Save File ^+S", + ["ctrl w"] = "Close ^+W", + ["ctrl p"] = "Print ^+P", + ["ctrl q"] = "Quit ^+Q", + + -- Edit + ["ctrl x"] = "Cut Line ^+X", + ["ctrl c"] = "Copy Line ^+C", + ["ctrl v"] = "Paste Line ^+V", + + -- Functions + ["ctrl g"] = "Go To Line ^+G", + ["ctrl i"] = "Re-Indent ^+I", + ["ctrl e"] = "Set Syntax ^+E", + ["ctrl 203"] = "Start of Line ^+<", + ["ctrl 205"] = "End of Line ^+>", + + -- Run + ["ctrl r"] = "Run Program ^+R", + ["ctrl shift r"] = "Run w/ Args ^+Shift+R" +} + +local menuFunctions = { + -- File +-- ["About"] = function() end, +-- ["Settings"] = function() end, + ["New File ^+N"] = function(path, lines) saveFile(path, lines) return "new" end, + ["Open File ^+O"] = function(path, lines) saveFile(path, lines) return "open" end, + ["Save File ^+S"] = function(path, lines) saveFile(path, lines) end, + ["Close ^+W"] = function(path, lines) saveFile(path, lines) return "menu" end, + ["Print ^+P"] = function(path, lines) saveFile(path, lines) return nil end, + ["Quit ^+Q"] = function(path, lines) saveFile(path, lines) return "exit" end, + + -- Edit + ["Cut Line ^+X"] = function(path, lines, y) + clipboard = lines[y] table.remove(lines, y) return nil, lines end, + ["Copy Line ^+C"] = function(path, lines, y) clipboard = lines[y] end, + ["Paste Line ^+V"] = function(path, lines, y) + if clipboard then table.insert(lines, y, clipboard) end return nil, lines end, + ["Delete Line"] = function(path, lines, y) table.remove(lines, y) return nil, lines end, + ["Clear Line"] = function(path, lines, y) lines[y] = "" return nil, lines, "cursor" end, + + -- Functions + ["Go To Line ^+G"] = function() return nil, "go to", goto() end, + ["Re-Indent ^+I"] = function(path, lines) + local a = reindent(lines) saveFile(path, lines) return nil, a + end, + ["Set Syntax ^+E"] = function(path, lines) + setsyntax() + if currentLanguage == languages.brainfuck and lines[1] ~= "-- Syntax: Brainfuck" then + table.insert(lines, 1, "-- Syntax: Brainfuck") + return nil, lines + end + end, + ["Start of Line ^+<"] = function() os.queueEvent("key", 199) end, + ["End of Line ^+>"] = function() os.queueEvent("key", 207) end, + + -- Run + ["Run Program ^+R"] = function(path, lines) + saveFile(path, lines) + return nil, run(path, lines, false) + end, + ["Run w/ Args ^+Shift+R"] = function(path, lines) + saveFile(path, lines) + return nil, run(path, lines, true) + end, +} + +local function drawMenu(open) + term.setCursorPos(1, 1) + term.setTextColor(colors[theme.textColor]) + term.setBackgroundColor(colors[theme.backgroundHighlight]) + term.clearLine() + local curX = 0 + for _, v in pairs(menu) do + term.setCursorPos(3 + curX, 1) + term.write(v[1]) + curX = curX + v[1]:len() + 3 + end + + if open then + local it = {} + local x = 1 + for _, v in pairs(menu) do + if open == v[1] then + it = v + break + end + x = x + v[1]:len() + 3 + end + x = x + 1 + + local items = {} + for i = 2, #it do + table.insert(items, it[i]) + end + + local len = 1 + for _, v in pairs(items) do if v:len() + 2 > len then len = v:len() + 2 end end + + for i, v in ipairs(items) do + term.setCursorPos(x, i + 1) + term.write(string.rep(" ", len)) + term.setCursorPos(x + 1, i + 1) + term.write(v) + end + term.setCursorPos(x, #items + 2) + term.write(string.rep(" ", len)) + return items, len + end +end + +local function triggerMenu(cx, cy) + -- Determine clicked menu + local curX = 0 + local open = nil + for _, v in pairs(menu) do + if cx >= curX + 3 and cx <= curX + v[1]:len() + 2 then + open = v[1] + break + end + curX = curX + v[1]:len() + 3 + end + local menux = curX + 2 + if not open then return false end + + -- Flash menu item + term.setCursorBlink(false) + term.setCursorPos(menux, 1) + term.setBackgroundColor(colors[theme.background]) + term.write(string.rep(" ", open:len() + 2)) + term.setCursorPos(menux + 1, 1) + term.write(open) + sleep(0.1) + local items, len = drawMenu(open) + + local ret = true + + -- Pull events on menu + local ox, oy = term.getCursorPos() + while type(ret) ~= "string" do + local e, but, x, y = os.pullEvent() + if e == "mouse_click" then + -- If clicked outside menu + if x < menux - 1 or x > menux + len - 1 then break + elseif y > #items + 2 then break + elseif y == 1 then break end + + for i, v in ipairs(items) do + if y == i + 1 and x >= menux and x <= menux + len - 2 then + -- Flash when clicked + term.setCursorPos(menux, y) + term.setBackgroundColor(colors[theme.background]) + term.write(string.rep(" ", len)) + term.setCursorPos(menux + 1, y) + term.write(v) + sleep(0.1) + drawMenu(open) + + -- Return item + ret = v + break + end + end + end + end + + term.setCursorPos(ox, oy) + term.setCursorBlink(true) + return ret +end + + +-- -------- Editing + +local standardsCompletions = { + "if%s+.+%s+then%s*$", + "for%s+.+%s+do%s*$", + "while%s+.+%s+do%s*$", + "repeat%s*$", + "function%s+[a-zA-Z_0-9]?\(.*\)%s*$", + "=%s*function%s*\(.*\)%s*$", + "else%s*$", + "elseif%s+.+%s+then%s*$" +} + +local liveCompletions = { + ["("] = ")", + ["{"] = "}", + ["["] = "]", + ["\""] = "\"", + ["'"] = "'", +} + +local x, y = 0, 0 +local edw, edh = 0, h - 1 +local offx, offy = 0, 1 +local scrollx, scrolly = 0, 0 +local lines = {} +local liveErr = currentLanguage.parseError(nil) +local displayCode = true +local lastEventClock = os.clock() + +local function attemptToHighlight(line, regex, col) + local match = string.match(line, regex) + if match then + if type(col) == "number" then term.setTextColor(col) + elseif type(col) == "function" then term.setTextColor(col(match)) end + term.write(match) + term.setTextColor(colors[theme.textColor]) + return line:sub(match:len() + 1, -1) + end + return nil +end + +local function writeHighlighted(line) + if currentLanguage == languages.lua then + while line:len() > 0 do + line = attemptToHighlight(line, "^%-%-%[%[.-%]%]", colors[theme.comment]) or + attemptToHighlight(line, "^%-%-.*", colors[theme.comment]) or + attemptToHighlight(line, "^\".*[^\\]\"", colors[theme.string]) or + attemptToHighlight(line, "^\'.*[^\\]\'", colors[theme.string]) or + attemptToHighlight(line, "^%[%[.-%]%]", colors[theme.string]) or + attemptToHighlight(line, "^[%w_]+", function(match) + if currentLanguage.keywords[match] then + return colors[theme[currentLanguage.keywords[match]]] + end + return colors[theme.textColor] + end) or + attemptToHighlight(line, "^[^%w_]", colors[theme.textColor]) + end + else term.write(line) end +end + +local function draw() + -- Menu + term.setTextColor(colors[theme.textColor]) + term.setBackgroundColor(colors[theme.editorBackground]) + term.clear() + drawMenu() + + -- Line numbers + offx, offy = tostring(#lines):len() + 1, 1 + edw, edh = w - offx, h - 1 + + -- Draw text + for i = 1, edh do + local a = lines[scrolly + i] + if a then + local ln = string.rep(" ", offx - 1 - tostring(scrolly + i):len()) .. tostring(scrolly + i) + local l = a:sub(scrollx + 1, edw + scrollx + 1) + ln = ln .. ":" + + if liveErr.line == scrolly + i then ln = string.rep(" ", offx - 2) .. "!:" end + + term.setCursorPos(1, i + offy) + term.setBackgroundColor(colors[theme.editorBackground]) + if scrolly + i == y then + if scrolly + i == liveErr.line and os.clock() - lastEventClock > 3 then + term.setBackgroundColor(colors[theme.editorErrorHighlight]) + else term.setBackgroundColor(colors[theme.editorLineHightlight]) end + term.clearLine() + elseif scrolly + i == liveErr.line then + term.setBackgroundColor(colors[theme.editorError]) + term.clearLine() + end + + term.setCursorPos(1 - scrollx + offx, i + offy) + if scrolly + i == y then + if scrolly + i == liveErr.line and os.clock() - lastEventClock > 3 then + term.setBackgroundColor(colors[theme.editorErrorHighlight]) + else term.setBackgroundColor(colors[theme.editorLineHightlight]) end + elseif scrolly + i == liveErr.line then term.setBackgroundColor(colors[theme.editorError]) + else term.setBackgroundColor(colors[theme.editorBackground]) end + if scrolly + i == liveErr.line then + if displayCode then term.write(a) + else term.write(liveErr.display) end + else writeHighlighted(a) end + + term.setCursorPos(1, i + offy) + if scrolly + i == y then + if scrolly + i == liveErr.line and os.clock() - lastEventClock > 3 then + term.setBackgroundColor(colors[theme.editorError]) + else term.setBackgroundColor(colors[theme.editorLineNumbersHighlight]) end + elseif scrolly + i == liveErr.line then + term.setBackgroundColor(colors[theme.editorErrorHighlight]) + else term.setBackgroundColor(colors[theme.editorLineNumbers]) end + term.write(ln) + end + end + term.setCursorPos(x - scrollx + offx, y - scrolly + offy) +end + +local function drawLine(...) + local ls = {...} + offx = tostring(#lines):len() + 1 + for _, ly in pairs(ls) do + local a = lines[ly] + if a then + local ln = string.rep(" ", offx - 1 - tostring(ly):len()) .. tostring(ly) + local l = a:sub(scrollx + 1, edw + scrollx + 1) + ln = ln .. ":" + + if liveErr.line == ly then ln = string.rep(" ", offx - 2) .. "!:" end + + term.setCursorPos(1, (ly - scrolly) + offy) + term.setBackgroundColor(colors[theme.editorBackground]) + if ly == y then + if ly == liveErr.line and os.clock() - lastEventClock > 3 then + term.setBackgroundColor(colors[theme.editorErrorHighlight]) + else term.setBackgroundColor(colors[theme.editorLineHightlight]) end + elseif ly == liveErr.line then + term.setBackgroundColor(colors[theme.editorError]) + end + term.clearLine() + + term.setCursorPos(1 - scrollx + offx, (ly - scrolly) + offy) + if ly == y then + if ly == liveErr.line and os.clock() - lastEventClock > 3 then + term.setBackgroundColor(colors[theme.editorErrorHighlight]) + else term.setBackgroundColor(colors[theme.editorLineHightlight]) end + elseif ly == liveErr.line then term.setBackgroundColor(colors[theme.editorError]) + else term.setBackgroundColor(colors[theme.editorBackground]) end + if ly == liveErr.line then + if displayCode then term.write(a) + else term.write(liveErr.display) end + else writeHighlighted(a) end + + term.setCursorPos(1, (ly - scrolly) + offy) + if ly == y then + if ly == liveErr.line and os.clock() - lastEventClock > 3 then + term.setBackgroundColor(colors[theme.editorError]) + else term.setBackgroundColor(colors[theme.editorLineNumbersHighlight]) end + elseif ly == liveErr.line then + term.setBackgroundColor(colors[theme.editorErrorHighlight]) + else term.setBackgroundColor(colors[theme.editorLineNumbers]) end + term.write(ln) + end + end + term.setCursorPos(x - scrollx + offx, y - scrolly + offy) +end + +local function cursorLoc(x, y, force) + local sx, sy = x - scrollx, y - scrolly + local redraw = false + if sx < 1 then + scrollx = x - 1 + sx = 1 + redraw = true + elseif sx > edw then + scrollx = x - edw + sx = edw + redraw = true + end if sy < 1 then + scrolly = y - 1 + sy = 1 + redraw = true + elseif sy > edh then + scrolly = y - edh + sy = edh + redraw = true + end if redraw or force then draw() end + term.setCursorPos(sx + offx, sy + offy) +end + +local function executeMenuItem(a, path) + if type(a) == "string" and menuFunctions[a] then + local opt, nl, gtln = menuFunctions[a](path, lines, y) + if type(opt) == "string" then term.setCursorBlink(false) return opt end + if type(nl) == "table" then + if #lines < 1 then table.insert(lines, "") end + y = math.min(y, #lines) + x = math.min(x, lines[y]:len() + 1) + lines = nl + elseif type(nl) == "string" then + if nl == "go to" and gtln then + x, y = 1, math.min(#lines, gtln) + cursorLoc(x, y) + end + end + end + term.setCursorBlink(true) + draw() + term.setCursorPos(x - scrollx + offx, y - scrolly + offy) +end + +local function edit(path) + -- Variables + x, y = 1, 1 + offx, offy = 0, 1 + scrollx, scrolly = 0, 0 + lines = loadFile(path) + if not lines then return "menu" end + + -- Enable brainfuck + if lines[1] == "-- Syntax: Brainfuck" then + currentLanguage = languages.brainfuck + end + + -- Clocks + local autosaveClock = os.clock() + local scrollClock = os.clock() -- To prevent redraw flicker + local liveErrorClock = os.clock() + local hasScrolled = false + + -- Draw + draw() + term.setCursorPos(x + offx, y + offy) + term.setCursorBlink(true) + + -- Main loop + local tid = os.startTimer(3) + while true do + local e, key, cx, cy = os.pullEvent() + if e == "key" and allowEditorEvent then + if key == 200 and y > 1 then + -- Up + x, y = math.min(x, lines[y - 1]:len() + 1), y - 1 + drawLine(y, y + 1) + cursorLoc(x, y) + elseif key == 208 and y < #lines then + -- Down + x, y = math.min(x, lines[y + 1]:len() + 1), y + 1 + drawLine(y, y - 1) + cursorLoc(x, y) + elseif key == 203 and x > 1 then + -- Left + x = x - 1 + local force = false + if y - scrolly + offy < offy + 1 then force = true end + cursorLoc(x, y, force) + elseif key == 205 and x < lines[y]:len() + 1 then + -- Right + x = x + 1 + local force = false + if y - scrolly + offy < offy + 1 then force = true end + cursorLoc(x, y, force) + elseif (key == 28 or key == 156) and (displayCode and true or y + scrolly - 1 == + liveErr.line) then + -- Enter + local f = nil + for _, v in pairs(standardsCompletions) do + if lines[y]:find(v) and x == #lines[y] + 1 then f = v end + end + + local _, spaces = lines[y]:find("^[ ]+") + if not spaces then spaces = 0 end + if f then + table.insert(lines, y + 1, string.rep(" ", spaces + 2)) + if not f:find("else", 1, true) and not f:find("elseif", 1, true) then + table.insert(lines, y + 2, string.rep(" ", spaces) .. + (f:find("repeat", 1, true) and "until " or f:find("{", 1, true) and "}" or + "end")) + end + x, y = spaces + 3, y + 1 + cursorLoc(x, y, true) + else + local oldLine = lines[y] + + lines[y] = lines[y]:sub(1, x - 1) + table.insert(lines, y + 1, string.rep(" ", spaces) .. oldLine:sub(x, -1)) + + x, y = spaces + 1, y + 1 + cursorLoc(x, y, true) + end + elseif key == 14 and (displayCode and true or y + scrolly - 1 == liveErr.line) then + -- Backspace + if x > 1 then + local f = false + for k, v in pairs(liveCompletions) do + if lines[y]:sub(x - 1, x - 1) == k then f = true end + end + + lines[y] = lines[y]:sub(1, x - 2) .. lines[y]:sub(x + (f and 1 or 0), -1) + drawLine(y) + x = x - 1 + cursorLoc(x, y) + elseif y > 1 then + local prevLen = lines[y - 1]:len() + 1 + lines[y - 1] = lines[y - 1] .. lines[y] + table.remove(lines, y) + x, y = prevLen, y - 1 + cursorLoc(x, y, true) + end + elseif key == 199 then + -- Home + x = 1 + local force = false + if y - scrolly + offy < offy + 1 then force = true end + cursorLoc(x, y, force) + elseif key == 207 then + -- End + x = lines[y]:len() + 1 + local force = false + if y - scrolly + offy < offy + 1 then force = true end + cursorLoc(x, y, force) + elseif key == 211 and (displayCode and true or y + scrolly - 1 == liveErr.line) then + -- Forward Delete + if x < lines[y]:len() + 1 then + lines[y] = lines[y]:sub(1, x - 1) .. lines[y]:sub(x + 1) + local force = false + if y - scrolly + offy < offy + 1 then force = true end + drawLine(y) + cursorLoc(x, y, force) + elseif y < #lines then + lines[y] = lines[y] .. lines[y + 1] + table.remove(lines, y + 1) + draw() + cursorLoc(x, y) + end + elseif key == 15 and (displayCode and true or y + scrolly - 1 == liveErr.line) then + -- Tab + lines[y] = string.rep(" ", tabWidth) .. lines[y] + x = x + 2 + local force = false + if y - scrolly + offy < offy + 1 then force = true end + drawLine(y) + cursorLoc(x, y, force) + elseif key == 201 then + -- Page up + y = math.min(math.max(y - edh, 1), #lines) + x = math.min(lines[y]:len() + 1, x) + cursorLoc(x, y, true) + elseif key == 209 then + -- Page down + y = math.min(math.max(y + edh, 1), #lines) + x = math.min(lines[y]:len() + 1, x) + cursorLoc(x, y, true) + end + elseif e == "char" and allowEditorEvent and (displayCode and true or + y + scrolly - 1 == liveErr.line) then + local shouldIgnore = false + for k, v in pairs(liveCompletions) do + if key == v and lines[y]:find(k, 1, true) and lines[y]:sub(x, x) == v then + shouldIgnore = true + end + end + + local addOne = false + if not shouldIgnore then + for k, v in pairs(liveCompletions) do + if key == k and lines[y]:sub(x, x) ~= k then key = key .. v addOne = true end + end + lines[y] = lines[y]:sub(1, x - 1) .. key .. lines[y]:sub(x, -1) + end + + x = x + (addOne and 1 or key:len()) + local force = false + if y - scrolly + offy < offy + 1 then force = true end + drawLine(y) + cursorLoc(x, y, force) + elseif e == "mouse_click" and key == 1 then + if cy > 1 then + if cx <= offx and cy - offy == liveErr.line - scrolly then + displayCode = not displayCode + drawLine(liveErr.line) + else + local oldy = y + y = math.min(math.max(scrolly + cy - offy, 1), #lines) + x = math.min(math.max(scrollx + cx - offx, 1), lines[y]:len() + 1) + if oldy ~= y then drawLine(oldy, y) end + cursorLoc(x, y) + end + else + local a = triggerMenu(cx, cy) + if a then + local opt = executeMenuItem(a, path) + if opt then return opt end + end + end + elseif e == "shortcut" then + local a = shortcuts[key .. " " .. cx] + if a then + local parent = nil + local curx = 0 + for i, mv in ipairs(menu) do + for _, iv in pairs(mv) do + if iv == a then + parent = menu[i][1] + break + end + end + if parent then break end + curx = curx + mv[1]:len() + 3 + end + local menux = curx + 2 + + -- Flash menu item + term.setCursorBlink(false) + term.setCursorPos(menux, 1) + term.setBackgroundColor(colors[theme.background]) + term.write(string.rep(" ", parent:len() + 2)) + term.setCursorPos(menux + 1, 1) + term.write(parent) + sleep(0.1) + drawMenu() + + -- Execute item + local opt = executeMenuItem(a, path) + if opt then return opt end + end + elseif e == "mouse_scroll" then + if key == -1 and scrolly > 0 then + scrolly = scrolly - 1 + if os.clock() - scrollClock > 0.0005 then + draw() + term.setCursorPos(x - scrollx + offx, y - scrolly + offy) + end + scrollClock = os.clock() + hasScrolled = true + elseif key == 1 and scrolly < #lines - edh then + scrolly = scrolly + 1 + if os.clock() - scrollClock > 0.0005 then + draw() + term.setCursorPos(x - scrollx + offx, y - scrolly + offy) + end + scrollClock = os.clock() + hasScrolled = true + end + elseif e == "timer" and key == tid then + drawLine(y) + tid = os.startTimer(3) + end + + -- Draw + if hasScrolled and os.clock() - scrollClock > 0.1 then + draw() + term.setCursorPos(x - scrollx + offx, y - scrolly + offy) + hasScrolled = false + end + + -- Autosave + if os.clock() - autosaveClock > autosaveInterval then + saveFile(path, lines) + autosaveClock = os.clock() + end + + -- Errors + if os.clock() - liveErrorClock > 1 then + local prevLiveErr = liveErr + liveErr = currentLanguage.parseError(nil) + local code = "" + for _, v in pairs(lines) do code = code .. v .. "\n" end + + liveErr = currentLanguage.getCompilerErrors(code) + liveErr.line = math.min(liveErr.line - 2, #lines) + if liveErr ~= prevLiveErr then draw() end + liveErrorClock = os.clock() + end + end + + return "menu" +end + + +-- -------- Open File + +local function newFile() + local wid = w - 13 + + -- Get name + title("Lua IDE - New File") + local name = centerRead(wid, "/") + if not name or name == "" then return "menu" end + name = "/" .. name + + -- Clear + title("Lua IDE - New File") + term.setTextColor(colors[theme.textColor]) + term.setBackgroundColor(colors[theme.promptHighlight]) + for i = 8, 10 do + term.setCursorPos(w/2 - wid/2, i) + term.write(string.rep(" ", wid)) + end + term.setCursorPos(1, 9) + if fs.isDir(name) then + centerPrint("Cannot Edit a Directory!") + sleep(1.6) + return "menu" + elseif fs.exists(name) then + centerPrint("File Already Exists!") + local opt = prompt({{"Open", w/2 - 9, 14}, {"Cancel", w/2 + 2, 14}}, "horizontal") + if opt == "Open" then return "edit", name + elseif opt == "Cancel" then return "menu" end + else return "edit", name end +end + +local function openFile() + local wid = w - 13 + + -- Get name + title("Lua IDE - Open File") + local name = centerRead(wid, "/") + if not name or name == "" then return "menu" end + name = "/" .. name + + -- Clear + title("Lua IDE - New File") + term.setTextColor(colors[theme.textColor]) + term.setBackgroundColor(colors[theme.promptHighlight]) + for i = 8, 10 do + term.setCursorPos(w/2 - wid/2, i) + term.write(string.rep(" ", wid)) + end + term.setCursorPos(1, 9) + if fs.isDir(name) then + centerPrint("Cannot Open a Directory!") + sleep(1.6) + return "menu" + elseif not fs.exists(name) then + centerPrint("File Doesn't Exist!") + local opt = prompt({{"Create", w/2 - 11, 14}, {"Cancel", w/2 + 2, 14}}, "horizontal") + if opt == "Create" then return "edit", name + elseif opt == "Cancel" then return "menu" end + else return "edit", name end +end + + +-- -------- Settings + +local function update() + local function draw(status) + title("LuaIDE - Update") + term.setBackgroundColor(colors[theme.prompt]) + term.setTextColor(colors[theme.textColor]) + for i = 8, 10 do + term.setCursorPos(w/2 - (status:len() + 4), i) + write(string.rep(" ", status:len() + 4)) + end + term.setCursorPos(w/2 - (status:len() + 4), 9) + term.write(" - " .. status .. " ") + + term.setBackgroundColor(colors[theme.errHighlight]) + for i = 8, 10 do + term.setCursorPos(w/2 + 2, i) + term.write(string.rep(" ", 10)) + end + term.setCursorPos(w/2 + 2, 9) + term.write(" > Cancel ") + end + + if not http then + draw("HTTP API Disabled!") + sleep(1.6) + return "settings" + end + + draw("Updating...") + local tID = os.startTimer(10) + http.request(updateURL) + while true do + local e, but, x, y = os.pullEvent() + if (e == "key" and but == 28) or + (e == "mouse_click" and x >= w/2 + 2 and x <= w/2 + 12 and y == 9) then + draw("Cancelled") + sleep(1.6) + break + elseif e == "http_success" and but == updateURL then + local new = x.readAll() + local curf = io.open(ideLocation, "r") + local cur = curf:read("*a") + curf:close() + + if cur ~= new then + draw("Update Found") + sleep(1.6) + local f = io.open(ideLocation, "w") + f:write(new) + f:close() + + draw("Click to Exit") + while true do + local e = os.pullEvent() + if e == "mouse_click" or (not isAdvanced() and e == "key") then break end + end + return "exit" + else + draw("No Updates Found!") + sleep(1.6) + break + end + elseif e == "http_failure" or (e == "timer" and but == tID) then + draw("Update Failed!") + sleep(1.6) + break + end + end + + return "settings" +end + +local function changeTheme() + title("LuaIDE - Theme") + + if isAdvanced() then + local disThemes = {"Back"} + for _, v in pairs(availableThemes) do table.insert(disThemes, v[1]) end + local t = scrollingPrompt(disThemes) + local url = nil + for _, v in pairs(availableThemes) do if v[1] == t then url = v[2] end end + + if not url then return "settings" end + if t == "Dawn (Default)" then + term.setBackgroundColor(colors[theme.backgroundHighlight]) + term.setCursorPos(3, 3) + term.clearLine() + term.write("LuaIDE - Loaded Theme!") + sleep(1.6) + + fs.delete(themeLocation) + theme = defaultTheme + return "menu" + end + + term.setBackgroundColor(colors[theme.backgroundHighlight]) + term.setCursorPos(3, 3) + term.clearLine() + term.write("LuaIDE - Downloading...") + + fs.delete("/.LuaIDE_temp_theme_file") + download(url, "/.LuaIDE_temp_theme_file") + local a = loadTheme("/.LuaIDE_temp_theme_file") + + term.setCursorPos(3, 3) + term.clearLine() + if a then + term.write("LuaIDE - Loaded Theme!") + fs.delete(themeLocation) + fs.move("/.LuaIDE_temp_theme_file", themeLocation) + theme = a + sleep(1.6) + return "menu" + end + + term.write("LuaIDE - Could Not Load Theme!") + fs.delete("/.LuaIDE_temp_theme_file") + sleep(1.6) + return "settings" + else + term.setCursorPos(1, 8) + centerPrint("Themes are not available on") + centerPrint("normal computers!") + end +end + +local function settings() + title("LuaIDE - Settings") + + local opt = prompt({{"Change Theme", w/2 - 17, 8}, {"Return to Menu", w/2 - 22, 13}, + {"Check for Updates", w/2 + 2, 8}, {"Exit IDE", w/2 + 2, 13, bg = colors[theme.err], + highlight = colors[theme.errHighlight]}}, "vertical", true) + if opt == "Change Theme" then return changeTheme() + elseif opt == "Check for Updates" then return update() + elseif opt == "Return to Menu" then return "menu" + elseif opt == "Exit IDE" then return "exit" end +end + + +-- -------- Menu + +local function menu() + title("Welcome to LuaIDE " .. version) + + local opt = prompt({{"New File", w/2 - 13, 8}, {"Open File", w/2 - 14, 13}, + {"Settings", w/2 + 2, 8}, {"Exit IDE", w/2 + 2, 13, bg = colors[theme.err], + highlight = colors[theme.errHighlight]}}, "vertical", true) + if opt == "New File" then return "new" + elseif opt == "Open File" then return "open" + elseif opt == "Settings" then return "settings" + elseif opt == "Exit IDE" then return "exit" end +end + + +-- -------- Main + +local function main(arguments) + local opt, data = "menu", nil + + -- Check arguments + if type(arguments) == "table" and #arguments > 0 then + local f = "/" .. shell.resolve(arguments[1]) + if fs.isDir(f) then print("Cannot edit a directory.") end + opt, data = "edit", f + end + + -- Main run loop + while true do + -- Menu + if opt == "menu" then opt = menu() end + + -- Other + if opt == "new" then opt, data = newFile() + elseif opt == "open" then opt, data = openFile() + elseif opt == "settings" then opt = settings() + end if opt == "exit" then break end + + -- Edit + if opt == "edit" and data then opt = edit(data) end + end +end + +-- Load Theme +if fs.exists(themeLocation) then theme = loadTheme(themeLocation) end +if not theme and isAdvanced() then theme = defaultTheme +elseif not theme then theme = normalTheme end + +-- Run +local _, err = pcall(function() + parallel.waitForAny(function() main(arguments) end, monitorKeyboardShortcuts) +end) + +-- Catch errors +if err and not err:find("Terminated") then + term.setCursorBlink(false) + title("LuaIDE - Crash! D:") + + term.setBackgroundColor(colors[theme.err]) + for i = 6, 8 do + term.setCursorPos(5, i) + term.write(string.rep(" ", 36)) + end + term.setCursorPos(6, 7) + term.write("LuaIDE Has Crashed! D:") + + term.setBackgroundColor(colors[theme.background]) + term.setCursorPos(2, 10) + print(err) + + term.setBackgroundColor(colors[theme.prompt]) + local _, cy = term.getCursorPos() + for i = cy + 1, cy + 4 do + term.setCursorPos(5, i) + term.write(string.rep(" ", 36)) + end + term.setCursorPos(6, cy + 2) + term.write("Please report this error to") + term.setCursorPos(6, cy + 3) + term.write("GravityScore! ") + + term.setBackgroundColor(colors[theme.background]) + if isAdvanced() then centerPrint("Click to Exit...", h - 1) + else centerPrint("Press Any Key to Exit...", h - 1) end + while true do + local e = os.pullEvent() + if e == "mouse_click" or (not isAdvanced() and e == "key") then break end + end + + -- Prevent key from being shown + os.queueEvent("") + os.pullEvent() +end + +-- Exit +term.setBackgroundColor(colors.black) +term.setTextColor(colors.white) +term.clear() +term.setCursorPos(1, 1) +centerPrint("Thank You for Using Lua IDE " .. version) +centerPrint("Made by GravityScore") diff --git a/MetroOS/Programs/Sketch/program b/MetroOS/Programs/Sketch/program new file mode 100644 index 0000000..8cb7681 --- /dev/null +++ b/MetroOS/Programs/Sketch/program @@ -0,0 +1,4014 @@ +if OneOS then + --running under OneOS + OneOS.ToolBarColour = colours.grey + OneOS.ToolBarTextColour = colours.white +end + +colours.transparent = -1 +colors.transparent = -1 + +--APIS-- + +--This is my drawing API, is is pretty much identical to what drives OneOS, PearOS, etc. +local _w, _h = term.getSize() + +local round = function(num, idp) + local mult = 10^(idp or 0) + return math.floor(num * mult + 0.5) / mult +end + +Clipboard = { + Content = nil, + Type = nil, + IsCut = false, + + Empty = function() + Clipboard.Content = nil + Clipboard.Type = nil + Clipboard.IsCut = false + end, + + isEmpty = function() + return Clipboard.Content == nil + end, + + Copy = function(content, _type) + Clipboard.Content = content + Clipboard.Type = _type or 'generic' + Clipboard.IsCut = false + end, + + Cut = function(content, _type) + Clipboard.Content = content + Clipboard.Type = _type or 'generic' + Clipboard.IsCut = true + end, + + Paste = function() + local c, t = Clipboard.Content, Clipboard.Type + if Clipboard.IsCut then + Clipboard.Empty() + end + return c, t + end +} + +if OneOS and OneOS.Clipboard then + Clipboard = OneOS.Clipboard +end + +Drawing = { + + Screen = { + Width = _w, + Height = _h + }, + + DrawCharacters = function (x, y, characters, textColour,bgColour) + Drawing.WriteStringToBuffer(x, y, characters, textColour, bgColour) + end, + + DrawBlankArea = function (x, y, w, h, colour) + Drawing.DrawArea (x, y, w, h, " ", 1, colour) + end, + + DrawArea = function (x, y, w, h, character, textColour, bgColour) + --width must be greater than 1, other wise we get a stack overflow + if w < 0 then + w = w * -1 + elseif w == 0 then + w = 1 + end + + for ix = 1, w do + local currX = x + ix - 1 + for iy = 1, h do + local currY = y + iy - 1 + Drawing.WriteToBuffer(currX, currY, character, textColour, bgColour) + end + end + end, + + DrawImage = function(_x,_y,tImage, w, h) + if tImage then + for y = 1, h do + if not tImage[y] then + break + end + for x = 1, w do + if not tImage[y][x] then + break + end + local bgColour = tImage[y][x] + local textColour = tImage.textcol[y][x] or colours.white + local char = tImage.text[y][x] + Drawing.WriteToBuffer(x+_x-1, y+_y-1, char, textColour, bgColour) + end + end + elseif w and h then + Drawing.DrawBlankArea(x, y, w, h, colours.green) + end + end, + --using .nft + LoadImage = function(path) + local image = { + text = {}, + textcol = {} + } + local _fs = fs + if OneOS then + _fs = OneOS.FS + end + if _fs.exists(path) then + local _open = io.open + if OneOS then + _open = OneOS.IO.open + end + local file = _open(path, "r") + local sLine = file:read() + local num = 1 + while sLine do + table.insert(image, num, {}) + table.insert(image.text, num, {}) + table.insert(image.textcol, num, {}) + + --As we're no longer 1-1, we keep track of what index to write to + local writeIndex = 1 + --Tells us if we've hit a 30 or 31 (BG and FG respectively)- next char specifies the curr colour + local bgNext, fgNext = false, false + --The current background and foreground colours + local currBG, currFG = nil,nil + for i=1,#sLine do + local nextChar = string.sub(sLine, i, i) + if nextChar:byte() == 30 then + bgNext = true + elseif nextChar:byte() == 31 then + fgNext = true + elseif bgNext then + currBG = Drawing.GetColour(nextChar) + bgNext = false + elseif fgNext then + currFG = Drawing.GetColour(nextChar) + fgNext = false + else + if nextChar ~= " " and currFG == nil then + currFG = colours.white + end + image[num][writeIndex] = currBG + image.textcol[num][writeIndex] = currFG + image.text[num][writeIndex] = nextChar + writeIndex = writeIndex + 1 + end + end + num = num+1 + sLine = file:read() + end + file:close() + end + return image + end, + + DrawCharactersCenter = function(x, y, w, h, characters, textColour,bgColour) + w = w or Drawing.Screen.Width + h = h or Drawing.Screen.Height + x = x or 0 + y = y or 0 + x = math.ceil((w - #characters) / 2) + x + y = math.floor(h / 2) + y + + Drawing.DrawCharacters(x, y, characters, textColour, bgColour) + end, + + GetColour = function(hex) + if hex == ' ' then + return colours.transparent + end + local value = tonumber(hex, 16) + if not value then return nil end + value = math.pow(2,value) + return value + end, + + Clear = function (_colour) + _colour = _colour or colours.black + Drawing.ClearBuffer() + Drawing.DrawBlankArea(1, 1, Drawing.Screen.Width, Drawing.Screen.Height, _colour) + end, + + Buffer = {}, + BackBuffer = {}, + + DrawBuffer = function() + for y,row in pairs(Drawing.Buffer) do + for x,pixel in pairs(row) do + local shouldDraw = true + local hasBackBuffer = true + if Drawing.BackBuffer[y] == nil or Drawing.BackBuffer[y][x] == nil or #Drawing.BackBuffer[y][x] ~= 3 then + hasBackBuffer = false + end + if hasBackBuffer and Drawing.BackBuffer[y][x][1] == Drawing.Buffer[y][x][1] and Drawing.BackBuffer[y][x][2] == Drawing.Buffer[y][x][2] and Drawing.BackBuffer[y][x][3] == Drawing.Buffer[y][x][3] then + shouldDraw = false + end + if shouldDraw then + term.setBackgroundColour(pixel[3]) + term.setTextColour(pixel[2]) + term.setCursorPos(x, y) + term.write(pixel[1]) + end + end + end + Drawing.BackBuffer = Drawing.Buffer + Drawing.Buffer = {} + term.setCursorPos(1,1) + end, + + ClearBuffer = function() + Drawing.Buffer = {} + end, + + WriteStringToBuffer = function (x, y, characters, textColour,bgColour) + for i = 1, #characters do + local character = characters:sub(i,i) + Drawing.WriteToBuffer(x + i - 1, y, character, textColour, bgColour) + end + end, + + WriteToBuffer = function(x, y, character, textColour,bgColour) + x = round(x) + y = round(y) + if bgColour == colours.transparent then + Drawing.Buffer[y] = Drawing.Buffer[y] or {} + Drawing.Buffer[y][x] = Drawing.Buffer[y][x] or {"", colours.white, colours.black} + Drawing.Buffer[y][x][1] = character + Drawing.Buffer[y][x][2] = textColour + else + Drawing.Buffer[y] = Drawing.Buffer[y] or {} + Drawing.Buffer[y][x] = {character, textColour, bgColour} + end + end, +} + +--Colour Deffitions-- +UIColours = { + Toolbar = colours.grey, + ToolbarText = colours.lightGrey, + ToolbarSelected = colours.lightBlue, + ControlText = colours.white, + ToolbarItemTitle = colours.black, + Background = colours.lightGrey, + MenuBackground = colours.white, + MenuText = colours.black, + MenuSeparatorText = colours.grey, + MenuDisabledText = colours.lightGrey, + Shadow = colours.grey, + TransparentBackgroundOne = colours.white, + TransparentBackgroundTwo = colours.lightGrey, + MenuBarActive = colours.white +} + +--Lists-- +Current = { + Artboard = nil, + Layer = nil, + Tool = nil, + ToolSize = 1, + Toolbar = nil, + Colour = colours.lightBlue, + Menu = nil, + MenuBar = nil, + Window = nil, + Input = nil, + CursorPos = {1,1}, + CursorColour = colours.black, + InterfaceVisible = true, + Selection = {}, + SelectionDrawTimer = nil, + HandDragStart = {}, + Modified = false, +} + +local isQuitting = false + +function PrintCentered(text, y) + local w, h = term.getSize() + x = math.ceil(math.ceil((w / 2) - (#text / 2)), 0)+1 + term.setCursorPos(x, y) + print(text) +end + +function DoVanillaClose() + term.setBackgroundColour(colours.black) + term.setTextColour(colours.white) + term.clear() + term.setCursorPos(1, 1) + PrintCentered("Thanks for using Sketch!", (Drawing.Screen.Height/2)-1) + term.setTextColour(colours.lightGrey) + PrintCentered("Photoshop Inspired Image Editor for ComputerCraft", (Drawing.Screen.Height/2)) + term.setTextColour(colours.white) + PrintCentered("(c) oeed 2013 - 2014", (Drawing.Screen.Height/2)+3) + term.setCursorPos(1, Drawing.Screen.Height) + error('', 0) +end + +function Close() + if isQuitting or not Current.Artboard or not Current.Modified then + if not OneOS then + DoVanillaClose() + end + return true + else + local _w = ButtonDialougeWindow:Initialise('Quit Sketch?', 'You have unsaved changes, do you want to quit anyway?', 'Quit', 'Cancel', function(window, success) + if success then + if OneOS then + OneOS.Close(true) + else + DoVanillaClose() + end + end + window:Close() + Draw() + end):Show() + --it's hacky but it works + os.queueEvent('mouse_click', 1, _w.X, _w.Y) + return false + end +end + +if OneOS then + OneOS.CanClose = function() + return Close() + end +end + +Lists = { + Artboards = {}, + Interface = { + Toolbars = {} + } +} + +Events = { + +} + +--Setters-- + +function SetColour(colour) + Current.Colour = colour + Draw() +end + +function SetTool(tool) + if tool and tool.Select and tool:Select() then + Current.Input = nil + Current.Tool = tool + return true + end + return false +end + +function GetAbsolutePosition(object) + local obj = object + local i = 0 + local x = 1 + local y = 1 + while true do + x = x + obj.X - 1 + y = y + obj.Y - 1 + + if not obj.Parent then + return {X = x, Y = y} + end + + obj = obj.Parent + + if i > 32 then + return {X = 1, Y = 1} + end + + i = i + 1 + end + +end + +--Object Defintions-- + +Pixel = { + TextColour = colours.black, + BackgroundColour = colours.white, + Character = " ", + Layer = nil, + + Draw = function(self, x, y) + if self.BackgroundColour ~= colours.transparent or self.Character ~= ' ' then + Drawing.WriteToBuffer(self.Layer.Artboard.X + x - 1, self.Layer.Artboard.Y + y - 1, self.Character, self.TextColour, self.BackgroundColour) + end + end, + + Initialise = function(self, textColour, backgroundColour, character, layer) + local new = {} -- the new instance + setmetatable( new, {__index = self} ) + new.TextColour = textColour or self.TextColour + new.BackgroundColour = backgroundColour or self.BackgroundColour + new.Character = character or self.Character + new.Layer = layer + return new + end, + + Set = function(self, textColour, backgroundColour, character) + self.TextColour = textColour or self.TextColour + self.BackgroundColour = backgroundColour or self.BackgroundColour + self.Character = character or self.Character + end +} + +Layer = { + Name = "", + Pixels = { + + }, + Artboard = nil, + BackgroundColour = colours.white, + Visible = true, + Index = 1, + + Draw = function(self) + if self.Visible then + for x = 1, self.Artboard.Width do + for y = 1, self.Artboard.Height do + self.Pixels[x][y]:Draw(x, y) + end + end + end + end, + + Remove = function(self) + for i, v in ipairs(self.Artboard.Layers) do + if v == Current.Layer then + Current.Artboard.Layers[i] = nil + Current.Layer = Current.Artboard.Layers[1] + ModuleNamed('Layers'):Update() + end + end + end, + + Initialise = function(self, name, backgroundColour, artboard, index, pixels) + local new = {} -- the new instance + setmetatable( new, {__index = self} ) + new.Name = name + new.Pixels = {} + new.BackgroundColour = backgroundColour + new.Artboard = artboard + new.Index = index or #artboard.Layers + 1 + if not pixels then + new:MakeAllBlankPixels() + else + new:MakeAllBlankPixels() + for x, col in ipairs(pixels) do + for y, pixel in ipairs(col) do + new:SetPixel(x, y, pixel.TextColour, pixel.BackgroundColour, pixel.Character) + end + end + end + + return new + end, + + SetPixel = function(self, x, y, textColour, backgroundColour, character) + textColour = textColour or Current.Colour + backgroundColour = backgroundColour or Current.Colour + character = character or " " + + if x < 1 or y < 1 or x > self.Artboard.Width or y > self.Artboard.Height then + return + end + + if self.Pixels[x][y] then + self.Pixels[x][y]:Set(textColour, backgroundColour, character) + self.Pixels[x][y]:Draw(x,y) + end + end, + + MakePixel = function(self, x, y, backgroundColour) + backgroundColour = backgroundColour or self.BackgroundColour + self.Pixels[x][y] = Pixel:Initialise(nil, backgroundColour, nil, self) + end, + + MakeColumn = function(self, x) + self.Pixels[x] = {} + end, + + MakeAllBlankPixels = function(self) + for x = 1, self.Artboard.Width do + if not self.Pixels[x] then + self:MakeColumn(x) + end + + for y = 1, self.Artboard.Height do + + if not self.Pixels[x][y] then + self:MakePixel(x, y) + end + + end + end + end, + + PixelsInSelection = function(self, cut) + local pixels = {} + if Current.Selection and Current.Selection[1] and Current.Selection[2] ~= nil then + local point1 = Current.Selection[1] + local point2 = Current.Selection[2] + + local size = point2 - point1 + local cornerX = point1.x + local cornerY = point1.y + for x = 1, size.x + 1 do + for y = 1, size.y + 1 do + if not pixels[x] then + pixels[x] = {} + end + if not self.Pixels[cornerX + x - 1] or not self.Pixels[cornerX + x - 1][cornerY + y - 1] then + break + end + local pixel = self.Pixels[cornerX + x - 1][cornerY + y - 1] + pixels[x][y] = Pixel:Initialise(pixel.TextColour, pixel.BackgroundColour, pixel.Character, Current.Layer) + if cut then + Current.Layer:SetPixel(cornerX + x - 1, cornerY + y - 1, nil, Current.Layer.BackgroundColour, nil) + end + end + end + end + return pixels + end, + + EraseSelection = function(self) + if Current.Selection and Current.Selection[1] and Current.Selection[2] ~= nil then + local point1 = Current.Selection[1] + local point2 = Current.Selection[2] + + local size = point2 - point1 + local cornerX = point1.x + local cornerY = point1.y + for x = 1, size.x + 1 do + for y = 1, size.y + 1 do + Current.Layer:SetPixel(cornerX + x - 1, cornerY + y - 1, nil, Current.Layer.BackgroundColour, nil) + end + end + end + end, + + InsertPixels = function(self, pixels) + local cornerX = Current.Selection[1].x + local cornerY = Current.Selection[1].y + for x, col in ipairs(pixels) do + for y, pixel in ipairs(col) do + Current.Layer:SetPixel(cornerX + x - 1, cornerY + y - 1, pixel.TextColour, pixel.BackgroundColour, pixel.Character) + end + end + end +} + +Artboard = { + X = 0, + Y = 0, + Name = "", + Path = "", + Width = 1, + Height = 1, + Layers = {}, + Format = nil, + SelectionIsBlack = true, + + Draw = function(self) + Drawing.DrawBlankArea(self.X + 1, self.Y + 1, self.Width, self.Height, UIColours.Shadow) + + local odd + for x = 1, self.Width do + odd = x % 2 + if odd == 1 then + odd = true + else + odd = false + end + for y = 1, self.Height do + if odd then + Drawing.WriteToBuffer(self.X + x - 1, self.Y + y - 1, ":", UIColours.TransparentBackgroundTwo, UIColours.TransparentBackgroundOne) + else + Drawing.WriteToBuffer(self.X + x - 1, self.Y + y - 1, ":", UIColours.TransparentBackgroundOne, UIColours.TransparentBackgroundTwo) + end + + odd = not odd + end + end + + for i, layer in ipairs(self.Layers) do + layer:Draw() + end + + if Current.Selection and Current.Selection[1] and Current.Selection[2] ~= nil then + local point1 = Current.Selection[1] + local point2 = Current.Selection[2] + + local size = point2 - point1 + + local isBlack = self.SelectionIsBlack + + local function c() + local c = colours.white + if isBlack then + c = colours.black + end + isBlack = not isBlack + return c + end + + function horizontal(y) + Drawing.WriteToBuffer(self.X - 1 + point1.x, self.Y - 1 + y, '+', c(), colours.transparent) + if size.x > 0 then + for i = 1, size.x - 1 do + Drawing.WriteToBuffer(self.X - 1 + point1.x + i, self.Y - 1 + y, '-', c(), colours.transparent) + end + else + for i = 1, (-1 * size.x) - 1 do + Drawing.WriteToBuffer(self.X - 1 + point1.x - i, self.Y - 1 + y, '-', c(), colours.transparent) + end + end + + Drawing.WriteToBuffer(self.X - 1 + point1.x + size.x, self.Y - 1 + y, '+', c(), colours.transparent) + end + + function vertical(x) + if size.y < 0 then + for i = 1, (-1 * size.y) - 1 do + Drawing.WriteToBuffer(self.X - 1 + x, self.Y - 1 + point1.y - i, '|', c(), colours.transparent) + end + else + for i = 1, size.y - 1 do + Drawing.WriteToBuffer(self.X - 1 + x, self.Y - 1 + point1.y + i, '|', c(), colours.transparent) + end + end + end + + horizontal(point1.y) + vertical(point1.x) + horizontal(point1.y + size.y) + vertical(point1.x + size.x) + end + end, + + Initialise = function(self, name, path, width, height, format, backgroundColour, layers) + local new = {} -- the new instance + setmetatable( new, {__index = self} ) + new.Y = 3 + new.X = 2 + new.Name = name + new.Path = path + new.Width = width + new.Height = height + new.Format = format + new.Layers = {} + if not layers then + new:MakeLayer('Background', backgroundColour) + else + for i, layer in ipairs(layers) do + new:MakeLayer(layer.Name, layer.BackgroundColour, layer.Index, layer.Pixels) + new.Layers[i].Visible = layer.Visible + end + Current.Layer = new.Layers[#new.Layers] + end + return new + end, + + Resize = function(self, top, bottom, left, right) + self.Height = self.Height + top + bottom + self.Width = self.Width + left + right + + for i, layer in ipairs(self.Layers) do + + if left < 0 then + for x = 1, -left do + table.remove(layer.Pixels, 1) + end + end + + if right < 0 then + for x = 1, -right do + table.remove(layer.Pixels, #layer.Pixels) + end + end + + for x = 1, left do + table.insert(layer.Pixels, 1, {}) + for y = 1, self.Height do + layer:MakePixel(1, y) + end + end + + for x = 1, right do + table.insert(layer.Pixels, {}) + for y = 1, self.Height do + layer:MakePixel(#layer.Pixels, y) + end + end + + for y = 1, top do + for x = 1, self.Width do + table.insert(layer.Pixels[x], 1, {}) + layer:MakePixel(x, 1) + end + end + + for y = 1, bottom do + for x = 1, self.Width do + table.insert(layer.Pixels[x], {}) + layer:MakePixel(x, #layer.Pixels[x]) + end + end + + if top < 0 then + for y = 1, -top do + for x = 1, self.Width do + table.remove(layer.Pixels[x], 1) + end + end + end + + if bottom < 0 then + for y = 1, -bottom do + for x = 1, self.Width do + table.remove(layer.Pixels[x], #layer.Pixels[x]) + end + end + end + end + end, + + MakeLayer = function(self, name, backgroundColour, index, pixels) + backgroundColour = backgroundColour or colours.white + name = name or "Layer" + local layer = Layer:Initialise(name, backgroundColour, self, index, pixels) + table.insert(self.Layers, layer) + Current.Layer = layer + ModuleNamed('Layers'):Update() + return layer + end, + + New = function(self, name, path, width, height, format, backgroundColour, layers) + local new = self:Initialise(name, path, width, height, format, backgroundColour, layers) + table.insert(Lists.Artboards, new) + Current.Artboard = new + --new:Save() + return new + end, + + Save = function(self, path) + Current.Artboard = self + path = path or self.Path + local _open = io.open + if OneOS then + _open = OneOS.IO.open + end + local file = _open(path, "w", true) + if self.Format == '.skch' then + file:write(textutils.serialize(SaveSKCH())) + else + local lines = {} + if self.Format == '.nfp' then + lines = SaveNFP() + elseif self.Format == '.nft' then + lines = SaveNFT() + end + + for i, line in ipairs(lines) do + file:write(line.."\n") + end + end + file:close() + Current.Modified = false + end, + + Click = function(self, side, x, y, drag) + if Current.Tool and Current.Layer and Current.Layer.Visible then + Current.Tool:Use(x, y, side, drag) + Current.Modified = true + return true + end + end +} + +Toolbar = { + X = 0, + Y = 0, + Width = 0, + ExpandedWidth = 14, + ClosedWidth = 2, + Height = 0, + Expanded = true, + ToolbarItems = {}, + + AbsolutePosition = function(self) + return {X = self.X, Y = self.Y} + end, + + Draw = function(self) + self:CalculateToolbarItemPositions() + --Drawing.DrawArea(self.X - 1, self.Y, 1, self.Height, "|", UIColours.ToolbarText, UIColours.Background) + + + + --if not Current.Window then + Drawing.DrawBlankArea(self.X, self.Y, self.Width, self.Height, UIColours.Toolbar) + --else + -- Drawing.DrawArea(self.X, self.Y, self.Width, self.Height, '|', colours.lightGrey, UIColours.Toolbar) + --end + for i, toolbarItem in ipairs(self.ToolbarItems) do + toolbarItem:Draw() + end + end, + + Initialise = function(self, side, expanded) + local new = {} -- the new instance + setmetatable( new, {__index = self} ) + new.Expanded = expanded + + if expanded then + new.Width = new.ExpandedWidth + else + new.Width = new.ClosedWidth + end + + if side == 'right' then + new.X = Drawing.Screen.Width - new.Width + 1 + end + + if side == 'right' or side == 'left' then + new.Height = Drawing.Screen.Width + end + + new.Y = 1 + + return new + end, + + AddToolbarItem = function(self, item) + table.insert(self.ToolbarItems, item) + self:CalculateToolbarItemPositions() + end, + + CalculateToolbarItemPositions = function(self) + local currY = 1 + for i, toolbarItem in ipairs(self.ToolbarItems) do + toolbarItem.Y = currY + currY = currY + toolbarItem.Height + end + end, + + Update = function(self) + for i, toolbarItem in ipairs(self.ToolbarItems) do + if toolbarItem.Module.Update then + toolbarItem.Module:Update(toolbarItem) + end + end + end, + + New = function(self, side, expanded) + local new = self:Initialise(side, expanded) + + --new:AddToolbarItem(ToolbarItem:Initialise("Colours", nil, true, new)) + --new:AddToolbarItem(ToolbarItem:Initialise("IDK", true, new)) + + table.insert(Lists.Interface.Toolbars, new) + return new + end, + + Click = function(self, side, x, y) + return false + end +} + +ToolbarItem = { + X = 0, + Y = 0, + Width = 0, + Height = 0, + ExpandedHeight = 5, + Expanded = true, + Toolbar = nil, + Title = "", + MenuIcon = "=", + ExpandedIcon = "+", + ContractIcon = "-", + ContentView = nil, + Module = nil, + MenuItems = nil, + + Draw = function(self) + Drawing.DrawBlankArea(self.X, self.Y, self.Width, 1, UIColours.ToolbarItemTitle) + Drawing.DrawCharacters(self.X + 1, self.Y, self.Title, UIColours.ToolbarText, UIColours.ToolbarItemTitle) + + Drawing.DrawCharacters(self.X + self.Width - 1, self.Y, self.MenuIcon, UIColours.ToolbarText, UIColours.ToolbarItemTitle) + + local expandContractIcon = self.ContractIcon + if not self.Expanded then + expandContractIcon = self.ExpandedIcon + end + + if self.Expanded and self.ContentView then + self.ContentView:Draw() + end + + Drawing.DrawCharacters(self.X + self.Width - 2, self.Y, expandContractIcon, UIColours.ToolbarText, UIColours.ToolbarItemTitle) + end, + + Initialise = function(self, module, height, expanded, toolbar, menuItems) + local new = {} -- the new instance + setmetatable( new, {__index = self} ) + new.Expanded = expanded + new.Title = module.Title + new.Width = toolbar.Width + new.Height = height or 5 + new.Module = module + new.MenuItems = menuItems or {} + table.insert(new.MenuItems, + { + Title = 'Shrink', + Click = function() + new:ToggleExpanded() + end + }) + new.ExpandedHeight = height or 5 + new.Y = 1 + new.X = toolbar.X + new.ContentView = ContentView:Initialise(1, 2, new.Width, new.Height - 1, nil, new) + new.Toolbar = toolbar + + return new + end, + + ToggleExpanded = function(self) + self.Expanded = not self.Expanded + if self.Expanded then + self.Height = self.ExpandedHeight + else + self.Height = 1 + end + end, + + Click = function(self, side, x, y) + local pos = GetAbsolutePosition(self) + if x == self.Width and y == 1 then + local expandContract = "Shrink" + + if not self.Expanded then + expandContract = "Expand" + end + self.MenuItems[#self.MenuItems].Title = expandContract + Menu:New(pos.X + x, pos.Y + y, self.MenuItems, self) + return true + elseif x == self.Width - 1 and y == 1 then + self:ToggleExpanded() + return true + elseif y ~= 1 then + return self.ContentView:Click(side, x - self.ContentView.X + 1, y - self.ContentView.Y + 1) + end + + return false + end +} + +ContentView = { + X = 1, + Y = 1, + Width = 0, + Height = 0, + Parent = nil, + Views = {}, + + AbsolutePosition = function(self) + return self.Parent:AbsolutePosition() + end, + + Draw = function(self) + for i, view in ipairs(self.Views) do + view:Draw() + end + end, + + Initialise = function(self, x, y, width, height, views, parent) + local new = {} -- the new instance + setmetatable( new, {__index = self} ) + new.Width = width + new.Height = height + new.Y = y + new.X = x + new.Views = views or {} + new.Parent = parent + return new + end, + + Click = function(self, side, x, y) + for k, view in pairs(self.Views) do + if DoClick(view, side, x, y) then + return true + end + end + end +} + +Button = { + X = 1, + Y = 1, + Width = 0, + Height = 0, + BackgroundColour = colours.lightGrey, + TextColour = colours.white, + ActiveBackgroundColour = colours.lightGrey, + Text = "", + Parent = nil, + _Click = nil, + Toggle = nil, + + AbsolutePosition = function(self) + return self.Parent:AbsolutePosition() + end, + + Draw = function(self) + local bg = self.BackgroundColour + local tc = self.TextColour + if type(bg) == 'function' then + bg = bg() + end + + if self.Toggle then + tc = UIColours.MenuBarActive + bg = self.ActiveBackgroundColour + end + + local pos = GetAbsolutePosition(self) + Drawing.DrawBlankArea(pos.X, pos.Y, self.Width, self.Height, bg) + Drawing.DrawCharactersCenter(pos.X, pos.Y, self.Width, self.Height, self.Text, tc, bg) + end, + + Initialise = function(self, x, y, width, height, backgroundColour, parent, click, text, textColour, toggle, activeBackgroundColour) + local new = {} -- the new instance + setmetatable( new, {__index = self} ) + height = height or 1 + new.Width = width or #text + 2 + new.Height = height + new.Y = y + new.X = x + new.Text = text or "" + new.BackgroundColour = backgroundColour or colours.lightGrey + new.TextColour = textColour or colours.white + new.ActiveBackgroundColour = activeBackgroundColour or colours.lightGrey + new.Parent = parent + new._Click = click + new.Toggle = toggle + return new + end, + + Click = function(self, side, x, y) + if self._Click then + if self:_Click(side, x, y, not self.Toggle) ~= false and self.Toggle ~= nil then + self.Toggle = not self.Toggle + Draw() + end + return true + else + return false + end + end +} + +TextBox = { + X = 1, + Y = 1, + Width = 0, + Height = 0, + BackgroundColour = colours.lightGrey, + TextColour = colours.black, + Parent = nil, + TextInput = nil, + + AbsolutePosition = function(self) + return self.Parent:AbsolutePosition() + end, + + Draw = function(self) + local pos = GetAbsolutePosition(self) + Drawing.DrawBlankArea(pos.X, pos.Y, self.Width, self.Height, self.BackgroundColour) + local text = self.TextInput.Value + if #text > (self.Width - 2) then + text = text:sub(#text-(self.Width - 3)) + if Current.Input == self.TextInput then + Current.CursorPos = {pos.X + 1 + self.Width-2, pos.Y} + end + else + if Current.Input == self.TextInput then + Current.CursorPos = {pos.X + 1 + self.TextInput.CursorPos, pos.Y} + end + end + Drawing.DrawCharacters(pos.X + 1, pos.Y, text, self.TextColour, self.BackgroundColour) + + term.setCursorBlink(true) + + Current.CursorColour = self.TextColour + end, + + Initialise = function(self, x, y, width, height, parent, text, backgroundColour, textColour, done, numerical) + local new = {} -- the new instance + setmetatable( new, {__index = self} ) + height = height or 1 + new.Width = width or #text + 2 + new.Height = height + new.Y = y + new.X = x + new.TextInput = TextInput:Initialise(text or '', function(key) + if done then + done(key) + end + Draw() + end, numerical) + new.BackgroundColour = backgroundColour or colours.lightGrey + new.TextColour = textColour or colours.black + new.Parent = parent + return new + end, + + Click = function(self, side, x, y) + Current.Input = self.TextInput + self:Draw() + end +} + +TextInput = { + Value = "", + Change = nil, + CursorPos = nil, + Numerical = false, + + Initialise = function(self, value, change, numerical) + local new = {} -- the new instance + setmetatable( new, {__index = self} ) + new.Value = value + new.Change = change + new.CursorPos = #value + new.Numerical = numerical + return new + end, + + Char = function(self, char) + if self.Numerical then + char = tostring(tonumber(char)) + end + if char == 'nil' then + return + end + self.Value = string.sub(self.Value, 1, self.CursorPos ) .. char .. string.sub( self.Value, self.CursorPos + 1 ) + + self.CursorPos = self.CursorPos + 1 + self.Change(key) + end, + + Key = function(self, key) + if key == keys.enter then + self.Change(key) + elseif key == keys.left then + -- Left + if self.CursorPos > 0 then + self.CursorPos = self.CursorPos - 1 + self.Change(key) + end + + elseif key == keys.right then + -- Right + if self.CursorPos < string.len(self.Value) then + self.CursorPos = self.CursorPos + 1 + self.Change(key) + end + + elseif key == keys.backspace then + -- Backspace + if self.CursorPos > 0 then + self.Value = string.sub( self.Value, 1, self.CursorPos - 1 ) .. string.sub( self.Value, self.CursorPos + 1 ) + self.CursorPos = self.CursorPos - 1 + end + self.Change(key) + elseif key == keys.home then + -- Home + self.CursorPos = 0 + self.Change(key) + elseif key == keys.delete then + if self.CursorPos < string.len(self.Value) then + self.Value = string.sub( self.Value, 1, self.CursorPos ) .. string.sub( self.Value, self.CursorPos + 2 ) + self.Change(key) + end + elseif key == keys["end"] then + -- End + self.CursorPos = string.len(self.Value) + self.Change(key) + end + end +} + +LayerItem = { + X = 1, + Y = 1, + Parent = nil, + Layer = nil, + + Draw = function(self) + self.Y = self.Layer.Index + + local pos = GetAbsolutePosition(self) + + local tc = colours.lightGrey + + if Current.Layer == self.Layer then + tc = colours.white + end + + Drawing.DrawBlankArea(pos.X, pos.Y, self.Width, self.Height, UIColours.Toolbar) + + Drawing.DrawCharacters(pos.X + 3, pos.Y, self.Layer.Name, tc, UIColours.Toolbar) + + if self.Layer.Visible then + Drawing.DrawCharacters(pos.X + 1, pos.Y, "@", tc, UIColours.Toolbar) + else + Drawing.DrawCharacters(pos.X + 1, pos.Y, "X", tc, UIColours.Toolbar) + end + + end, + + Initialise = function(self, layer, parent) + local new = {} -- the new instance + setmetatable( new, {__index = self} ) + new.Width = parent.Width + new.Height = 1 + new.Y = 1 + new.X = 1 + new.Layer = layer + new.Parent = parent + return new + end, + + Click = function(self, side, x, y) + if x == 2 then + self.Layer.Visible = not self.Layer.Visible + else + Current.Layer = self.Layer + end + return true + end +} + +Menu = { + X = 0, + Y = 0, + Width = 0, + Height = 0, + Owner = nil, + Items = {}, + RemoveTop = false, + + Draw = function(self) + Drawing.DrawBlankArea(self.X + 1, self.Y + 1, self.Width, self.Height, UIColours.Shadow) + if not self.RemoveTop then + Drawing.DrawBlankArea(self.X, self.Y, self.Width, self.Height, UIColours.MenuBackground) + for i, item in ipairs(self.Items) do + if item.Separator then + Drawing.DrawArea(self.X, self.Y + i, self.Width, 1, '-', colours.grey, UIColours.MenuBackground) + else + local textColour = UIColours.MenuText + if (item.Enabled and type(item.Enabled) == 'function' and item.Enabled() == false) or item.Enabled == false then + textColour = UIColours.MenuDisabledText + end + Drawing.DrawCharacters(self.X + 1, self.Y + i, item.Title, textColour, UIColours.MenuBackground) + end + end + else + Drawing.DrawBlankArea(self.X, self.Y, self.Width, self.Height, UIColours.MenuBackground) + for i, item in ipairs(self.Items) do + if item.Separator then + Drawing.DrawArea(self.X, self.Y + i - 1, self.Width, 1, '-', colours.grey, UIColours.MenuBackground) + else + local textColour = UIColours.MenuText + if (item.Enabled and type(item.Enabled) == 'function' and item.Enabled() == false) or item.Enabled == false then + textColour = UIColours.MenuDisabledText + end + Drawing.DrawCharacters(self.X + 1, self.Y + i - 1, item.Title, textColour, UIColours.MenuBackground) + + Drawing.DrawCharacters(self.X - 1 + self.Width-#item.KeyName, self.Y + i - 1, item.KeyName, textColour, UIColours.MenuBackground) + end + end + end + end, + + NameForKey = function(self, key) + if key == keys.leftCtrl then + return '^' + elseif key == keys.tab then + return 'Tab' + elseif key == keys.delete then + return 'Delete' + elseif key == keys.n then + return 'N' + elseif key == keys.s then + return 'S' + elseif key == keys.o then + return 'O' + elseif key == keys.z then + return 'Z' + elseif key == keys.y then + return 'Y' + elseif key == keys.c then + return 'C' + elseif key == keys.x then + return 'X' + elseif key == keys.v then + return 'V' + elseif key == keys.r then + return 'R' + elseif key == keys.l then + return 'L' + elseif key == keys.t then + return 'T' + elseif key == keys.h then + return 'H' + elseif key == keys.e then + return 'E' + elseif key == keys.p then + return 'P' + elseif key == keys.f then + return 'F' + elseif key == keys.m then + return 'M' + else + return '?' + end + end, + + Initialise = function(self, x, y, items, owner, removeTop) + local new = {} -- the new instance + setmetatable( new, {__index = self} ) + if not owner then + return + end + + local keyNames = {} + + for i, v in ipairs(items) do + items[i].KeyName = '' + if v.Keys then + for _i, key in ipairs(v.Keys) do + items[i].KeyName = items[i].KeyName .. self:NameForKey(key) + end + end + if items[i].KeyName ~= '' then + table.insert(keyNames, items[i].KeyName) + end + end + local keysLength = LongestString(keyNames) + if keysLength > 0 then + keysLength = keysLength + 2 + end + + new.Width = LongestString(items, 'Title') + 2 + keysLength + if new.Width < 10 then + new.Width = 10 + end + new.Height = #items + 2 + new.RemoveTop = removeTop or false + if removeTop then + new.Height = new.Height - 1 + end + + if y < 1 then + y = 1 + end + if x < 1 then + x = 1 + end + + if y + new.Height > Drawing.Screen.Height + 1 then + y = Drawing.Screen.Height - new.Height + end + if x + new.Width > Drawing.Screen.Width + 1 then + x = Drawing.Screen.Width - new.Width + end + + + new.Y = y + new.X = x + new.Items = items + new.Owner = owner + return new + end, + + New = function(self, x, y, items, owner, removeTop) + if Current.Menu and Current.Menu.Owner == owner then + Current.Menu = nil + return + end + + local new = self:Initialise(x, y, items, owner, removeTop) + Current.Menu = new + return new + end, + + Click = function(self, side, x, y) + local i = y-1 + if self.RemoveTop then + i = y + end + if i >= 1 and y < self.Height then + if not ((self.Items[i].Enabled and type(self.Items[i].Enabled) == 'function' and self.Items[i].Enabled() == false) or self.Items[i].Enabled == false) and self.Items[i].Click then + self.Items[i]:Click() + if Current.Menu.Owner and Current.Menu.Owner.Toggle then + Current.Menu.Owner.Toggle = false + end + Current.Menu = nil + self = nil + end + return true + end + end +} + +MenuBar = { + X = 1, + Y = 1, + Width = Drawing.Screen.Width, + Height = 1, + MenuBarItems = {}, + + AbsolutePosition = function(self) + return {X = self.X, Y = self.Y} + end, + + Draw = function(self) + --Drawing.DrawArea(self.X - 1, self.Y, 1, self.Height, "|", UIColours.ToolbarText, UIColours.Background) + + Drawing.DrawBlankArea(self.X, self.Y, self.Width, self.Height, UIColours.Toolbar) + for i, button in ipairs(self.MenuBarItems) do + button:Draw() + end + end, + + Initialise = function(self, items) + local new = {} -- the new instance + setmetatable( new, {__index = self} ) + new.X = 1 + new.Y = 1 + new.MenuBarItems = items + return new + end, + + AddToolbarItem = function(self, item) + table.insert(self.ToolbarItems, item) + self:CalculateToolbarItemPositions() + end, + + CalculateToolbarItemPositions = function(self) + local currY = 1 + for i, toolbarItem in ipairs(self.ToolbarItems) do + toolbarItem.Y = currY + currY = currY + toolbarItem.Height + end + end, + + Click = function(self, side, x, y) + for i, item in ipairs(self.MenuBarItems) do + if item.X <= x and item.X + item.Width > x then + if item:Click(item, side, x - item.X + 1, 1) then + break + end + end + end + return false + end +} + +--Modules-- + +Modules = { + { + Title = "Colours", + ToolbarItem = nil, + Initialise = function(self) + self.ToolbarItem = ToolbarItem:Initialise(self, nil, true, Current.Toolbar) + + local buttons = {} + + local i = 0 + + local coloursWidth = 8 + local _colours = { + colours.brown, + colours.yellow, + colours.orange, + colours.red, + colours.green, + colours.lime, + colours.magenta, + colours.pink, + colours.purple, + colours.blue, + colours.cyan, + colours.lightBlue, + colours.lightGrey, + colours.grey, + colours.black, + colours.white + } + + for k, colour in pairs(_colours) do + if type(colour) == 'number' and colour ~= -1 then + i = i + 1 + + local y = math.floor(i/(coloursWidth/2)) + + local x = (i%(coloursWidth/2)) + if x == 0 then + x = (coloursWidth/2) + y = y -1 + end + + table.insert(buttons, + { + X = x*2 - 2 + self.ToolbarItem.Width - coloursWidth, + Y = y+1, + Width = 2, + Height = 1, + BackgroundColour = colour, + Click = function(self, side, x, y) + SetColour(self.BackgroundColour) + end + } + ) + end + end + + for i, button in ipairs(buttons) do + table.insert(self.ToolbarItem.ContentView.Views, + Button:Initialise(button.X, button.Y, button.Width, button.Height, button.BackgroundColour, self.ToolbarItem.ContentView, button.Click)) + end + + table.insert(self.ToolbarItem.ContentView.Views, + Button:Initialise(1, 1, 4, 3, function()return Current.Colour end, self.ToolbarItem.ContentView, nil)) + + Current.Toolbar:AddToolbarItem(self.ToolbarItem) + end + }, + + { + Title = "Tools", + ToolbarItem = nil, + Update = function(self) + for i, view in ipairs(self.ToolbarItem.ContentView.Views) do + if (Current.Tool and Current.Tool.Name == view.Text) then + view.TextColour = colours.white + else + view.TextColour = colours.lightGrey + end + end + self.ToolbarItem.ContentView.Views[1].Text = 'Size: '..Current.ToolSize + end, + + Initialise = function(self) + self.ToolbarItem = ToolbarItem:Initialise(self, #Tools+2, true, Current.Toolbar, + {{ + Title = "Change Tool Size", + Click = function() + DisplayToolSizeWindow() + end, + }}) + + table.insert(self.ToolbarItem.ContentView.Views, Button:Initialise(1, 1, self.ToolbarItem.Width, 1, UIColours.Toolbar, self.ToolbarItem.ContentView, DisplayToolSizeWindow, 'Size: '..Current.ToolSize)) + + local y = 2 + for i, tool in ipairs(Tools) do + table.insert(self.ToolbarItem.ContentView.Views, Button:Initialise(1, y, self.ToolbarItem.Width, 1, UIColours.Toolbar, self.ToolbarItem.ContentView, function() SetTool(tool) self:Update(self.ToolbarItem) end, tool.Name)) + y = y + 1 + end + + self:Update(self.ToolbarItem) + + Current.Toolbar:AddToolbarItem(self.ToolbarItem) + end + }, + + { + Title = "Layers", + ToolbarItem = nil, + Update = function(self) + if Current.Artboard then + self.ToolbarItem.ContentView.Views = {} + for i = 1, #Current.Artboard.Layers do + table.insert(self.ToolbarItem.ContentView.Views, LayerItem:Initialise(Current.Artboard.Layers[#Current.Artboard.Layers-i+1], self.ToolbarItem.ContentView)) + end + end + end, + + Initialise = function(self) + self.ToolbarItem = ToolbarItem:Initialise(self, nil, true, Current.Toolbar, + {{ + Title = "New Layer", + Click = function() + MakeNewLayer() + end, + Enabled = function() + return CheckOpenArtboard() + end + }, + { + Title = 'Delete Layer', + Click = function() + DeleteLayer() + end, + Enabled = function() + return CheckSelectedLayer() + end + }, + { + Title = 'Rename Layer...', + Click = function() + RenameLayer() + end, + Enabled = function() + return CheckSelectedLayer() + end + }}) + + self:Update() + + Current.Toolbar:AddToolbarItem(self.ToolbarItem) + end + } + +} + +function ModuleNamed(name) + for i, v in ipairs(Modules) do + if v.Title == name then + return v + end + end +end + +--Tools-- + +function ToolAffectedPixels(x, y) + if not CheckSelectedLayer() then + return {} + end + if Current.ToolSize == 1 then + if Current.Layer.Pixels[x] and Current.Layer.Pixels[x][y] then + return {{Current.Layer.Pixels[x][y], x, y}} + end + else + local pixels = {} + local cornerX = x - math.ceil(Current.ToolSize/2) + local cornerY = y - math.ceil(Current.ToolSize/2) + for _x = 1, Current.ToolSize do + for _y = 1, Current.ToolSize do + if Current.Layer.Pixels[cornerX + _x] and Current.Layer.Pixels[cornerX + _x][cornerY + _y] then + table.insert(pixels, {Current.Layer.Pixels[cornerX + _x][cornerY + _y], cornerX + _x, cornerY + _y}) + end + end + end + return pixels + end +end +local moveStartPoint = {} +Tools = { + { + Name = "Hand", + Use = function(self, x, y, side, drag) + Current.Input = nil + if drag and Current.HandDragStart and Current.HandDragStart[1] and Current.HandDragStart[2] then + local deltaX = x - Current.HandDragStart[1] + local deltaY = y - Current.HandDragStart[2] + Current.Artboard.X = Current.Artboard.X + deltaX + Current.Artboard.Y = Current.Artboard.Y + deltaY + else + Current.HandDragStart = {x, y} + end + sleep(0) + end, + Select = function(self) + return true + end + }, + + { + Name = "Pencil", + Use = function(self, _x, _y, side, artboard) + Current.Input = nil + for i, pixel in ipairs(ToolAffectedPixels(_x, _y)) do + if side == 1 then + pixel[1].BackgroundColour = Current.Colour + elseif side == 2 then + pixel[1].TextColour = Current.Colour + end + pixel[1]:Draw(pixel[2], pixel[3]) + end + end, + Select = function(self) + return true + end + }, + + { + Name = "Eraser", + Use = function(self, x, y, side) + Current.Input = nil + Current.Layer:SetPixel(x, y, nil, Current.Layer.BackgroundColour, nil) + for i, pixel in ipairs(ToolAffectedPixels(x, y)) do + Current.Layer:SetPixel(pixel[2], pixel[3], nil, Current.Layer.BackgroundColour, nil) + end + end, + Select = function(self) + return true + end + }, + + { + Name = "Fill Bucket", + Use = function(self, x, y, side) + local replaceColour = Current.Layer.Pixels[x][y].BackgroundColour + if side == 2 then + replaceColour = Current.Layer.Pixels[x][y].TextColour + end + + local nodes = {{X = x, Y = y}} + + while #nodes > 0 do + local node = nodes[1] + if Current.Layer.Pixels[node.X] and Current.Layer.Pixels[node.X][node.Y] then + local replacing = Current.Layer.Pixels[node.X][node.Y].BackgroundColour + if side == 2 then + replacing = Current.Layer.Pixels[node.X][node.Y].TextColour + end + if replacing == replaceColour and replacing ~= Current.Colour then + if side == 1 then + Current.Layer.Pixels[node.X][node.Y].BackgroundColour = Current.Colour + elseif side == 2 then + Current.Layer.Pixels[node.X][node.Y].TextColour = Current.Colour + end + table.insert(nodes, {X = node.X, Y = node.Y + 1}) + table.insert(nodes, {X = node.X + 1, Y = node.Y}) + if x > 1 then + table.insert(nodes, {X = node.X - 1, Y = node.Y}) + end + if y > 1 then + table.insert(nodes, {X = node.X, Y = node.Y - 1}) + end + end + end + table.remove(nodes, 1) + end + Draw() + end, + Select = function(self) + return true + end + }, + + { + Name = "Select", + Use = function(self, x, y, side, drag) + Current.Input = nil + if not drag then + Current.Selection[1] = vector.new(x, y, 0) + Current.Selection[2] = nil + else + Current.Selection[2] = vector.new(x, y, 0) + end + end, + Select = function(self) + return true + end + }, + + { + Name = "Move", + Use = function(self, x, y, side, drag) + Current.Input = nil + + if Current.Selection and Current.Selection[1] and Current.Selection[2] ~= nil then + if drag and moveStartPoint then + local pixels = Current.Layer:PixelsInSelection(true) + local size = Current.Selection[1] - Current.Selection[2] + Current.Selection[1] = vector.new(x-moveStartPoint[1], y-moveStartPoint[2], 0) + Current.Selection[2] = vector.new(x-moveStartPoint[1]-size.x, y-moveStartPoint[2]-size.y, 0) + Current.Layer:InsertPixels(pixels) + else + moveStartPoint = {x-Current.Selection[1].x, y-Current.Selection[1].y} + end + end + end, + Select = function(self) + return true + end + }, + + { + Name = "Text", + Use = function(self, x, y) + Current.Input = TextInput:Initialise('', function(key) + if key == keys.delete or key == keys.backspace then + if #Current.Input.Value == 0 then + if Current.Layer.Pixels[x] and Current.Layer.Pixels[x][y] then + Current.Layer.Pixels[x][y]:Set(nil, nil, ' ') + local newPos = Current.CursorPos[1] - Current.Artboard.X + if newPos < Current.Artboard.X - 1 then + newPos = Current.Artboard.X - 1 + end + Current.Tool:Use(newPos, Current.CursorPos[2] - Current.Artboard.Y + 1) + Draw() + end + return + else + if Current.Layer.Pixels[x+#Current.Input.Value] and Current.Layer.Pixels[x+#Current.Input.Value][y] then + Current.Layer.Pixels[x+#Current.Input.Value][y]:Set(nil, nil, ' ') + end + end + else + local i = #Current.Input.Value + if Current.Layer.Pixels[x+i-1] then + Current.Layer.Pixels[x+i-1][y]:Set(Current.Colour, nil, Current.Input.Value:sub(i,i)) + Current.Layer.Pixels[x+i-1][y]:Draw(x+i-1, y) + end + end + + local newPos = x+Current.Input.CursorPos + + if newPos > Current.Artboard.Width then + Current.Input.CursorPos = Current.Input.CursorPos - 1 + end + + Current.CursorPos = {x+Current.Input.CursorPos + Current.Artboard.X - 1, y + Current.Artboard.Y - 1} + Current.CursorColour = Current.Colour + Draw() + end) + + Current.CursorPos = {x + Current.Artboard.X - 1, y + Current.Artboard.Y - 1} + Current.CursorColour = Current.Colour + end, + Select = function(self) + if Current.Artboard.Format == '.nfp' then + ButtonDialougeWindow:Initialise('NFP does not support text!', 'The format you are using, NFP, does not support text. Use NFT or SKCH to use text.', 'Ok', nil, function(window) + window:Close() + end):Show() + return false + else + return true + end + end + } +} + + +function ToolNamed(name) + for i, v in ipairs(Tools) do + if v.Name == name then + return v + end + end +end + +--Windows-- + +NewDocumentWindow = { + X = 1, + Y = 1, + Width = 0, + Height = 0, + CursorPos = 1, + Visible = true, + Return = nil, + OkButton = nil, + Format = '.skch', + ImageBackgroundColour = colours.white, + NameLabelHighlight = false, + SizeLabelHighlight = false, + + + AbsolutePosition = function(self) + return {X = self.X, Y = self.Y} + end, + + Draw = function(self) + if not self.Visible then + return + end + Drawing.DrawBlankArea(self.X + 1, self.Y+1, self.Width, self.Height, colours.grey) + Drawing.DrawBlankArea(self.X, self.Y, self.Width, 1, colours.lightGrey) + Drawing.DrawBlankArea(self.X, self.Y+1, self.Width, self.Height-1, colours.white) + Drawing.DrawCharactersCenter(self.X, self.Y, self.Width, 1, self.Title, colours.black, colours.lightGrey) + + local nameLabelColour = colours.black + if self.NameLabelHighlight then + nameLabelColour = colours.red + end + + Drawing.DrawCharacters(self.X+1, self.Y+2, "Name", nameLabelColour, colours.white) + Drawing.DrawCharacters(self.X+1, self.Y+4, "Type", colours.black, colours.white) + + local sizeLabelColour = colours.black + if self.SizeLabelHighlight then + sizeLabelColour = colours.red + end + Drawing.DrawCharacters(self.X+1, self.Y+6, "Size", sizeLabelColour, colours.white) + Drawing.DrawCharacters(self.X+11, self.Y+6, "x", colours.black, colours.white) + Drawing.DrawCharacters(self.X+1, self.Y+8, "Background", colours.black, colours.white) + + self.OkButton:Draw() + self.CancelButton:Draw() + self.SKCHButton:Draw() + self.NFTButton:Draw() + self.NFPButton:Draw() + self.PathTextBox:Draw() + self.WidthTextBox:Draw() + self.HeightTextBox:Draw() + self.WhiteButton:Draw() + self.BlackButton:Draw() + self.TransparentButton:Draw() + end, + + Initialise = function(self, returnFunc) + local new = {} -- the new instance + setmetatable( new, {__index = self} ) + new.Width = 32 + new.Height = 13 + new.Return = returnFunc + new.X = math.ceil((Drawing.Screen.Width - new.Width) / 2) + new.Y = math.ceil((Drawing.Screen.Height - new.Height) / 2) + new.Title = 'New Document' + new.Visible = true + new.NameLabelHighlight = false + new.SizeLabelHighlight = false + new.Format = '.skch' + new.OkButton = Button:Initialise(new.Width - 4, new.Height - 1, nil, nil, colours.lightGrey, new, function(self, side, x, y, toggle) + local path = new.PathTextBox.TextInput.Value + local ok = true + new.NameLabelHighlight = false + new.SizeLabelHighlight = false + local _fs = fs + if OneOS then + _fs = OneOS.FS + end + if path:sub(-1) == '/' or _fs.isDir(path) or #path == 0 then + ok = false + new.NameLabelHighlight = true + end + + if #new.WidthTextBox.TextInput.Value == 0 or tonumber(new.WidthTextBox.TextInput.Value) <= 0 then + ok = false + new.SizeLabelHighlight = true + end + + if #new.HeightTextBox.TextInput.Value == 0 or tonumber(new.HeightTextBox.TextInput.Value) <= 0 then + ok = false + new.SizeLabelHighlight = true + end + + if ok then + returnFunc(new, true, path, tonumber(new.WidthTextBox.TextInput.Value), tonumber(new.HeightTextBox.TextInput.Value), new.Format, new.ImageBackgroundColour) + else + Draw() + end + end, 'Ok', colours.black) + new.CancelButton = Button:Initialise(new.Width - 13, new.Height - 1, nil, nil, colours.lightGrey, new, function(self, side, x, y, toggle)returnFunc(new, false)end, 'Cancel', colours.black) + + new.SKCHButton = Button:Initialise(7, 5, nil, nil, colours.lightGrey, new, function(self, side, x, y, toggle) + new.NFTButton.Toggle = false + new.NFPButton.Toggle = false + self.Toggle = false + new.Format = '.skch' + end, '.skch', colours.black, true, colours.lightBlue) + new.NFTButton = Button:Initialise(15, 5, nil, nil, colours.lightGrey, new, function(self, side, x, y, toggle) + new.SKCHButton.Toggle = false + new.NFPButton.Toggle = false + self.Toggle = false + new.Format = '.nft' + end, '.nft', colours.black, false, colours.lightBlue) + new.NFPButton = Button:Initialise(22, 5, nil, nil, colours.lightGrey, new, function(self, side, x, y, toggle) + new.SKCHButton.Toggle = false + new.NFTButton.Toggle = false + self.Toggle = false + new.Format = '.nfp' + end, '.nfp', colours.black, false, colours.lightBlue) + + local path = '' + if OneOS then + path = '/Desktop/' + end + new.PathTextBox = TextBox:Initialise(7, 3, new.Width - 7, 1, new, path, nil, nil, function(key) + if key == keys.enter or key == keys.tab then + Current.Input = new.WidthTextBox.TextInput + end + end) + new.WidthTextBox = TextBox:Initialise(7, 7, 4, 1, new, tostring(15), nil, nil, function() + if key == keys.enter or key == keys.tab then + Current.Input = new.HeightTextBox.TextInput + end + end, true) + new.HeightTextBox = TextBox:Initialise(14, 7, 4, 1, new, tostring(10), nil, nil, function() + if key == keys.enter or key == keys.tab then + Current.Input = new.PathTextBox.TextInput + end + end, true) + Current.Input = new.PathTextBox.TextInput + + + new.WhiteButton = Button:Initialise(2, 10, nil, nil, colours.lightGrey, new, function(self, side, x, y, toggle) + new.TransparentButton.Toggle = false + new.BlackButton.Toggle = false + self.Toggle = false + new.ImageBackgroundColour = colours.white + end, 'White', colours.black, true, colours.lightBlue) + new.BlackButton = Button:Initialise(10, 10, nil, nil, colours.lightGrey, new, function(self, side, x, y, toggle) + new.TransparentButton.Toggle = false + new.WhiteButton.Toggle = false + self.Toggle = false + new.ImageBackgroundColour = colours.black + end, 'Black', colours.black, false, colours.lightBlue) + new.TransparentButton = Button:Initialise(18, 10, nil, nil, colours.lightGrey, new, function(self, side, x, y, toggle) + new.WhiteButton.Toggle = false + new.BlackButton.Toggle = false + self.Toggle = false + new.ImageBackgroundColour = colours.transparent + end, 'Transparent', colours.black, false, colours.lightBlue) + + return new + end, + + Show = function(self) + Current.Window = self + return self + end, + + Close = function(self) + Current.Input = nil + Current.Window = nil + self = nil + end, + + Flash = function(self) + self.Visible = false + Draw() + sleep(0.15) + self.Visible = true + Draw() + sleep(0.15) + self.Visible = false + Draw() + sleep(0.15) + self.Visible = true + Draw() + end, + + ButtonClick = function(self, button, x, y) + if button.X <= x and button.Y <= y and button.X + button.Width > x and button.Y + button.Height > y then + button:Click() + end + end, + + Click = function(self, side, x, y) + local items = {self.OkButton, self.CancelButton, self.SKCHButton, self.NFTButton, self.NFPButton, self.PathTextBox, self.WidthTextBox, self.HeightTextBox, self.WhiteButton, self.BlackButton, self.TransparentButton} + for i, v in ipairs(items) do + if CheckClick(v, x, y) then + v:Click(side, x, y) + end + end + return true + end +} + +local TidyPath = function(path) + path = '/'..path + local _fs = fs + if OneOS then + _fs = OneOS.FS + end + if _fs.isDir(path) then + path = path .. '/' + end + + path, n = path:gsub("//", "/") + while n > 0 do + path, n = path:gsub("//", "/") + end + return path +end + +local WrapText = function(text, maxWidth) + local lines = {''} + for word, space in text:gmatch('(%S+)(%s*)') do + local temp = lines[#lines] .. word .. space:gsub('\n','') + if #temp > maxWidth then + table.insert(lines, '') + end + if space:find('\n') then + lines[#lines] = lines[#lines] .. word + + space = space:gsub('\n', function() + table.insert(lines, '') + return '' + end) + else + lines[#lines] = lines[#lines] .. word .. space + end + end + return lines +end + +OpenDocumentWindow = { + X = 1, + Y = 1, + Width = 0, + Height = 0, + CursorPos = 1, + Visible = true, + Return = nil, + OpenButton = nil, + PathTextBox = nil, + CurrentDirectory = '/', + Scroll = 0, + MaxScroll = 0, + GoUpButton = nil, + SelectedFile = '', + Files = {}, + Typed = false, + + AbsolutePosition = function(self) + return {X = self.X, Y = self.Y} + end, + + Draw = function(self) + if not self.Visible then + return + end + Drawing.DrawBlankArea(self.X + 1, self.Y+1, self.Width, self.Height, colours.grey) + Drawing.DrawBlankArea(self.X, self.Y, self.Width, 3, colours.lightGrey) + Drawing.DrawBlankArea(self.X, self.Y+1, self.Width, self.Height-6, colours.white) + Drawing.DrawCharactersCenter(self.X, self.Y, self.Width, 1, self.Title, colours.black, colours.lightGrey) + Drawing.DrawBlankArea(self.X, self.Y + self.Height - 5, self.Width, 5, colours.lightGrey) + self:DrawFiles() + + local _fs = fs + if OneOS then + _fs = OneOS.FS + end + if (_fs.exists(self.PathTextBox.TextInput.Value)) or (self.SelectedFile and #self.SelectedFile > 0 and _fs.exists(self.CurrentDirectory .. self.SelectedFile)) then + self.OpenButton.TextColour = colours.black + else + self.OpenButton.TextColour = colours.lightGrey + end + + self.PathTextBox:Draw() + self.OpenButton:Draw() + self.CancelButton:Draw() + self.GoUpButton:Draw() + end, + + DrawFiles = function(self) + local _fs = fs + if OneOS then + _fs = OneOS.FS + end + for i, file in ipairs(self.Files) do + if i > self.Scroll and i - self.Scroll <= 11 then + if file == self.SelectedFile then + Drawing.DrawCharacters(self.X + 1, self.Y + i - self.Scroll, file, colours.white, colours.lightBlue) + elseif string.find(file, '%.skch') or string.find(file, '%.nft') or string.find(file, '%.nfp') or _fs.isDir(self.CurrentDirectory .. file) then + Drawing.DrawCharacters(self.X + 1, self.Y + i - self.Scroll, file, colours.black, colours.white) + else + Drawing.DrawCharacters(self.X + 1, self.Y + i - self.Scroll, file, colours.grey, colours.white) + end + end + end + self.MaxScroll = #self.Files - 11 + if self.MaxScroll < 0 then + self.MaxScroll = 0 + end + end, + + Initialise = function(self, returnFunc) + local new = {} -- the new instance + setmetatable( new, {__index = self} ) + new.Width = 32 + new.Height = 17 + new.Return = returnFunc + new.X = math.ceil((Drawing.Screen.Width - new.Width) / 2) + new.Y = math.ceil((Drawing.Screen.Height - new.Height) / 2) + new.Title = 'Open Document' + new.Visible = true + new.CurrentDirectory = '/' + new.SelectedFile = nil + if OneOS then + new.CurrentDirectory = '/Desktop/' + end + local _fs = fs + if OneOS then + _fs = OneOS.FS + end + new.OpenButton = Button:Initialise(new.Width - 6, new.Height - 1, nil, nil, colours.white, new, function(self, side, x, y, toggle) + if _fs.exists(new.PathTextBox.TextInput.Value) and self.TextColour == colours.black and not _fs.isDir(new.PathTextBox.TextInput.Value) then + returnFunc(new, true, TidyPath(new.PathTextBox.TextInput.Value)) + elseif new.SelectedFile and self.TextColour == colours.black and _fs.isDir(new.CurrentDirectory .. new.SelectedFile) then + new:GoToDirectory(new.CurrentDirectory .. new.SelectedFile) + elseif new.SelectedFile and self.TextColour == colours.black then + returnFunc(new, true, TidyPath(new.CurrentDirectory .. '/' .. new.SelectedFile)) + end + end, 'Open', colours.black) + new.CancelButton = Button:Initialise(new.Width - 15, new.Height - 1, nil, nil, colours.white, new, function(self, side, x, y, toggle) + returnFunc(new, false) + end, 'Cancel', colours.black) + new.GoUpButton = Button:Initialise(2, new.Height - 1, nil, nil, colours.white, new, function(self, side, x, y, toggle) + local folderName = _fs.getName(new.CurrentDirectory) + local parentDirectory = new.CurrentDirectory:sub(1, #new.CurrentDirectory-#folderName-1) + new:GoToDirectory(parentDirectory) + end, 'Go Up', colours.black) + new.PathTextBox = TextBox:Initialise(2, new.Height - 3, new.Width - 2, 1, new, new.CurrentDirectory, colours.white, colours.black) + new:GoToDirectory(new.CurrentDirectory) + return new + end, + + Show = function(self) + Current.Window = self + return self + end, + + Close = function(self) + Current.Input = nil + Current.Window = nil + self = nil + end, + + GoToDirectory = function(self, path) + path = TidyPath(path) + self.CurrentDirectory = path + self.Scroll = 0 + self.SelectedFile = nil + self.Typed = false + self.PathTextBox.TextInput.Value = path + local _fs = fs + if OneOS then + _fs = OneOS.FS + end + self.Files = _fs.list(self.CurrentDirectory) + Draw() + end, + + Flash = function(self) + self.Visible = false + Draw() + sleep(0.15) + self.Visible = true + Draw() + sleep(0.15) + self.Visible = false + Draw() + sleep(0.15) + self.Visible = true + Draw() + end, + + Click = function(self, side, x, y) + local items = {self.OpenButton, self.CancelButton, self.PathTextBox, self.GoUpButton} + local found = false + for i, v in ipairs(items) do + if CheckClick(v, x, y) then + v:Click(side, x, y) + found = true + end + end + + if not found then + if y <= 12 then + local _fs = fs + if OneOS then + _fs = OneOS.FS + end + self.SelectedFile = _fs.list(self.CurrentDirectory)[y-1] + self.PathTextBox.TextInput.Value = TidyPath(self.CurrentDirectory .. '/' .. self.SelectedFile) + Draw() + end + end + return true + end +} + +ButtonDialougeWindow = { + X = 1, + Y = 1, + Width = 0, + Height = 0, + CursorPos = 1, + Visible = true, + CancelButton = nil, + OkButton = nil, + Lines = {}, + + AbsolutePosition = function(self) + return {X = self.X, Y = self.Y} + end, + + Draw = function(self) + if not self.Visible then + return + end + Drawing.DrawBlankArea(self.X + 1, self.Y+1, self.Width, self.Height, colours.grey) + Drawing.DrawBlankArea(self.X, self.Y, self.Width, 1, colours.lightGrey) + Drawing.DrawBlankArea(self.X, self.Y+1, self.Width, self.Height-1, colours.white) + Drawing.DrawCharactersCenter(self.X, self.Y, self.Width, 1, self.Title, colours.black, colours.lightGrey) + + for i, text in ipairs(self.Lines) do + Drawing.DrawCharacters(self.X + 1, self.Y + 1 + i, text, colours.black, colours.white) + end + + self.OkButton:Draw() + if self.CancelButton then + self.CancelButton:Draw() + end + end, + + Initialise = function(self, title, message, okText, cancelText, returnFunc) + local new = {} -- the new instance + setmetatable( new, {__index = self} ) + new.Width = 28 + new.Lines = WrapText(message, new.Width - 2) + new.Height = 5 + #new.Lines + new.Return = returnFunc + new.X = math.ceil((Drawing.Screen.Width - new.Width) / 2) + new.Y = math.ceil((Drawing.Screen.Height - new.Height) / 2) + new.Title = title + new.Visible = true + new.Visible = true + new.OkButton = Button:Initialise(new.Width - #okText - 2, new.Height - 1, nil, 1, nil, new, function() + returnFunc(new, true) + end, okText) + if cancelText then + new.CancelButton = Button:Initialise(new.Width - #okText - 2 - 1 - #cancelText - 2, new.Height - 1, nil, 1, nil, new, function() + returnFunc(new, false) + end, cancelText) + end + + return new + end, + + Show = function(self) + Current.Window = self + return self + end, + + Close = function(self) + Current.Window = nil + self = nil + end, + + Flash = function(self) + self.Visible = false + Draw() + sleep(0.15) + self.Visible = true + Draw() + sleep(0.15) + self.Visible = false + Draw() + sleep(0.15) + self.Visible = true + Draw() + end, + + Click = function(self, side, x, y) + local items = {self.OkButton, self.CancelButton} + local found = false + for i, v in ipairs(items) do + if CheckClick(v, x, y) then + v:Click(side, x, y) + found = true + end + end + return true + end +} + +TextDialougeWindow = { + X = 1, + Y = 1, + Width = 0, + Height = 0, + CursorPos = 1, + Visible = true, + CancelButton = nil, + OkButton = nil, + Lines = {}, + TextInput = nil, + + AbsolutePosition = function(self) + return {X = self.X, Y = self.Y} + end, + + Draw = function(self) + if not self.Visible then + return + end + Drawing.DrawBlankArea(self.X + 1, self.Y+1, self.Width, self.Height, colours.grey) + Drawing.DrawBlankArea(self.X, self.Y, self.Width, 1, colours.lightGrey) + Drawing.DrawBlankArea(self.X, self.Y+1, self.Width, self.Height-1, colours.white) + Drawing.DrawCharactersCenter(self.X, self.Y, self.Width, 1, self.Title, colours.black, colours.lightGrey) + + for i, text in ipairs(self.Lines) do + Drawing.DrawCharacters(self.X + 1, self.Y + 1 + i, text, colours.black, colours.white) + end + + + Drawing.DrawBlankArea(self.X + 1, self.Y + self.Height - 4, self.Width - 2, 1, colours.lightGrey) + Drawing.DrawCharacters(self.X + 2, self.Y + self.Height - 4, self.TextInput.Value, colours.black, colours.lightGrey) + Current.CursorPos = {self.X + 2 + self.TextInput.CursorPos, self.Y + self.Height - 4} + Current.CursorColour = colours.black + + self.OkButton:Draw() + if self.CancelButton then + self.CancelButton:Draw() + end + end, + + Initialise = function(self, title, message, okText, cancelText, returnFunc, numerical) + local new = {} -- the new instance + setmetatable( new, {__index = self} ) + new.Width = 28 + new.Lines = WrapText(message, new.Width - 2) + new.Height = 7 + #new.Lines + new.Return = returnFunc + new.X = math.ceil((Drawing.Screen.Width - new.Width) / 2) + new.Y = math.ceil((Drawing.Screen.Height - new.Height) / 2) + new.Title = title + new.Visible = true + new.Visible = true + new.OkButton = Button:Initialise(new.Width - #okText - 2, new.Height - 1, nil, 1, nil, new, function() + if #new.TextInput.Value > 0 then + returnFunc(new, true, new.TextInput.Value) + end + end, okText) + if cancelText then + new.CancelButton = Button:Initialise(new.Width - #okText - 2 - 1 - #cancelText - 2, new.Height - 1, nil, 1, nil, new, function() + returnFunc(new, false) + end, cancelText) + end + new.TextInput = TextInput:Initialise('', function(enter) + if enter then + new.OkButton:Click() + end + Draw() + end, numerical) + + Current.Input = new.TextInput + + return new + end, + + Show = function(self) + Current.Window = self + return self + end, + + Close = function(self) + Current.Window = nil + Current.Input = nil + self = nil + end, + + Flash = function(self) + self.Visible = false + Draw() + sleep(0.15) + self.Visible = true + Draw() + sleep(0.15) + self.Visible = false + Draw() + sleep(0.15) + self.Visible = true + Draw() + end, + + Click = function(self, side, x, y) + local items = {self.OkButton, self.CancelButton} + local found = false + for i, v in ipairs(items) do + if CheckClick(v, x, y) then + v:Click(side, x, y) + found = true + end + end + return true + end +} + +ResizeDocumentWindow = { + X = 1, + Y = 1, + Width = 0, + Height = 0, + CursorPos = 1, + Visible = true, + Return = nil, + OkButton = nil, + AnchorPosition = 5, + WidthLabelHighlight = false, + HeightLabelHighlight = false, + + AbsolutePosition = function(self) + return {X = self.X, Y = self.Y} + end, + + Draw = function(self) + if not self.Visible then + return + end + Drawing.DrawBlankArea(self.X + 1, self.Y+1, self.Width, self.Height, colours.grey) + Drawing.DrawBlankArea(self.X, self.Y, self.Width, 1, colours.lightGrey) + Drawing.DrawBlankArea(self.X, self.Y+1, self.Width, self.Height-1, colours.white) + Drawing.DrawCharactersCenter(self.X, self.Y, self.Width, 1, self.Title, colours.black, colours.lightGrey) + + Drawing.DrawCharacters(self.X+1, self.Y+2, "New Size", colours.lightGrey, colours.white) + if (#self.WidthTextBox.TextInput.Value > 0 and tonumber(self.WidthTextBox.TextInput.Value) < Current.Artboard.Width) or (#self.HeightTextBox.TextInput.Value > 0 and tonumber(self.HeightTextBox.TextInput.Value) < Current.Artboard.Height) then + Drawing.DrawCharacters(self.X+1, self.Y+8, "Clipping will occur!", colours.red, colours.white) + end + + local widthLabelColour = colours.black + if self.WidthLabelHighlight then + widthLabelColour = colours.red + end + + local heightLabelColour = colours.black + if self.HeightLabelHighlight then + heightLabelColour = colours.red + end + + Drawing.DrawCharacters(self.X+1, self.Y+4, "Width", widthLabelColour, colours.white) + Drawing.DrawCharacters(self.X+1, self.Y+6, "Height", heightLabelColour, colours.white) + + Drawing.DrawCharacters(self.X+14, self.Y+2, "Anchor", colours.lightGrey, colours.white) + + self.WidthTextBox:Draw() + self.HeightTextBox:Draw() + self.OkButton:Draw() + self.Anchor1:Draw() + self.Anchor2:Draw() + self.Anchor3:Draw() + self.Anchor4:Draw() + self.Anchor5:Draw() + self.Anchor6:Draw() + self.Anchor7:Draw() + self.Anchor8:Draw() + self.Anchor9:Draw() + end, + + Initialise = function(self, returnFunc) + local new = {} -- the new instance + setmetatable( new, {__index = self} ) + new.Width = 27 + new.Height = 10 + new.Return = returnFunc + new.X = math.ceil((Drawing.Screen.Width - new.Width) / 2) + new.Y = math.ceil((Drawing.Screen.Height - new.Height) / 2) + new.Title = 'Resize Document' + new.Visible = true + + new.WidthTextBox = TextBox:Initialise(9, 5, 4, 1, new, tostring(Current.Artboard.Width), nil, nil, function() + new:UpdateAnchorButtons() + end, true) + new.HeightTextBox = TextBox:Initialise(9, 7, 4, 1, new, tostring(Current.Artboard.Height), nil, nil, function() + new:UpdateAnchorButtons() + end, true) + new.OkButton = Button:Initialise(new.Width - 4, new.Height - 1, nil, nil, colours.lightGrey, new, function(self, side, x, y, toggle) + local ok = true + new.WidthLabelHighlight = false + new.HeightLabelHighlight = false + + if #new.WidthTextBox.TextInput.Value == 0 or tonumber(new.WidthTextBox.TextInput.Value) <= 0 then + ok = false + new.WidthLabelHighlight = true + end + + if #new.HeightTextBox.TextInput.Value == 0 or tonumber(new.HeightTextBox.TextInput.Value) <= 0 then + ok = false + new.HeightLabelHighlight = true + end + + if ok then + returnFunc(new, tonumber(new.WidthTextBox.TextInput.Value), tonumber(new.HeightTextBox.TextInput.Value), new.AnchorPosition) + else + Draw() + end + end, 'Ok', colours.black) + + local anchorX = 15 + local anchorY = 5 + new.Anchor1 = Button:Initialise(anchorX, anchorY, 1, 1, colours.lightGrey, new, function(self, side, x, y, toggle)new.AnchorPosition = 1 new:UpdateAnchorButtons() end, ' ', colours.black) + new.Anchor2 = Button:Initialise(anchorX+1, anchorY, 1, 1, colours.lightGrey, new, function(self, side, x, y, toggle)new.AnchorPosition = 2 new:UpdateAnchorButtons() end, '^', colours.black) + new.Anchor3 = Button:Initialise(anchorX+2, anchorY, 1, 1, colours.lightGrey, new, function(self, side, x, y, toggle)new.AnchorPosition = 3 new:UpdateAnchorButtons() end, ' ', colours.black) + new.Anchor4 = Button:Initialise(anchorX, anchorY+1, 1, 1, colours.lightGrey, new, function(self, side, x, y, toggle)new.AnchorPosition = 4 new:UpdateAnchorButtons() end, '<', colours.black) + new.Anchor5 = Button:Initialise(anchorX+1, anchorY+1, 1, 1, colours.lightGrey, new, function(self, side, x, y, toggle)new.AnchorPosition = 5 new:UpdateAnchorButtons() end, '#', colours.black) + new.Anchor6 = Button:Initialise(anchorX+2, anchorY+1, 1, 1, colours.lightGrey, new, function(self, side, x, y, toggle)new.AnchorPosition = 6 new:UpdateAnchorButtons() end, '>', colours.black) + new.Anchor7 = Button:Initialise(anchorX, anchorY+2, 1, 1, colours.lightGrey, new, function(self, side, x, y, toggle)new.AnchorPosition = 7 new:UpdateAnchorButtons() end, ' ', colours.black) + new.Anchor8 = Button:Initialise(anchorX+1, anchorY+2, 1, 1, colours.lightGrey, new, function(self, side, x, y, toggle)new.AnchorPosition = 8 new:UpdateAnchorButtons() end, 'v', colours.black) + new.Anchor9 = Button:Initialise(anchorX+2, anchorY+2, 1, 1, colours.lightGrey, new, function(self, side, x, y, toggle)new.AnchorPosition = 9 new:UpdateAnchorButtons() end, ' ', colours.black) + + return new + end, + + UpdateAnchorButtons = function(self) + local anchor1 = ' ' + local anchor2 = ' ' + local anchor3 = ' ' + local anchor4 = ' ' + local anchor5 = ' ' + local anchor6 = ' ' + local anchor7 = ' ' + local anchor8 = ' ' + local anchor9 = ' ' + self.AnchorPosition = self.AnchorPosition or 5 + if self.AnchorPosition == 1 then + anchor1 = '#' + anchor2 = '>' + anchor4 = 'v' + elseif self.AnchorPosition == 2 then + anchor1 = '<' + anchor2 = '#' + anchor3 = '>' + anchor5 = 'v' + elseif self.AnchorPosition == 3 then + anchor2 = '<' + anchor3 = '#' + anchor6 = 'v' + elseif self.AnchorPosition == 4 then + anchor1 = '^' + anchor4 = '#' + anchor5 = '>' + anchor7 = 'v' + elseif self.AnchorPosition == 5 then + anchor2 = '^' + anchor4 = '<' + anchor5 = '#' + anchor6 = '>' + anchor8 = 'v' + elseif self.AnchorPosition == 6 then + anchor3 = '^' + anchor6 = '#' + anchor5 = '<' + anchor9 = 'v' + elseif self.AnchorPosition == 7 then + anchor4 = '^' + anchor7 = '#' + anchor8 = '>' + elseif self.AnchorPosition == 8 then + anchor5 = '^' + anchor8 = '#' + anchor7 = '<' + anchor9 = '>' + elseif self.AnchorPosition == 9 then + anchor6 = '^' + anchor9 = '#' + anchor8 = '<' + end + + if #self.HeightTextBox.TextInput.Value > 0 and Current.Artboard.Height > tonumber(self.HeightTextBox.TextInput.Value) then + local r = function(str) + if string.find(str, "%^") then + str = str:gsub('%^','v') + elseif string.find(str, "v") then + str = str:gsub('v','%^') + end + return str + end + anchor1 = r(anchor1) + anchor2 = r(anchor2) + anchor3 = r(anchor3) + anchor4 = r(anchor4) + anchor5 = r(anchor5) + anchor6 = r(anchor6) + anchor7 = r(anchor7) + anchor8 = r(anchor8) + anchor9 = r(anchor9) + end + + if #self.WidthTextBox.TextInput.Value > 0 and Current.Artboard.Width > tonumber(self.WidthTextBox.TextInput.Value) then + local r = function(str) + if string.find(str, ">") then + str = str:gsub('>','<') + elseif string.find(str, "<") then + str = str:gsub('<','>') + end + return str + end + anchor1 = r(anchor1) + anchor2 = r(anchor2) + anchor3 = r(anchor3) + anchor4 = r(anchor4) + anchor5 = r(anchor5) + anchor6 = r(anchor6) + anchor7 = r(anchor7) + anchor8 = r(anchor8) + anchor9 = r(anchor9) + end + + self.Anchor1.Text = anchor1 + self.Anchor2.Text = anchor2 + self.Anchor3.Text = anchor3 + self.Anchor4.Text = anchor4 + self.Anchor5.Text = anchor5 + self.Anchor6.Text = anchor6 + self.Anchor7.Text = anchor7 + self.Anchor8.Text = anchor8 + self.Anchor9.Text = anchor9 + end, + + Show = function(self) + Current.Window = self + return self + end, + + Close = function(self) + Current.Input = nil + Current.Window = nil + self = nil + end, + + Flash = function(self) + self.Visible = false + Draw() + sleep(0.15) + self.Visible = true + Draw() + sleep(0.15) + self.Visible = false + Draw() + sleep(0.15) + self.Visible = true + Draw() + end, + + ButtonClick = function(self, button, x, y) + if button.X <= x and button.Y <= y and button.X + button.Width > x and button.Y + button.Height > y then + button:Click() + end + end, + + Click = function(self, side, x, y) + local items = {self.OkButton, self.WidthTextBox, self.HeightTextBox, self.Anchor1, self.Anchor2, self.Anchor3, self.Anchor4, self.Anchor5, self.Anchor6, self.Anchor7, self.Anchor8, self.Anchor9} + for i, v in ipairs(items) do + if CheckClick(v, x, y) then + v:Click(side, x, y) + end + end + return true + end +} + +---------------------- + +function CheckOpenArtboard() + if Current.Artboard then + return true + else + return false + end +end + +function CheckSelectedLayer() + if Current.Artboard and Current.Layer then + return true + else + return false + end +end + +function DisplayNewDocumentWindow() + NewDocumentWindow:Initialise(function(self, success, path, width, height, format, backgroundColour) + if success then + if path:sub(-4) ~= format then + path = path .. format + end + local oldWindow = self + Current.Input = nil + Current.Window = nil + makeDocument = function()oldWindow:Close()NewDocument(path, width, height, format, backgroundColour)end + local _fs = fs + if OneOS then + _fs = OneOS.FS + end + if _fs.exists(path) then + ButtonDialougeWindow:Initialise('File Exists', path..' already exists! Use a different name and try again.', 'Ok', nil, function(window, ok) + window:Close() + oldWindow:Show() + end):Show() + elseif format == '.nfp' then + Current.Window = nil + ButtonDialougeWindow:Initialise('Use NFP?', 'The NFT format does not support text or layers, if you use it you will only be able to use 1 layer and not have any text.', 'Use NFP', 'Cancel', function(window, ok) + window:Close() + if ok then + makeDocument() + else + oldWindow:Show() + end + end):Show() + elseif format == '.nft' then + ButtonDialougeWindow:Initialise('Use NFT?', 'The NFT format does not support layers, if you use it you will only be able to use 1 layer.', 'Use NFT', 'Cancel', function(window, ok) + window:Close() + if ok then + makeDocument() + else + oldWindow:Show() + end + end):Show() + else + makeDocument() + end + + + else + self:Close() + end + end):Show() +end + +function NewDocument(path, width, height, format, backgroundColour) + local _fs = fs + if OneOS then + _fs = OneOS.FS + end + ab = Artboard:New(_fs.getName(path), path, width, height, format, backgroundColour) + Current.Tool = Tools[2] + Current.Toolbar:Update() + Current.Modified = false + Draw() +end + +function DisplayToolSizeWindow() + if not CheckOpenArtboard() then + return + end + TextDialougeWindow:Initialise('Change Tool Size', 'Enter the new tool size you\'d like to use.', 'Ok', 'Cancel', function(window, success, value) + if success then + Current.ToolSize = math.ceil(tonumber(value)) + if Current.ToolSize < 1 then + Current.ToolSize = 1 + elseif Current.ToolSize > 50 then + Current.ToolSize = 50 + end + ModuleNamed('Tools'):Update() + end + window:Close() + end, true):Show() +end + +--[[ + Attempt to figure out what format the image is if it doesn't have an extension +]]-- +function GetFormat(path) + local _fs = fs + if OneOS then + _fs = OneOS.FS + end + local file = _fs.open(path, 'r') + local content = file.readAll() + file.close() + if type(textutils.unserialize(content)) == 'table' then + -- It's a serlized table, asume sketch + return '.skch' + elseif string.find(content, string.char(30)) or string.find(content, string.char(31)) then + -- Contains the characters that set colours, asume nft + return '.nft' + else + -- Otherwise asume nfp + return '.nfp' + end +end + +function DisplayOpenDocumentWindow() + OpenDocumentWindow:Initialise(function(self, success, path) + self:Close() + if success then + OpenDocument(path) + end + end):Show() +end + + +local function Extension(path, addDot) + if not path then + return nil + elseif not string.find(fs.getName(path), '%.') then + if not addDot then + return fs.getName(path) + else + return '' + end + else + local _path = path + if path:sub(#path) == '/' then + _path = path:sub(1,#path-1) + end + local extension = _path:gmatch('\.[0-9a-z]+$')() + if extension then + extension = extension:sub(2) + else + --extension = nil + return '' + end + if addDot then + extension = '.'..extension + end + return extension:lower() + end +end + +local RemoveExtension = function(path) + if path:sub(1,1) == '.' then + return path + end + local extension = Extension(path) + if extension == path then + return fs.getName(path) + end + return string.gsub(path, extension, ''):sub(1, -2) +end +--[[ + Open a documet at a given path +]]-- +function OpenDocument(path) + local _fs = fs + if OneOS then + _fs = OneOS.FS + end + if _fs.exists(path) and not _fs.isDir(path) then + local format = Extension(path, true) + if (not format or format == '') and (format ~= '.nfp' and format ~= '.nft' and format ~= '.skch') then + format = GetFormat(path) + end + local layers = {} + if format == '.nfp' then + layers = ReadNFP(path) + elseif format == '.nft' then + layers = ReadNFT(path) + elseif format == '.skch' then + layers = ReadSKCH(path) + end + + for i, layer in ipairs(layers) do + if layer.Visible == nil then + layer.Visible = true + end + if layer.Index == nil then + layer.Index = 1 + end + if layer.Name == nil then + if layer.Index == 1 then + layer.Name = 'Background' + else + layer.Name = 'Layer' + end + end + if layer.BackgroundColour == nil then + layer.BackgroundColour = colours.white + end + end + + if not layers[1] then + --log('File could not be read.') + return + end + + local width = #layers[1].Pixels + local height = #layers[1].Pixels[1] + + Current.Artboard = nil + local _fs = fs + if OneOS then + _fs = OneOS.FS + end + ab = Artboard:New(_fs.getName('Image'), path, width, height, format, nil, layers) + Current.Tool = Tools[2] + Current.Toolbar:Update() + Current.Modified = false + Draw() + end +end + +function MakeNewLayer() + if not CheckOpenArtboard() then + return + end + if Current.Artboard.Format == '.skch' then + TextDialougeWindow:Initialise('New Layer Name', 'Enter the name you want for the next layer.', 'Ok', 'Cancel', function(window, success, value) + if success then + Current.Artboard:MakeLayer(value, colours.transparent) + end + window:Close() + end):Show() + else + local format = 'NFP' + if Current.Artboard.Format == '.nft' then + format = 'NFT' + end + ButtonDialougeWindow:Initialise(format..' does not support layers!', 'The format you are using, '..format..', does not support multiple layers. Use SKCH to have more than one layer.', 'Ok', nil, function(window) + window:Close() + end):Show() + end +end + +function ResizeDocument() + if not CheckOpenArtboard() then + return + end + ResizeDocumentWindow:Initialise(function(window, width, height, anchor) + window:Close() + local topResize = 0 + local rightResize = 0 + local bottomResize = 0 + local leftResize = 0 + + if anchor == 1 then + rightResize = 1 + bottomResize = 1 + elseif anchor == 2 then + rightResize = 0.5 + leftResize = 0.5 + bottomResize = 1 + elseif anchor == 3 then + leftResize = 1 + bottomResize = 1 + elseif anchor == 4 then + rightResize = 1 + bottomResize = 0.5 + topResize = 0.5 + elseif anchor == 5 then + rightResize = 0.5 + leftResize = 0.5 + bottomResize = 0.5 + topResize = 0.5 + elseif anchor == 6 then + leftResize = 1 + bottomResize = 0.5 + topResize = 0.5 + elseif anchor == 7 then + rightResize = 1 + topResize = 1 + elseif anchor == 8 then + rightResize = 0.5 + leftResize = 0.5 + topResize = 1 + elseif anchor == 9 then + leftResize = 1 + topResize = 1 + end + + topResize = topResize * (height - Current.Artboard.Height) + if topResize > 0 then + topResize = math.floor(topResize) + else + topResize = math.ceil(topResize) + end + + bottomResize = bottomResize * (height - Current.Artboard.Height) + if bottomResize > 0 then + bottomResize = math.ceil(bottomResize) + else + bottomResize = math.floor(bottomResize) + end + + leftResize = leftResize * (width - Current.Artboard.Width) + if leftResize > 0 then + leftResize = math.floor(leftResize) + else + leftResize = math.ceil(leftResize) + end + + rightResize = rightResize * (width - Current.Artboard.Width) + if rightResize > 0 then + rightResize = math.ceil(rightResize) + else + rightResize = math.floor(rightResize) + end + + Current.Artboard:Resize(topResize, bottomResize, leftResize, rightResize) + end):Show() +end + +function RenameLayer() + if not CheckOpenArtboard() then + return + end + if Current.Artboard.Format == '.skch' then + TextDialougeWindow:Initialise("Rename Layer '"..Current.Layer.Name.."'", 'Enter the new name you want the layer to be called.', 'Ok', 'Cancel', function(window, success, value) + if success then + Current.Layer.Name = value + end + window:Close() + end):Show() + else + local format = 'NFP' + if Current.Artboard.Format == '.nft' then + format = 'NFT' + end + ButtonDialougeWindow:Initialise(format..' does not support layers!', 'The format you are using, '..format..', does not support renaming layers. Use SKCH to rename layers.', 'Ok', nil, function(window) + window:Close() + end):Show() + end +end + +function DeleteLayer() + if not CheckOpenArtboard() then + return + end + if Current.Artboard.Format == '.skch' then + if #Current.Artboard.Layers > 1 then + ButtonDialougeWindow:Initialise("Delete Layer '"..Current.Layer.Name.."'?", 'Are you sure you want delete the layer?', 'Ok', 'Cancel', function(window, success) + if success then + Current.Layer:Remove() + end + window:Close() + end):Show() + else + ButtonDialougeWindow:Initialise('Can not delete layer!', 'You can not delete the last layer of an image! Make another layer to delete this one.', 'Ok', nil, function(window) + window:Close() + end):Show() + end + else + local format = 'NFP' + if Current.Artboard.Format == '.nft' then + format = 'NFT' + end + ButtonDialougeWindow:Initialise(format..' does not support layers!', 'The format you are using, '..format..', does not support deleting layers. Use SKCH to deleting layers.', 'Ok', nil, function(window) + window:Close() + end):Show() + end +end + +needsDraw = false +isDrawing = false +function Draw() + if isDrawing then + needsDraw = true + return + end + needsDraw = false + isDrawing = true + if not Current.Window then + Drawing.Clear(UIColours.Background) + else + Drawing.DrawArea(1, 2, Drawing.Screen.Width, Drawing.Screen.Height, '|', colours.black, colours.lightGrey) + end + + if Current.Artboard then + ab:Draw() + end + + if Current.InterfaceVisible then + Current.MenuBar:Draw() + Current.Toolbar.Width = Current.Toolbar.ExpandedWidth + Current.Toolbar:Draw() + else + Current.Toolbar.Width = Current.Toolbar.ExpandedWidth + end + + if Current.InterfaceVisible and Current.Menu then + Current.Menu:Draw() + end + + if Current.Window then + Current.Window:Draw() + end + + if not Current.InterfaceVisible then + ShowInterfaceButton:Draw() + end + + Drawing.DrawBuffer() + if Current.Input and not Current.Menu then + term.setCursorPos(Current.CursorPos[1], Current.CursorPos[2]) + term.setCursorBlink(true) + term.setTextColour(Current.CursorColour) + else + term.setCursorBlink(false) + end + + if Current.Selection and Current.Selection[1] and Current.Selection[2] ~= nil then + Current.SelectionDrawTimer = os.startTimer(0.5) + end + isDrawing = false + if needsDraw then + Draw() + end +end + +function LoadMenuBar() + Current.MenuBar = MenuBar:Initialise({ + Button:Initialise(1, 1, nil, nil, colours.grey, Current.MenuBar, function(self, side, x, y, toggle) + if toggle then + Menu:New(1, 2, { + { + Title = "New...", + Click = function() + DisplayNewDocumentWindow() + end, + Keys = { + keys.leftCtrl, + keys.n + } + }, + { + Title = 'Open...', + Click = function() + DisplayOpenDocumentWindow() + end, + Keys = { + keys.leftCtrl, + keys.o + } + }, + { + Separator = true + }, + { + Title = 'Save...', + Click = function() + Current.Artboard:Save() + end, + Keys = { + keys.leftCtrl, + keys.s + }, + Enabled = function() + return CheckOpenArtboard() + end + }, + { + Separator = true + }, + { + Title = 'Quit', + Click = function() + if Close() then + OneOS.Close() + end + end + }, + --[[ + { + Title = 'Save As...', + Click = function() + + end + } + ]]-- + }, self, true) + else + Current.Menu = nil + end + return true + end, 'File', colours.lightGrey, false), + Button:Initialise(7, 1, nil, nil, colours.grey, Current.MenuBar, function(self, side, x, y, toggle) + if not self.Toggle then + Menu:New(7, 2, { + --[[ + { + Title = "Undo", + Click = function() + end, + Keys = { + keys.leftCtrl, + keys.z + }, + Enabled = function() + return false + end + }, + { + Title = 'Redo', + Click = function() + + end, + Keys = { + keys.leftCtrl, + keys.y + }, + Enabled = function() + return false + end + }, + { + Separator = true + }, + ]]-- + { + Title = 'Cut', + Click = function() + Clipboard.Cut(Current.Layer:PixelsInSelection(true), 'sketchpixels') + end, + Keys = { + keys.leftCtrl, + keys.x + }, + Enabled = function() + return Current.Selection and Current.Selection[1] and Current.Selection[2] ~= nil + end + }, + { + Title = 'Copy', + Click = function() + Clipboard.Copy(Current.Layer:PixelsInSelection(), 'sketchpixels') + end, + Keys = { + keys.leftCtrl, + keys.c + }, + Enabled = function() + return Current.Selection and Current.Selection[1] and Current.Selection[2] ~= nil + end + }, + { + Title = 'Paste', + Click = function() + Current.Layer:InsertPixels(Clipboard.Paste()) + end, + Keys = { + keys.leftCtrl, + keys.v + }, + Enabled = function() + return (not Clipboard.isEmpty()) and Clipboard.Type == 'sketchpixels' + end + } + }, self, true) + else + Current.Menu = nil + end + return true + end, 'Edit', colours.lightGrey, false), + Button:Initialise(13, 1, nil, nil, colours.grey, Current.MenuBar, function(self, side, x, y, toggle) + if toggle then + Menu:New(13, 2, { + { + Title = "Resize...", + Click = function() + ResizeDocument() + end, + Keys = { + keys.leftCtrl, + keys.r + }, + Enabled = function() + return CheckOpenArtboard() + end + }, + { + Title = "Crop", + Click = function() + local top = 0 + local left = 0 + local bottom = 0 + local right = 0 + if Current.Selection[1].x < Current.Selection[2].x then + left = Current.Selection[1].x - 1 + right = Current.Artboard.Width - Current.Selection[2].x + else + left = Current.Selection[2].x - 1 + right = Current.Artboard.Width - Current.Selection[1].x + end + if Current.Selection[1].y < Current.Selection[2].y then + top = Current.Selection[1].y - 1 + bottom = Current.Artboard.Height - Current.Selection[2].y + else + top = Current.Selection[2].y - 1 + bottom = Current.Artboard.Height - Current.Selection[1].y + end + Current.Artboard:Resize(-1*top, -1*bottom, -1*left, -1*right) + + Current.Selection[2] = nil + end, + Enabled = function() + if CheckSelectedLayer() and Current.Selection and Current.Selection[1] and Current.Selection[2] ~= nil then + return true + else + return false + end + end + }, + { + Separator = true + }, + { + Title = 'New Layer...', + Click = function() + MakeNewLayer() + end, + Keys = { + keys.leftCtrl, + keys.l + }, + Enabled = function() + return CheckOpenArtboard() + end + }, + { + Title = 'Delete Layer', + Click = function() + DeleteLayer() + end, + Enabled = function() + return CheckSelectedLayer() + end + }, + { + Title = 'Rename Layer...', + Click = function() + RenameLayer() + end, + Enabled = function() + return CheckSelectedLayer() + end + }, + { + Separator = true + }, + { + Title = 'Erase Selection', + Click = function() + Current.Layer:EraseSelection() + end, + Keys = { + keys.delete + }, + Enabled = function() + if CheckSelectedLayer() and Current.Selection and Current.Selection[1] and Current.Selection[2] ~= nil then + return true + else + return false + end + end + }, + { + Separator = true + }, + { + Title = 'Hide Interface', + Click = function() + Current.InterfaceVisible = not Current.InterfaceVisible + end, + Keys = { + keys.tab + } + } + }, self, true) + else + Current.Menu = nil + end + return true + end, 'Image', colours.lightGrey, false), + + Button:Initialise(20, 1, nil, nil, colours.grey, Current.MenuBar, function(self, side, x, y, toggle) + if toggle then + local menuItems = {{ + Title = "Change Size", + Click = function() + DisplayToolSizeWindow() + end, + Keys = { + keys.leftCtrl, + keys.t + } + }, + { + Separator = true + } + } + + local _keys = {'h','p','e','f','s','m','t'} + for i, tool in ipairs(Tools) do + table.insert(menuItems, { + Title = tool.Name, + Click = function() + SetTool(tool) + local m = ModuleNamed('Tools') + m:Update(m.ToolbarItem) + end, + Keys = { + keys[_keys[i]] + }, + Enabled = function() + return CheckOpenArtboard() + end + }) + end + + Menu:New(20, 2, menuItems, self, true) + else + Current.Menu = nil + end + return true + end, 'Tools', colours.lightGrey, false), + }) +end + +function Timer(event, timer) + if timer == Current.ControlPressedTimer then + Current.ControlPressedTimer = nil + elseif timer == Current.SelectionDrawTimer then + if Current.Artboard then + Current.Artboard.SelectionIsBlack = not Current.Artboard.SelectionIsBlack + Draw() + end + end +end + +function Initialise(arg) + if not OneOS then + SplashScreen() + end + EventRegister('mouse_click', TryClick) + EventRegister('mouse_drag', function(event, side, x, y)TryClick(event, side, x, y, true)end) + EventRegister('mouse_scroll', Scroll) + EventRegister('key', HandleKey) + EventRegister('char', HandleKey) + EventRegister('timer', Timer) + EventRegister('terminate', function(event) if Close() then error( "Terminated", 0 ) end end) + + + Current.Toolbar = Toolbar:New('right', true) + + for k, v in pairs(Modules) do + v:Initialise() + end + + --term.setBackgroundColour(UIColours.Background) + --term.clear() + + LoadMenuBar() + + local _fs = fs + if OneOS then + _fs = OneOS.FS + end + if arg and _fs.exists(arg) then + OpenDocument(arg) + else + DisplayNewDocumentWindow() + Current.Window.Visible = false + end + + ShowInterfaceButton = Button:Initialise(Drawing.Screen.Width - 15, 1, nil, 1, colours.grey, nil, function(self) + Current.InterfaceVisible = true + Draw() + end, 'Show Interface') + + Draw() + if Current.Window then + Current.Window.Visible = true + Draw() + end + + EventHandler() +end + +function SplashScreen() + local splashIcon = {{1,1,1,256,256,256,256,256,256,256,256,1,1,1,},{1,256,256,8,8,8,8,8,8,8,8,256,256,1,},{256,8,8,8,8,8,8,8,8,8,8,8,8,256,},{256,256,256,8,8,8,8,8,8,8,8,256,256,256,},{256,256,256,256,256,256,256,256,256,256,256,256,256,256,},{2048,2048,256,256,256,256,256,256,256,256,256,256,2048,2048,},{2048,2048,2048,2048,2048,2048,2048,2048,2048,2048,2048,2048,2048,2048,},{2048,2048,2048,2048,2048,2048,2048,2048,2048,2048,2048,2048,2048,2048,},{2048,2048,2048,2048,2048,2048,2048,2048,2048,2048,2048,2048,2048,2048,},{2048,2048,2048,2048,2048,2048,2048,2048,2048,2048,2048,2048,2048,2048,},{256,256,2048,2048,2048,2048,2048,2048,2048,2048,2048,2048,256,256,},{1,256,256,256,256,256,256,256,256,256,256,256,256,1,},{1,1,1,256,256,256,256,256,256,256,256,1,1,1,},["text"]={{" "," "," "," "," "," "," "," "," "," "," "," "," "," ",},{" "," "," "," "," "," "," "," "," "," "," "," "," "," ",},{" "," "," "," "," "," "," "," "," "," "," "," "," "," ",},{" "," "," "," "," "," "," "," "," "," "," "," "," "," ",},{" "," "," "," "," "," "," "," "," "," "," "," "," "," ",},{" "," "," "," "," "," "," "," "," "," "," "," "," "," ",},{" "," "," "," "," "," "," "," "," "," "," "," "," "," ",},{" "," "," "," ","S","k","e","t","c","h"," "," "," "," ",},{" "," "," "," "," "," ","b","y"," "," "," "," "," "," ",},{" "," "," "," "," ","o","e","e","d"," "," "," "," "," ",},{" "," "," "," "," "," "," "," "," "," "," "," "," "," ",},{" "," "," "," "," "," "," "," "," "," "," "," "," "," ",},{" "," "," "," "," "," "," "," "," "," "," "," "," "," ",},},["textcol"]={{32768,32768,32768,32768,32768,32768,32768,32768,32768,32768,32768,32768,32768,32768,},{32768,32768,32768,256,32768,32768,32768,32768,32768,32768,32768,32768,32768,32768,},{32768,32768,32768,32768,32768,32768,32768,32768,32768,32768,32768,32768,32768,32768,},{32768,32768,32768,32768,32768,32768,32768,32768,32768,32768,32768,32768,32768,32768,},{32768,32768,32768,32768,32768,32768,32768,32768,32768,32768,32768,32768,32768,32768,},{32768,32768,32768,32768,32768,32768,32768,32768,32768,32768,32768,32768,32768,32768,},{32768,32768,32768,32768,32768,32768,32768,32768,32768,32768,32768,32768,32768,32768,},{32768,32768,32768,32768,1,1,1,1,1,1,32768,32768,32768,32768,},{32768,32768,32768,32768,8,8,8,8,8,8,8,32768,32768,32768,},{32768,32768,32768,32768,1,1,1,1,1,32768,8,32768,32768,32768,},{32768,32768,32768,32768,32768,32768,32768,32768,32768,32768,32768,32768,32768,32768,},{32768,32768,32768,32768,32768,32768,32768,32768,32768,32768,32768,32768,32768,32768,},{32768,32768,32768,32768,32768,32768,32768,32768,32768,32768,32768,32768,32768,32768,},},} + Drawing.Clear(colours.white) + Drawing.DrawImage((Drawing.Screen.Width - 14)/2, (Drawing.Screen.Height - 13)/2, splashIcon, 14, 13) + Drawing.DrawBuffer() + parallel.waitForAny(function()sleep(1)end, function()os.pullEvent('mouse_click')end) +end + +LongestString = function(input, key) + local length = 0 + for i = 1, #input do + local value = input[i] + if key then + if value[key] then + value = value[key] + else + value = '' + end + end + local titleLength = string.len(value) + if titleLength > length then + length = titleLength + end + end + return length +end + +function HandleKey(...) + local args = {...} + local event = args[1] + local keychar = args[2] + if event == 'key' and Current.Tool and Current.Tool.Name == 'Text' and Current.Input and (keychar == keys.up or keychar == keys.down or keychar == keys.left or keychar == keys.right) then + local currentPos = {Current.CursorPos[1] - Current.Artboard.X + 1, Current.CursorPos[2] - Current.Artboard.Y + 1} + if keychar == keys.up then + currentPos[2] = currentPos[2] - 1 + elseif keychar == keys.down then + currentPos[2] = currentPos[2] + 1 + elseif keychar == keys.left then + currentPos[1] = currentPos[1] - 1 + elseif keychar == keys.right then + currentPos[1] = currentPos[1] + 1 + end + + if currentPos[1] < 1 then + currentPos[1] = 1 + end + + if currentPos[1] > Current.Artboard.Width then + currentPos[1] = Current.Artboard.Width + end + + if currentPos[2] < 1 then + currentPos[2] = 1 + end + + if currentPos[2] > Current.Artboard.Height then + currentPos[2] = Current.Artboard.Height + end + + Current.Tool:Use(currentPos[1], currentPos[2]) + Current.Modified = true + Draw() + elseif Current.Input then + if event == 'char' then + Current.Input:Char(keychar) + elseif event == 'key' then + Current.Input:Key(keychar) + end + elseif event == 'key' then + CheckKeyboardShortcut(keychar) + end +end + +function Scroll(event, direction, x, y) + if Current.Window and Current.Window.OpenButton then + Current.Window.Scroll = Current.Window.Scroll + direction + if Current.Window.Scroll < 0 then + Current.Window.Scroll = 0 + elseif Current.Window.Scroll > Current.Window.MaxScroll then + Current.Window.Scroll = Current.Window.MaxScroll + end + end + Draw() +end + +function CheckKeyboardShortcut(key) + local shortcuts = {} + + if key == keys.leftCtrl then + Current.ControlPressedTimer = os.startTimer(0.5) + return + end + if Current.ControlPressedTimer then + shortcuts[keys.n] = function() DisplayNewDocumentWindow() end + shortcuts[keys.o] = function() DisplayOpenDocumentWindow() end + shortcuts[keys.s] = function() Current.Artboard:Save() end + shortcuts[keys.x] = function() if Current.Selection and Current.Selection[1] and Current.Selection[2] ~= nil then Clipboard.Cut(Current.Layer:PixelsInSelection(true), 'sketchpixels') end end + shortcuts[keys.c] = function() if Current.Selection and Current.Selection[1] and Current.Selection[2] ~= nil then Clipboard.Copy(Current.Layer:PixelsInSelection(), 'sketchpixels') end end + shortcuts[keys.v] = function() if (not Clipboard.isEmpty()) and Clipboard.Type == 'sketchpixels' then Current.Layer:InsertPixels(Clipboard.Paste()) end end + shortcuts[keys.r] = function() ResizeDocument() end + shortcuts[keys.l] = function() MakeNewLayer() end + end + + shortcuts[keys.delete] = function() if CheckSelectedLayer() and Current.Selection and Current.Selection[1] and Current.Selection[2] ~= nil then Current.Layer:EraseSelection() Draw() end end + shortcuts[keys.backspace] = shortcuts[keys.delete] + shortcuts[keys.tab] = function() Current.InterfaceVisible = not Current.InterfaceVisible Draw() end + + shortcuts[keys.h] = function() SetTool(ToolNamed('Hand')) ModuleNamed('Tools'):Update() Draw() end + shortcuts[keys.e] = function() SetTool(ToolNamed('Eraser')) ModuleNamed('Tools'):Update() Draw() end + shortcuts[keys.p] = function() SetTool(ToolNamed('Pencil')) ModuleNamed('Tools'):Update() Draw() end + shortcuts[keys.f] = function() SetTool(ToolNamed('Fill Bucket')) ModuleNamed('Tools'):Update() Draw() end + shortcuts[keys.m] = function() SetTool(ToolNamed('Move')) ModuleNamed('Tools'):Update() Draw() end + shortcuts[keys.s] = function() SetTool(ToolNamed('Select')) ModuleNamed('Tools'):Update() Draw() end + shortcuts[keys.t] = function() SetTool(ToolNamed('Text')) ModuleNamed('Tools'):Update() Draw() end + + if shortcuts[key] then + shortcuts[key]() + return true + else + return false + end +end + +--[[ + Check if the given object falls under the click coordinates +]]-- +function CheckClick(object, x, y) + if object.X <= x and object.Y <= y and object.X + object.Width > x and object.Y + object.Height > y then + return true + end +end + +--[[ + Attempt to clicka given object +]]-- +function DoClick(object, side, x, y, drag) + if object and CheckClick(object, x, y) then + return object:Click(side, x - object.X + 1, y - object.Y + 1, drag) + end +end + +--[[ + Try to click at the given coordinates +]]-- +function TryClick(event, side, x, y, drag) + if Current.InterfaceVisible and Current.Menu then + if DoClick(Current.Menu, side, x, y, drag) then + Draw() + return + else + if Current.Menu.Owner and Current.Menu.Owner.Toggle then + Current.Menu.Owner.Toggle = false + end + Current.Menu = nil + Draw() + return + end + elseif Current.Window then + if DoClick(Current.Window, side, x, y, drag) then + Draw() + return + else + Current.Window:Flash() + return + end + end + local interfaceElements = {} + + if Current.InterfaceVisible then + table.insert(interfaceElements, Current.MenuBar) + else + table.insert(interfaceElements, ShowInterfaceButton) + end + + for i, v in ipairs(Lists.Interface.Toolbars) do + for i, v2 in ipairs(v.ToolbarItems) do + table.insert(interfaceElements, v2) + end + table.insert(interfaceElements, v) + end + + table.insert(interfaceElements, Current.Artboard) + + for i, object in ipairs(interfaceElements) do + if DoClick(object, side, x, y, drag) then + Draw() + return + end + end + Draw() +end + +--[[ + Registers functions to run on certain events +]]-- +function EventRegister(event, func) + if not Events[event] then + Events[event] = {} + end + + table.insert(Events[event], func) +end + +--[[ + The main loop event handler, runs registered event functinos +]]-- +function EventHandler() + while true do + local event, arg1, arg2, arg3, arg4 = os.pullEventRaw() + if Events[event] then + for i, e in ipairs(Events[event]) do + e(event, arg1, arg2, arg3, arg4) + end + end + end +end + +--[[ + Thanks to NitrogenFingers for the colour functions and NFT + NFP read/write functions +]]-- + +--[[ + Gets the hex value from a colour +]]-- +local hexnums = { [10] = "a", [11] = "b", [12] = "c", [13] = "d", [14] = "e" , [15] = "f" } +local function getHexOf(colour) + if colour == colours.transparent or not colour or not tonumber(colour) then + return " " + end + local value = math.log(colour)/math.log(2) + if value > 9 then + value = hexnums[value] + end + return value +end + +--[[ + Gets the colour from a hex value +]]-- +local function getColourOf(hex) + if hex == ' ' then + return colours.transparent + end + local value = tonumber(hex, 16) + if not value then return nil end + value = math.pow(2,value) + return value +end + +--[[ + Saves the current artboard in .skch format +]]-- +function SaveSKCH() + local layers = {} + for i, l in ipairs(Current.Artboard.Layers) do + local pixels = SaveNFT(i) + local layer = { + Name = l.Name, + Pixels = pixels, + BackgroundColour = l.BackgroundColour, + Visible = l.Visible, + Index = l.Index, + } + table.insert(layers, layer) + end + return layers +end + +--[[ + Saves the current artboard in .nft format +]]-- +function SaveNFT(layer) + layer = layer or 1 + local lines = {} + local width = Current.Artboard.Width + local height = Current.Artboard.Height + for y = 1, height do + local line = '' + local currentBackgroundColour = nil + local currentTextColour = nil + for x = 1, width do + local pixel = Current.Artboard.Layers[layer].Pixels[x][y] + if pixel.BackgroundColour ~= currentBackgroundColour then + line = line..string.char(30)..getHexOf(pixel.BackgroundColour) + currentBackgroundColour = pixel.BackgroundColour + end + if pixel.TextColour ~= currentTextColour then + line = line..string.char(31)..getHexOf(pixel.TextColour) + currentTextColour = pixel.TextColour + end + line = line .. pixel.Character + end + table.insert(lines, line) + end + return lines +end + +--[[ + Saves the current artboard in .nfp format +]]-- +function SaveNFP() + local lines = {} + local width = Current.Artboard.Width + local height = Current.Artboard.Height + for y = 1, height do + local line = '' + for x = 1, width do + line = line .. getHexOf(Current.Artboard.Layers[1].Pixels[x][y].BackgroundColour) + end + table.insert(lines, line) + end + return lines +end + +--[[ + Reads a .nfp file from the given path +]]-- +function ReadNFP(path) + local pixels = {} + local _fs = fs + if OneOS then + _fs = OneOS.FS + end + local file = _fs.open(path, 'r') + local line = file.readLine() + local y = 1 + while line do + for x = 1, #line do + if not pixels[x] then + pixels[x] = {} + end + pixels[x][y] = {BackgroundColour = getColourOf(line:sub(x,x))} + end + y = y + 1 + line = file.readLine() + end + file.close() + return {{Pixels = pixels}} +end + +--[[ + Reads a .nft file from the given path +]]-- +function ReadNFT(path) + local _fs = fs + if OneOS then + _fs = OneOS.FS + end + local file = _fs.open(path, 'r') + local line = file.readLine() + local lines = {} + while line do + table.insert(lines, line) + line = file.readLine() + end + file.close() + return {{Pixels = ParseNFT(lines)}} +end + +--[[ + Converts the lines of an .nft document to readble pixel data +]]-- +function ParseNFT(lines) + local pixels = {} + for y, line in ipairs(lines) do + local bgNext, fgNext = false, false + local currBG, currFG = nil,nil + local writePosition = 1 + for x = 1, #line do + if not pixels[writePosition] then + pixels[writePosition] = {} + end + + local nextChar = string.sub(line, x, x) + if nextChar:byte() == 30 then + bgNext = true + elseif nextChar:byte() == 31 then + fgNext = true + elseif bgNext then + currBG = getColourOf(nextChar) + if currBG == nil then + currBG = colours.transparent + end + bgNext = false + elseif fgNext then + currFG = getColourOf(nextChar) + fgNext = false + else + if nextChar ~= " " and currFG == nil then + currFG = colours.white + end + pixels[writePosition][y] = {BackgroundColour = currBG, TextColour = currFG, Character = nextChar} + writePosition = writePosition + 1 + end + end + end + return pixels +end + +--[[ + Read a .skch file from the given path +]]-- +function ReadSKCH(path) + local _fs = fs + if OneOS then + _fs = OneOS.FS + end + local file = _fs.open(path, 'r') + local _layers = textutils.unserialize(file.readAll()) + file.close() + local layers = {} + + for i, l in ipairs(_layers) do + local layer = { + Name = l.Name, + Pixels = ParseNFT(l.Pixels), + BackgroundColour = l.BackgroundColour, + Visible = l.Visible, + Index = l.Index, + } + table.insert(layers, layer) + end + return layers +end + +--[[ + Start the program after all functions and tables are loaded +]]-- +if term.isColor and term.isColor() then + Initialise(...) +else + print('Sorry, but Sketch only works on Advanced (gold) Computers') +end diff --git a/MetroOS/README.md b/MetroOS/README.md new file mode 100644 index 0000000..b27d30f --- /dev/null +++ b/MetroOS/README.md @@ -0,0 +1,10 @@ +# CarbonOS +Welcome to Carbon! This OS will have tons of features, a GUI, and so much more! +But keep in mind, the OS is still in beta and might have crashes and bugs. +Please report all issues on the Issues tab! +To test it out type pastebin run 5BsVEnu3 +This OS does not auto-update yet but it will auto-update soon! + +[Visit Page](http://carbonos.tk/) + +**THIS OS IS DISCONTINUED AND WILL NO LONGER BE RECEVING ANY UPDATES** diff --git a/MetroOS/System/.version b/MetroOS/System/.version new file mode 100644 index 0000000..752d822 --- /dev/null +++ b/MetroOS/System/.version @@ -0,0 +1 @@ +1.0.5.3 diff --git a/MetroOS/System/APIs/crasher b/MetroOS/System/APIs/crasher new file mode 100644 index 0000000..30056fc --- /dev/null +++ b/MetroOS/System/APIs/crasher @@ -0,0 +1,12 @@ +-- This is a very old crash handler. +-- I have kept it here for you to look at it but the new crash handler uses pcall instead of functions +function crash(message) + term.setBackgroundColor(colors.blue) + term.setCursorPos(1,1) + print("Sorry! CarbonOS has Encountered a Serious Error!") + print("Error: ", message) + print() + print("Press any key to reboot.") + os.pullEvent("key") + os.reboot() +end diff --git a/MetroOS/System/APIs/sha256 b/MetroOS/System/APIs/sha256 new file mode 100644 index 0000000..df0a7c5 --- /dev/null +++ b/MetroOS/System/APIs/sha256 @@ -0,0 +1,197 @@ +-- SHA-256 implementation in CC-Lua +-- HMAC and PBKDF2 with SHA-256 +-- By Anavrins + +-- For more help and details, you can PM me on the CC forums, http://www.computercraft.info/forums2/index.php?/user/12870-anavrins/ +-- You can use this code in your projects without asking me, as long as credit is given by keeping the first three lines of this paste. + +local mod32 = 2^32 +local sha_hashlen = 32 +local sha_blocksize = 64 + +local band = bit32 and bit32.band or bit.band +local bnot = bit32 and bit32.bnot or bit.bnot +local bxor = bit32 and bit32.bxor or bit.bxor +local blshift = bit32 and bit32.lshift or bit.blshift +local upack = unpack + +local function rrotate(n, b) + local s = n/(2^b) + local f = s%1 + return (s-f) + f*mod32 +end +local function brshift(int, by) -- Thanks bit32 for bad rshift + local s = int / (2^by) + return s - s%1 +end + +local H = { + 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, + 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19, +} + +local K = { + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2, +} + +local function counter(incr) + local t1, t2 = 0, 0 + if 0xFFFFFFFF - t1 < incr then + t2 = t2 + 1 + t1 = incr - (0xFFFFFFFF - t1) - 1 + else t1 = t1 + incr + end + return t2, t1 +end + +local function BE_toInt(bs, i) + return blshift((bs[i] or 0), 24) + blshift((bs[i+1] or 0), 16) + blshift((bs[i+2] or 0), 8) + (bs[i+3] or 0) +end + +local function preprocess(data) + local len = #data + local proc = {} + data[#data+1] = 0x80 + while #data%64~=56 do data[#data+1] = 0 end + local blocks = math.ceil(#data/64) + for i = 1, blocks do + proc[i] = {} + for j = 1, 16 do + proc[i][j] = BE_toInt(data, 1+((i-1)*64)+((j-1)*4)) + end + end + proc[blocks][15], proc[blocks][16] = counter(len*8) + return proc +end + +local function digestblock(w, C) + for j = 17, 64 do + local v = w[j-15] + local s0 = bxor(bxor(rrotate(w[j-15], 7), rrotate(w[j-15], 18)), brshift(w[j-15], 3)) + local s1 = bxor(bxor(rrotate(w[j-2], 17), rrotate(w[j-2], 19)), brshift(w[j-2], 10)) + w[j] = (w[j-16] + s0 + w[j-7] + s1)%mod32 + end + local a, b, c, d, e, f, g, h = upack(C) + for j = 1, 64 do + local S1 = bxor(bxor(rrotate(e, 6), rrotate(e, 11)), rrotate(e, 25)) + local ch = bxor(band(e, f), band(bnot(e), g)) + local temp1 = (h + S1 + ch + K[j] + w[j])%mod32 + local S0 = bxor(bxor(rrotate(a, 2), rrotate(a, 13)), rrotate(a, 22)) + local maj = bxor(bxor(band(a, b), band(a, c)), band(b, c)) + local temp2 = (S0 + maj)%mod32 + h, g, f, e, d, c, b, a = g, f, e, (d+temp1)%mod32, c, b, a, (temp1+temp2)%mod32 + end + C[1] = (C[1] + a)%mod32 + C[2] = (C[2] + b)%mod32 + C[3] = (C[3] + c)%mod32 + C[4] = (C[4] + d)%mod32 + C[5] = (C[5] + e)%mod32 + C[6] = (C[6] + f)%mod32 + C[7] = (C[7] + g)%mod32 + C[8] = (C[8] + h)%mod32 + return C +end + +local mt = { + __tostring = function(a) return string.char(unpack(a)) end, + __index = { + toHex = function(self, s) return ("%02x"):rep(#self):format(unpack(self)) end, + isEqual = function(self, t) + if type(t) ~= "table" then return false end + if #self ~= #t then return false end + local ret = 0 + for i = 1, #self do + ret = bit32.bor(ret, bxor(self[i], t[i])) + end + return ret == 0 + end + } +} + +local function toBytes(t, n) + local b = {} + for i = 1, n do + b[(i-1)*4+1] = band(brshift(band(t[i], 0xFF000000), 24), 0xFF) + b[(i-1)*4+2] = band(brshift(band(t[i], 0xFF0000), 16), 0xFF) + b[(i-1)*4+3] = band(brshift(band(t[i], 0xFF00), 8), 0xFF) + b[(i-1)*4+4] = band(t[i], 0xFF) + end + return setmetatable(b, mt) +end + +function digest(data) + data = data or "" + data = type(data) == "string" and {data:byte(1,-1)} or data + + data = preprocess(data) + local C = {upack(H)} + for i = 1, #data do C = digestblock(data[i], C) end + return toBytes(C, 8) +end + +function hmac(data, key) + local data = type(data) == "table" and {upack(data)} or {tostring(data):byte(1,-1)} + local key = type(key) == "table" and {upack(key)} or {tostring(key):byte(1,-1)} + + local blocksize = sha_blocksize + + key = #key > blocksize and digest(key) or key + + local ipad = {} + local opad = {} + local padded_key = {} + + for i = 1, blocksize do + ipad[i] = bxor(0x36, key[i] or 0) + opad[i] = bxor(0x5C, key[i] or 0) + end + + for i = 1, #data do + ipad[blocksize+i] = data[i] + end + + ipad = digest(ipad) + + for i = 1, blocksize do + padded_key[i] = opad[i] + padded_key[blocksize+i] = ipad[i] + end + + return digest(padded_key) +end + +function pbkdf2(pass, salt, iter, dklen) + local out = {} + local hashlen = sha_hashlen + local block = 1 + dklen = dklen or 32 + + while dklen > 0 do + local ikey = {} + local isalt = type(salt) == "table" and {upack(salt)} or {tostring(salt):byte(1,-1)} + local clen = dklen > hashlen and hashlen or dklen + + isalt[#isalt+1] = band(brshift(band(block, 0xFF000000), 24), 0xFF) + isalt[#isalt+1] = band(brshift(band(block, 0xFF0000), 16), 0xFF) + isalt[#isalt+1] = band(brshift(band(block, 0xFF00), 8), 0xFF) + isalt[#isalt+1] = band(block, 0xFF) + + for j = 1, iter do + isalt = hmac(isalt, pass) + for k = 1, clen do ikey[k] = bxor(isalt[k], ikey[k] or 0) end + if j % 200 == 0 then os.queueEvent("PBKDF2", j) coroutine.yield("PBKDF2") end + end + dklen = dklen - clen + block = block+1 + for k = 1, clen do out[#out+1] = ikey[k] end + end + + return setmetatable(out, mt) +end \ No newline at end of file diff --git a/MetroOS/System/Images/boot b/MetroOS/System/Images/boot new file mode 100644 index 0000000..54f741a --- /dev/null +++ b/MetroOS/System/Images/boot @@ -0,0 +1,12 @@ +7777777777777777777777777777777777 +77777777777777777777777777777777778 +77788877888778887788877788778887778 +77877778777878778787787877878778778 +77877778777878778787787877878778778 +77877778777878778788877877878778778 +77877778888878887787787877878778778 +77877778777878778787787877878778778 +77788878777878778788877788778778778 +77777777777777777777777777777777778 +77777777777777777777777777777777778 +8888888888888888888888888888888888 diff --git a/MetroOS/System/Images/desktop b/MetroOS/System/Images/desktop new file mode 100644 index 0000000..352e581 --- /dev/null +++ b/MetroOS/System/Images/desktop @@ -0,0 +1,18 @@ +ccccccccccccccccccccccccccccccccccccccccccccccccc +ccccccccccccccccccccccccccccccccccccccccccccccccc +ccccccccccccccccccccccccccccccccccccccccccccccccc +ccccccccccccccccccccccccccccccccccccccccccccccccc +ccccccccccccccccccccccccccccccccccccccccccccccccc +ccccccccccccccccccccccccccccccccccccccccccccccccc +ccccccccccccccccccccccccccccccccccccccccccccccccc +ccccccccccccccccccccccccccccccccccccccccccccccccc +ccccccccccccccccccccccccccccccccccccccccccccccccc +ccccccccccccccccccccccccccccccccccccccccccccccccc +ccccccccccccccccccccccccccccccccccccccccccccccccc +ccccccccccccccccccccccccccccccccccccccccccccccccc +ccccccccccccccccccccccccccccccccccccccccccccccccc +ccccccccccccccccccccccccccccccccccccccccccccccccc +ccccccccccccccccccccccccccccccccccccccccccccccccc +ccccccccccccccccccccccccccccccccccccccccccccccccc +ccccccccccccccccccccccccccccccccccccccccccccccccc +ccccccccccccccccccccccccccccccccccccccccccccccccc diff --git a/MetroOS/System/autoupdater b/MetroOS/System/autoupdater new file mode 100644 index 0000000..db7b76a --- /dev/null +++ b/MetroOS/System/autoupdater @@ -0,0 +1,87 @@ +local nextFile = "/os" +local files = { + [1] = { + "/System/Images/boot", + "https://raw.githubusercontent.com/Carbon-OS/CarbonOS/master/System/Images/boot" + }, + [2] = { + "startup", + "https://raw.githubusercontent.com/Carbon-OS/CarbonOS/master/startup" + }, + [3] = { + "/Programs/LuaIDE/program", + "https://raw.githubusercontent.com/Carbon-OS/CarbonOS/master/Programs/LuaIDE/program" + }, + [4] = { + "/Programs/Sketch/program", + "https://raw.githubusercontent.com/Carbon-OS/CarbonOS/master/Programs/Sketch/program" + }, + [5] = { + "/Desktop/LuaIDE", + "https://raw.githubusercontent.com/Carbon-OS/CarbonOS/master/Desktop/LuaIDE" + }, + [6] = { + "/Desktop/Sketch", + "https://raw.githubusercontent.com/Carbon-OS/CarbonOS/master/Desktop/Sketch" + }, + [7] = { + "/System/Images/desktop", + "https://raw.githubusercontent.com/Carbon-OS/CarbonOS/master/System/Images/desktop" + }, + [8] = { + "/System/settings", + "https://raw.githubusercontent.com/Carbon-OS/CarbonOS/master/System/settings" + }, + [9] = { + "/System/autoupdater", + "https://raw.githubusercontent.com/Carbon-OS/CarbonOS/master/System/autoupdater" + }, + [10] = { + "/System/.version", + "https://raw.githubusercontent.com/Carbon-OS/CarbonOS/master/System/.version" + }, + [11] = { + "/os", + "https://raw.githubusercontent.com/Carbon-OS/CarbonOS/master/os" + }, + [12] = { + "/System/APIs/crasher", + "https://raw.githubusercontent.com/Carbon-OS/CarbonOS/master/System/APIs/crasher" + }, + [13] = { + "/System/APIs/sha256", + "https://raw.githubusercontent.com/Carbon-OS/CarbonOS/master/System/APIs/sha256" + } +} + + + +remoteVersion = http.get("https://raw.githubusercontent.com/Carbon-OS/CarbonOS/master/System/.version") + +local localVersion = fs.open("System/.version", "r") +local rVersion = remoteVersion.readAll() +local lVersion = localVersion.readAll() + +localVersion.close() + +if rVersion ~= lVersion then + print("Downloading Update...") + print("Your Verison: ", lVersion) + print("New Version: ", rVersion) + for k, v in pairs(files) do + local currentFile = fs.open(v[1], "w") + + local remoteFile = http.get(v[2]) + + if remoteFile ~= nil then + currentFile.write(remoteFile.readAll()) + end + + currentFile.close() + + remoteFile.close() + end +end + +local next = nextFile +shell.run(next) diff --git a/MetroOS/System/latestversion b/MetroOS/System/latestversion new file mode 100644 index 0000000..0cfbf08 --- /dev/null +++ b/MetroOS/System/latestversion @@ -0,0 +1 @@ +2 diff --git a/MetroOS/System/settings b/MetroOS/System/settings new file mode 100644 index 0000000..74cae84 --- /dev/null +++ b/MetroOS/System/settings @@ -0,0 +1,9 @@ +term.setBackgroundColor(colors.orange) +term.clear() +term.setBackgroundColor(colors.gray) +term.setCursorPos(10,1) +print("Settings") +term.setCursorPos(3,5) +print("Security") +term.setCursorPos(3,6) +print("Storage") diff --git a/MetroOS/os b/MetroOS/os new file mode 100644 index 0000000..05ec92f --- /dev/null +++ b/MetroOS/os @@ -0,0 +1,39 @@ +function BSOD(err) -- BSOD Error Handler + term.setBackgroundColor(colors.blue) + term.clear() + term.setCursorPos(1,1) + print("CarbonOS has Crashed!") + print() + print("Error: ", err) + print() + print("Please report this error at https://github.com/Carbon-OS/CarbonOS/issues!") +end + +local function init() + -- The main OS code + local bootImg = paintutils.loadImage("/System/Images/boot") + local desktopImg = paintutils.loadImage("/System/Images/desktop") + term.clear() + paintutils.drawImage(bootImg, 1,1) + local w, h = term.getSize() + local text = "Discontinued" + local nw = w - text + term.setTextColor(colors.white) + term.setCursorPos(nw, y) + write("Discontinued") + os.sleep(2) + term.setBackgroundColor(colors.brown) + term.clear() + paintutils.drawImage(desktopImg, 1,1) --Displays the desktop + local w, h = term.getSize() + term.setBackgroundColor(colors.green) + term.setTextColor(colors.white) + term.setCursorPos(1, h) + term.write("Start ") --Displays the start button + local event, x, y, button = os.pullEvent("mouse_click") --Recognizes mouse clicks +end + +local ok, err = pcall(init) --Crash Handler +if err then + BSOD(err) +end diff --git a/MetroOS/setup b/MetroOS/setup new file mode 100644 index 0000000..49ecaa1 --- /dev/null +++ b/MetroOS/setup @@ -0,0 +1,101 @@ +local files = { + [1] = { + "/System/Images/boot", + "https://raw.githubusercontent.com/Carbon-OS/CarbonOS/master/System/Images/boot" + }, + [2] = { + "startup", + "https://raw.githubusercontent.com/Carbon-OS/CarbonOS/master/startup" + }, + [3] = { + "/Programs/LuaIDE/program", + "https://raw.githubusercontent.com/Carbon-OS/CarbonOS/master/Programs/LuaIDE/program" + }, + [4] = { + "/Programs/Sketch/program", + "https://raw.githubusercontent.com/Carbon-OS/CarbonOS/master/Programs/Sketch/program" + }, + [5] = { + "/Desktop/LuaIDE", + "https://raw.githubusercontent.com/Carbon-OS/CarbonOS/master/Desktop/LuaIDE" + }, + [6] = { + "/Desktop/Sketch", + "https://raw.githubusercontent.com/Carbon-OS/CarbonOS/master/Desktop/Sketch" + }, + [7] = { + "/System/Images/desktop", + "https://raw.githubusercontent.com/Carbon-OS/CarbonOS/master/System/Images/desktop" + }, + [8] = { + "/System/settings", + "https://raw.githubusercontent.com/Carbon-OS/CarbonOS/master/System/settings" + }, + [9] = { + "/System/autoupdater", + "https://raw.githubusercontent.com/Carbon-OS/CarbonOS/master/System/autoupdater" + }, + [10] = { + "/System/.version", + "https://raw.githubusercontent.com/Carbon-OS/CarbonOS/master/System/.version" + }, + [11] = { + "/os", + "https://raw.githubusercontent.com/Carbon-OS/CarbonOS/master/os" + }, + [12] = { + "/System/APIs/crasher", + "https://raw.githubusercontent.com/Carbon-OS/CarbonOS/master/System/APIs/crasher" + }, + [13] = { + "/System/APIs/sha256", + "https://raw.githubusercontent.com/Carbon-OS/CarbonOS/master/System/APIs/sha256" + } +} +if term.isColor() == false then + print("Sorry, But you need an Advanced PC to run this OS!") +else +term.setBackgroundColor(colors.gray) +term.clear() +term.setCursorPos(16,8) +print("Welcome to Carbon!") +term.setCursorPos(11,10) +print("A Fast, Simple, and Secure OS") +term.setCursorPos(21,16) +term.setBackgroundColor(colors.lightGray) +print("Install") +while true do +local event, side, x, y = os.pullEvent("mouse_click") +if x >= 21 and x < 28 and y == 16 then + term.clear() + shell.run("mkdir", "System") + term.setCursorPos(1,1) + textutils.slowPrint("Installing Carbon...") + local req + local code + local file + for k,v in pairs(files) do + print("Downloading ", v[2], "...") + req = http.get(v[2]) + if req ~= nil then + code = req.readAll() + req.close() + else + print("Failed!") + end + + file = fs.open(v[1], "w") + file.write(code) + file.close() +end +file = fs.open("/System/.firstuse", "w") +file.write("true") +file.close() +print("Done!") +print("Rebooting...") +os.sleep(1) +os.reboot() + +end +end +end diff --git a/MetroOS/startup b/MetroOS/startup new file mode 100644 index 0000000..8ac95a0 --- /dev/null +++ b/MetroOS/startup @@ -0,0 +1 @@ +shell.run("/System/autoupdater") From 2d5300963d18dcb2677e7e907e7ff9823b1e9eef Mon Sep 17 00:00:00 2001 From: rservices Date: Sat, 25 Apr 2020 05:14:29 -0600 Subject: [PATCH 052/125] Updated OS --- MetroOS/Desktop/LuaIDE | 1 - MetroOS/Desktop/Sketch | 1 - MetroOS/LICENSE | 674 ------ MetroOS/MetroOS | 353 +++ MetroOS/Programs/LuaIDE/program | 2224 ----------------- MetroOS/Programs/Sketch/program | 4014 ------------------------------- MetroOS/README.md | 10 - MetroOS/System/.version | 1 - MetroOS/System/APIs/crasher | 12 - MetroOS/System/APIs/sha256 | 197 -- MetroOS/System/Images/boot | 12 - MetroOS/System/Images/desktop | 18 - MetroOS/System/autoupdater | 87 - MetroOS/System/latestversion | 1 - MetroOS/System/settings | 9 - MetroOS/os | 39 - MetroOS/setup | 101 - MetroOS/startup | 1 - 18 files changed, 353 insertions(+), 7402 deletions(-) delete mode 100644 MetroOS/Desktop/LuaIDE delete mode 100644 MetroOS/Desktop/Sketch delete mode 100644 MetroOS/LICENSE create mode 100644 MetroOS/MetroOS delete mode 100644 MetroOS/Programs/LuaIDE/program delete mode 100644 MetroOS/Programs/Sketch/program delete mode 100644 MetroOS/README.md delete mode 100644 MetroOS/System/.version delete mode 100644 MetroOS/System/APIs/crasher delete mode 100644 MetroOS/System/APIs/sha256 delete mode 100644 MetroOS/System/Images/boot delete mode 100644 MetroOS/System/Images/desktop delete mode 100644 MetroOS/System/autoupdater delete mode 100644 MetroOS/System/latestversion delete mode 100644 MetroOS/System/settings delete mode 100644 MetroOS/os delete mode 100644 MetroOS/setup delete mode 100644 MetroOS/startup diff --git a/MetroOS/Desktop/LuaIDE b/MetroOS/Desktop/LuaIDE deleted file mode 100644 index 8ab8299..0000000 --- a/MetroOS/Desktop/LuaIDE +++ /dev/null @@ -1 +0,0 @@ -/Programs/LuaIDE/program diff --git a/MetroOS/Desktop/Sketch b/MetroOS/Desktop/Sketch deleted file mode 100644 index 5bdad26..0000000 --- a/MetroOS/Desktop/Sketch +++ /dev/null @@ -1 +0,0 @@ -/Programs/Sketch/program diff --git a/MetroOS/LICENSE b/MetroOS/LICENSE deleted file mode 100644 index 9cecc1d..0000000 --- a/MetroOS/LICENSE +++ /dev/null @@ -1,674 +0,0 @@ - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - {one line to give the program's name and a brief idea of what it does.} - Copyright (C) {year} {name of author} - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - {project} Copyright (C) {year} {fullname} - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - - The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. diff --git a/MetroOS/MetroOS b/MetroOS/MetroOS new file mode 100644 index 0000000..9255426 --- /dev/null +++ b/MetroOS/MetroOS @@ -0,0 +1,353 @@ +local files = { + ["init"] = "require \"UIButton\"\ +require \"UIContainer\"\ +require \"UIText\"\ +require \"UILabel\"\ +require \"UITextInput\"\ +require \"UIMenu\"\ +require \"UITabs\"\ +require \"UIFileDialogue\"\ +\ +application.terminateable = false\ +\ +local blocksize = 5\ +local bit32 = 2 ^ 32\ +local prime1 = 8747\ +local prime2 = 2147483647\ +local bit32half = bit32/2\ +local floor = math.floor\ +local function keygen( seed, length ) local count = 1 local keys, garbage = {}, {} for i = 1, length, blocksize do seed = ( seed * ( seed > bit32half and prime2 or prime1 ) + 1 ) % bit32 + count count = count + 1 garbage[i] = math.max( 0, ( seed - 1 ) % 10 - 4 ) keys[i] = seed % 256 end return keys, garbage end\ +local h = {}\ +for i = 0, 15 do h[i] = (\"%X\"):format( i ) end\ +local hl = {}\ +for i = 0, 15 do hl[(\"%X\"):format( i )] = i end\ +local function tohex2( n ) return h[math.floor( n / 16 )] .. h[n % 16] end\ +local function fromhex2( h ) return hl[h:sub( 1, 1 )] * 16 + hl[h:sub( 2, 2 )] end\ +local function numbertochars( n ) local s = tohex2( n % 256 ) for i = 1, 3 do n = math.floor( n / 256 ) s = tohex2( n % 256 ) .. s end return s end\ +local function charstonumber( c ) local n = 0 for i = 1, 4 do n = n * 256 + fromhex2( c:sub( i * 2 - 1 ) ) end return n end\ +local function newgarbage( length ) if length == 0 then return \"\" end return tohex2( math.random( 0, 255 ) ) .. newgarbage( length - 1 ) end\ +local function stringkey( str ) local key = 0 for i = 1, #str do key = key * 256 + str:byte( i ) end return key end\ +local encrypt = {}\ +function encrypt.encrypt( text, key ) key = type( key ) == \"string\" and stringkey( key ) or key text = textutils.serialize( text ) if type( key ) ~= \"number\" then return error( \"expected number/string key, got \" .. type( key ) ) end local keys, garbage = keygen( key, #text ) local cipher = { numbertochars( #text ) } math.randomseed( os.clock() ) for i = 1, #text, blocksize do for n = 0, blocksize - 1 do if i + n <= #text then cipher[#cipher + 1] = tohex2( bit.bxor( text:byte( i + n ), keys[i] ) ) else break end end cipher[#cipher + 1] = newgarbage( garbage[i] ) end return table.concat( cipher ) end\ +function encrypt.decrypt( cipher, key ) key = type( key ) == \"string\" and stringkey( key ) or key if type( cipher ) ~= \"string\" then return error( \"expected string cipher, got \" .. type( cipher ) ) end if type( key ) ~= \"number\" then return error( \"expected number/string key, got \" .. type( key ) ) end local length = charstonumber( cipher:sub( 1, 8 ) ) cipher = cipher:sub( 9 ) local keys, garbage = keygen( key, length ) local text = {} local i = 1 while #cipher > 0 do local block = cipher:sub( 1, math.min( #cipher - garbage[i] * 2, blocksize * 2 ) ) for n = 1, #block, 2 do text[#text + 1] = string.char( bit.bxor( fromhex2( block:sub( n ) ), keys[i] ) ) end cipher = cipher:sub( blocksize * 2 + garbage[i] * 2 + 1 ) i = i + blocksize end return textutils.unserialize( table.concat( text ) ) end\ +local MOD = 2^32\ +local MODM = MOD-1\ +local function memoize(f) local mt = {} local t = setmetatable({}, mt) function mt:__index(k) local v = f(k) t[k] = v return v end return t end\ +local function make_bitop_uncached(t, m) local function bitop(a, b) local res,p = 0,1 while a ~= 0 and b ~= 0 do local am, bm = a % m, b % m res = res + t[am][bm] * p a = (a - am) / m b = (b - bm) / m p = p*m end res = res + (a + b) * p return res end return bitop end\ +local function make_bitop(t) local op1 = make_bitop_uncached(t,2^1) local op2 = memoize(function(a) return memoize(function(b) return op1(a, b) end) end) return make_bitop_uncached(op2, 2 ^ (t.n or 1)) end\ +local bxor1 = make_bitop({[0] = {[0] = 0,[1] = 1}, [1] = {[0] = 1, [1] = 0}, n = 4})\ +local function bxor(a, b, c, ...) local z = nil if b then a = a % MOD b = b % MOD z = bxor1(a, b) if c then z = bxor(z, c, ...) end return z elseif a then return a % MOD else return 0 end end\ +local function band(a, b, c, ...) local z if b then a = a % MOD b = b % MOD z = ((a + b) - bxor1(a,b)) / 2 if c then z = bit32_band(z, c, ...) end return z elseif a then return a % MOD else return MODM end end\ +local function bnot(x) return (-1 - x) % MOD end\ +local function rshift1(a, disp) if disp < 0 then return lshift(a,-disp) end return math.floor(a % 2 ^ 32 / 2 ^ disp) end\ +local function rshift(x, disp) if disp > 31 or disp < -31 then return 0 end return rshift1(x % MOD, disp) end\ +local function lshift(a, disp) if disp < 0 then return rshift(a,-disp) end return (a * 2 ^ disp) % 2 ^ 32 end\ +local function rrotate(x, disp) x = x % MOD disp = disp % 32 local low = band(x, 2 ^ disp - 1) return rshift(x, disp) + lshift(low, 32 - disp) end\ +local k = {0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2, }\ +local function str2hexa(s) return (string.gsub(s, \".\", function(c) return string.format(\"%02x\", string.byte(c)) end)) end\ +local function num2s(l, n) local s = \"\"for i = 1, n do local rem = l % 256 s = string.char(rem) .. s l = (l - rem) / 256 end return s end\ +local function s232num(s, i) local n = 0 for i = i, i + 3 do n = n*256 + string.byte(s, i) end return n end\ +local function preproc(msg, len) local extra = 64 - ((len + 9) % 64) len = num2s(8 * len, 8) msg = msg .. \"\\128\" .. string.rep(\"\\0\", extra) .. len assert(#msg % 64 == 0) return msg end\ +local function initH256(H) H[1] = 0x6a09e667 H[2] = 0xbb67ae85 H[3] = 0x3c6ef372 H[4] = 0xa54ff53a H[5] = 0x510e527f H[6] = 0x9b05688c H[7] = 0x1f83d9ab H[8] = 0x5be0cd19 return H end\ +local function digestblock(msg, i, H) local w = {} for j = 1, 16 do w[j] = s232num(msg, i + (j - 1)*4) end for j = 17, 64 do local v = w[j - 15] local s0 = bxor(rrotate(v, 7), rrotate(v, 18), rshift(v, 3)) v = w[j - 2] w[j] = w[j - 16] + s0 + w[j - 7] + bxor(rrotate(v, 17), rrotate(v, 19), rshift(v, 10)) end local a, b, c, d, e, f, g, h = H[1], H[2], H[3], H[4], H[5], H[6], H[7], H[8] for i = 1, 64 do local s0 = bxor(rrotate(a, 2), rrotate(a, 13), rrotate(a, 22)) local maj = bxor(band(a, b), band(a, c), band(b, c)) local t2 = s0 + maj local s1 = bxor(rrotate(e, 6), rrotate(e, 11), rrotate(e, 25)) local ch = bxor (band(e, f), band(bnot(e), g)) local t1 = h + s1 + ch + k[i] + w[i] h, g, f, e, d, c, b, a = g, f, e, d + t1, c, b, a, t1 + t2 end H[1] = band(H[1] + a) H[2] = band(H[2] + b) H[3] = band(H[3] + c) H[4] = band(H[4] + d) H[5] = band(H[5] + e) H[6] = band(H[6] + f) H[7] = band(H[7] + g) H[8] = band(H[8] + h) end\ +local function sha256(msg) msg = preproc(msg, #msg) local H = initH256({}) for i = 1, #msg, 64 do digestblock(msg, i, H) end return str2hexa(num2s(H[1], 4) .. num2s(H[2], 4) .. num2s(H[3], 4) .. num2s(H[4], 4) .. num2s(H[5], 4) .. num2s(H[6], 4) .. num2s(H[7], 4) .. num2s(H[8], 4)) end\ +local tKeys = {\"a\", \"b\", \"c\", \"d\", \"e\", \"f\", \"g\", \"h\", \"i\", \"j\", \"k\", \"l\", \"m\", \"n\", \"o\", \"p\", \"q\", \"r\", \"s\", \"t\", \"u\", \"v\", \"w\", \"x\", \"y\", \"z\", 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 } --, \"&\", \"%\", \"#\", \"$\", \"!\", \"@\", \"?\", \"=\", \"/\", \"\\\\\", \"(\", \")\", \"[\", \"]\", \"{\", \"}\" }\ +table.size = function( tbl ) local count = 0 for k,v in pairs( tbl ) do count = count + 1 end return count end\ +\ +local function GenerateSalt()\ +\9local Count = 0\ +\9local Salt = \"\"\ +\9local function RandomKey()\ +\9\9local key = false\ +\9\9key = tKeys[ math.random( 1, table.size( tKeys ) ) ]\ +\9\9if type( key ) ~= \"number\" then\ +\9\9\9local x = math.random( 1, 2 )\ +\9\9\9if x == 1 then\ +\9\9\9\9key\9= string.lower( key )\ +\9\9\9elseif x == 2 then\ +\9\9\9\9key = string.upper( key )\ +\9\9\9end\ +\9\9end\ +\9\9return key\ +\9end\ +\9for SaltCount = 1, 32 do\ +\9\9Salt = Salt .. RandomKey()\ +\9\9Count = Count + 1\ +\9\9if Count == 16 then\ +\9\9\9sleep( 0 )\ +\9\9\9Count = 0\ +\9\9end\ +\9end\ +\9return Salt\ +end\ +local locSalt = GenerateSalt()\ +\ +local lock = {\ +\9name = \"MetroOSLock\",\ +\9authorized = \"MetroOS - Computer unlocked\",\ +\9authorized2 = os.version(),\ +\9author = \"OutragedMetro\",\ +\9config = \".MetroOSSecureLock.cfg\",\ +}\ +local framerate = .05\ +local config = {}\ +\ +local loadFrame = application.view:addChild( UIContainer( 0, 0, application.view.width, application.view.height ) )\ +loadFrame.colour = colours.grey\ +loadFrame.transitionTime = 1\ +local note = loadFrame:addChild( UIText( 0, -math.floor( application.view.height / 3.5 ), 20, 5, \"\\n@acThis program\\ncannot be @teterminated\" ) )\ +note.colour = colours.lightGrey\ +local blackFrame = application.view:addChild( UIContainer( 0, -application.view.height, application.view.width, application.view.height ) )\ +blackFrame.colour = colours.black\ +blackFrame.transitionTime = .75\ +\ +application.view:createShortcut( \"Terminate\", \"ctrl-t\", function()\ +\9note.y = -note.height\ +\9note.text = \"\\n@acThis program\\ncannot be \\n@teterminated\"\ +\9note.animatedY = 0\ +\9Timer.queue( 2.5, function()\ +\9\9note.animatedY = -note.height\ +\9end )\ +end )\ +application.view:createShortcut( \"Reboot\", \"ctrl-r\", function()\ +\9note.y = -note.height\ +\9note.text = \"\\n@acRebooting computer in @te1 second\"\ +\9note.animatedY = 0\ +\9Timer.queue( 1, function()\ +\9\9os.reboot()\ +\9end )\ +end )\ +\ +local tOName = blackFrame:addChild( UILabel( 0, 1, lock.authorized ) )\ +tOName.x = math.floor( application.view.width / 2 - #lock.authorized / 2 )\ +local tOName2 = blackFrame:addChild( UILabel( 0, 2, lock.authorized2 ) )\ +tOName2.x = math.floor( application.view.width / 2 - #lock.authorized2 / 2 )\ +\ +local tName = loadFrame:addChild( UILabel( 0, 1, lock.name ) )\ +tName.x = math.floor( application.view.width / 2 - #lock.name / 2 )\ +local tBy = loadFrame:addChild( UILabel( 0, application.view.height-2, \"By \" .. lock.author ) )\ +tBy.x = math.floor( application.view.width / 2 - #lock.author / 2 )\ +local tP = {}\ +\ +local function checkConfig()\ +\9if not fs.exists( lock.config ) then\ +\9\9local newPass = loadFrame:addChild( UILabel( 0, math.floor( application.view.height / 2 ) - 1, \"Enter new password\" ) )\ +\9\9newPass.transitionTime = .5\ +\9\9newPass.animatedX = math.floor( application.view.width / 2 - newPass.width / 2 )\ +\9\9inputPass = loadFrame:addChild( UITextInput( 0, math.floor( application.view.height / 2 ), application.view.width / 2 ) )\ +\9\9inputPass.transitionTime = .5\ +\9\9inputPass.focussed = true\ +\9\9inputPass.colour = colours.lightGrey\ +\9\9inputPass.mask = \"#\"\ +\9\9inputPass.animatedX = math.floor( application.view.width / 2 - inputPass.width / 2 )\ +\9\9function inputPass:onEnter()\ +\9\9\9if not tP[1] then\ +\9\9\9\9tP[1] = sha256( locSalt .. inputPass.text )\ +\9\9\9\9inputPass.text = \"\"\ +\9\9\9\9inputPass.focussed = true\ +\9\9\9\9newPass.text = \"Re-enter password\"\ +\9\9\9\9newPass.animatedX = math.floor( application.view.width / 2 - newPass.width / 2 )\ +\9\9\9\9newPass.textColour = colours.orange\ +\9\9\9else\ +\9\9\9\9tP[2] = sha256( locSalt .. inputPass.text )\ +\9\9\9\9inputPass.text = \"\"\ +\9\9\9\9inputPass.focussed = true\ +\9\9\9\9if tP[1] ~= tP[2] then\ +\9\9\9\9\9tP = {}\ +\9\9\9\9\9newPass.text = \"Passwords did not match\"\ +\9\9\9\9\9newPass.animatedX = math.floor( application.view.width / 2 - newPass.width / 2 )\ +\9\9\9\9\9newPass.textColour = colours.red\ +\9\9\9\9\9Timer.queue( 1, function()\ +\9\9\9\9\9\9newPass:remove()\ +\9\9\9\9\9\9inputPass:remove()\ +\9\9\9\9\9\9checkConfig()\ +\9\9\9\9\9end )\ +\9\9\9\9else\ +\9\9\9\9\9inputPass.focussed = false\ +\9\9\9\9\9newPass.text = \"Passwords matching!\"\ +\9\9\9\9\9newPass.animatedX = math.floor( application.view.width / 2 - newPass.width / 2 )\ +\9\9\9\9\9newPass.textColour = colours.lime\ +\9\9\9\9\9config.pass = tP[1]\ +\9\9\9\9\9config.salt = locSalt\ +\9\9\9\9\9Timer.queue( 1, function()\ +\9\9\9\9\9\9newPass:transitionOutRight()\ +\9\9\9\9\9\9inputPass:transitionOutBottom()\ +\9\9\9\9\9\9Timer.queue( .3, function()\ +\9\9\9\9\9\9\9tP = {}\ +\9\9\9\9\9\9\9newPass:remove()\ +\9\9\9\9\9\9\9inputPass:remove()\ +\9\9\9\9\9\9end )\ +\9\9\9\9\9end )\ +\9\9\9\9\9Timer.queue( 1, function()\ +\9\9\9\9\9\9local doorSettings = loadFrame:addChild( UILabel( 0, math.floor( application.view.height / 2 ) - 5, \"Choose redstone output side\" ) )\ +\9\9\9\9\9\9local doorSettings1 = loadFrame:addChild( UILabel( 0, math.floor( application.view.height / 2 ) - 4, \"\\\"None\\\" = Unlock computer shell\" ) )\ +\9\9\9\9\9\9local redstoneside = loadFrame:addChild( UITabs( 0, math.floor( application.view.height / 2 ) - 2, math.floor( application.view.width / 2.25 ), { \"None\", \"Top\", \"Bottom\", \"Right\", \"Left\", \"Front\", \"Back\" } ) )\ +\9\9\9\9\9\9redstoneside:select(1)\ +\9\9\9\9\9\9redstoneside.transitionTime = .2\ +\9\9\9\9\9\9redstoneside.colour = colours.grey\ +\9\9\9\9\9\9redstoneside.textColour = colours.white\ +\9\9\9\9\9\9local continue = loadFrame:addChild( UIButton( 0, math.floor( application.view.height / 2 ) + 3, math.floor( application.view.width / 3 ), 3, \"Continue\" ) )\ +\9\9\9\9\9\9continue.colour = colours.lightGrey\ +\9\9\9\9\9\9continue.animatedX = math.floor( application.view.width / 2 - continue.width / 2 )\ +\9\9\9\9\9\9redstoneside.animatedX = math.floor( application.view.width / 2 - redstoneside.width / 2 )\ +\9\9\9\9\9\9doorSettings.animatedX = math.floor( application.view.width / 2 - doorSettings.width / 2 )\ +\9\9\9\9\9\9doorSettings1.animatedX = math.floor( application.view.width / 2 - doorSettings1.width / 2 )\ +\9\9\9\9\9\9function continue:onClick()\ +\9\9\9\9\9\9\9if redstoneside.selected ~= 0 and redstoneside.selected ~= 1 then\ +\9\9\9\9\9\9\9\9config.redstone = redstoneside.options[ redstoneside.selected ] or false\ +\9\9\9\9\9\9\9else\ +\9\9\9\9\9\9\9\9config.redstone = false\ +\9\9\9\9\9\9\9end\ +\9\9\9\9\9\9\9doorSettings:transitionOutTop()\ +\9\9\9\9\9\9\9doorSettings1:transitionOutRight()\ +\9\9\9\9\9\9\9redstoneside:transitionOutLeft()\ +\9\9\9\9\9\9\9continue:transitionOutBottom()\ +\9\9\9\9\9\9\9Timer.queue( .5, function()\ +\9\9\9\9\9\9\9\9doorSettings:remove()\ +\9\9\9\9\9\9\9\9doorSettings1:remove()\ +\9\9\9\9\9\9\9\9redstoneside:remove()\ +\9\9\9\9\9\9\9\9continue:remove()\ +\9\9\9\9\9\9\9\9ok, err = pcall(function() \ +\9\9\9\9\9\9\9\9\9local SaveFile = fs.open( lock.config, \"w\" )\ +\9\9\9\9\9\9\9\9\9SaveFile.writeLine( encrypt.encrypt( textutils.serialize( config ), config.pass ) )\ +\9\9\9\9\9\9\9\9\9SaveFile.close()\ +\9\9\9\9\9\9\9\9end )\ +\9\9\9\9\9\9\9\9checkConfig()\ +\9\9\9\9\9\9\9end )\ +\9\9\9\9\9\9end\ +\9\9\9\9\9end )\ +\9\9\9\9end\ +\9\9\9end\ +\9\9end\ +\9else\ +\9\9\ +\9\9main()\ +\9end\ +end\ +\ +function main()\ +\9local inputTextPass = loadFrame:addChild( UILabel( 0, math.floor( application.view.height / 2 )-1, \"Password\" ) )\ +\9inputTextPass.transitionTime = .25\ +\9inputTextPass.animatedX = math.floor( application.view.width/2 - inputTextPass.width/2 )\ +\9local inputFieldPass = loadFrame:addChild( UITextInput( 0, math.floor( application.view.height / 2 ), application.view.width/2 ) )\ +\9inputFieldPass.transitionTime = .25\ +\9inputFieldPass.colour = colours.lightGrey\ +\9inputFieldPass.mask = \"#\"\ +\9inputFieldPass.focussed = true\ +\9inputFieldPass.animatedX = math.floor( application.view.width/2 - inputFieldPass.width/2 )\ +\9function inputFieldPass:onEnter()\ +\9\9inputTextPass.text = \"Validating\"\ +\9\9inputTextPass.animatedX = math.floor( application.view.width/2 - inputTextPass.width/2 )\ +\9\9local file = fs.open( lock.config, \"r\" )\ +\9\9local config = textutils.unserialize( encrypt.decrypt( file.readAll(), sha256( inputFieldPass.text ) ) )\ +\9\9file.close()\ +\9\9if sha256( config.salt .. inputFieldPass.text ) == config.pass then\ +\9\9\9inputFieldPass.text = \"\"\ +\9\9\9inputTextPass.text = \"Password correct\"\ +\9\9\9inputTextPass.textColour = colours.lime\ +\9\9\9inputTextPass.animatedX = math.floor( application.view.width/2 - inputTextPass.width/2 )\ +\9\9\9if config.redstone then\ +\9\9\9\9rs.setOutput( config.redstone:lower(), true )\ +\9\9\9\9Timer.queue( 5, function() \ +\9\9\9\9\9rs.setOutput( config.redstone:lower(), false )\ +\9\9\9\9\9inputTextPass:remove()\ +\9\9\9\9\9inputFieldPass:remove()\ +\9\9\9\9\9main()\ +\9\9\9\9end )\ +\9\9\9else\ +\9\9\9\9Timer.queue( .25, function()\ +\9\9\9\9\9inputTextPass:transitionOutLeft()\ +\9\9\9\9\9inputFieldPass:transitionOutBottom()\ +\9\9\9\9\9Timer.queue( .5, function()\ +\9\9\9\9\9\9loadFrame.colour = colours.lightGrey\ +\9\9\9\9\9\9loadFrame:transitionOutBottom()\ +\9\9\9\9\9\9blackFrame:transitionInTop()\ +\9\9\9\9\9\9Timer.queue( .8, function() \ +\9\9\9\9\9\9\9term.setCursorPos( 1, 5 )\ +\9\9\9\9\9\9\9application:stop()\ +\9\9\9\9\9\9end )\ +\9\9\9\9\9end )\ +\9\9\9\9end )\ +\9\9\9end\ +\9\9else\ +\9\9\9inputTextPass.text = \"Password incorrect\"\ +\9\9\9inputTextPass.textColour = colours.red\ +\9\9\9inputTextPass.animatedX = math.floor( application.view.width/2 - inputTextPass.width/2 )\ +\9\9\9Timer.queue( 1, function()\ +\9\9\9\9inputTextPass:remove()\ +\9\9\9\9inputFieldPass:remove()\ +\9\9\9\9main()\ +\9\9\9end )\ +\9\9end\ +\9end\ +end\ +local _ok, result = pcall( checkConfig )"; +} +if shell.getRunningProgram() ~= "startup" then + if fs.exists( "startup" ) then + fs.move( "startup", "__oldstartup" ) + end + fs.move( shell.getRunningProgram(), "startup" ) + os.reboot() +--[[else + local protecting = { "startup", ".MetroOSSecureLock.cfg" } + local oldCopy = fs.copy + local oldMove = fs.move + local oldDelete = fs.delete + local oldEdit = fs.open + fs.delete = function( path ) + for k, v in pairs( protecting ) do + if fs.getName( path ) == v then + return error( "Cannot remove MetroOS", 0 ) + end + end + return oldDelete( path ) + end + fs.open = function( path, method, pass ) + if method == "r" then + return oldEdit( path, method ) + else + return error( "Cannot edit MetroOS", 0 ) + end + end + fs.move = function( path, newPath ) + for k, v in pairs( protecting ) do + if fs.getName( path ) == v then + return error( "Cannot move MetroOS", 0 ) + end + end + return oldMove( path, newPath ) + end + fs.copy = function( path, newPath ) + for k, v in pairs( protecting ) do + if fs.getName( path ) == v then + return error( "Cannot copy MetroOS", 0 ) + end + end + return oldCopy( path, newPath ) + end--]] +end +if not fs.exists "Flare" then + print "Downloading Flare" + local h = http.get "https://pastebin.com/raw/SD25GhYf" + if h then + local f, err = load( h.readAll(), "installer", nil, _ENV or getfenv() ) + h.close() + f() + else + return error( "Cannot install Flare", 0 ) + end +end +local loader +local h = fs.open( "Flare/run.lua", "r" ) +if h then + loader = h.readAll() + h.close() +else + error( "failed to read Flare", 0 ) +end +local f, err = load( loader, "Flare", nil, _ENV or getfenv() ) +if not f then + error( "there was a problem with Flare!: " .. err, 0 ) +end +f( files, "init" ) \ No newline at end of file diff --git a/MetroOS/Programs/LuaIDE/program b/MetroOS/Programs/LuaIDE/program deleted file mode 100644 index f0bb742..0000000 --- a/MetroOS/Programs/LuaIDE/program +++ /dev/null @@ -1,2224 +0,0 @@ - --- --- Lua IDE --- Made by GravityScore --- - - - - --- Variables - -local version = "1.1" -local arguments = {...} - - -local w, h = term.getSize() -local tabWidth = 2 - - -local autosaveInterval = 20 -local allowEditorEvent = true -local keyboardShortcutTimeout = 0.4 - - -local clipboard = nil - - -local theme = { - background = colors.gray, - titleBar = colors.lightGray, - - top = colors.lightBlue, - bottom = colors.cyan, - - button = colors.cyan, - buttonHighlighted = colors.lightBlue, - - dangerButton = colors.red, - dangerButtonHighlighted = colors.pink, - - text = colors.white, - folder = colors.lime, - readOnly = colors.red, -} - - -local languages = {} -local currentLanguage = {} - - -local updateURL = "https://raw.github.com/GravityScore/LuaIDE/master/computercraft/ide.lua" -local ideLocation = "/" .. shell.getRunningProgram() -local themeLocation = "/.luaide_theme" - -local function isAdvanced() - return term.isColor and term.isColor() -end - - - - --- -------- Utilities - -local function modRead(properties) - local w, h = term.getSize() - local defaults = {replaceChar = nil, history = nil, visibleLength = nil, textLength = nil, - liveUpdates = nil, exitOnKey = nil} - if not properties then properties = {} end - for k, v in pairs(defaults) do if not properties[k] then properties[k] = v end end - if properties.replaceChar then properties.replaceChar = properties.replaceChar:sub(1, 1) end - if not properties.visibleLength then properties.visibleLength = w end - - local sx, sy = term.getCursorPos() - local line = "" - local pos = 0 - local historyPos = nil - - local function redraw(repl) - local scroll = 0 - if properties.visibleLength and sx + pos > properties.visibleLength + 1 then - scroll = (sx + pos) - (properties.visibleLength + 1) - end - - term.setCursorPos(sx, sy) - local a = repl or properties.replaceChar - if a then term.write(string.rep(a, line:len() - scroll)) - else term.write(line:sub(scroll + 1, -1)) end - term.setCursorPos(sx + pos - scroll, sy) - end - - local function sendLiveUpdates(event, ...) - if type(properties.liveUpdates) == "function" then - local ox, oy = term.getCursorPos() - local a, data = properties.liveUpdates(line, event, ...) - if a == true and data == nil then - term.setCursorBlink(false) - return line - elseif a == true and data ~= nil then - term.setCursorBlink(false) - return data - end - term.setCursorPos(ox, oy) - end - end - - term.setCursorBlink(true) - while true do - local e, but, x, y, p4, p5 = os.pullEvent() - - if e == "char" then - local s = false - if properties.textLength and line:len() < properties.textLength then s = true - elseif not properties.textLength then s = true end - - local canType = true - if not properties.grantPrint and properties.refusePrint then - local canTypeKeys = {} - if type(properties.refusePrint) == "table" then - for _, v in pairs(properties.refusePrint) do - table.insert(canTypeKeys, tostring(v):sub(1, 1)) - end - elseif type(properties.refusePrint) == "string" then - for char in properties.refusePrint:gmatch(".") do - table.insert(canTypeKeys, char) - end - end - for _, v in pairs(canTypeKeys) do if but == v then canType = false end end - elseif properties.grantPrint then - canType = false - local canTypeKeys = {} - if type(properties.grantPrint) == "table" then - for _, v in pairs(properties.grantPrint) do - table.insert(canTypeKeys, tostring(v):sub(1, 1)) - end - elseif type(properties.grantPrint) == "string" then - for char in properties.grantPrint:gmatch(".") do - table.insert(canTypeKeys, char) - end - end - for _, v in pairs(canTypeKeys) do if but == v then canType = true end end - end - - if s and canType then - line = line:sub(1, pos) .. but .. line:sub(pos + 1, -1) - pos = pos + 1 - redraw() - end - elseif e == "key" then - if but == keys.enter then break - elseif but == keys.left then if pos > 0 then pos = pos - 1 redraw() end - elseif but == keys.right then if pos < line:len() then pos = pos + 1 redraw() end - elseif (but == keys.up or but == keys.down) and properties.history then - redraw(" ") - if but == keys.up then - if historyPos == nil and #properties.history > 0 then - historyPos = #properties.history - elseif historyPos > 1 then - historyPos = historyPos - 1 - end - elseif but == keys.down then - if historyPos == #properties.history then historyPos = nil - elseif historyPos ~= nil then historyPos = historyPos + 1 end - end - - if properties.history and historyPos then - line = properties.history[historyPos] - pos = line:len() - else - line = "" - pos = 0 - end - - redraw() - local a = sendLiveUpdates("history") - if a then return a end - elseif but == keys.backspace and pos > 0 then - redraw(" ") - line = line:sub(1, pos - 1) .. line:sub(pos + 1, -1) - pos = pos - 1 - redraw() - local a = sendLiveUpdates("delete") - if a then return a end - elseif but == keys.home then - pos = 0 - redraw() - elseif but == keys.delete and pos < line:len() then - redraw(" ") - line = line:sub(1, pos) .. line:sub(pos + 2, -1) - redraw() - local a = sendLiveUpdates("delete") - if a then return a end - elseif but == keys["end"] then - pos = line:len() - redraw() - elseif properties.exitOnKey then - if but == properties.exitOnKey or (properties.exitOnKey == "control" and - (but == 29 or but == 157)) then - term.setCursorBlink(false) - return nil - end - end - end - local a = sendLiveUpdates(e, but, x, y, p4, p5) - if a then return a end - end - - term.setCursorBlink(false) - if line ~= nil then line = line:gsub("^%s*(.-)%s*$", "%1") end - return line -end - - --- -------- Themes - -local defaultTheme = { - background = "gray", - backgroundHighlight = "lightGray", - prompt = "cyan", - promptHighlight = "lightBlue", - err = "red", - errHighlight = "pink", - - editorBackground = "gray", - editorLineHightlight = "lightBlue", - editorLineNumbers = "gray", - editorLineNumbersHighlight = "lightGray", - editorError = "pink", - editorErrorHighlight = "red", - - textColor = "white", - conditional = "yellow", - constant = "orange", - ["function"] = "magenta", - string = "red", - comment = "lime" -} - -local normalTheme = { - background = "black", - backgroundHighlight = "black", - prompt = "black", - promptHighlight = "black", - err = "black", - errHighlight = "black", - - editorBackground = "black", - editorLineHightlight = "black", - editorLineNumbers = "black", - editorLineNumbersHighlight = "white", - editorError = "black", - editorErrorHighlight = "black", - - textColor = "white", - conditional = "white", - constant = "white", - ["function"] = "white", - string = "white", - comment = "white" -} - -local availableThemes = { - {"Water (Default)", "https://raw.github.com/GravityScore/LuaIDE/master/themes/default.txt"}, - {"Fire", "https://raw.github.com/GravityScore/LuaIDE/master/themes/fire.txt"}, - {"Sublime Text 2", "https://raw.github.com/GravityScore/LuaIDE/master/themes/st2.txt"}, - {"Midnight", "https://raw.github.com/GravityScore/LuaIDE/master/themes/midnight.txt"}, - {"TheOriginalBIT", "https://raw.github.com/GravityScore/LuaIDE/master/themes/bit.txt"}, - {"Superaxander", "https://raw.github.com/GravityScore/LuaIDE/master/themes/superaxander.txt"}, - {"Forest", "https://raw.github.com/GravityScore/LuaIDE/master/themes/forest.txt"}, - {"Night", "https://raw.github.com/GravityScore/LuaIDE/master/themes/night.txt"}, - {"Original", "https://raw.github.com/GravityScore/LuaIDE/master/themes/original.txt"}, -} - -local function loadTheme(path) - local f = io.open(path) - local l = f:read("*l") - local config = {} - while l ~= nil do - local k, v = string.match(l, "^(%a+)=(%a+)") - if k and v then config[k] = v end - l = f:read("*l") - end - f:close() - return config -end - --- Load Theme -if isAdvanced() then theme = defaultTheme -else theme = normalTheme end - - --- -------- Drawing - -local function centerPrint(text, ny) - if type(text) == "table" then for _, v in pairs(text) do centerPrint(v) end - else - local x, y = term.getCursorPos() - local w, h = term.getSize() - term.setCursorPos(w/2 - text:len()/2 + (#text % 2 == 0 and 1 or 0), ny or y) - print(text) - end -end - -local function title(t) - term.setTextColor(colors[theme.textColor]) - term.setBackgroundColor(colors[theme.background]) - term.clear() - - term.setBackgroundColor(colors[theme.backgroundHighlight]) - for i = 2, 4 do term.setCursorPos(1, i) term.clearLine() end - term.setCursorPos(3, 3) - term.write(t) -end - -local function centerRead(wid, begt) - local function liveUpdate(line, e, but, x, y, p4, p5) - if isAdvanced() and e == "mouse_click" and x >= w/2 - wid/2 and x <= w/2 - wid/2 + 10 - and y >= 13 and y <= 15 then - return true, "" - end - end - - if not begt then begt = "" end - term.setTextColor(colors[theme.textColor]) - term.setBackgroundColor(colors[theme.promptHighlight]) - for i = 8, 10 do - term.setCursorPos(w/2 - wid/2, i) - term.write(string.rep(" ", wid)) - end - - if isAdvanced() then - term.setBackgroundColor(colors[theme.errHighlight]) - for i = 13, 15 do - term.setCursorPos(w/2 - wid/2 + 1, i) - term.write(string.rep(" ", 10)) - end - term.setCursorPos(w/2 - wid/2 + 2, 14) - term.write("> Cancel") - end - - term.setBackgroundColor(colors[theme.promptHighlight]) - term.setCursorPos(w/2 - wid/2 + 1, 9) - term.write("> " .. begt) - return modRead({visibleLength = w/2 + wid/2, liveUpdates = liveUpdate}) -end - - --- -------- Prompt - -local function prompt(list, dir, isGrid) - local function draw(sel) - for i, v in ipairs(list) do - if i == sel then term.setBackgroundColor(v.highlight or colors[theme.promptHighlight]) - else term.setBackgroundColor(v.bg or colors[theme.prompt]) end - term.setTextColor(v.tc or colors[theme.textColor]) - for i = -1, 1 do - term.setCursorPos(v[2], v[3] + i) - term.write(string.rep(" ", v[1]:len() + 4)) - end - - term.setCursorPos(v[2], v[3]) - if i == sel then - term.setBackgroundColor(v.highlight or colors[theme.promptHighlight]) - term.write(" > ") - else term.write(" - ") end - term.write(v[1] .. " ") - end - end - - local key1 = dir == "horizontal" and 203 or 200 - local key2 = dir == "horizontal" and 205 or 208 - local sel = 1 - draw(sel) - - while true do - local e, but, x, y = os.pullEvent() - if e == "key" and but == 28 then - return list[sel][1] - elseif e == "key" and but == key1 and sel > 1 then - sel = sel - 1 - draw(sel) - elseif e == "key" and but == key2 and ((err == true and sel < #list - 1) or (sel < #list)) then - sel = sel + 1 - draw(sel) - elseif isGrid and e == "key" and but == 203 and sel > 2 then - sel = sel - 2 - draw(sel) - elseif isGrid and e == "key" and but == 205 and sel < 3 then - sel = sel + 2 - draw(sel) - elseif e == "mouse_click" then - for i, v in ipairs(list) do - if x >= v[2] - 1 and x <= v[2] + v[1]:len() + 3 and y >= v[3] - 1 and y <= v[3] + 1 then - return list[i][1] - end - end - end - end -end - -local function scrollingPrompt(list) - local function draw(items, sel, loc) - for i, v in ipairs(items) do - local bg = colors[theme.prompt] - local bghigh = colors[theme.promptHighlight] - if v:find("Back") or v:find("Return") then - bg = colors[theme.err] - bghigh = colors[theme.errHighlight] - end - - if i == sel then term.setBackgroundColor(bghigh) - else term.setBackgroundColor(bg) end - term.setTextColor(colors[theme.textColor]) - for x = -1, 1 do - term.setCursorPos(3, (i * 4) + x + 4) - term.write(string.rep(" ", w - 13)) - end - - term.setCursorPos(3, i * 4 + 4) - if i == sel then - term.setBackgroundColor(bghigh) - term.write(" > ") - else term.write(" - ") end - term.write(v .. " ") - end - end - - local function updateDisplayList(items, loc, len) - local ret = {} - for i = 1, len do - local item = items[i + loc - 1] - if item then table.insert(ret, item) end - end - return ret - end - - -- Variables - local sel = 1 - local loc = 1 - local len = 3 - local disList = updateDisplayList(list, loc, len) - draw(disList, sel, loc) - - -- Loop - while true do - local e, key, x, y = os.pullEvent() - - if e == "mouse_click" then - for i, v in ipairs(disList) do - if x >= 3 and x <= w - 11 and y >= i * 4 + 3 and y <= i * 4 + 5 then return v end - end - elseif e == "key" and key == 200 then - if sel > 1 then - sel = sel - 1 - draw(disList, sel, loc) - elseif loc > 1 then - loc = loc - 1 - disList = updateDisplayList(list, loc, len) - draw(disList, sel, loc) - end - elseif e == "key" and key == 208 then - if sel < len then - sel = sel + 1 - draw(disList, sel, loc) - elseif loc + len - 1 < #list then - loc = loc + 1 - disList = updateDisplayList(list, loc, len) - draw(disList, sel, loc) - end - elseif e == "mouse_scroll" then - os.queueEvent("key", key == -1 and 200 or 208) - elseif e == "key" and key == 28 then - return disList[sel] - end - end -end - -function monitorKeyboardShortcuts() - local ta, tb = nil, nil - local allowChar = false - local shiftPressed = false - while true do - local event, char = os.pullEvent() - if event == "key" and (char == 42 or char == 52) then - shiftPressed = true - tb = os.startTimer(keyboardShortcutTimeout) - elseif event == "key" and (char == 29 or char == 157 or char == 219 or char == 220) then - allowEditorEvent = false - allowChar = true - ta = os.startTimer(keyboardShortcutTimeout) - elseif event == "key" and allowChar then - local name = nil - for k, v in pairs(keys) do - if v == char then - if shiftPressed then os.queueEvent("shortcut", "ctrl shift", k:lower()) - else os.queueEvent("shortcut", "ctrl", k:lower()) end - sleep(0.005) - allowEditorEvent = true - end - end - if shiftPressed then os.queueEvent("shortcut", "ctrl shift", char) - else os.queueEvent("shortcut", "ctrl", char) end - elseif event == "timer" and char == ta then - allowEditorEvent = true - allowChar = false - elseif event == "timer" and char == tb then - shiftPressed = false - end - end -end - - --- -------- Saving and Loading - -local function download(url, path) - for i = 1, 3 do - local response = http.get(url) - if response then - local data = response.readAll() - response.close() - if path then - local f = io.open(path, "w") - f:write(data) - f:close() - end - return true - end - end - - return false -end - -local function saveFile(path, lines) - local dir = path:sub(1, path:len() - fs.getName(path):len()) - if not fs.exists(dir) then fs.makeDir(dir) end - if not fs.isDir(path) and not fs.isReadOnly(path) then - local a = "" - for _, v in pairs(lines) do a = a .. v .. "\n" end - - local f = io.open(path, "w") - f:write(a) - f:close() - return true - else return false end -end - -local function loadFile(path) - if not fs.exists(path) then - local dir = path:sub(1, path:len() - fs.getName(path):len()) - if not fs.exists(dir) then fs.makeDir(dir) end - local f = io.open(path, "w") - f:write("") - f:close() - end - - local l = {} - if fs.exists(path) and not fs.isDir(path) then - local f = io.open(path, "r") - if f then - local a = f:read("*l") - while a do - table.insert(l, a) - a = f:read("*l") - end - f:close() - end - else return nil end - - if #l < 1 then table.insert(l, "") end - return l -end - - --- -------- Languages - -languages.lua = {} -languages.brainfuck = {} -languages.none = {} - --- Lua - -languages.lua.helpTips = { - "A function you tried to call doesn't exist.", - "You made a typo.", - "The index of an array is nil.", - "The wrong variable type was passed.", - "A function/variable doesn't exist.", - "You missed an 'end'.", - "You missed a 'then'.", - "You declared a variable incorrectly.", - "One of your variables is mysteriously nil." -} - -languages.lua.defaultHelpTips = { - 2, 5 -} - -languages.lua.errors = { - ["Attempt to call nil."] = {1, 2}, - ["Attempt to index nil."] = {3, 2}, - [".+ expected, got .+"] = {4, 2, 9}, - ["'end' expected"] = {6, 2}, - ["'then' expected"] = {7, 2}, - ["'=' expected"] = {8, 2} -} - -languages.lua.keywords = { - ["and"] = "conditional", - ["break"] = "conditional", - ["do"] = "conditional", - ["else"] = "conditional", - ["elseif"] = "conditional", - ["end"] = "conditional", - ["for"] = "conditional", - ["function"] = "conditional", - ["if"] = "conditional", - ["in"] = "conditional", - ["local"] = "conditional", - ["not"] = "conditional", - ["or"] = "conditional", - ["repeat"] = "conditional", - ["return"] = "conditional", - ["then"] = "conditional", - ["until"] = "conditional", - ["while"] = "conditional", - - ["true"] = "constant", - ["false"] = "constant", - ["nil"] = "constant", - - ["print"] = "function", - ["write"] = "function", - ["sleep"] = "function", - ["pairs"] = "function", - ["ipairs"] = "function", - ["loadstring"] = "function", - ["loadfile"] = "function", - ["dofile"] = "function", - ["rawset"] = "function", - ["rawget"] = "function", - ["setfenv"] = "function", - ["getfenv"] = "function", -} - -languages.lua.parseError = function(e) - local ret = {filename = "unknown", line = -1, display = "Unknown!", err = ""} - if e and e ~= "" then - ret.err = e - if e:find(":") then - ret.filename = e:sub(1, e:find(":") - 1):gsub("^%s*(.-)%s*$", "%1") - -- The "" is needed to circumvent a CC bug - e = (e:sub(e:find(":") + 1) .. ""):gsub("^%s*(.-)%s*$", "%1") - if e:find(":") then - ret.line = e:sub(1, e:find(":") - 1) - e = e:sub(e:find(":") + 2):gsub("^%s*(.-)%s*$", "%1") .. "" - end - end - ret.display = e:sub(1, 1):upper() .. e:sub(2, -1) .. "." - end - - return ret -end - -languages.lua.getCompilerErrors = function(code) - code = "local function ee65da6af1cb6f63fee9a081246f2fd92b36ef2(...)\n\n" .. code .. "\n\nend" - local fn, err = loadstring(code) - if not err then - local _, e = pcall(fn) - if e then err = e end - end - - if err then - local a = err:find("]", 1, true) - if a then err = "string" .. err:sub(a + 1, -1) end - local ret = languages.lua.parseError(err) - if tonumber(ret.line) then ret.line = tonumber(ret.line) end - return ret - else return languages.lua.parseError(nil) end -end - -languages.lua.run = function(path, ar) - local fn, err = loadfile(path) - setfenv(fn, getfenv()) - if not err then - _, err = pcall(function() fn(unpack(ar)) end) - end - return err -end - - --- Brainfuck - -languages.brainfuck.helpTips = { - "Well idk...", - "Isn't this the whole point of the language?", - "Ya know... Not being able to debug it?", - "You made a typo." -} - -languages.brainfuck.defaultHelpTips = { - 1, 2, 3 -} - -languages.brainfuck.errors = { - ["No matching '['"] = {1, 2, 3, 4} -} - -languages.brainfuck.keywords = {} - -languages.brainfuck.parseError = function(e) - local ret = {filename = "unknown", line = -1, display = "Unknown!", err = ""} - if e and e ~= "" then - ret.err = e - ret.line = e:sub(1, e:find(":") - 1) - e = e:sub(e:find(":") + 2):gsub("^%s*(.-)%s*$", "%1") .. "" - ret.display = e:sub(1, 1):upper() .. e:sub(2, -1) .. "." - end - - return ret -end - -languages.brainfuck.mapLoops = function(code) - -- Map loops - local loopLocations = {} - local loc = 1 - local line = 1 - for let in string.gmatch(code, ".") do - if let == "[" then - loopLocations[loc] = true - elseif let == "]" then - local found = false - for i = loc, 1, -1 do - if loopLocations[i] == true then - loopLocations[i] = loc - found = true - end - end - - if not found then - return line .. ": No matching '['" - end - end - - if let == "\n" then line = line + 1 end - loc = loc + 1 - end - return loopLocations -end - -languages.brainfuck.getCompilerErrors = function(code) - local a = languages.brainfuck.mapLoops(code) - if type(a) == "string" then return languages.brainfuck.parseError(a) - else return languages.brainfuck.parseError(nil) end -end - -languages.brainfuck.run = function(path) - -- Read from file - local f = io.open(path, "r") - local content = f:read("*a") - f:close() - - -- Define environment - local dataCells = {} - local dataPointer = 1 - local instructionPointer = 1 - - -- Map loops - local loopLocations = languages.brainfuck.mapLoops(content) - if type(loopLocations) == "string" then return loopLocations end - - -- Execute code - while true do - local let = content:sub(instructionPointer, instructionPointer) - - if let == ">" then - dataPointer = dataPointer + 1 - if not dataCells[tostring(dataPointer)] then dataCells[tostring(dataPointer)] = 0 end - elseif let == "<" then - if not dataCells[tostring(dataPointer)] then dataCells[tostring(dataPointer)] = 0 end - dataPointer = dataPointer - 1 - if not dataCells[tostring(dataPointer)] then dataCells[tostring(dataPointer)] = 0 end - elseif let == "+" then - if not dataCells[tostring(dataPointer)] then dataCells[tostring(dataPointer)] = 0 end - dataCells[tostring(dataPointer)] = dataCells[tostring(dataPointer)] + 1 - elseif let == "-" then - if not dataCells[tostring(dataPointer)] then dataCells[tostring(dataPointer)] = 0 end - dataCells[tostring(dataPointer)] = dataCells[tostring(dataPointer)] - 1 - elseif let == "." then - if not dataCells[tostring(dataPointer)] then dataCells[tostring(dataPointer)] = 0 end - if term.getCursorPos() >= w then print("") end - write(string.char(math.max(1, dataCells[tostring(dataPointer)]))) - elseif let == "," then - if not dataCells[tostring(dataPointer)] then dataCells[tostring(dataPointer)] = 0 end - term.setCursorBlink(true) - local e, but = os.pullEvent("char") - term.setCursorBlink(false) - dataCells[tostring(dataPointer)] = string.byte(but) - if term.getCursorPos() >= w then print("") end - write(but) - elseif let == "/" then - if not dataCells[tostring(dataPointer)] then dataCells[tostring(dataPointer)] = 0 end - if term.getCursorPos() >= w then print("") end - write(dataCells[tostring(dataPointer)]) - elseif let == "[" then - if dataCells[tostring(dataPointer)] == 0 then - for k, v in pairs(loopLocations) do - if k == instructionPointer then instructionPointer = v end - end - end - elseif let == "]" then - for k, v in pairs(loopLocations) do - if v == instructionPointer then instructionPointer = k - 1 end - end - end - - instructionPointer = instructionPointer + 1 - if instructionPointer > content:len() then print("") break end - end -end - --- None - -languages.none.helpTips = {} -languages.none.defaultHelpTips = {} -languages.none.errors = {} -languages.none.keywords = {} - -languages.none.parseError = function(err) - return {filename = "", line = -1, display = "", err = ""} -end - -languages.none.getCompilerErrors = function(code) - return languages.none.parseError(nil) -end - -languages.none.run = function(path) end - - --- Load language -currentLanguage = languages.lua - - --- -------- Run GUI - -local function viewErrorHelp(e) - title("LuaIDE - Error Help") - - local tips = nil - for k, v in pairs(currentLanguage.errors) do - if e.display:find(k) then tips = v break end - end - - term.setBackgroundColor(colors[theme.err]) - for i = 6, 8 do - term.setCursorPos(5, i) - term.write(string.rep(" ", 35)) - end - - term.setBackgroundColor(colors[theme.prompt]) - for i = 10, 18 do - term.setCursorPos(5, i) - term.write(string.rep(" ", 46)) - end - - if tips then - term.setBackgroundColor(colors[theme.err]) - term.setCursorPos(6, 7) - term.write("Error Help") - - term.setBackgroundColor(colors[theme.prompt]) - for i, v in ipairs(tips) do - term.setCursorPos(7, i + 10) - term.write("- " .. currentLanguage.helpTips[v]) - end - else - term.setBackgroundColor(colors[theme.err]) - term.setCursorPos(6, 7) - term.write("No Error Tips Available!") - - term.setBackgroundColor(colors[theme.prompt]) - term.setCursorPos(6, 11) - term.write("There are no error tips available, but") - term.setCursorPos(6, 12) - term.write("you could see if it was any of these:") - - for i, v in ipairs(currentLanguage.defaultHelpTips) do - term.setCursorPos(7, i + 12) - term.write("- " .. currentLanguage.helpTips[v]) - end - end - - prompt({{"Back", w - 8, 7}}, "horizontal") -end - -local function run(path, lines, useArgs) - local ar = {} - if useArgs then - title("LuaIDE - Run " .. fs.getName(path)) - local s = centerRead(w - 13, fs.getName(path) .. " ") - for m in string.gmatch(s, "[^ \t]+") do ar[#ar + 1] = m:gsub("^%s*(.-)%s*$", "%1") end - end - - saveFile(path, lines) - term.setCursorBlink(false) - term.setBackgroundColor(colors.black) - term.setTextColor(colors.white) - term.clear() - term.setCursorPos(1, 1) - local err = currentLanguage.run(path, ar) - - term.setBackgroundColor(colors.black) - print("\n") - if err then - if isAdvanced() then term.setTextColor(colors.red) end - centerPrint("The program has crashed!") - end - term.setTextColor(colors.white) - centerPrint("Press any key to return to LuaIDE...") - while true do - local e = os.pullEvent() - if e == "key" then break end - end - - -- To prevent key from showing up in editor - os.queueEvent("") - os.pullEvent() - - if err then - if currentLanguage == languages.lua and err:find("]") then - err = fs.getName(path) .. err:sub(err:find("]", 1, true) + 1, -1) - end - - while true do - title("LuaIDE - Error!") - - term.setBackgroundColor(colors[theme.err]) - for i = 6, 8 do - term.setCursorPos(3, i) - term.write(string.rep(" ", w - 5)) - end - term.setCursorPos(4, 7) - term.write("The program has crashed!") - - term.setBackgroundColor(colors[theme.prompt]) - for i = 10, 14 do - term.setCursorPos(3, i) - term.write(string.rep(" ", w - 5)) - end - - local formattedErr = currentLanguage.parseError(err) - term.setCursorPos(4, 11) - term.write("Line: " .. formattedErr.line) - term.setCursorPos(4, 12) - term.write("Error:") - term.setCursorPos(5, 13) - - local a = formattedErr.display - local b = nil - if a:len() > w - 8 then - for i = a:len(), 1, -1 do - if a:sub(i, i) == " " then - b = a:sub(i + 1, -1) - a = a:sub(1, i) - break - end - end - end - - term.write(a) - if b then - term.setCursorPos(5, 14) - term.write(b) - end - - local opt = prompt({{"Error Help", w/2 - 15, 17}, {"Go To Line", w/2 + 2, 17}}, - "horizontal") - if opt == "Error Help" then - viewErrorHelp(formattedErr) - elseif opt == "Go To Line" then - -- To prevent key from showing up in editor - os.queueEvent("") - os.pullEvent() - - return "go to", tonumber(formattedErr.line) - end - end - end -end - - --- -------- Functions - -local function goto() - term.setBackgroundColor(colors[theme.backgroundHighlight]) - term.setCursorPos(2, 1) - term.clearLine() - term.write("Line: ") - local line = modRead({visibleLength = w - 2}) - - local num = tonumber(line) - if num and num > 0 then return num - else - term.setCursorPos(2, 1) - term.clearLine() - term.write("Not a line number!") - sleep(1.6) - return nil - end -end - -local function setsyntax() - local opts = { - "[Lua] Brainfuck None ", - " Lua [Brainfuck] None ", - " Lua Brainfuck [None]" - } - local sel = 1 - - term.setCursorBlink(false) - term.setBackgroundColor(colors[theme.backgroundHighlight]) - term.setCursorPos(2, 1) - term.clearLine() - term.write(opts[sel]) - while true do - local e, but, x, y = os.pullEvent("key") - if but == 203 then - sel = math.max(1, sel - 1) - term.setCursorPos(2, 1) - term.clearLine() - term.write(opts[sel]) - elseif but == 205 then - sel = math.min(#opts, sel + 1) - term.setCursorPos(2, 1) - term.clearLine() - term.write(opts[sel]) - elseif but == 28 then - if sel == 1 then currentLanguage = languages.lua - elseif sel == 2 then currentLanguage = languages.brainfuck - elseif sel == 3 then currentLanguage = languages.none end - term.setCursorBlink(true) - return - end - end -end - - --- -------- Re-Indenting - -local tabWidth = 2 - -local comments = {} -local strings = {} - -local increment = { - "if%s+.+%s+then%s*$", - "for%s+.+%s+do%s*$", - "while%s+.+%s+do%s*$", - "repeat%s*$", - "function%s+[a-zA-Z_0-9]\(.*\)%s*$" -} - -local decrement = { - "end", - "until%s+.+" -} - -local special = { - "else%s*$", - "elseif%s+.+%s+then%s*$" -} - -local function check(func) - for _, v in pairs(func) do - local cLineStart = v["lineStart"] - local cLineEnd = v["lineEnd"] - local cCharStart = v["charStart"] - local cCharEnd = v["charEnd"] - - if line >= cLineStart and line <= cLineEnd then - if line == cLineStart then return cCharStart < charNumb - elseif line == cLineEnd then return cCharEnd > charNumb - else return true end - end - end -end - -local function isIn(line, loc) - if check(comments) then return true end - if check(strings) then return true end - return false -end - -local function setComment(ls, le, cs, ce) - comments[#comments + 1] = {} - comments[#comments].lineStart = ls - comments[#comments].lineEnd = le - comments[#comments].charStart = cs - comments[#comments].charEnd = ce -end - -local function setString(ls, le, cs, ce) - strings[#strings + 1] = {} - strings[#strings].lineStart = ls - strings[#strings].lineEnd = le - strings[#strings].charStart = cs - strings[#strings].charEnd = ce -end - -local function map(contents) - local inCom = false - local inStr = false - - for i = 1, #contents do - if content[i]:find("%-%-%[%[") and not inStr and not inCom then - local cStart = content[i]:find("%-%-%[%[") - setComment(i, nil, cStart, nil) - inCom = true - elseif content[i]:find("%-%-%[=%[") and not inStr and not inCom then - local cStart = content[i]:find("%-%-%[=%[") - setComment(i, nil, cStart, nil) - inCom = true - elseif content[i]:find("%[%[") and not inStr and not inCom then - local cStart = content[i]:find("%[%[") - setString(i, nil, cStart, nil) - inStr = true - elseif content[i]:find("%[=%[") and not inStr and not inCom then - local cStart = content[i]:find("%[=%[") - setString(i, nil, cStart, nil) - inStr = true - end - - if content[i]:find("%]%]") and inStr and not inCom then - local cStart, cEnd = content[i]:find("%]%]") - strings[#strings].lineEnd = i - strings[#strings].charEnd = cEnd - inStr = false - elseif content[i]:find("%]=%]") and inStr and not inCom then - local cStart, cEnd = content[i]:find("%]=%]") - strings[#strings].lineEnd = i - strings[#strings].charEnd = cEnd - inStr = false - end - - if content[i]:find("%]%]") and not inStr and inCom then - local cStart, cEnd = content[i]:find("%]%]") - comments[#comments].lineEnd = i - comments[#comments].charEnd = cEnd - inCom = false - elseif content[i]:find("%]=%]") and not inStr and inCom then - local cStart, cEnd = content[i]:find("%]=%]") - comments[#comments].lineEnd = i - comments[#comments].charEnd = cEnd - inCom = false - end - - if content[i]:find("%-%-") and not inStr and not inCom then - local cStart = content[i]:find("%-%-") - setComment(i, i, cStart, -1) - elseif content[i]:find("'") and not inStr and not inCom then - local cStart, cEnd = content[i]:find("'") - local nextChar = content[i]:sub(cEnd + 1, string.len(content[i])) - local _, cEnd = nextChar:find("'") - setString(i, i, cStart, cEnd) - elseif content[i]:find('"') and not inStr and not inCom then - local cStart, cEnd = content[i]:find('"') - local nextChar = content[i]:sub(cEnd + 1, string.len(content[i])) - local _, cEnd = nextChar:find('"') - setString(i, i, cStart, cEnd) - end - end -end - -local function reindent(contents) - local err = nil - if currentLanguage ~= languages.lua then - err = "Cannot indent languages other than Lua!" - elseif currentLanguage.getCompilerErrors(table.concat(contents, "\n")).line ~= -1 then - err = "Cannot indent a program with errors!" - end - - if err then - term.setCursorBlink(false) - term.setCursorPos(2, 1) - term.setBackgroundColor(colors[theme.backgroundHighlight]) - term.clearLine() - term.write(err) - sleep(1.6) - return contents - end - - local new = {} - local level = 0 - for k, v in pairs(contents) do - local incrLevel = false - local foundIncr = false - for _, incr in pairs(increment) do - if v:find(incr) and not isIn(k, v:find(incr)) then - incrLevel = true - end - if v:find(incr:sub(1, -2)) and not isIn(k, v:find(incr)) then - foundIncr = true - end - end - - local decrLevel = false - if not incrLevel then - for _, decr in pairs(decrement) do - if v:find(decr) and not isIn(k, v:find(decr)) and not foundIncr then - level = math.max(0, level - 1) - decrLevel = true - end - end - end - - if not decrLevel then - for _, sp in pairs(special) do - if v:find(sp) and not isIn(k, v:find(sp)) then - incrLevel = true - level = math.max(0, level - 1) - end - end - end - - new[k] = string.rep(" ", level * tabWidth) .. v - if incrLevel then level = level + 1 end - end - - return new -end - - --- -------- Menu - -local menu = { - [1] = {"File", --- "About", --- "Settings", --- "", - "New File ^+N", - "Open File ^+O", - "Save File ^+S", - "Close ^+W", - "Print ^+P", - "Quit ^+Q" - }, [2] = {"Edit", - "Cut Line ^+X", - "Copy Line ^+C", - "Paste Line ^+V", - "Delete Line", - "Clear Line" - }, [3] = {"Functions", - "Go To Line ^+G", - "Re-Indent ^+I", - "Set Syntax ^+E", - "Start of Line ^+<", - "End of Line ^+>" - }, [4] = {"Run", - "Run Program ^+R", - "Run w/ Args ^+Shift+R" - } -} - -local shortcuts = { - -- File - ["ctrl n"] = "New File ^+N", - ["ctrl o"] = "Open File ^+O", - ["ctrl s"] = "Save File ^+S", - ["ctrl w"] = "Close ^+W", - ["ctrl p"] = "Print ^+P", - ["ctrl q"] = "Quit ^+Q", - - -- Edit - ["ctrl x"] = "Cut Line ^+X", - ["ctrl c"] = "Copy Line ^+C", - ["ctrl v"] = "Paste Line ^+V", - - -- Functions - ["ctrl g"] = "Go To Line ^+G", - ["ctrl i"] = "Re-Indent ^+I", - ["ctrl e"] = "Set Syntax ^+E", - ["ctrl 203"] = "Start of Line ^+<", - ["ctrl 205"] = "End of Line ^+>", - - -- Run - ["ctrl r"] = "Run Program ^+R", - ["ctrl shift r"] = "Run w/ Args ^+Shift+R" -} - -local menuFunctions = { - -- File --- ["About"] = function() end, --- ["Settings"] = function() end, - ["New File ^+N"] = function(path, lines) saveFile(path, lines) return "new" end, - ["Open File ^+O"] = function(path, lines) saveFile(path, lines) return "open" end, - ["Save File ^+S"] = function(path, lines) saveFile(path, lines) end, - ["Close ^+W"] = function(path, lines) saveFile(path, lines) return "menu" end, - ["Print ^+P"] = function(path, lines) saveFile(path, lines) return nil end, - ["Quit ^+Q"] = function(path, lines) saveFile(path, lines) return "exit" end, - - -- Edit - ["Cut Line ^+X"] = function(path, lines, y) - clipboard = lines[y] table.remove(lines, y) return nil, lines end, - ["Copy Line ^+C"] = function(path, lines, y) clipboard = lines[y] end, - ["Paste Line ^+V"] = function(path, lines, y) - if clipboard then table.insert(lines, y, clipboard) end return nil, lines end, - ["Delete Line"] = function(path, lines, y) table.remove(lines, y) return nil, lines end, - ["Clear Line"] = function(path, lines, y) lines[y] = "" return nil, lines, "cursor" end, - - -- Functions - ["Go To Line ^+G"] = function() return nil, "go to", goto() end, - ["Re-Indent ^+I"] = function(path, lines) - local a = reindent(lines) saveFile(path, lines) return nil, a - end, - ["Set Syntax ^+E"] = function(path, lines) - setsyntax() - if currentLanguage == languages.brainfuck and lines[1] ~= "-- Syntax: Brainfuck" then - table.insert(lines, 1, "-- Syntax: Brainfuck") - return nil, lines - end - end, - ["Start of Line ^+<"] = function() os.queueEvent("key", 199) end, - ["End of Line ^+>"] = function() os.queueEvent("key", 207) end, - - -- Run - ["Run Program ^+R"] = function(path, lines) - saveFile(path, lines) - return nil, run(path, lines, false) - end, - ["Run w/ Args ^+Shift+R"] = function(path, lines) - saveFile(path, lines) - return nil, run(path, lines, true) - end, -} - -local function drawMenu(open) - term.setCursorPos(1, 1) - term.setTextColor(colors[theme.textColor]) - term.setBackgroundColor(colors[theme.backgroundHighlight]) - term.clearLine() - local curX = 0 - for _, v in pairs(menu) do - term.setCursorPos(3 + curX, 1) - term.write(v[1]) - curX = curX + v[1]:len() + 3 - end - - if open then - local it = {} - local x = 1 - for _, v in pairs(menu) do - if open == v[1] then - it = v - break - end - x = x + v[1]:len() + 3 - end - x = x + 1 - - local items = {} - for i = 2, #it do - table.insert(items, it[i]) - end - - local len = 1 - for _, v in pairs(items) do if v:len() + 2 > len then len = v:len() + 2 end end - - for i, v in ipairs(items) do - term.setCursorPos(x, i + 1) - term.write(string.rep(" ", len)) - term.setCursorPos(x + 1, i + 1) - term.write(v) - end - term.setCursorPos(x, #items + 2) - term.write(string.rep(" ", len)) - return items, len - end -end - -local function triggerMenu(cx, cy) - -- Determine clicked menu - local curX = 0 - local open = nil - for _, v in pairs(menu) do - if cx >= curX + 3 and cx <= curX + v[1]:len() + 2 then - open = v[1] - break - end - curX = curX + v[1]:len() + 3 - end - local menux = curX + 2 - if not open then return false end - - -- Flash menu item - term.setCursorBlink(false) - term.setCursorPos(menux, 1) - term.setBackgroundColor(colors[theme.background]) - term.write(string.rep(" ", open:len() + 2)) - term.setCursorPos(menux + 1, 1) - term.write(open) - sleep(0.1) - local items, len = drawMenu(open) - - local ret = true - - -- Pull events on menu - local ox, oy = term.getCursorPos() - while type(ret) ~= "string" do - local e, but, x, y = os.pullEvent() - if e == "mouse_click" then - -- If clicked outside menu - if x < menux - 1 or x > menux + len - 1 then break - elseif y > #items + 2 then break - elseif y == 1 then break end - - for i, v in ipairs(items) do - if y == i + 1 and x >= menux and x <= menux + len - 2 then - -- Flash when clicked - term.setCursorPos(menux, y) - term.setBackgroundColor(colors[theme.background]) - term.write(string.rep(" ", len)) - term.setCursorPos(menux + 1, y) - term.write(v) - sleep(0.1) - drawMenu(open) - - -- Return item - ret = v - break - end - end - end - end - - term.setCursorPos(ox, oy) - term.setCursorBlink(true) - return ret -end - - --- -------- Editing - -local standardsCompletions = { - "if%s+.+%s+then%s*$", - "for%s+.+%s+do%s*$", - "while%s+.+%s+do%s*$", - "repeat%s*$", - "function%s+[a-zA-Z_0-9]?\(.*\)%s*$", - "=%s*function%s*\(.*\)%s*$", - "else%s*$", - "elseif%s+.+%s+then%s*$" -} - -local liveCompletions = { - ["("] = ")", - ["{"] = "}", - ["["] = "]", - ["\""] = "\"", - ["'"] = "'", -} - -local x, y = 0, 0 -local edw, edh = 0, h - 1 -local offx, offy = 0, 1 -local scrollx, scrolly = 0, 0 -local lines = {} -local liveErr = currentLanguage.parseError(nil) -local displayCode = true -local lastEventClock = os.clock() - -local function attemptToHighlight(line, regex, col) - local match = string.match(line, regex) - if match then - if type(col) == "number" then term.setTextColor(col) - elseif type(col) == "function" then term.setTextColor(col(match)) end - term.write(match) - term.setTextColor(colors[theme.textColor]) - return line:sub(match:len() + 1, -1) - end - return nil -end - -local function writeHighlighted(line) - if currentLanguage == languages.lua then - while line:len() > 0 do - line = attemptToHighlight(line, "^%-%-%[%[.-%]%]", colors[theme.comment]) or - attemptToHighlight(line, "^%-%-.*", colors[theme.comment]) or - attemptToHighlight(line, "^\".*[^\\]\"", colors[theme.string]) or - attemptToHighlight(line, "^\'.*[^\\]\'", colors[theme.string]) or - attemptToHighlight(line, "^%[%[.-%]%]", colors[theme.string]) or - attemptToHighlight(line, "^[%w_]+", function(match) - if currentLanguage.keywords[match] then - return colors[theme[currentLanguage.keywords[match]]] - end - return colors[theme.textColor] - end) or - attemptToHighlight(line, "^[^%w_]", colors[theme.textColor]) - end - else term.write(line) end -end - -local function draw() - -- Menu - term.setTextColor(colors[theme.textColor]) - term.setBackgroundColor(colors[theme.editorBackground]) - term.clear() - drawMenu() - - -- Line numbers - offx, offy = tostring(#lines):len() + 1, 1 - edw, edh = w - offx, h - 1 - - -- Draw text - for i = 1, edh do - local a = lines[scrolly + i] - if a then - local ln = string.rep(" ", offx - 1 - tostring(scrolly + i):len()) .. tostring(scrolly + i) - local l = a:sub(scrollx + 1, edw + scrollx + 1) - ln = ln .. ":" - - if liveErr.line == scrolly + i then ln = string.rep(" ", offx - 2) .. "!:" end - - term.setCursorPos(1, i + offy) - term.setBackgroundColor(colors[theme.editorBackground]) - if scrolly + i == y then - if scrolly + i == liveErr.line and os.clock() - lastEventClock > 3 then - term.setBackgroundColor(colors[theme.editorErrorHighlight]) - else term.setBackgroundColor(colors[theme.editorLineHightlight]) end - term.clearLine() - elseif scrolly + i == liveErr.line then - term.setBackgroundColor(colors[theme.editorError]) - term.clearLine() - end - - term.setCursorPos(1 - scrollx + offx, i + offy) - if scrolly + i == y then - if scrolly + i == liveErr.line and os.clock() - lastEventClock > 3 then - term.setBackgroundColor(colors[theme.editorErrorHighlight]) - else term.setBackgroundColor(colors[theme.editorLineHightlight]) end - elseif scrolly + i == liveErr.line then term.setBackgroundColor(colors[theme.editorError]) - else term.setBackgroundColor(colors[theme.editorBackground]) end - if scrolly + i == liveErr.line then - if displayCode then term.write(a) - else term.write(liveErr.display) end - else writeHighlighted(a) end - - term.setCursorPos(1, i + offy) - if scrolly + i == y then - if scrolly + i == liveErr.line and os.clock() - lastEventClock > 3 then - term.setBackgroundColor(colors[theme.editorError]) - else term.setBackgroundColor(colors[theme.editorLineNumbersHighlight]) end - elseif scrolly + i == liveErr.line then - term.setBackgroundColor(colors[theme.editorErrorHighlight]) - else term.setBackgroundColor(colors[theme.editorLineNumbers]) end - term.write(ln) - end - end - term.setCursorPos(x - scrollx + offx, y - scrolly + offy) -end - -local function drawLine(...) - local ls = {...} - offx = tostring(#lines):len() + 1 - for _, ly in pairs(ls) do - local a = lines[ly] - if a then - local ln = string.rep(" ", offx - 1 - tostring(ly):len()) .. tostring(ly) - local l = a:sub(scrollx + 1, edw + scrollx + 1) - ln = ln .. ":" - - if liveErr.line == ly then ln = string.rep(" ", offx - 2) .. "!:" end - - term.setCursorPos(1, (ly - scrolly) + offy) - term.setBackgroundColor(colors[theme.editorBackground]) - if ly == y then - if ly == liveErr.line and os.clock() - lastEventClock > 3 then - term.setBackgroundColor(colors[theme.editorErrorHighlight]) - else term.setBackgroundColor(colors[theme.editorLineHightlight]) end - elseif ly == liveErr.line then - term.setBackgroundColor(colors[theme.editorError]) - end - term.clearLine() - - term.setCursorPos(1 - scrollx + offx, (ly - scrolly) + offy) - if ly == y then - if ly == liveErr.line and os.clock() - lastEventClock > 3 then - term.setBackgroundColor(colors[theme.editorErrorHighlight]) - else term.setBackgroundColor(colors[theme.editorLineHightlight]) end - elseif ly == liveErr.line then term.setBackgroundColor(colors[theme.editorError]) - else term.setBackgroundColor(colors[theme.editorBackground]) end - if ly == liveErr.line then - if displayCode then term.write(a) - else term.write(liveErr.display) end - else writeHighlighted(a) end - - term.setCursorPos(1, (ly - scrolly) + offy) - if ly == y then - if ly == liveErr.line and os.clock() - lastEventClock > 3 then - term.setBackgroundColor(colors[theme.editorError]) - else term.setBackgroundColor(colors[theme.editorLineNumbersHighlight]) end - elseif ly == liveErr.line then - term.setBackgroundColor(colors[theme.editorErrorHighlight]) - else term.setBackgroundColor(colors[theme.editorLineNumbers]) end - term.write(ln) - end - end - term.setCursorPos(x - scrollx + offx, y - scrolly + offy) -end - -local function cursorLoc(x, y, force) - local sx, sy = x - scrollx, y - scrolly - local redraw = false - if sx < 1 then - scrollx = x - 1 - sx = 1 - redraw = true - elseif sx > edw then - scrollx = x - edw - sx = edw - redraw = true - end if sy < 1 then - scrolly = y - 1 - sy = 1 - redraw = true - elseif sy > edh then - scrolly = y - edh - sy = edh - redraw = true - end if redraw or force then draw() end - term.setCursorPos(sx + offx, sy + offy) -end - -local function executeMenuItem(a, path) - if type(a) == "string" and menuFunctions[a] then - local opt, nl, gtln = menuFunctions[a](path, lines, y) - if type(opt) == "string" then term.setCursorBlink(false) return opt end - if type(nl) == "table" then - if #lines < 1 then table.insert(lines, "") end - y = math.min(y, #lines) - x = math.min(x, lines[y]:len() + 1) - lines = nl - elseif type(nl) == "string" then - if nl == "go to" and gtln then - x, y = 1, math.min(#lines, gtln) - cursorLoc(x, y) - end - end - end - term.setCursorBlink(true) - draw() - term.setCursorPos(x - scrollx + offx, y - scrolly + offy) -end - -local function edit(path) - -- Variables - x, y = 1, 1 - offx, offy = 0, 1 - scrollx, scrolly = 0, 0 - lines = loadFile(path) - if not lines then return "menu" end - - -- Enable brainfuck - if lines[1] == "-- Syntax: Brainfuck" then - currentLanguage = languages.brainfuck - end - - -- Clocks - local autosaveClock = os.clock() - local scrollClock = os.clock() -- To prevent redraw flicker - local liveErrorClock = os.clock() - local hasScrolled = false - - -- Draw - draw() - term.setCursorPos(x + offx, y + offy) - term.setCursorBlink(true) - - -- Main loop - local tid = os.startTimer(3) - while true do - local e, key, cx, cy = os.pullEvent() - if e == "key" and allowEditorEvent then - if key == 200 and y > 1 then - -- Up - x, y = math.min(x, lines[y - 1]:len() + 1), y - 1 - drawLine(y, y + 1) - cursorLoc(x, y) - elseif key == 208 and y < #lines then - -- Down - x, y = math.min(x, lines[y + 1]:len() + 1), y + 1 - drawLine(y, y - 1) - cursorLoc(x, y) - elseif key == 203 and x > 1 then - -- Left - x = x - 1 - local force = false - if y - scrolly + offy < offy + 1 then force = true end - cursorLoc(x, y, force) - elseif key == 205 and x < lines[y]:len() + 1 then - -- Right - x = x + 1 - local force = false - if y - scrolly + offy < offy + 1 then force = true end - cursorLoc(x, y, force) - elseif (key == 28 or key == 156) and (displayCode and true or y + scrolly - 1 == - liveErr.line) then - -- Enter - local f = nil - for _, v in pairs(standardsCompletions) do - if lines[y]:find(v) and x == #lines[y] + 1 then f = v end - end - - local _, spaces = lines[y]:find("^[ ]+") - if not spaces then spaces = 0 end - if f then - table.insert(lines, y + 1, string.rep(" ", spaces + 2)) - if not f:find("else", 1, true) and not f:find("elseif", 1, true) then - table.insert(lines, y + 2, string.rep(" ", spaces) .. - (f:find("repeat", 1, true) and "until " or f:find("{", 1, true) and "}" or - "end")) - end - x, y = spaces + 3, y + 1 - cursorLoc(x, y, true) - else - local oldLine = lines[y] - - lines[y] = lines[y]:sub(1, x - 1) - table.insert(lines, y + 1, string.rep(" ", spaces) .. oldLine:sub(x, -1)) - - x, y = spaces + 1, y + 1 - cursorLoc(x, y, true) - end - elseif key == 14 and (displayCode and true or y + scrolly - 1 == liveErr.line) then - -- Backspace - if x > 1 then - local f = false - for k, v in pairs(liveCompletions) do - if lines[y]:sub(x - 1, x - 1) == k then f = true end - end - - lines[y] = lines[y]:sub(1, x - 2) .. lines[y]:sub(x + (f and 1 or 0), -1) - drawLine(y) - x = x - 1 - cursorLoc(x, y) - elseif y > 1 then - local prevLen = lines[y - 1]:len() + 1 - lines[y - 1] = lines[y - 1] .. lines[y] - table.remove(lines, y) - x, y = prevLen, y - 1 - cursorLoc(x, y, true) - end - elseif key == 199 then - -- Home - x = 1 - local force = false - if y - scrolly + offy < offy + 1 then force = true end - cursorLoc(x, y, force) - elseif key == 207 then - -- End - x = lines[y]:len() + 1 - local force = false - if y - scrolly + offy < offy + 1 then force = true end - cursorLoc(x, y, force) - elseif key == 211 and (displayCode and true or y + scrolly - 1 == liveErr.line) then - -- Forward Delete - if x < lines[y]:len() + 1 then - lines[y] = lines[y]:sub(1, x - 1) .. lines[y]:sub(x + 1) - local force = false - if y - scrolly + offy < offy + 1 then force = true end - drawLine(y) - cursorLoc(x, y, force) - elseif y < #lines then - lines[y] = lines[y] .. lines[y + 1] - table.remove(lines, y + 1) - draw() - cursorLoc(x, y) - end - elseif key == 15 and (displayCode and true or y + scrolly - 1 == liveErr.line) then - -- Tab - lines[y] = string.rep(" ", tabWidth) .. lines[y] - x = x + 2 - local force = false - if y - scrolly + offy < offy + 1 then force = true end - drawLine(y) - cursorLoc(x, y, force) - elseif key == 201 then - -- Page up - y = math.min(math.max(y - edh, 1), #lines) - x = math.min(lines[y]:len() + 1, x) - cursorLoc(x, y, true) - elseif key == 209 then - -- Page down - y = math.min(math.max(y + edh, 1), #lines) - x = math.min(lines[y]:len() + 1, x) - cursorLoc(x, y, true) - end - elseif e == "char" and allowEditorEvent and (displayCode and true or - y + scrolly - 1 == liveErr.line) then - local shouldIgnore = false - for k, v in pairs(liveCompletions) do - if key == v and lines[y]:find(k, 1, true) and lines[y]:sub(x, x) == v then - shouldIgnore = true - end - end - - local addOne = false - if not shouldIgnore then - for k, v in pairs(liveCompletions) do - if key == k and lines[y]:sub(x, x) ~= k then key = key .. v addOne = true end - end - lines[y] = lines[y]:sub(1, x - 1) .. key .. lines[y]:sub(x, -1) - end - - x = x + (addOne and 1 or key:len()) - local force = false - if y - scrolly + offy < offy + 1 then force = true end - drawLine(y) - cursorLoc(x, y, force) - elseif e == "mouse_click" and key == 1 then - if cy > 1 then - if cx <= offx and cy - offy == liveErr.line - scrolly then - displayCode = not displayCode - drawLine(liveErr.line) - else - local oldy = y - y = math.min(math.max(scrolly + cy - offy, 1), #lines) - x = math.min(math.max(scrollx + cx - offx, 1), lines[y]:len() + 1) - if oldy ~= y then drawLine(oldy, y) end - cursorLoc(x, y) - end - else - local a = triggerMenu(cx, cy) - if a then - local opt = executeMenuItem(a, path) - if opt then return opt end - end - end - elseif e == "shortcut" then - local a = shortcuts[key .. " " .. cx] - if a then - local parent = nil - local curx = 0 - for i, mv in ipairs(menu) do - for _, iv in pairs(mv) do - if iv == a then - parent = menu[i][1] - break - end - end - if parent then break end - curx = curx + mv[1]:len() + 3 - end - local menux = curx + 2 - - -- Flash menu item - term.setCursorBlink(false) - term.setCursorPos(menux, 1) - term.setBackgroundColor(colors[theme.background]) - term.write(string.rep(" ", parent:len() + 2)) - term.setCursorPos(menux + 1, 1) - term.write(parent) - sleep(0.1) - drawMenu() - - -- Execute item - local opt = executeMenuItem(a, path) - if opt then return opt end - end - elseif e == "mouse_scroll" then - if key == -1 and scrolly > 0 then - scrolly = scrolly - 1 - if os.clock() - scrollClock > 0.0005 then - draw() - term.setCursorPos(x - scrollx + offx, y - scrolly + offy) - end - scrollClock = os.clock() - hasScrolled = true - elseif key == 1 and scrolly < #lines - edh then - scrolly = scrolly + 1 - if os.clock() - scrollClock > 0.0005 then - draw() - term.setCursorPos(x - scrollx + offx, y - scrolly + offy) - end - scrollClock = os.clock() - hasScrolled = true - end - elseif e == "timer" and key == tid then - drawLine(y) - tid = os.startTimer(3) - end - - -- Draw - if hasScrolled and os.clock() - scrollClock > 0.1 then - draw() - term.setCursorPos(x - scrollx + offx, y - scrolly + offy) - hasScrolled = false - end - - -- Autosave - if os.clock() - autosaveClock > autosaveInterval then - saveFile(path, lines) - autosaveClock = os.clock() - end - - -- Errors - if os.clock() - liveErrorClock > 1 then - local prevLiveErr = liveErr - liveErr = currentLanguage.parseError(nil) - local code = "" - for _, v in pairs(lines) do code = code .. v .. "\n" end - - liveErr = currentLanguage.getCompilerErrors(code) - liveErr.line = math.min(liveErr.line - 2, #lines) - if liveErr ~= prevLiveErr then draw() end - liveErrorClock = os.clock() - end - end - - return "menu" -end - - --- -------- Open File - -local function newFile() - local wid = w - 13 - - -- Get name - title("Lua IDE - New File") - local name = centerRead(wid, "/") - if not name or name == "" then return "menu" end - name = "/" .. name - - -- Clear - title("Lua IDE - New File") - term.setTextColor(colors[theme.textColor]) - term.setBackgroundColor(colors[theme.promptHighlight]) - for i = 8, 10 do - term.setCursorPos(w/2 - wid/2, i) - term.write(string.rep(" ", wid)) - end - term.setCursorPos(1, 9) - if fs.isDir(name) then - centerPrint("Cannot Edit a Directory!") - sleep(1.6) - return "menu" - elseif fs.exists(name) then - centerPrint("File Already Exists!") - local opt = prompt({{"Open", w/2 - 9, 14}, {"Cancel", w/2 + 2, 14}}, "horizontal") - if opt == "Open" then return "edit", name - elseif opt == "Cancel" then return "menu" end - else return "edit", name end -end - -local function openFile() - local wid = w - 13 - - -- Get name - title("Lua IDE - Open File") - local name = centerRead(wid, "/") - if not name or name == "" then return "menu" end - name = "/" .. name - - -- Clear - title("Lua IDE - New File") - term.setTextColor(colors[theme.textColor]) - term.setBackgroundColor(colors[theme.promptHighlight]) - for i = 8, 10 do - term.setCursorPos(w/2 - wid/2, i) - term.write(string.rep(" ", wid)) - end - term.setCursorPos(1, 9) - if fs.isDir(name) then - centerPrint("Cannot Open a Directory!") - sleep(1.6) - return "menu" - elseif not fs.exists(name) then - centerPrint("File Doesn't Exist!") - local opt = prompt({{"Create", w/2 - 11, 14}, {"Cancel", w/2 + 2, 14}}, "horizontal") - if opt == "Create" then return "edit", name - elseif opt == "Cancel" then return "menu" end - else return "edit", name end -end - - --- -------- Settings - -local function update() - local function draw(status) - title("LuaIDE - Update") - term.setBackgroundColor(colors[theme.prompt]) - term.setTextColor(colors[theme.textColor]) - for i = 8, 10 do - term.setCursorPos(w/2 - (status:len() + 4), i) - write(string.rep(" ", status:len() + 4)) - end - term.setCursorPos(w/2 - (status:len() + 4), 9) - term.write(" - " .. status .. " ") - - term.setBackgroundColor(colors[theme.errHighlight]) - for i = 8, 10 do - term.setCursorPos(w/2 + 2, i) - term.write(string.rep(" ", 10)) - end - term.setCursorPos(w/2 + 2, 9) - term.write(" > Cancel ") - end - - if not http then - draw("HTTP API Disabled!") - sleep(1.6) - return "settings" - end - - draw("Updating...") - local tID = os.startTimer(10) - http.request(updateURL) - while true do - local e, but, x, y = os.pullEvent() - if (e == "key" and but == 28) or - (e == "mouse_click" and x >= w/2 + 2 and x <= w/2 + 12 and y == 9) then - draw("Cancelled") - sleep(1.6) - break - elseif e == "http_success" and but == updateURL then - local new = x.readAll() - local curf = io.open(ideLocation, "r") - local cur = curf:read("*a") - curf:close() - - if cur ~= new then - draw("Update Found") - sleep(1.6) - local f = io.open(ideLocation, "w") - f:write(new) - f:close() - - draw("Click to Exit") - while true do - local e = os.pullEvent() - if e == "mouse_click" or (not isAdvanced() and e == "key") then break end - end - return "exit" - else - draw("No Updates Found!") - sleep(1.6) - break - end - elseif e == "http_failure" or (e == "timer" and but == tID) then - draw("Update Failed!") - sleep(1.6) - break - end - end - - return "settings" -end - -local function changeTheme() - title("LuaIDE - Theme") - - if isAdvanced() then - local disThemes = {"Back"} - for _, v in pairs(availableThemes) do table.insert(disThemes, v[1]) end - local t = scrollingPrompt(disThemes) - local url = nil - for _, v in pairs(availableThemes) do if v[1] == t then url = v[2] end end - - if not url then return "settings" end - if t == "Dawn (Default)" then - term.setBackgroundColor(colors[theme.backgroundHighlight]) - term.setCursorPos(3, 3) - term.clearLine() - term.write("LuaIDE - Loaded Theme!") - sleep(1.6) - - fs.delete(themeLocation) - theme = defaultTheme - return "menu" - end - - term.setBackgroundColor(colors[theme.backgroundHighlight]) - term.setCursorPos(3, 3) - term.clearLine() - term.write("LuaIDE - Downloading...") - - fs.delete("/.LuaIDE_temp_theme_file") - download(url, "/.LuaIDE_temp_theme_file") - local a = loadTheme("/.LuaIDE_temp_theme_file") - - term.setCursorPos(3, 3) - term.clearLine() - if a then - term.write("LuaIDE - Loaded Theme!") - fs.delete(themeLocation) - fs.move("/.LuaIDE_temp_theme_file", themeLocation) - theme = a - sleep(1.6) - return "menu" - end - - term.write("LuaIDE - Could Not Load Theme!") - fs.delete("/.LuaIDE_temp_theme_file") - sleep(1.6) - return "settings" - else - term.setCursorPos(1, 8) - centerPrint("Themes are not available on") - centerPrint("normal computers!") - end -end - -local function settings() - title("LuaIDE - Settings") - - local opt = prompt({{"Change Theme", w/2 - 17, 8}, {"Return to Menu", w/2 - 22, 13}, - {"Check for Updates", w/2 + 2, 8}, {"Exit IDE", w/2 + 2, 13, bg = colors[theme.err], - highlight = colors[theme.errHighlight]}}, "vertical", true) - if opt == "Change Theme" then return changeTheme() - elseif opt == "Check for Updates" then return update() - elseif opt == "Return to Menu" then return "menu" - elseif opt == "Exit IDE" then return "exit" end -end - - --- -------- Menu - -local function menu() - title("Welcome to LuaIDE " .. version) - - local opt = prompt({{"New File", w/2 - 13, 8}, {"Open File", w/2 - 14, 13}, - {"Settings", w/2 + 2, 8}, {"Exit IDE", w/2 + 2, 13, bg = colors[theme.err], - highlight = colors[theme.errHighlight]}}, "vertical", true) - if opt == "New File" then return "new" - elseif opt == "Open File" then return "open" - elseif opt == "Settings" then return "settings" - elseif opt == "Exit IDE" then return "exit" end -end - - --- -------- Main - -local function main(arguments) - local opt, data = "menu", nil - - -- Check arguments - if type(arguments) == "table" and #arguments > 0 then - local f = "/" .. shell.resolve(arguments[1]) - if fs.isDir(f) then print("Cannot edit a directory.") end - opt, data = "edit", f - end - - -- Main run loop - while true do - -- Menu - if opt == "menu" then opt = menu() end - - -- Other - if opt == "new" then opt, data = newFile() - elseif opt == "open" then opt, data = openFile() - elseif opt == "settings" then opt = settings() - end if opt == "exit" then break end - - -- Edit - if opt == "edit" and data then opt = edit(data) end - end -end - --- Load Theme -if fs.exists(themeLocation) then theme = loadTheme(themeLocation) end -if not theme and isAdvanced() then theme = defaultTheme -elseif not theme then theme = normalTheme end - --- Run -local _, err = pcall(function() - parallel.waitForAny(function() main(arguments) end, monitorKeyboardShortcuts) -end) - --- Catch errors -if err and not err:find("Terminated") then - term.setCursorBlink(false) - title("LuaIDE - Crash! D:") - - term.setBackgroundColor(colors[theme.err]) - for i = 6, 8 do - term.setCursorPos(5, i) - term.write(string.rep(" ", 36)) - end - term.setCursorPos(6, 7) - term.write("LuaIDE Has Crashed! D:") - - term.setBackgroundColor(colors[theme.background]) - term.setCursorPos(2, 10) - print(err) - - term.setBackgroundColor(colors[theme.prompt]) - local _, cy = term.getCursorPos() - for i = cy + 1, cy + 4 do - term.setCursorPos(5, i) - term.write(string.rep(" ", 36)) - end - term.setCursorPos(6, cy + 2) - term.write("Please report this error to") - term.setCursorPos(6, cy + 3) - term.write("GravityScore! ") - - term.setBackgroundColor(colors[theme.background]) - if isAdvanced() then centerPrint("Click to Exit...", h - 1) - else centerPrint("Press Any Key to Exit...", h - 1) end - while true do - local e = os.pullEvent() - if e == "mouse_click" or (not isAdvanced() and e == "key") then break end - end - - -- Prevent key from being shown - os.queueEvent("") - os.pullEvent() -end - --- Exit -term.setBackgroundColor(colors.black) -term.setTextColor(colors.white) -term.clear() -term.setCursorPos(1, 1) -centerPrint("Thank You for Using Lua IDE " .. version) -centerPrint("Made by GravityScore") diff --git a/MetroOS/Programs/Sketch/program b/MetroOS/Programs/Sketch/program deleted file mode 100644 index 8cb7681..0000000 --- a/MetroOS/Programs/Sketch/program +++ /dev/null @@ -1,4014 +0,0 @@ -if OneOS then - --running under OneOS - OneOS.ToolBarColour = colours.grey - OneOS.ToolBarTextColour = colours.white -end - -colours.transparent = -1 -colors.transparent = -1 - ---APIS-- - ---This is my drawing API, is is pretty much identical to what drives OneOS, PearOS, etc. -local _w, _h = term.getSize() - -local round = function(num, idp) - local mult = 10^(idp or 0) - return math.floor(num * mult + 0.5) / mult -end - -Clipboard = { - Content = nil, - Type = nil, - IsCut = false, - - Empty = function() - Clipboard.Content = nil - Clipboard.Type = nil - Clipboard.IsCut = false - end, - - isEmpty = function() - return Clipboard.Content == nil - end, - - Copy = function(content, _type) - Clipboard.Content = content - Clipboard.Type = _type or 'generic' - Clipboard.IsCut = false - end, - - Cut = function(content, _type) - Clipboard.Content = content - Clipboard.Type = _type or 'generic' - Clipboard.IsCut = true - end, - - Paste = function() - local c, t = Clipboard.Content, Clipboard.Type - if Clipboard.IsCut then - Clipboard.Empty() - end - return c, t - end -} - -if OneOS and OneOS.Clipboard then - Clipboard = OneOS.Clipboard -end - -Drawing = { - - Screen = { - Width = _w, - Height = _h - }, - - DrawCharacters = function (x, y, characters, textColour,bgColour) - Drawing.WriteStringToBuffer(x, y, characters, textColour, bgColour) - end, - - DrawBlankArea = function (x, y, w, h, colour) - Drawing.DrawArea (x, y, w, h, " ", 1, colour) - end, - - DrawArea = function (x, y, w, h, character, textColour, bgColour) - --width must be greater than 1, other wise we get a stack overflow - if w < 0 then - w = w * -1 - elseif w == 0 then - w = 1 - end - - for ix = 1, w do - local currX = x + ix - 1 - for iy = 1, h do - local currY = y + iy - 1 - Drawing.WriteToBuffer(currX, currY, character, textColour, bgColour) - end - end - end, - - DrawImage = function(_x,_y,tImage, w, h) - if tImage then - for y = 1, h do - if not tImage[y] then - break - end - for x = 1, w do - if not tImage[y][x] then - break - end - local bgColour = tImage[y][x] - local textColour = tImage.textcol[y][x] or colours.white - local char = tImage.text[y][x] - Drawing.WriteToBuffer(x+_x-1, y+_y-1, char, textColour, bgColour) - end - end - elseif w and h then - Drawing.DrawBlankArea(x, y, w, h, colours.green) - end - end, - --using .nft - LoadImage = function(path) - local image = { - text = {}, - textcol = {} - } - local _fs = fs - if OneOS then - _fs = OneOS.FS - end - if _fs.exists(path) then - local _open = io.open - if OneOS then - _open = OneOS.IO.open - end - local file = _open(path, "r") - local sLine = file:read() - local num = 1 - while sLine do - table.insert(image, num, {}) - table.insert(image.text, num, {}) - table.insert(image.textcol, num, {}) - - --As we're no longer 1-1, we keep track of what index to write to - local writeIndex = 1 - --Tells us if we've hit a 30 or 31 (BG and FG respectively)- next char specifies the curr colour - local bgNext, fgNext = false, false - --The current background and foreground colours - local currBG, currFG = nil,nil - for i=1,#sLine do - local nextChar = string.sub(sLine, i, i) - if nextChar:byte() == 30 then - bgNext = true - elseif nextChar:byte() == 31 then - fgNext = true - elseif bgNext then - currBG = Drawing.GetColour(nextChar) - bgNext = false - elseif fgNext then - currFG = Drawing.GetColour(nextChar) - fgNext = false - else - if nextChar ~= " " and currFG == nil then - currFG = colours.white - end - image[num][writeIndex] = currBG - image.textcol[num][writeIndex] = currFG - image.text[num][writeIndex] = nextChar - writeIndex = writeIndex + 1 - end - end - num = num+1 - sLine = file:read() - end - file:close() - end - return image - end, - - DrawCharactersCenter = function(x, y, w, h, characters, textColour,bgColour) - w = w or Drawing.Screen.Width - h = h or Drawing.Screen.Height - x = x or 0 - y = y or 0 - x = math.ceil((w - #characters) / 2) + x - y = math.floor(h / 2) + y - - Drawing.DrawCharacters(x, y, characters, textColour, bgColour) - end, - - GetColour = function(hex) - if hex == ' ' then - return colours.transparent - end - local value = tonumber(hex, 16) - if not value then return nil end - value = math.pow(2,value) - return value - end, - - Clear = function (_colour) - _colour = _colour or colours.black - Drawing.ClearBuffer() - Drawing.DrawBlankArea(1, 1, Drawing.Screen.Width, Drawing.Screen.Height, _colour) - end, - - Buffer = {}, - BackBuffer = {}, - - DrawBuffer = function() - for y,row in pairs(Drawing.Buffer) do - for x,pixel in pairs(row) do - local shouldDraw = true - local hasBackBuffer = true - if Drawing.BackBuffer[y] == nil or Drawing.BackBuffer[y][x] == nil or #Drawing.BackBuffer[y][x] ~= 3 then - hasBackBuffer = false - end - if hasBackBuffer and Drawing.BackBuffer[y][x][1] == Drawing.Buffer[y][x][1] and Drawing.BackBuffer[y][x][2] == Drawing.Buffer[y][x][2] and Drawing.BackBuffer[y][x][3] == Drawing.Buffer[y][x][3] then - shouldDraw = false - end - if shouldDraw then - term.setBackgroundColour(pixel[3]) - term.setTextColour(pixel[2]) - term.setCursorPos(x, y) - term.write(pixel[1]) - end - end - end - Drawing.BackBuffer = Drawing.Buffer - Drawing.Buffer = {} - term.setCursorPos(1,1) - end, - - ClearBuffer = function() - Drawing.Buffer = {} - end, - - WriteStringToBuffer = function (x, y, characters, textColour,bgColour) - for i = 1, #characters do - local character = characters:sub(i,i) - Drawing.WriteToBuffer(x + i - 1, y, character, textColour, bgColour) - end - end, - - WriteToBuffer = function(x, y, character, textColour,bgColour) - x = round(x) - y = round(y) - if bgColour == colours.transparent then - Drawing.Buffer[y] = Drawing.Buffer[y] or {} - Drawing.Buffer[y][x] = Drawing.Buffer[y][x] or {"", colours.white, colours.black} - Drawing.Buffer[y][x][1] = character - Drawing.Buffer[y][x][2] = textColour - else - Drawing.Buffer[y] = Drawing.Buffer[y] or {} - Drawing.Buffer[y][x] = {character, textColour, bgColour} - end - end, -} - ---Colour Deffitions-- -UIColours = { - Toolbar = colours.grey, - ToolbarText = colours.lightGrey, - ToolbarSelected = colours.lightBlue, - ControlText = colours.white, - ToolbarItemTitle = colours.black, - Background = colours.lightGrey, - MenuBackground = colours.white, - MenuText = colours.black, - MenuSeparatorText = colours.grey, - MenuDisabledText = colours.lightGrey, - Shadow = colours.grey, - TransparentBackgroundOne = colours.white, - TransparentBackgroundTwo = colours.lightGrey, - MenuBarActive = colours.white -} - ---Lists-- -Current = { - Artboard = nil, - Layer = nil, - Tool = nil, - ToolSize = 1, - Toolbar = nil, - Colour = colours.lightBlue, - Menu = nil, - MenuBar = nil, - Window = nil, - Input = nil, - CursorPos = {1,1}, - CursorColour = colours.black, - InterfaceVisible = true, - Selection = {}, - SelectionDrawTimer = nil, - HandDragStart = {}, - Modified = false, -} - -local isQuitting = false - -function PrintCentered(text, y) - local w, h = term.getSize() - x = math.ceil(math.ceil((w / 2) - (#text / 2)), 0)+1 - term.setCursorPos(x, y) - print(text) -end - -function DoVanillaClose() - term.setBackgroundColour(colours.black) - term.setTextColour(colours.white) - term.clear() - term.setCursorPos(1, 1) - PrintCentered("Thanks for using Sketch!", (Drawing.Screen.Height/2)-1) - term.setTextColour(colours.lightGrey) - PrintCentered("Photoshop Inspired Image Editor for ComputerCraft", (Drawing.Screen.Height/2)) - term.setTextColour(colours.white) - PrintCentered("(c) oeed 2013 - 2014", (Drawing.Screen.Height/2)+3) - term.setCursorPos(1, Drawing.Screen.Height) - error('', 0) -end - -function Close() - if isQuitting or not Current.Artboard or not Current.Modified then - if not OneOS then - DoVanillaClose() - end - return true - else - local _w = ButtonDialougeWindow:Initialise('Quit Sketch?', 'You have unsaved changes, do you want to quit anyway?', 'Quit', 'Cancel', function(window, success) - if success then - if OneOS then - OneOS.Close(true) - else - DoVanillaClose() - end - end - window:Close() - Draw() - end):Show() - --it's hacky but it works - os.queueEvent('mouse_click', 1, _w.X, _w.Y) - return false - end -end - -if OneOS then - OneOS.CanClose = function() - return Close() - end -end - -Lists = { - Artboards = {}, - Interface = { - Toolbars = {} - } -} - -Events = { - -} - ---Setters-- - -function SetColour(colour) - Current.Colour = colour - Draw() -end - -function SetTool(tool) - if tool and tool.Select and tool:Select() then - Current.Input = nil - Current.Tool = tool - return true - end - return false -end - -function GetAbsolutePosition(object) - local obj = object - local i = 0 - local x = 1 - local y = 1 - while true do - x = x + obj.X - 1 - y = y + obj.Y - 1 - - if not obj.Parent then - return {X = x, Y = y} - end - - obj = obj.Parent - - if i > 32 then - return {X = 1, Y = 1} - end - - i = i + 1 - end - -end - ---Object Defintions-- - -Pixel = { - TextColour = colours.black, - BackgroundColour = colours.white, - Character = " ", - Layer = nil, - - Draw = function(self, x, y) - if self.BackgroundColour ~= colours.transparent or self.Character ~= ' ' then - Drawing.WriteToBuffer(self.Layer.Artboard.X + x - 1, self.Layer.Artboard.Y + y - 1, self.Character, self.TextColour, self.BackgroundColour) - end - end, - - Initialise = function(self, textColour, backgroundColour, character, layer) - local new = {} -- the new instance - setmetatable( new, {__index = self} ) - new.TextColour = textColour or self.TextColour - new.BackgroundColour = backgroundColour or self.BackgroundColour - new.Character = character or self.Character - new.Layer = layer - return new - end, - - Set = function(self, textColour, backgroundColour, character) - self.TextColour = textColour or self.TextColour - self.BackgroundColour = backgroundColour or self.BackgroundColour - self.Character = character or self.Character - end -} - -Layer = { - Name = "", - Pixels = { - - }, - Artboard = nil, - BackgroundColour = colours.white, - Visible = true, - Index = 1, - - Draw = function(self) - if self.Visible then - for x = 1, self.Artboard.Width do - for y = 1, self.Artboard.Height do - self.Pixels[x][y]:Draw(x, y) - end - end - end - end, - - Remove = function(self) - for i, v in ipairs(self.Artboard.Layers) do - if v == Current.Layer then - Current.Artboard.Layers[i] = nil - Current.Layer = Current.Artboard.Layers[1] - ModuleNamed('Layers'):Update() - end - end - end, - - Initialise = function(self, name, backgroundColour, artboard, index, pixels) - local new = {} -- the new instance - setmetatable( new, {__index = self} ) - new.Name = name - new.Pixels = {} - new.BackgroundColour = backgroundColour - new.Artboard = artboard - new.Index = index or #artboard.Layers + 1 - if not pixels then - new:MakeAllBlankPixels() - else - new:MakeAllBlankPixels() - for x, col in ipairs(pixels) do - for y, pixel in ipairs(col) do - new:SetPixel(x, y, pixel.TextColour, pixel.BackgroundColour, pixel.Character) - end - end - end - - return new - end, - - SetPixel = function(self, x, y, textColour, backgroundColour, character) - textColour = textColour or Current.Colour - backgroundColour = backgroundColour or Current.Colour - character = character or " " - - if x < 1 or y < 1 or x > self.Artboard.Width or y > self.Artboard.Height then - return - end - - if self.Pixels[x][y] then - self.Pixels[x][y]:Set(textColour, backgroundColour, character) - self.Pixels[x][y]:Draw(x,y) - end - end, - - MakePixel = function(self, x, y, backgroundColour) - backgroundColour = backgroundColour or self.BackgroundColour - self.Pixels[x][y] = Pixel:Initialise(nil, backgroundColour, nil, self) - end, - - MakeColumn = function(self, x) - self.Pixels[x] = {} - end, - - MakeAllBlankPixels = function(self) - for x = 1, self.Artboard.Width do - if not self.Pixels[x] then - self:MakeColumn(x) - end - - for y = 1, self.Artboard.Height do - - if not self.Pixels[x][y] then - self:MakePixel(x, y) - end - - end - end - end, - - PixelsInSelection = function(self, cut) - local pixels = {} - if Current.Selection and Current.Selection[1] and Current.Selection[2] ~= nil then - local point1 = Current.Selection[1] - local point2 = Current.Selection[2] - - local size = point2 - point1 - local cornerX = point1.x - local cornerY = point1.y - for x = 1, size.x + 1 do - for y = 1, size.y + 1 do - if not pixels[x] then - pixels[x] = {} - end - if not self.Pixels[cornerX + x - 1] or not self.Pixels[cornerX + x - 1][cornerY + y - 1] then - break - end - local pixel = self.Pixels[cornerX + x - 1][cornerY + y - 1] - pixels[x][y] = Pixel:Initialise(pixel.TextColour, pixel.BackgroundColour, pixel.Character, Current.Layer) - if cut then - Current.Layer:SetPixel(cornerX + x - 1, cornerY + y - 1, nil, Current.Layer.BackgroundColour, nil) - end - end - end - end - return pixels - end, - - EraseSelection = function(self) - if Current.Selection and Current.Selection[1] and Current.Selection[2] ~= nil then - local point1 = Current.Selection[1] - local point2 = Current.Selection[2] - - local size = point2 - point1 - local cornerX = point1.x - local cornerY = point1.y - for x = 1, size.x + 1 do - for y = 1, size.y + 1 do - Current.Layer:SetPixel(cornerX + x - 1, cornerY + y - 1, nil, Current.Layer.BackgroundColour, nil) - end - end - end - end, - - InsertPixels = function(self, pixels) - local cornerX = Current.Selection[1].x - local cornerY = Current.Selection[1].y - for x, col in ipairs(pixels) do - for y, pixel in ipairs(col) do - Current.Layer:SetPixel(cornerX + x - 1, cornerY + y - 1, pixel.TextColour, pixel.BackgroundColour, pixel.Character) - end - end - end -} - -Artboard = { - X = 0, - Y = 0, - Name = "", - Path = "", - Width = 1, - Height = 1, - Layers = {}, - Format = nil, - SelectionIsBlack = true, - - Draw = function(self) - Drawing.DrawBlankArea(self.X + 1, self.Y + 1, self.Width, self.Height, UIColours.Shadow) - - local odd - for x = 1, self.Width do - odd = x % 2 - if odd == 1 then - odd = true - else - odd = false - end - for y = 1, self.Height do - if odd then - Drawing.WriteToBuffer(self.X + x - 1, self.Y + y - 1, ":", UIColours.TransparentBackgroundTwo, UIColours.TransparentBackgroundOne) - else - Drawing.WriteToBuffer(self.X + x - 1, self.Y + y - 1, ":", UIColours.TransparentBackgroundOne, UIColours.TransparentBackgroundTwo) - end - - odd = not odd - end - end - - for i, layer in ipairs(self.Layers) do - layer:Draw() - end - - if Current.Selection and Current.Selection[1] and Current.Selection[2] ~= nil then - local point1 = Current.Selection[1] - local point2 = Current.Selection[2] - - local size = point2 - point1 - - local isBlack = self.SelectionIsBlack - - local function c() - local c = colours.white - if isBlack then - c = colours.black - end - isBlack = not isBlack - return c - end - - function horizontal(y) - Drawing.WriteToBuffer(self.X - 1 + point1.x, self.Y - 1 + y, '+', c(), colours.transparent) - if size.x > 0 then - for i = 1, size.x - 1 do - Drawing.WriteToBuffer(self.X - 1 + point1.x + i, self.Y - 1 + y, '-', c(), colours.transparent) - end - else - for i = 1, (-1 * size.x) - 1 do - Drawing.WriteToBuffer(self.X - 1 + point1.x - i, self.Y - 1 + y, '-', c(), colours.transparent) - end - end - - Drawing.WriteToBuffer(self.X - 1 + point1.x + size.x, self.Y - 1 + y, '+', c(), colours.transparent) - end - - function vertical(x) - if size.y < 0 then - for i = 1, (-1 * size.y) - 1 do - Drawing.WriteToBuffer(self.X - 1 + x, self.Y - 1 + point1.y - i, '|', c(), colours.transparent) - end - else - for i = 1, size.y - 1 do - Drawing.WriteToBuffer(self.X - 1 + x, self.Y - 1 + point1.y + i, '|', c(), colours.transparent) - end - end - end - - horizontal(point1.y) - vertical(point1.x) - horizontal(point1.y + size.y) - vertical(point1.x + size.x) - end - end, - - Initialise = function(self, name, path, width, height, format, backgroundColour, layers) - local new = {} -- the new instance - setmetatable( new, {__index = self} ) - new.Y = 3 - new.X = 2 - new.Name = name - new.Path = path - new.Width = width - new.Height = height - new.Format = format - new.Layers = {} - if not layers then - new:MakeLayer('Background', backgroundColour) - else - for i, layer in ipairs(layers) do - new:MakeLayer(layer.Name, layer.BackgroundColour, layer.Index, layer.Pixels) - new.Layers[i].Visible = layer.Visible - end - Current.Layer = new.Layers[#new.Layers] - end - return new - end, - - Resize = function(self, top, bottom, left, right) - self.Height = self.Height + top + bottom - self.Width = self.Width + left + right - - for i, layer in ipairs(self.Layers) do - - if left < 0 then - for x = 1, -left do - table.remove(layer.Pixels, 1) - end - end - - if right < 0 then - for x = 1, -right do - table.remove(layer.Pixels, #layer.Pixels) - end - end - - for x = 1, left do - table.insert(layer.Pixels, 1, {}) - for y = 1, self.Height do - layer:MakePixel(1, y) - end - end - - for x = 1, right do - table.insert(layer.Pixels, {}) - for y = 1, self.Height do - layer:MakePixel(#layer.Pixels, y) - end - end - - for y = 1, top do - for x = 1, self.Width do - table.insert(layer.Pixels[x], 1, {}) - layer:MakePixel(x, 1) - end - end - - for y = 1, bottom do - for x = 1, self.Width do - table.insert(layer.Pixels[x], {}) - layer:MakePixel(x, #layer.Pixels[x]) - end - end - - if top < 0 then - for y = 1, -top do - for x = 1, self.Width do - table.remove(layer.Pixels[x], 1) - end - end - end - - if bottom < 0 then - for y = 1, -bottom do - for x = 1, self.Width do - table.remove(layer.Pixels[x], #layer.Pixels[x]) - end - end - end - end - end, - - MakeLayer = function(self, name, backgroundColour, index, pixels) - backgroundColour = backgroundColour or colours.white - name = name or "Layer" - local layer = Layer:Initialise(name, backgroundColour, self, index, pixels) - table.insert(self.Layers, layer) - Current.Layer = layer - ModuleNamed('Layers'):Update() - return layer - end, - - New = function(self, name, path, width, height, format, backgroundColour, layers) - local new = self:Initialise(name, path, width, height, format, backgroundColour, layers) - table.insert(Lists.Artboards, new) - Current.Artboard = new - --new:Save() - return new - end, - - Save = function(self, path) - Current.Artboard = self - path = path or self.Path - local _open = io.open - if OneOS then - _open = OneOS.IO.open - end - local file = _open(path, "w", true) - if self.Format == '.skch' then - file:write(textutils.serialize(SaveSKCH())) - else - local lines = {} - if self.Format == '.nfp' then - lines = SaveNFP() - elseif self.Format == '.nft' then - lines = SaveNFT() - end - - for i, line in ipairs(lines) do - file:write(line.."\n") - end - end - file:close() - Current.Modified = false - end, - - Click = function(self, side, x, y, drag) - if Current.Tool and Current.Layer and Current.Layer.Visible then - Current.Tool:Use(x, y, side, drag) - Current.Modified = true - return true - end - end -} - -Toolbar = { - X = 0, - Y = 0, - Width = 0, - ExpandedWidth = 14, - ClosedWidth = 2, - Height = 0, - Expanded = true, - ToolbarItems = {}, - - AbsolutePosition = function(self) - return {X = self.X, Y = self.Y} - end, - - Draw = function(self) - self:CalculateToolbarItemPositions() - --Drawing.DrawArea(self.X - 1, self.Y, 1, self.Height, "|", UIColours.ToolbarText, UIColours.Background) - - - - --if not Current.Window then - Drawing.DrawBlankArea(self.X, self.Y, self.Width, self.Height, UIColours.Toolbar) - --else - -- Drawing.DrawArea(self.X, self.Y, self.Width, self.Height, '|', colours.lightGrey, UIColours.Toolbar) - --end - for i, toolbarItem in ipairs(self.ToolbarItems) do - toolbarItem:Draw() - end - end, - - Initialise = function(self, side, expanded) - local new = {} -- the new instance - setmetatable( new, {__index = self} ) - new.Expanded = expanded - - if expanded then - new.Width = new.ExpandedWidth - else - new.Width = new.ClosedWidth - end - - if side == 'right' then - new.X = Drawing.Screen.Width - new.Width + 1 - end - - if side == 'right' or side == 'left' then - new.Height = Drawing.Screen.Width - end - - new.Y = 1 - - return new - end, - - AddToolbarItem = function(self, item) - table.insert(self.ToolbarItems, item) - self:CalculateToolbarItemPositions() - end, - - CalculateToolbarItemPositions = function(self) - local currY = 1 - for i, toolbarItem in ipairs(self.ToolbarItems) do - toolbarItem.Y = currY - currY = currY + toolbarItem.Height - end - end, - - Update = function(self) - for i, toolbarItem in ipairs(self.ToolbarItems) do - if toolbarItem.Module.Update then - toolbarItem.Module:Update(toolbarItem) - end - end - end, - - New = function(self, side, expanded) - local new = self:Initialise(side, expanded) - - --new:AddToolbarItem(ToolbarItem:Initialise("Colours", nil, true, new)) - --new:AddToolbarItem(ToolbarItem:Initialise("IDK", true, new)) - - table.insert(Lists.Interface.Toolbars, new) - return new - end, - - Click = function(self, side, x, y) - return false - end -} - -ToolbarItem = { - X = 0, - Y = 0, - Width = 0, - Height = 0, - ExpandedHeight = 5, - Expanded = true, - Toolbar = nil, - Title = "", - MenuIcon = "=", - ExpandedIcon = "+", - ContractIcon = "-", - ContentView = nil, - Module = nil, - MenuItems = nil, - - Draw = function(self) - Drawing.DrawBlankArea(self.X, self.Y, self.Width, 1, UIColours.ToolbarItemTitle) - Drawing.DrawCharacters(self.X + 1, self.Y, self.Title, UIColours.ToolbarText, UIColours.ToolbarItemTitle) - - Drawing.DrawCharacters(self.X + self.Width - 1, self.Y, self.MenuIcon, UIColours.ToolbarText, UIColours.ToolbarItemTitle) - - local expandContractIcon = self.ContractIcon - if not self.Expanded then - expandContractIcon = self.ExpandedIcon - end - - if self.Expanded and self.ContentView then - self.ContentView:Draw() - end - - Drawing.DrawCharacters(self.X + self.Width - 2, self.Y, expandContractIcon, UIColours.ToolbarText, UIColours.ToolbarItemTitle) - end, - - Initialise = function(self, module, height, expanded, toolbar, menuItems) - local new = {} -- the new instance - setmetatable( new, {__index = self} ) - new.Expanded = expanded - new.Title = module.Title - new.Width = toolbar.Width - new.Height = height or 5 - new.Module = module - new.MenuItems = menuItems or {} - table.insert(new.MenuItems, - { - Title = 'Shrink', - Click = function() - new:ToggleExpanded() - end - }) - new.ExpandedHeight = height or 5 - new.Y = 1 - new.X = toolbar.X - new.ContentView = ContentView:Initialise(1, 2, new.Width, new.Height - 1, nil, new) - new.Toolbar = toolbar - - return new - end, - - ToggleExpanded = function(self) - self.Expanded = not self.Expanded - if self.Expanded then - self.Height = self.ExpandedHeight - else - self.Height = 1 - end - end, - - Click = function(self, side, x, y) - local pos = GetAbsolutePosition(self) - if x == self.Width and y == 1 then - local expandContract = "Shrink" - - if not self.Expanded then - expandContract = "Expand" - end - self.MenuItems[#self.MenuItems].Title = expandContract - Menu:New(pos.X + x, pos.Y + y, self.MenuItems, self) - return true - elseif x == self.Width - 1 and y == 1 then - self:ToggleExpanded() - return true - elseif y ~= 1 then - return self.ContentView:Click(side, x - self.ContentView.X + 1, y - self.ContentView.Y + 1) - end - - return false - end -} - -ContentView = { - X = 1, - Y = 1, - Width = 0, - Height = 0, - Parent = nil, - Views = {}, - - AbsolutePosition = function(self) - return self.Parent:AbsolutePosition() - end, - - Draw = function(self) - for i, view in ipairs(self.Views) do - view:Draw() - end - end, - - Initialise = function(self, x, y, width, height, views, parent) - local new = {} -- the new instance - setmetatable( new, {__index = self} ) - new.Width = width - new.Height = height - new.Y = y - new.X = x - new.Views = views or {} - new.Parent = parent - return new - end, - - Click = function(self, side, x, y) - for k, view in pairs(self.Views) do - if DoClick(view, side, x, y) then - return true - end - end - end -} - -Button = { - X = 1, - Y = 1, - Width = 0, - Height = 0, - BackgroundColour = colours.lightGrey, - TextColour = colours.white, - ActiveBackgroundColour = colours.lightGrey, - Text = "", - Parent = nil, - _Click = nil, - Toggle = nil, - - AbsolutePosition = function(self) - return self.Parent:AbsolutePosition() - end, - - Draw = function(self) - local bg = self.BackgroundColour - local tc = self.TextColour - if type(bg) == 'function' then - bg = bg() - end - - if self.Toggle then - tc = UIColours.MenuBarActive - bg = self.ActiveBackgroundColour - end - - local pos = GetAbsolutePosition(self) - Drawing.DrawBlankArea(pos.X, pos.Y, self.Width, self.Height, bg) - Drawing.DrawCharactersCenter(pos.X, pos.Y, self.Width, self.Height, self.Text, tc, bg) - end, - - Initialise = function(self, x, y, width, height, backgroundColour, parent, click, text, textColour, toggle, activeBackgroundColour) - local new = {} -- the new instance - setmetatable( new, {__index = self} ) - height = height or 1 - new.Width = width or #text + 2 - new.Height = height - new.Y = y - new.X = x - new.Text = text or "" - new.BackgroundColour = backgroundColour or colours.lightGrey - new.TextColour = textColour or colours.white - new.ActiveBackgroundColour = activeBackgroundColour or colours.lightGrey - new.Parent = parent - new._Click = click - new.Toggle = toggle - return new - end, - - Click = function(self, side, x, y) - if self._Click then - if self:_Click(side, x, y, not self.Toggle) ~= false and self.Toggle ~= nil then - self.Toggle = not self.Toggle - Draw() - end - return true - else - return false - end - end -} - -TextBox = { - X = 1, - Y = 1, - Width = 0, - Height = 0, - BackgroundColour = colours.lightGrey, - TextColour = colours.black, - Parent = nil, - TextInput = nil, - - AbsolutePosition = function(self) - return self.Parent:AbsolutePosition() - end, - - Draw = function(self) - local pos = GetAbsolutePosition(self) - Drawing.DrawBlankArea(pos.X, pos.Y, self.Width, self.Height, self.BackgroundColour) - local text = self.TextInput.Value - if #text > (self.Width - 2) then - text = text:sub(#text-(self.Width - 3)) - if Current.Input == self.TextInput then - Current.CursorPos = {pos.X + 1 + self.Width-2, pos.Y} - end - else - if Current.Input == self.TextInput then - Current.CursorPos = {pos.X + 1 + self.TextInput.CursorPos, pos.Y} - end - end - Drawing.DrawCharacters(pos.X + 1, pos.Y, text, self.TextColour, self.BackgroundColour) - - term.setCursorBlink(true) - - Current.CursorColour = self.TextColour - end, - - Initialise = function(self, x, y, width, height, parent, text, backgroundColour, textColour, done, numerical) - local new = {} -- the new instance - setmetatable( new, {__index = self} ) - height = height or 1 - new.Width = width or #text + 2 - new.Height = height - new.Y = y - new.X = x - new.TextInput = TextInput:Initialise(text or '', function(key) - if done then - done(key) - end - Draw() - end, numerical) - new.BackgroundColour = backgroundColour or colours.lightGrey - new.TextColour = textColour or colours.black - new.Parent = parent - return new - end, - - Click = function(self, side, x, y) - Current.Input = self.TextInput - self:Draw() - end -} - -TextInput = { - Value = "", - Change = nil, - CursorPos = nil, - Numerical = false, - - Initialise = function(self, value, change, numerical) - local new = {} -- the new instance - setmetatable( new, {__index = self} ) - new.Value = value - new.Change = change - new.CursorPos = #value - new.Numerical = numerical - return new - end, - - Char = function(self, char) - if self.Numerical then - char = tostring(tonumber(char)) - end - if char == 'nil' then - return - end - self.Value = string.sub(self.Value, 1, self.CursorPos ) .. char .. string.sub( self.Value, self.CursorPos + 1 ) - - self.CursorPos = self.CursorPos + 1 - self.Change(key) - end, - - Key = function(self, key) - if key == keys.enter then - self.Change(key) - elseif key == keys.left then - -- Left - if self.CursorPos > 0 then - self.CursorPos = self.CursorPos - 1 - self.Change(key) - end - - elseif key == keys.right then - -- Right - if self.CursorPos < string.len(self.Value) then - self.CursorPos = self.CursorPos + 1 - self.Change(key) - end - - elseif key == keys.backspace then - -- Backspace - if self.CursorPos > 0 then - self.Value = string.sub( self.Value, 1, self.CursorPos - 1 ) .. string.sub( self.Value, self.CursorPos + 1 ) - self.CursorPos = self.CursorPos - 1 - end - self.Change(key) - elseif key == keys.home then - -- Home - self.CursorPos = 0 - self.Change(key) - elseif key == keys.delete then - if self.CursorPos < string.len(self.Value) then - self.Value = string.sub( self.Value, 1, self.CursorPos ) .. string.sub( self.Value, self.CursorPos + 2 ) - self.Change(key) - end - elseif key == keys["end"] then - -- End - self.CursorPos = string.len(self.Value) - self.Change(key) - end - end -} - -LayerItem = { - X = 1, - Y = 1, - Parent = nil, - Layer = nil, - - Draw = function(self) - self.Y = self.Layer.Index - - local pos = GetAbsolutePosition(self) - - local tc = colours.lightGrey - - if Current.Layer == self.Layer then - tc = colours.white - end - - Drawing.DrawBlankArea(pos.X, pos.Y, self.Width, self.Height, UIColours.Toolbar) - - Drawing.DrawCharacters(pos.X + 3, pos.Y, self.Layer.Name, tc, UIColours.Toolbar) - - if self.Layer.Visible then - Drawing.DrawCharacters(pos.X + 1, pos.Y, "@", tc, UIColours.Toolbar) - else - Drawing.DrawCharacters(pos.X + 1, pos.Y, "X", tc, UIColours.Toolbar) - end - - end, - - Initialise = function(self, layer, parent) - local new = {} -- the new instance - setmetatable( new, {__index = self} ) - new.Width = parent.Width - new.Height = 1 - new.Y = 1 - new.X = 1 - new.Layer = layer - new.Parent = parent - return new - end, - - Click = function(self, side, x, y) - if x == 2 then - self.Layer.Visible = not self.Layer.Visible - else - Current.Layer = self.Layer - end - return true - end -} - -Menu = { - X = 0, - Y = 0, - Width = 0, - Height = 0, - Owner = nil, - Items = {}, - RemoveTop = false, - - Draw = function(self) - Drawing.DrawBlankArea(self.X + 1, self.Y + 1, self.Width, self.Height, UIColours.Shadow) - if not self.RemoveTop then - Drawing.DrawBlankArea(self.X, self.Y, self.Width, self.Height, UIColours.MenuBackground) - for i, item in ipairs(self.Items) do - if item.Separator then - Drawing.DrawArea(self.X, self.Y + i, self.Width, 1, '-', colours.grey, UIColours.MenuBackground) - else - local textColour = UIColours.MenuText - if (item.Enabled and type(item.Enabled) == 'function' and item.Enabled() == false) or item.Enabled == false then - textColour = UIColours.MenuDisabledText - end - Drawing.DrawCharacters(self.X + 1, self.Y + i, item.Title, textColour, UIColours.MenuBackground) - end - end - else - Drawing.DrawBlankArea(self.X, self.Y, self.Width, self.Height, UIColours.MenuBackground) - for i, item in ipairs(self.Items) do - if item.Separator then - Drawing.DrawArea(self.X, self.Y + i - 1, self.Width, 1, '-', colours.grey, UIColours.MenuBackground) - else - local textColour = UIColours.MenuText - if (item.Enabled and type(item.Enabled) == 'function' and item.Enabled() == false) or item.Enabled == false then - textColour = UIColours.MenuDisabledText - end - Drawing.DrawCharacters(self.X + 1, self.Y + i - 1, item.Title, textColour, UIColours.MenuBackground) - - Drawing.DrawCharacters(self.X - 1 + self.Width-#item.KeyName, self.Y + i - 1, item.KeyName, textColour, UIColours.MenuBackground) - end - end - end - end, - - NameForKey = function(self, key) - if key == keys.leftCtrl then - return '^' - elseif key == keys.tab then - return 'Tab' - elseif key == keys.delete then - return 'Delete' - elseif key == keys.n then - return 'N' - elseif key == keys.s then - return 'S' - elseif key == keys.o then - return 'O' - elseif key == keys.z then - return 'Z' - elseif key == keys.y then - return 'Y' - elseif key == keys.c then - return 'C' - elseif key == keys.x then - return 'X' - elseif key == keys.v then - return 'V' - elseif key == keys.r then - return 'R' - elseif key == keys.l then - return 'L' - elseif key == keys.t then - return 'T' - elseif key == keys.h then - return 'H' - elseif key == keys.e then - return 'E' - elseif key == keys.p then - return 'P' - elseif key == keys.f then - return 'F' - elseif key == keys.m then - return 'M' - else - return '?' - end - end, - - Initialise = function(self, x, y, items, owner, removeTop) - local new = {} -- the new instance - setmetatable( new, {__index = self} ) - if not owner then - return - end - - local keyNames = {} - - for i, v in ipairs(items) do - items[i].KeyName = '' - if v.Keys then - for _i, key in ipairs(v.Keys) do - items[i].KeyName = items[i].KeyName .. self:NameForKey(key) - end - end - if items[i].KeyName ~= '' then - table.insert(keyNames, items[i].KeyName) - end - end - local keysLength = LongestString(keyNames) - if keysLength > 0 then - keysLength = keysLength + 2 - end - - new.Width = LongestString(items, 'Title') + 2 + keysLength - if new.Width < 10 then - new.Width = 10 - end - new.Height = #items + 2 - new.RemoveTop = removeTop or false - if removeTop then - new.Height = new.Height - 1 - end - - if y < 1 then - y = 1 - end - if x < 1 then - x = 1 - end - - if y + new.Height > Drawing.Screen.Height + 1 then - y = Drawing.Screen.Height - new.Height - end - if x + new.Width > Drawing.Screen.Width + 1 then - x = Drawing.Screen.Width - new.Width - end - - - new.Y = y - new.X = x - new.Items = items - new.Owner = owner - return new - end, - - New = function(self, x, y, items, owner, removeTop) - if Current.Menu and Current.Menu.Owner == owner then - Current.Menu = nil - return - end - - local new = self:Initialise(x, y, items, owner, removeTop) - Current.Menu = new - return new - end, - - Click = function(self, side, x, y) - local i = y-1 - if self.RemoveTop then - i = y - end - if i >= 1 and y < self.Height then - if not ((self.Items[i].Enabled and type(self.Items[i].Enabled) == 'function' and self.Items[i].Enabled() == false) or self.Items[i].Enabled == false) and self.Items[i].Click then - self.Items[i]:Click() - if Current.Menu.Owner and Current.Menu.Owner.Toggle then - Current.Menu.Owner.Toggle = false - end - Current.Menu = nil - self = nil - end - return true - end - end -} - -MenuBar = { - X = 1, - Y = 1, - Width = Drawing.Screen.Width, - Height = 1, - MenuBarItems = {}, - - AbsolutePosition = function(self) - return {X = self.X, Y = self.Y} - end, - - Draw = function(self) - --Drawing.DrawArea(self.X - 1, self.Y, 1, self.Height, "|", UIColours.ToolbarText, UIColours.Background) - - Drawing.DrawBlankArea(self.X, self.Y, self.Width, self.Height, UIColours.Toolbar) - for i, button in ipairs(self.MenuBarItems) do - button:Draw() - end - end, - - Initialise = function(self, items) - local new = {} -- the new instance - setmetatable( new, {__index = self} ) - new.X = 1 - new.Y = 1 - new.MenuBarItems = items - return new - end, - - AddToolbarItem = function(self, item) - table.insert(self.ToolbarItems, item) - self:CalculateToolbarItemPositions() - end, - - CalculateToolbarItemPositions = function(self) - local currY = 1 - for i, toolbarItem in ipairs(self.ToolbarItems) do - toolbarItem.Y = currY - currY = currY + toolbarItem.Height - end - end, - - Click = function(self, side, x, y) - for i, item in ipairs(self.MenuBarItems) do - if item.X <= x and item.X + item.Width > x then - if item:Click(item, side, x - item.X + 1, 1) then - break - end - end - end - return false - end -} - ---Modules-- - -Modules = { - { - Title = "Colours", - ToolbarItem = nil, - Initialise = function(self) - self.ToolbarItem = ToolbarItem:Initialise(self, nil, true, Current.Toolbar) - - local buttons = {} - - local i = 0 - - local coloursWidth = 8 - local _colours = { - colours.brown, - colours.yellow, - colours.orange, - colours.red, - colours.green, - colours.lime, - colours.magenta, - colours.pink, - colours.purple, - colours.blue, - colours.cyan, - colours.lightBlue, - colours.lightGrey, - colours.grey, - colours.black, - colours.white - } - - for k, colour in pairs(_colours) do - if type(colour) == 'number' and colour ~= -1 then - i = i + 1 - - local y = math.floor(i/(coloursWidth/2)) - - local x = (i%(coloursWidth/2)) - if x == 0 then - x = (coloursWidth/2) - y = y -1 - end - - table.insert(buttons, - { - X = x*2 - 2 + self.ToolbarItem.Width - coloursWidth, - Y = y+1, - Width = 2, - Height = 1, - BackgroundColour = colour, - Click = function(self, side, x, y) - SetColour(self.BackgroundColour) - end - } - ) - end - end - - for i, button in ipairs(buttons) do - table.insert(self.ToolbarItem.ContentView.Views, - Button:Initialise(button.X, button.Y, button.Width, button.Height, button.BackgroundColour, self.ToolbarItem.ContentView, button.Click)) - end - - table.insert(self.ToolbarItem.ContentView.Views, - Button:Initialise(1, 1, 4, 3, function()return Current.Colour end, self.ToolbarItem.ContentView, nil)) - - Current.Toolbar:AddToolbarItem(self.ToolbarItem) - end - }, - - { - Title = "Tools", - ToolbarItem = nil, - Update = function(self) - for i, view in ipairs(self.ToolbarItem.ContentView.Views) do - if (Current.Tool and Current.Tool.Name == view.Text) then - view.TextColour = colours.white - else - view.TextColour = colours.lightGrey - end - end - self.ToolbarItem.ContentView.Views[1].Text = 'Size: '..Current.ToolSize - end, - - Initialise = function(self) - self.ToolbarItem = ToolbarItem:Initialise(self, #Tools+2, true, Current.Toolbar, - {{ - Title = "Change Tool Size", - Click = function() - DisplayToolSizeWindow() - end, - }}) - - table.insert(self.ToolbarItem.ContentView.Views, Button:Initialise(1, 1, self.ToolbarItem.Width, 1, UIColours.Toolbar, self.ToolbarItem.ContentView, DisplayToolSizeWindow, 'Size: '..Current.ToolSize)) - - local y = 2 - for i, tool in ipairs(Tools) do - table.insert(self.ToolbarItem.ContentView.Views, Button:Initialise(1, y, self.ToolbarItem.Width, 1, UIColours.Toolbar, self.ToolbarItem.ContentView, function() SetTool(tool) self:Update(self.ToolbarItem) end, tool.Name)) - y = y + 1 - end - - self:Update(self.ToolbarItem) - - Current.Toolbar:AddToolbarItem(self.ToolbarItem) - end - }, - - { - Title = "Layers", - ToolbarItem = nil, - Update = function(self) - if Current.Artboard then - self.ToolbarItem.ContentView.Views = {} - for i = 1, #Current.Artboard.Layers do - table.insert(self.ToolbarItem.ContentView.Views, LayerItem:Initialise(Current.Artboard.Layers[#Current.Artboard.Layers-i+1], self.ToolbarItem.ContentView)) - end - end - end, - - Initialise = function(self) - self.ToolbarItem = ToolbarItem:Initialise(self, nil, true, Current.Toolbar, - {{ - Title = "New Layer", - Click = function() - MakeNewLayer() - end, - Enabled = function() - return CheckOpenArtboard() - end - }, - { - Title = 'Delete Layer', - Click = function() - DeleteLayer() - end, - Enabled = function() - return CheckSelectedLayer() - end - }, - { - Title = 'Rename Layer...', - Click = function() - RenameLayer() - end, - Enabled = function() - return CheckSelectedLayer() - end - }}) - - self:Update() - - Current.Toolbar:AddToolbarItem(self.ToolbarItem) - end - } - -} - -function ModuleNamed(name) - for i, v in ipairs(Modules) do - if v.Title == name then - return v - end - end -end - ---Tools-- - -function ToolAffectedPixels(x, y) - if not CheckSelectedLayer() then - return {} - end - if Current.ToolSize == 1 then - if Current.Layer.Pixels[x] and Current.Layer.Pixels[x][y] then - return {{Current.Layer.Pixels[x][y], x, y}} - end - else - local pixels = {} - local cornerX = x - math.ceil(Current.ToolSize/2) - local cornerY = y - math.ceil(Current.ToolSize/2) - for _x = 1, Current.ToolSize do - for _y = 1, Current.ToolSize do - if Current.Layer.Pixels[cornerX + _x] and Current.Layer.Pixels[cornerX + _x][cornerY + _y] then - table.insert(pixels, {Current.Layer.Pixels[cornerX + _x][cornerY + _y], cornerX + _x, cornerY + _y}) - end - end - end - return pixels - end -end -local moveStartPoint = {} -Tools = { - { - Name = "Hand", - Use = function(self, x, y, side, drag) - Current.Input = nil - if drag and Current.HandDragStart and Current.HandDragStart[1] and Current.HandDragStart[2] then - local deltaX = x - Current.HandDragStart[1] - local deltaY = y - Current.HandDragStart[2] - Current.Artboard.X = Current.Artboard.X + deltaX - Current.Artboard.Y = Current.Artboard.Y + deltaY - else - Current.HandDragStart = {x, y} - end - sleep(0) - end, - Select = function(self) - return true - end - }, - - { - Name = "Pencil", - Use = function(self, _x, _y, side, artboard) - Current.Input = nil - for i, pixel in ipairs(ToolAffectedPixels(_x, _y)) do - if side == 1 then - pixel[1].BackgroundColour = Current.Colour - elseif side == 2 then - pixel[1].TextColour = Current.Colour - end - pixel[1]:Draw(pixel[2], pixel[3]) - end - end, - Select = function(self) - return true - end - }, - - { - Name = "Eraser", - Use = function(self, x, y, side) - Current.Input = nil - Current.Layer:SetPixel(x, y, nil, Current.Layer.BackgroundColour, nil) - for i, pixel in ipairs(ToolAffectedPixels(x, y)) do - Current.Layer:SetPixel(pixel[2], pixel[3], nil, Current.Layer.BackgroundColour, nil) - end - end, - Select = function(self) - return true - end - }, - - { - Name = "Fill Bucket", - Use = function(self, x, y, side) - local replaceColour = Current.Layer.Pixels[x][y].BackgroundColour - if side == 2 then - replaceColour = Current.Layer.Pixels[x][y].TextColour - end - - local nodes = {{X = x, Y = y}} - - while #nodes > 0 do - local node = nodes[1] - if Current.Layer.Pixels[node.X] and Current.Layer.Pixels[node.X][node.Y] then - local replacing = Current.Layer.Pixels[node.X][node.Y].BackgroundColour - if side == 2 then - replacing = Current.Layer.Pixels[node.X][node.Y].TextColour - end - if replacing == replaceColour and replacing ~= Current.Colour then - if side == 1 then - Current.Layer.Pixels[node.X][node.Y].BackgroundColour = Current.Colour - elseif side == 2 then - Current.Layer.Pixels[node.X][node.Y].TextColour = Current.Colour - end - table.insert(nodes, {X = node.X, Y = node.Y + 1}) - table.insert(nodes, {X = node.X + 1, Y = node.Y}) - if x > 1 then - table.insert(nodes, {X = node.X - 1, Y = node.Y}) - end - if y > 1 then - table.insert(nodes, {X = node.X, Y = node.Y - 1}) - end - end - end - table.remove(nodes, 1) - end - Draw() - end, - Select = function(self) - return true - end - }, - - { - Name = "Select", - Use = function(self, x, y, side, drag) - Current.Input = nil - if not drag then - Current.Selection[1] = vector.new(x, y, 0) - Current.Selection[2] = nil - else - Current.Selection[2] = vector.new(x, y, 0) - end - end, - Select = function(self) - return true - end - }, - - { - Name = "Move", - Use = function(self, x, y, side, drag) - Current.Input = nil - - if Current.Selection and Current.Selection[1] and Current.Selection[2] ~= nil then - if drag and moveStartPoint then - local pixels = Current.Layer:PixelsInSelection(true) - local size = Current.Selection[1] - Current.Selection[2] - Current.Selection[1] = vector.new(x-moveStartPoint[1], y-moveStartPoint[2], 0) - Current.Selection[2] = vector.new(x-moveStartPoint[1]-size.x, y-moveStartPoint[2]-size.y, 0) - Current.Layer:InsertPixels(pixels) - else - moveStartPoint = {x-Current.Selection[1].x, y-Current.Selection[1].y} - end - end - end, - Select = function(self) - return true - end - }, - - { - Name = "Text", - Use = function(self, x, y) - Current.Input = TextInput:Initialise('', function(key) - if key == keys.delete or key == keys.backspace then - if #Current.Input.Value == 0 then - if Current.Layer.Pixels[x] and Current.Layer.Pixels[x][y] then - Current.Layer.Pixels[x][y]:Set(nil, nil, ' ') - local newPos = Current.CursorPos[1] - Current.Artboard.X - if newPos < Current.Artboard.X - 1 then - newPos = Current.Artboard.X - 1 - end - Current.Tool:Use(newPos, Current.CursorPos[2] - Current.Artboard.Y + 1) - Draw() - end - return - else - if Current.Layer.Pixels[x+#Current.Input.Value] and Current.Layer.Pixels[x+#Current.Input.Value][y] then - Current.Layer.Pixels[x+#Current.Input.Value][y]:Set(nil, nil, ' ') - end - end - else - local i = #Current.Input.Value - if Current.Layer.Pixels[x+i-1] then - Current.Layer.Pixels[x+i-1][y]:Set(Current.Colour, nil, Current.Input.Value:sub(i,i)) - Current.Layer.Pixels[x+i-1][y]:Draw(x+i-1, y) - end - end - - local newPos = x+Current.Input.CursorPos - - if newPos > Current.Artboard.Width then - Current.Input.CursorPos = Current.Input.CursorPos - 1 - end - - Current.CursorPos = {x+Current.Input.CursorPos + Current.Artboard.X - 1, y + Current.Artboard.Y - 1} - Current.CursorColour = Current.Colour - Draw() - end) - - Current.CursorPos = {x + Current.Artboard.X - 1, y + Current.Artboard.Y - 1} - Current.CursorColour = Current.Colour - end, - Select = function(self) - if Current.Artboard.Format == '.nfp' then - ButtonDialougeWindow:Initialise('NFP does not support text!', 'The format you are using, NFP, does not support text. Use NFT or SKCH to use text.', 'Ok', nil, function(window) - window:Close() - end):Show() - return false - else - return true - end - end - } -} - - -function ToolNamed(name) - for i, v in ipairs(Tools) do - if v.Name == name then - return v - end - end -end - ---Windows-- - -NewDocumentWindow = { - X = 1, - Y = 1, - Width = 0, - Height = 0, - CursorPos = 1, - Visible = true, - Return = nil, - OkButton = nil, - Format = '.skch', - ImageBackgroundColour = colours.white, - NameLabelHighlight = false, - SizeLabelHighlight = false, - - - AbsolutePosition = function(self) - return {X = self.X, Y = self.Y} - end, - - Draw = function(self) - if not self.Visible then - return - end - Drawing.DrawBlankArea(self.X + 1, self.Y+1, self.Width, self.Height, colours.grey) - Drawing.DrawBlankArea(self.X, self.Y, self.Width, 1, colours.lightGrey) - Drawing.DrawBlankArea(self.X, self.Y+1, self.Width, self.Height-1, colours.white) - Drawing.DrawCharactersCenter(self.X, self.Y, self.Width, 1, self.Title, colours.black, colours.lightGrey) - - local nameLabelColour = colours.black - if self.NameLabelHighlight then - nameLabelColour = colours.red - end - - Drawing.DrawCharacters(self.X+1, self.Y+2, "Name", nameLabelColour, colours.white) - Drawing.DrawCharacters(self.X+1, self.Y+4, "Type", colours.black, colours.white) - - local sizeLabelColour = colours.black - if self.SizeLabelHighlight then - sizeLabelColour = colours.red - end - Drawing.DrawCharacters(self.X+1, self.Y+6, "Size", sizeLabelColour, colours.white) - Drawing.DrawCharacters(self.X+11, self.Y+6, "x", colours.black, colours.white) - Drawing.DrawCharacters(self.X+1, self.Y+8, "Background", colours.black, colours.white) - - self.OkButton:Draw() - self.CancelButton:Draw() - self.SKCHButton:Draw() - self.NFTButton:Draw() - self.NFPButton:Draw() - self.PathTextBox:Draw() - self.WidthTextBox:Draw() - self.HeightTextBox:Draw() - self.WhiteButton:Draw() - self.BlackButton:Draw() - self.TransparentButton:Draw() - end, - - Initialise = function(self, returnFunc) - local new = {} -- the new instance - setmetatable( new, {__index = self} ) - new.Width = 32 - new.Height = 13 - new.Return = returnFunc - new.X = math.ceil((Drawing.Screen.Width - new.Width) / 2) - new.Y = math.ceil((Drawing.Screen.Height - new.Height) / 2) - new.Title = 'New Document' - new.Visible = true - new.NameLabelHighlight = false - new.SizeLabelHighlight = false - new.Format = '.skch' - new.OkButton = Button:Initialise(new.Width - 4, new.Height - 1, nil, nil, colours.lightGrey, new, function(self, side, x, y, toggle) - local path = new.PathTextBox.TextInput.Value - local ok = true - new.NameLabelHighlight = false - new.SizeLabelHighlight = false - local _fs = fs - if OneOS then - _fs = OneOS.FS - end - if path:sub(-1) == '/' or _fs.isDir(path) or #path == 0 then - ok = false - new.NameLabelHighlight = true - end - - if #new.WidthTextBox.TextInput.Value == 0 or tonumber(new.WidthTextBox.TextInput.Value) <= 0 then - ok = false - new.SizeLabelHighlight = true - end - - if #new.HeightTextBox.TextInput.Value == 0 or tonumber(new.HeightTextBox.TextInput.Value) <= 0 then - ok = false - new.SizeLabelHighlight = true - end - - if ok then - returnFunc(new, true, path, tonumber(new.WidthTextBox.TextInput.Value), tonumber(new.HeightTextBox.TextInput.Value), new.Format, new.ImageBackgroundColour) - else - Draw() - end - end, 'Ok', colours.black) - new.CancelButton = Button:Initialise(new.Width - 13, new.Height - 1, nil, nil, colours.lightGrey, new, function(self, side, x, y, toggle)returnFunc(new, false)end, 'Cancel', colours.black) - - new.SKCHButton = Button:Initialise(7, 5, nil, nil, colours.lightGrey, new, function(self, side, x, y, toggle) - new.NFTButton.Toggle = false - new.NFPButton.Toggle = false - self.Toggle = false - new.Format = '.skch' - end, '.skch', colours.black, true, colours.lightBlue) - new.NFTButton = Button:Initialise(15, 5, nil, nil, colours.lightGrey, new, function(self, side, x, y, toggle) - new.SKCHButton.Toggle = false - new.NFPButton.Toggle = false - self.Toggle = false - new.Format = '.nft' - end, '.nft', colours.black, false, colours.lightBlue) - new.NFPButton = Button:Initialise(22, 5, nil, nil, colours.lightGrey, new, function(self, side, x, y, toggle) - new.SKCHButton.Toggle = false - new.NFTButton.Toggle = false - self.Toggle = false - new.Format = '.nfp' - end, '.nfp', colours.black, false, colours.lightBlue) - - local path = '' - if OneOS then - path = '/Desktop/' - end - new.PathTextBox = TextBox:Initialise(7, 3, new.Width - 7, 1, new, path, nil, nil, function(key) - if key == keys.enter or key == keys.tab then - Current.Input = new.WidthTextBox.TextInput - end - end) - new.WidthTextBox = TextBox:Initialise(7, 7, 4, 1, new, tostring(15), nil, nil, function() - if key == keys.enter or key == keys.tab then - Current.Input = new.HeightTextBox.TextInput - end - end, true) - new.HeightTextBox = TextBox:Initialise(14, 7, 4, 1, new, tostring(10), nil, nil, function() - if key == keys.enter or key == keys.tab then - Current.Input = new.PathTextBox.TextInput - end - end, true) - Current.Input = new.PathTextBox.TextInput - - - new.WhiteButton = Button:Initialise(2, 10, nil, nil, colours.lightGrey, new, function(self, side, x, y, toggle) - new.TransparentButton.Toggle = false - new.BlackButton.Toggle = false - self.Toggle = false - new.ImageBackgroundColour = colours.white - end, 'White', colours.black, true, colours.lightBlue) - new.BlackButton = Button:Initialise(10, 10, nil, nil, colours.lightGrey, new, function(self, side, x, y, toggle) - new.TransparentButton.Toggle = false - new.WhiteButton.Toggle = false - self.Toggle = false - new.ImageBackgroundColour = colours.black - end, 'Black', colours.black, false, colours.lightBlue) - new.TransparentButton = Button:Initialise(18, 10, nil, nil, colours.lightGrey, new, function(self, side, x, y, toggle) - new.WhiteButton.Toggle = false - new.BlackButton.Toggle = false - self.Toggle = false - new.ImageBackgroundColour = colours.transparent - end, 'Transparent', colours.black, false, colours.lightBlue) - - return new - end, - - Show = function(self) - Current.Window = self - return self - end, - - Close = function(self) - Current.Input = nil - Current.Window = nil - self = nil - end, - - Flash = function(self) - self.Visible = false - Draw() - sleep(0.15) - self.Visible = true - Draw() - sleep(0.15) - self.Visible = false - Draw() - sleep(0.15) - self.Visible = true - Draw() - end, - - ButtonClick = function(self, button, x, y) - if button.X <= x and button.Y <= y and button.X + button.Width > x and button.Y + button.Height > y then - button:Click() - end - end, - - Click = function(self, side, x, y) - local items = {self.OkButton, self.CancelButton, self.SKCHButton, self.NFTButton, self.NFPButton, self.PathTextBox, self.WidthTextBox, self.HeightTextBox, self.WhiteButton, self.BlackButton, self.TransparentButton} - for i, v in ipairs(items) do - if CheckClick(v, x, y) then - v:Click(side, x, y) - end - end - return true - end -} - -local TidyPath = function(path) - path = '/'..path - local _fs = fs - if OneOS then - _fs = OneOS.FS - end - if _fs.isDir(path) then - path = path .. '/' - end - - path, n = path:gsub("//", "/") - while n > 0 do - path, n = path:gsub("//", "/") - end - return path -end - -local WrapText = function(text, maxWidth) - local lines = {''} - for word, space in text:gmatch('(%S+)(%s*)') do - local temp = lines[#lines] .. word .. space:gsub('\n','') - if #temp > maxWidth then - table.insert(lines, '') - end - if space:find('\n') then - lines[#lines] = lines[#lines] .. word - - space = space:gsub('\n', function() - table.insert(lines, '') - return '' - end) - else - lines[#lines] = lines[#lines] .. word .. space - end - end - return lines -end - -OpenDocumentWindow = { - X = 1, - Y = 1, - Width = 0, - Height = 0, - CursorPos = 1, - Visible = true, - Return = nil, - OpenButton = nil, - PathTextBox = nil, - CurrentDirectory = '/', - Scroll = 0, - MaxScroll = 0, - GoUpButton = nil, - SelectedFile = '', - Files = {}, - Typed = false, - - AbsolutePosition = function(self) - return {X = self.X, Y = self.Y} - end, - - Draw = function(self) - if not self.Visible then - return - end - Drawing.DrawBlankArea(self.X + 1, self.Y+1, self.Width, self.Height, colours.grey) - Drawing.DrawBlankArea(self.X, self.Y, self.Width, 3, colours.lightGrey) - Drawing.DrawBlankArea(self.X, self.Y+1, self.Width, self.Height-6, colours.white) - Drawing.DrawCharactersCenter(self.X, self.Y, self.Width, 1, self.Title, colours.black, colours.lightGrey) - Drawing.DrawBlankArea(self.X, self.Y + self.Height - 5, self.Width, 5, colours.lightGrey) - self:DrawFiles() - - local _fs = fs - if OneOS then - _fs = OneOS.FS - end - if (_fs.exists(self.PathTextBox.TextInput.Value)) or (self.SelectedFile and #self.SelectedFile > 0 and _fs.exists(self.CurrentDirectory .. self.SelectedFile)) then - self.OpenButton.TextColour = colours.black - else - self.OpenButton.TextColour = colours.lightGrey - end - - self.PathTextBox:Draw() - self.OpenButton:Draw() - self.CancelButton:Draw() - self.GoUpButton:Draw() - end, - - DrawFiles = function(self) - local _fs = fs - if OneOS then - _fs = OneOS.FS - end - for i, file in ipairs(self.Files) do - if i > self.Scroll and i - self.Scroll <= 11 then - if file == self.SelectedFile then - Drawing.DrawCharacters(self.X + 1, self.Y + i - self.Scroll, file, colours.white, colours.lightBlue) - elseif string.find(file, '%.skch') or string.find(file, '%.nft') or string.find(file, '%.nfp') or _fs.isDir(self.CurrentDirectory .. file) then - Drawing.DrawCharacters(self.X + 1, self.Y + i - self.Scroll, file, colours.black, colours.white) - else - Drawing.DrawCharacters(self.X + 1, self.Y + i - self.Scroll, file, colours.grey, colours.white) - end - end - end - self.MaxScroll = #self.Files - 11 - if self.MaxScroll < 0 then - self.MaxScroll = 0 - end - end, - - Initialise = function(self, returnFunc) - local new = {} -- the new instance - setmetatable( new, {__index = self} ) - new.Width = 32 - new.Height = 17 - new.Return = returnFunc - new.X = math.ceil((Drawing.Screen.Width - new.Width) / 2) - new.Y = math.ceil((Drawing.Screen.Height - new.Height) / 2) - new.Title = 'Open Document' - new.Visible = true - new.CurrentDirectory = '/' - new.SelectedFile = nil - if OneOS then - new.CurrentDirectory = '/Desktop/' - end - local _fs = fs - if OneOS then - _fs = OneOS.FS - end - new.OpenButton = Button:Initialise(new.Width - 6, new.Height - 1, nil, nil, colours.white, new, function(self, side, x, y, toggle) - if _fs.exists(new.PathTextBox.TextInput.Value) and self.TextColour == colours.black and not _fs.isDir(new.PathTextBox.TextInput.Value) then - returnFunc(new, true, TidyPath(new.PathTextBox.TextInput.Value)) - elseif new.SelectedFile and self.TextColour == colours.black and _fs.isDir(new.CurrentDirectory .. new.SelectedFile) then - new:GoToDirectory(new.CurrentDirectory .. new.SelectedFile) - elseif new.SelectedFile and self.TextColour == colours.black then - returnFunc(new, true, TidyPath(new.CurrentDirectory .. '/' .. new.SelectedFile)) - end - end, 'Open', colours.black) - new.CancelButton = Button:Initialise(new.Width - 15, new.Height - 1, nil, nil, colours.white, new, function(self, side, x, y, toggle) - returnFunc(new, false) - end, 'Cancel', colours.black) - new.GoUpButton = Button:Initialise(2, new.Height - 1, nil, nil, colours.white, new, function(self, side, x, y, toggle) - local folderName = _fs.getName(new.CurrentDirectory) - local parentDirectory = new.CurrentDirectory:sub(1, #new.CurrentDirectory-#folderName-1) - new:GoToDirectory(parentDirectory) - end, 'Go Up', colours.black) - new.PathTextBox = TextBox:Initialise(2, new.Height - 3, new.Width - 2, 1, new, new.CurrentDirectory, colours.white, colours.black) - new:GoToDirectory(new.CurrentDirectory) - return new - end, - - Show = function(self) - Current.Window = self - return self - end, - - Close = function(self) - Current.Input = nil - Current.Window = nil - self = nil - end, - - GoToDirectory = function(self, path) - path = TidyPath(path) - self.CurrentDirectory = path - self.Scroll = 0 - self.SelectedFile = nil - self.Typed = false - self.PathTextBox.TextInput.Value = path - local _fs = fs - if OneOS then - _fs = OneOS.FS - end - self.Files = _fs.list(self.CurrentDirectory) - Draw() - end, - - Flash = function(self) - self.Visible = false - Draw() - sleep(0.15) - self.Visible = true - Draw() - sleep(0.15) - self.Visible = false - Draw() - sleep(0.15) - self.Visible = true - Draw() - end, - - Click = function(self, side, x, y) - local items = {self.OpenButton, self.CancelButton, self.PathTextBox, self.GoUpButton} - local found = false - for i, v in ipairs(items) do - if CheckClick(v, x, y) then - v:Click(side, x, y) - found = true - end - end - - if not found then - if y <= 12 then - local _fs = fs - if OneOS then - _fs = OneOS.FS - end - self.SelectedFile = _fs.list(self.CurrentDirectory)[y-1] - self.PathTextBox.TextInput.Value = TidyPath(self.CurrentDirectory .. '/' .. self.SelectedFile) - Draw() - end - end - return true - end -} - -ButtonDialougeWindow = { - X = 1, - Y = 1, - Width = 0, - Height = 0, - CursorPos = 1, - Visible = true, - CancelButton = nil, - OkButton = nil, - Lines = {}, - - AbsolutePosition = function(self) - return {X = self.X, Y = self.Y} - end, - - Draw = function(self) - if not self.Visible then - return - end - Drawing.DrawBlankArea(self.X + 1, self.Y+1, self.Width, self.Height, colours.grey) - Drawing.DrawBlankArea(self.X, self.Y, self.Width, 1, colours.lightGrey) - Drawing.DrawBlankArea(self.X, self.Y+1, self.Width, self.Height-1, colours.white) - Drawing.DrawCharactersCenter(self.X, self.Y, self.Width, 1, self.Title, colours.black, colours.lightGrey) - - for i, text in ipairs(self.Lines) do - Drawing.DrawCharacters(self.X + 1, self.Y + 1 + i, text, colours.black, colours.white) - end - - self.OkButton:Draw() - if self.CancelButton then - self.CancelButton:Draw() - end - end, - - Initialise = function(self, title, message, okText, cancelText, returnFunc) - local new = {} -- the new instance - setmetatable( new, {__index = self} ) - new.Width = 28 - new.Lines = WrapText(message, new.Width - 2) - new.Height = 5 + #new.Lines - new.Return = returnFunc - new.X = math.ceil((Drawing.Screen.Width - new.Width) / 2) - new.Y = math.ceil((Drawing.Screen.Height - new.Height) / 2) - new.Title = title - new.Visible = true - new.Visible = true - new.OkButton = Button:Initialise(new.Width - #okText - 2, new.Height - 1, nil, 1, nil, new, function() - returnFunc(new, true) - end, okText) - if cancelText then - new.CancelButton = Button:Initialise(new.Width - #okText - 2 - 1 - #cancelText - 2, new.Height - 1, nil, 1, nil, new, function() - returnFunc(new, false) - end, cancelText) - end - - return new - end, - - Show = function(self) - Current.Window = self - return self - end, - - Close = function(self) - Current.Window = nil - self = nil - end, - - Flash = function(self) - self.Visible = false - Draw() - sleep(0.15) - self.Visible = true - Draw() - sleep(0.15) - self.Visible = false - Draw() - sleep(0.15) - self.Visible = true - Draw() - end, - - Click = function(self, side, x, y) - local items = {self.OkButton, self.CancelButton} - local found = false - for i, v in ipairs(items) do - if CheckClick(v, x, y) then - v:Click(side, x, y) - found = true - end - end - return true - end -} - -TextDialougeWindow = { - X = 1, - Y = 1, - Width = 0, - Height = 0, - CursorPos = 1, - Visible = true, - CancelButton = nil, - OkButton = nil, - Lines = {}, - TextInput = nil, - - AbsolutePosition = function(self) - return {X = self.X, Y = self.Y} - end, - - Draw = function(self) - if not self.Visible then - return - end - Drawing.DrawBlankArea(self.X + 1, self.Y+1, self.Width, self.Height, colours.grey) - Drawing.DrawBlankArea(self.X, self.Y, self.Width, 1, colours.lightGrey) - Drawing.DrawBlankArea(self.X, self.Y+1, self.Width, self.Height-1, colours.white) - Drawing.DrawCharactersCenter(self.X, self.Y, self.Width, 1, self.Title, colours.black, colours.lightGrey) - - for i, text in ipairs(self.Lines) do - Drawing.DrawCharacters(self.X + 1, self.Y + 1 + i, text, colours.black, colours.white) - end - - - Drawing.DrawBlankArea(self.X + 1, self.Y + self.Height - 4, self.Width - 2, 1, colours.lightGrey) - Drawing.DrawCharacters(self.X + 2, self.Y + self.Height - 4, self.TextInput.Value, colours.black, colours.lightGrey) - Current.CursorPos = {self.X + 2 + self.TextInput.CursorPos, self.Y + self.Height - 4} - Current.CursorColour = colours.black - - self.OkButton:Draw() - if self.CancelButton then - self.CancelButton:Draw() - end - end, - - Initialise = function(self, title, message, okText, cancelText, returnFunc, numerical) - local new = {} -- the new instance - setmetatable( new, {__index = self} ) - new.Width = 28 - new.Lines = WrapText(message, new.Width - 2) - new.Height = 7 + #new.Lines - new.Return = returnFunc - new.X = math.ceil((Drawing.Screen.Width - new.Width) / 2) - new.Y = math.ceil((Drawing.Screen.Height - new.Height) / 2) - new.Title = title - new.Visible = true - new.Visible = true - new.OkButton = Button:Initialise(new.Width - #okText - 2, new.Height - 1, nil, 1, nil, new, function() - if #new.TextInput.Value > 0 then - returnFunc(new, true, new.TextInput.Value) - end - end, okText) - if cancelText then - new.CancelButton = Button:Initialise(new.Width - #okText - 2 - 1 - #cancelText - 2, new.Height - 1, nil, 1, nil, new, function() - returnFunc(new, false) - end, cancelText) - end - new.TextInput = TextInput:Initialise('', function(enter) - if enter then - new.OkButton:Click() - end - Draw() - end, numerical) - - Current.Input = new.TextInput - - return new - end, - - Show = function(self) - Current.Window = self - return self - end, - - Close = function(self) - Current.Window = nil - Current.Input = nil - self = nil - end, - - Flash = function(self) - self.Visible = false - Draw() - sleep(0.15) - self.Visible = true - Draw() - sleep(0.15) - self.Visible = false - Draw() - sleep(0.15) - self.Visible = true - Draw() - end, - - Click = function(self, side, x, y) - local items = {self.OkButton, self.CancelButton} - local found = false - for i, v in ipairs(items) do - if CheckClick(v, x, y) then - v:Click(side, x, y) - found = true - end - end - return true - end -} - -ResizeDocumentWindow = { - X = 1, - Y = 1, - Width = 0, - Height = 0, - CursorPos = 1, - Visible = true, - Return = nil, - OkButton = nil, - AnchorPosition = 5, - WidthLabelHighlight = false, - HeightLabelHighlight = false, - - AbsolutePosition = function(self) - return {X = self.X, Y = self.Y} - end, - - Draw = function(self) - if not self.Visible then - return - end - Drawing.DrawBlankArea(self.X + 1, self.Y+1, self.Width, self.Height, colours.grey) - Drawing.DrawBlankArea(self.X, self.Y, self.Width, 1, colours.lightGrey) - Drawing.DrawBlankArea(self.X, self.Y+1, self.Width, self.Height-1, colours.white) - Drawing.DrawCharactersCenter(self.X, self.Y, self.Width, 1, self.Title, colours.black, colours.lightGrey) - - Drawing.DrawCharacters(self.X+1, self.Y+2, "New Size", colours.lightGrey, colours.white) - if (#self.WidthTextBox.TextInput.Value > 0 and tonumber(self.WidthTextBox.TextInput.Value) < Current.Artboard.Width) or (#self.HeightTextBox.TextInput.Value > 0 and tonumber(self.HeightTextBox.TextInput.Value) < Current.Artboard.Height) then - Drawing.DrawCharacters(self.X+1, self.Y+8, "Clipping will occur!", colours.red, colours.white) - end - - local widthLabelColour = colours.black - if self.WidthLabelHighlight then - widthLabelColour = colours.red - end - - local heightLabelColour = colours.black - if self.HeightLabelHighlight then - heightLabelColour = colours.red - end - - Drawing.DrawCharacters(self.X+1, self.Y+4, "Width", widthLabelColour, colours.white) - Drawing.DrawCharacters(self.X+1, self.Y+6, "Height", heightLabelColour, colours.white) - - Drawing.DrawCharacters(self.X+14, self.Y+2, "Anchor", colours.lightGrey, colours.white) - - self.WidthTextBox:Draw() - self.HeightTextBox:Draw() - self.OkButton:Draw() - self.Anchor1:Draw() - self.Anchor2:Draw() - self.Anchor3:Draw() - self.Anchor4:Draw() - self.Anchor5:Draw() - self.Anchor6:Draw() - self.Anchor7:Draw() - self.Anchor8:Draw() - self.Anchor9:Draw() - end, - - Initialise = function(self, returnFunc) - local new = {} -- the new instance - setmetatable( new, {__index = self} ) - new.Width = 27 - new.Height = 10 - new.Return = returnFunc - new.X = math.ceil((Drawing.Screen.Width - new.Width) / 2) - new.Y = math.ceil((Drawing.Screen.Height - new.Height) / 2) - new.Title = 'Resize Document' - new.Visible = true - - new.WidthTextBox = TextBox:Initialise(9, 5, 4, 1, new, tostring(Current.Artboard.Width), nil, nil, function() - new:UpdateAnchorButtons() - end, true) - new.HeightTextBox = TextBox:Initialise(9, 7, 4, 1, new, tostring(Current.Artboard.Height), nil, nil, function() - new:UpdateAnchorButtons() - end, true) - new.OkButton = Button:Initialise(new.Width - 4, new.Height - 1, nil, nil, colours.lightGrey, new, function(self, side, x, y, toggle) - local ok = true - new.WidthLabelHighlight = false - new.HeightLabelHighlight = false - - if #new.WidthTextBox.TextInput.Value == 0 or tonumber(new.WidthTextBox.TextInput.Value) <= 0 then - ok = false - new.WidthLabelHighlight = true - end - - if #new.HeightTextBox.TextInput.Value == 0 or tonumber(new.HeightTextBox.TextInput.Value) <= 0 then - ok = false - new.HeightLabelHighlight = true - end - - if ok then - returnFunc(new, tonumber(new.WidthTextBox.TextInput.Value), tonumber(new.HeightTextBox.TextInput.Value), new.AnchorPosition) - else - Draw() - end - end, 'Ok', colours.black) - - local anchorX = 15 - local anchorY = 5 - new.Anchor1 = Button:Initialise(anchorX, anchorY, 1, 1, colours.lightGrey, new, function(self, side, x, y, toggle)new.AnchorPosition = 1 new:UpdateAnchorButtons() end, ' ', colours.black) - new.Anchor2 = Button:Initialise(anchorX+1, anchorY, 1, 1, colours.lightGrey, new, function(self, side, x, y, toggle)new.AnchorPosition = 2 new:UpdateAnchorButtons() end, '^', colours.black) - new.Anchor3 = Button:Initialise(anchorX+2, anchorY, 1, 1, colours.lightGrey, new, function(self, side, x, y, toggle)new.AnchorPosition = 3 new:UpdateAnchorButtons() end, ' ', colours.black) - new.Anchor4 = Button:Initialise(anchorX, anchorY+1, 1, 1, colours.lightGrey, new, function(self, side, x, y, toggle)new.AnchorPosition = 4 new:UpdateAnchorButtons() end, '<', colours.black) - new.Anchor5 = Button:Initialise(anchorX+1, anchorY+1, 1, 1, colours.lightGrey, new, function(self, side, x, y, toggle)new.AnchorPosition = 5 new:UpdateAnchorButtons() end, '#', colours.black) - new.Anchor6 = Button:Initialise(anchorX+2, anchorY+1, 1, 1, colours.lightGrey, new, function(self, side, x, y, toggle)new.AnchorPosition = 6 new:UpdateAnchorButtons() end, '>', colours.black) - new.Anchor7 = Button:Initialise(anchorX, anchorY+2, 1, 1, colours.lightGrey, new, function(self, side, x, y, toggle)new.AnchorPosition = 7 new:UpdateAnchorButtons() end, ' ', colours.black) - new.Anchor8 = Button:Initialise(anchorX+1, anchorY+2, 1, 1, colours.lightGrey, new, function(self, side, x, y, toggle)new.AnchorPosition = 8 new:UpdateAnchorButtons() end, 'v', colours.black) - new.Anchor9 = Button:Initialise(anchorX+2, anchorY+2, 1, 1, colours.lightGrey, new, function(self, side, x, y, toggle)new.AnchorPosition = 9 new:UpdateAnchorButtons() end, ' ', colours.black) - - return new - end, - - UpdateAnchorButtons = function(self) - local anchor1 = ' ' - local anchor2 = ' ' - local anchor3 = ' ' - local anchor4 = ' ' - local anchor5 = ' ' - local anchor6 = ' ' - local anchor7 = ' ' - local anchor8 = ' ' - local anchor9 = ' ' - self.AnchorPosition = self.AnchorPosition or 5 - if self.AnchorPosition == 1 then - anchor1 = '#' - anchor2 = '>' - anchor4 = 'v' - elseif self.AnchorPosition == 2 then - anchor1 = '<' - anchor2 = '#' - anchor3 = '>' - anchor5 = 'v' - elseif self.AnchorPosition == 3 then - anchor2 = '<' - anchor3 = '#' - anchor6 = 'v' - elseif self.AnchorPosition == 4 then - anchor1 = '^' - anchor4 = '#' - anchor5 = '>' - anchor7 = 'v' - elseif self.AnchorPosition == 5 then - anchor2 = '^' - anchor4 = '<' - anchor5 = '#' - anchor6 = '>' - anchor8 = 'v' - elseif self.AnchorPosition == 6 then - anchor3 = '^' - anchor6 = '#' - anchor5 = '<' - anchor9 = 'v' - elseif self.AnchorPosition == 7 then - anchor4 = '^' - anchor7 = '#' - anchor8 = '>' - elseif self.AnchorPosition == 8 then - anchor5 = '^' - anchor8 = '#' - anchor7 = '<' - anchor9 = '>' - elseif self.AnchorPosition == 9 then - anchor6 = '^' - anchor9 = '#' - anchor8 = '<' - end - - if #self.HeightTextBox.TextInput.Value > 0 and Current.Artboard.Height > tonumber(self.HeightTextBox.TextInput.Value) then - local r = function(str) - if string.find(str, "%^") then - str = str:gsub('%^','v') - elseif string.find(str, "v") then - str = str:gsub('v','%^') - end - return str - end - anchor1 = r(anchor1) - anchor2 = r(anchor2) - anchor3 = r(anchor3) - anchor4 = r(anchor4) - anchor5 = r(anchor5) - anchor6 = r(anchor6) - anchor7 = r(anchor7) - anchor8 = r(anchor8) - anchor9 = r(anchor9) - end - - if #self.WidthTextBox.TextInput.Value > 0 and Current.Artboard.Width > tonumber(self.WidthTextBox.TextInput.Value) then - local r = function(str) - if string.find(str, ">") then - str = str:gsub('>','<') - elseif string.find(str, "<") then - str = str:gsub('<','>') - end - return str - end - anchor1 = r(anchor1) - anchor2 = r(anchor2) - anchor3 = r(anchor3) - anchor4 = r(anchor4) - anchor5 = r(anchor5) - anchor6 = r(anchor6) - anchor7 = r(anchor7) - anchor8 = r(anchor8) - anchor9 = r(anchor9) - end - - self.Anchor1.Text = anchor1 - self.Anchor2.Text = anchor2 - self.Anchor3.Text = anchor3 - self.Anchor4.Text = anchor4 - self.Anchor5.Text = anchor5 - self.Anchor6.Text = anchor6 - self.Anchor7.Text = anchor7 - self.Anchor8.Text = anchor8 - self.Anchor9.Text = anchor9 - end, - - Show = function(self) - Current.Window = self - return self - end, - - Close = function(self) - Current.Input = nil - Current.Window = nil - self = nil - end, - - Flash = function(self) - self.Visible = false - Draw() - sleep(0.15) - self.Visible = true - Draw() - sleep(0.15) - self.Visible = false - Draw() - sleep(0.15) - self.Visible = true - Draw() - end, - - ButtonClick = function(self, button, x, y) - if button.X <= x and button.Y <= y and button.X + button.Width > x and button.Y + button.Height > y then - button:Click() - end - end, - - Click = function(self, side, x, y) - local items = {self.OkButton, self.WidthTextBox, self.HeightTextBox, self.Anchor1, self.Anchor2, self.Anchor3, self.Anchor4, self.Anchor5, self.Anchor6, self.Anchor7, self.Anchor8, self.Anchor9} - for i, v in ipairs(items) do - if CheckClick(v, x, y) then - v:Click(side, x, y) - end - end - return true - end -} - ----------------------- - -function CheckOpenArtboard() - if Current.Artboard then - return true - else - return false - end -end - -function CheckSelectedLayer() - if Current.Artboard and Current.Layer then - return true - else - return false - end -end - -function DisplayNewDocumentWindow() - NewDocumentWindow:Initialise(function(self, success, path, width, height, format, backgroundColour) - if success then - if path:sub(-4) ~= format then - path = path .. format - end - local oldWindow = self - Current.Input = nil - Current.Window = nil - makeDocument = function()oldWindow:Close()NewDocument(path, width, height, format, backgroundColour)end - local _fs = fs - if OneOS then - _fs = OneOS.FS - end - if _fs.exists(path) then - ButtonDialougeWindow:Initialise('File Exists', path..' already exists! Use a different name and try again.', 'Ok', nil, function(window, ok) - window:Close() - oldWindow:Show() - end):Show() - elseif format == '.nfp' then - Current.Window = nil - ButtonDialougeWindow:Initialise('Use NFP?', 'The NFT format does not support text or layers, if you use it you will only be able to use 1 layer and not have any text.', 'Use NFP', 'Cancel', function(window, ok) - window:Close() - if ok then - makeDocument() - else - oldWindow:Show() - end - end):Show() - elseif format == '.nft' then - ButtonDialougeWindow:Initialise('Use NFT?', 'The NFT format does not support layers, if you use it you will only be able to use 1 layer.', 'Use NFT', 'Cancel', function(window, ok) - window:Close() - if ok then - makeDocument() - else - oldWindow:Show() - end - end):Show() - else - makeDocument() - end - - - else - self:Close() - end - end):Show() -end - -function NewDocument(path, width, height, format, backgroundColour) - local _fs = fs - if OneOS then - _fs = OneOS.FS - end - ab = Artboard:New(_fs.getName(path), path, width, height, format, backgroundColour) - Current.Tool = Tools[2] - Current.Toolbar:Update() - Current.Modified = false - Draw() -end - -function DisplayToolSizeWindow() - if not CheckOpenArtboard() then - return - end - TextDialougeWindow:Initialise('Change Tool Size', 'Enter the new tool size you\'d like to use.', 'Ok', 'Cancel', function(window, success, value) - if success then - Current.ToolSize = math.ceil(tonumber(value)) - if Current.ToolSize < 1 then - Current.ToolSize = 1 - elseif Current.ToolSize > 50 then - Current.ToolSize = 50 - end - ModuleNamed('Tools'):Update() - end - window:Close() - end, true):Show() -end - ---[[ - Attempt to figure out what format the image is if it doesn't have an extension -]]-- -function GetFormat(path) - local _fs = fs - if OneOS then - _fs = OneOS.FS - end - local file = _fs.open(path, 'r') - local content = file.readAll() - file.close() - if type(textutils.unserialize(content)) == 'table' then - -- It's a serlized table, asume sketch - return '.skch' - elseif string.find(content, string.char(30)) or string.find(content, string.char(31)) then - -- Contains the characters that set colours, asume nft - return '.nft' - else - -- Otherwise asume nfp - return '.nfp' - end -end - -function DisplayOpenDocumentWindow() - OpenDocumentWindow:Initialise(function(self, success, path) - self:Close() - if success then - OpenDocument(path) - end - end):Show() -end - - -local function Extension(path, addDot) - if not path then - return nil - elseif not string.find(fs.getName(path), '%.') then - if not addDot then - return fs.getName(path) - else - return '' - end - else - local _path = path - if path:sub(#path) == '/' then - _path = path:sub(1,#path-1) - end - local extension = _path:gmatch('\.[0-9a-z]+$')() - if extension then - extension = extension:sub(2) - else - --extension = nil - return '' - end - if addDot then - extension = '.'..extension - end - return extension:lower() - end -end - -local RemoveExtension = function(path) - if path:sub(1,1) == '.' then - return path - end - local extension = Extension(path) - if extension == path then - return fs.getName(path) - end - return string.gsub(path, extension, ''):sub(1, -2) -end ---[[ - Open a documet at a given path -]]-- -function OpenDocument(path) - local _fs = fs - if OneOS then - _fs = OneOS.FS - end - if _fs.exists(path) and not _fs.isDir(path) then - local format = Extension(path, true) - if (not format or format == '') and (format ~= '.nfp' and format ~= '.nft' and format ~= '.skch') then - format = GetFormat(path) - end - local layers = {} - if format == '.nfp' then - layers = ReadNFP(path) - elseif format == '.nft' then - layers = ReadNFT(path) - elseif format == '.skch' then - layers = ReadSKCH(path) - end - - for i, layer in ipairs(layers) do - if layer.Visible == nil then - layer.Visible = true - end - if layer.Index == nil then - layer.Index = 1 - end - if layer.Name == nil then - if layer.Index == 1 then - layer.Name = 'Background' - else - layer.Name = 'Layer' - end - end - if layer.BackgroundColour == nil then - layer.BackgroundColour = colours.white - end - end - - if not layers[1] then - --log('File could not be read.') - return - end - - local width = #layers[1].Pixels - local height = #layers[1].Pixels[1] - - Current.Artboard = nil - local _fs = fs - if OneOS then - _fs = OneOS.FS - end - ab = Artboard:New(_fs.getName('Image'), path, width, height, format, nil, layers) - Current.Tool = Tools[2] - Current.Toolbar:Update() - Current.Modified = false - Draw() - end -end - -function MakeNewLayer() - if not CheckOpenArtboard() then - return - end - if Current.Artboard.Format == '.skch' then - TextDialougeWindow:Initialise('New Layer Name', 'Enter the name you want for the next layer.', 'Ok', 'Cancel', function(window, success, value) - if success then - Current.Artboard:MakeLayer(value, colours.transparent) - end - window:Close() - end):Show() - else - local format = 'NFP' - if Current.Artboard.Format == '.nft' then - format = 'NFT' - end - ButtonDialougeWindow:Initialise(format..' does not support layers!', 'The format you are using, '..format..', does not support multiple layers. Use SKCH to have more than one layer.', 'Ok', nil, function(window) - window:Close() - end):Show() - end -end - -function ResizeDocument() - if not CheckOpenArtboard() then - return - end - ResizeDocumentWindow:Initialise(function(window, width, height, anchor) - window:Close() - local topResize = 0 - local rightResize = 0 - local bottomResize = 0 - local leftResize = 0 - - if anchor == 1 then - rightResize = 1 - bottomResize = 1 - elseif anchor == 2 then - rightResize = 0.5 - leftResize = 0.5 - bottomResize = 1 - elseif anchor == 3 then - leftResize = 1 - bottomResize = 1 - elseif anchor == 4 then - rightResize = 1 - bottomResize = 0.5 - topResize = 0.5 - elseif anchor == 5 then - rightResize = 0.5 - leftResize = 0.5 - bottomResize = 0.5 - topResize = 0.5 - elseif anchor == 6 then - leftResize = 1 - bottomResize = 0.5 - topResize = 0.5 - elseif anchor == 7 then - rightResize = 1 - topResize = 1 - elseif anchor == 8 then - rightResize = 0.5 - leftResize = 0.5 - topResize = 1 - elseif anchor == 9 then - leftResize = 1 - topResize = 1 - end - - topResize = topResize * (height - Current.Artboard.Height) - if topResize > 0 then - topResize = math.floor(topResize) - else - topResize = math.ceil(topResize) - end - - bottomResize = bottomResize * (height - Current.Artboard.Height) - if bottomResize > 0 then - bottomResize = math.ceil(bottomResize) - else - bottomResize = math.floor(bottomResize) - end - - leftResize = leftResize * (width - Current.Artboard.Width) - if leftResize > 0 then - leftResize = math.floor(leftResize) - else - leftResize = math.ceil(leftResize) - end - - rightResize = rightResize * (width - Current.Artboard.Width) - if rightResize > 0 then - rightResize = math.ceil(rightResize) - else - rightResize = math.floor(rightResize) - end - - Current.Artboard:Resize(topResize, bottomResize, leftResize, rightResize) - end):Show() -end - -function RenameLayer() - if not CheckOpenArtboard() then - return - end - if Current.Artboard.Format == '.skch' then - TextDialougeWindow:Initialise("Rename Layer '"..Current.Layer.Name.."'", 'Enter the new name you want the layer to be called.', 'Ok', 'Cancel', function(window, success, value) - if success then - Current.Layer.Name = value - end - window:Close() - end):Show() - else - local format = 'NFP' - if Current.Artboard.Format == '.nft' then - format = 'NFT' - end - ButtonDialougeWindow:Initialise(format..' does not support layers!', 'The format you are using, '..format..', does not support renaming layers. Use SKCH to rename layers.', 'Ok', nil, function(window) - window:Close() - end):Show() - end -end - -function DeleteLayer() - if not CheckOpenArtboard() then - return - end - if Current.Artboard.Format == '.skch' then - if #Current.Artboard.Layers > 1 then - ButtonDialougeWindow:Initialise("Delete Layer '"..Current.Layer.Name.."'?", 'Are you sure you want delete the layer?', 'Ok', 'Cancel', function(window, success) - if success then - Current.Layer:Remove() - end - window:Close() - end):Show() - else - ButtonDialougeWindow:Initialise('Can not delete layer!', 'You can not delete the last layer of an image! Make another layer to delete this one.', 'Ok', nil, function(window) - window:Close() - end):Show() - end - else - local format = 'NFP' - if Current.Artboard.Format == '.nft' then - format = 'NFT' - end - ButtonDialougeWindow:Initialise(format..' does not support layers!', 'The format you are using, '..format..', does not support deleting layers. Use SKCH to deleting layers.', 'Ok', nil, function(window) - window:Close() - end):Show() - end -end - -needsDraw = false -isDrawing = false -function Draw() - if isDrawing then - needsDraw = true - return - end - needsDraw = false - isDrawing = true - if not Current.Window then - Drawing.Clear(UIColours.Background) - else - Drawing.DrawArea(1, 2, Drawing.Screen.Width, Drawing.Screen.Height, '|', colours.black, colours.lightGrey) - end - - if Current.Artboard then - ab:Draw() - end - - if Current.InterfaceVisible then - Current.MenuBar:Draw() - Current.Toolbar.Width = Current.Toolbar.ExpandedWidth - Current.Toolbar:Draw() - else - Current.Toolbar.Width = Current.Toolbar.ExpandedWidth - end - - if Current.InterfaceVisible and Current.Menu then - Current.Menu:Draw() - end - - if Current.Window then - Current.Window:Draw() - end - - if not Current.InterfaceVisible then - ShowInterfaceButton:Draw() - end - - Drawing.DrawBuffer() - if Current.Input and not Current.Menu then - term.setCursorPos(Current.CursorPos[1], Current.CursorPos[2]) - term.setCursorBlink(true) - term.setTextColour(Current.CursorColour) - else - term.setCursorBlink(false) - end - - if Current.Selection and Current.Selection[1] and Current.Selection[2] ~= nil then - Current.SelectionDrawTimer = os.startTimer(0.5) - end - isDrawing = false - if needsDraw then - Draw() - end -end - -function LoadMenuBar() - Current.MenuBar = MenuBar:Initialise({ - Button:Initialise(1, 1, nil, nil, colours.grey, Current.MenuBar, function(self, side, x, y, toggle) - if toggle then - Menu:New(1, 2, { - { - Title = "New...", - Click = function() - DisplayNewDocumentWindow() - end, - Keys = { - keys.leftCtrl, - keys.n - } - }, - { - Title = 'Open...', - Click = function() - DisplayOpenDocumentWindow() - end, - Keys = { - keys.leftCtrl, - keys.o - } - }, - { - Separator = true - }, - { - Title = 'Save...', - Click = function() - Current.Artboard:Save() - end, - Keys = { - keys.leftCtrl, - keys.s - }, - Enabled = function() - return CheckOpenArtboard() - end - }, - { - Separator = true - }, - { - Title = 'Quit', - Click = function() - if Close() then - OneOS.Close() - end - end - }, - --[[ - { - Title = 'Save As...', - Click = function() - - end - } - ]]-- - }, self, true) - else - Current.Menu = nil - end - return true - end, 'File', colours.lightGrey, false), - Button:Initialise(7, 1, nil, nil, colours.grey, Current.MenuBar, function(self, side, x, y, toggle) - if not self.Toggle then - Menu:New(7, 2, { - --[[ - { - Title = "Undo", - Click = function() - end, - Keys = { - keys.leftCtrl, - keys.z - }, - Enabled = function() - return false - end - }, - { - Title = 'Redo', - Click = function() - - end, - Keys = { - keys.leftCtrl, - keys.y - }, - Enabled = function() - return false - end - }, - { - Separator = true - }, - ]]-- - { - Title = 'Cut', - Click = function() - Clipboard.Cut(Current.Layer:PixelsInSelection(true), 'sketchpixels') - end, - Keys = { - keys.leftCtrl, - keys.x - }, - Enabled = function() - return Current.Selection and Current.Selection[1] and Current.Selection[2] ~= nil - end - }, - { - Title = 'Copy', - Click = function() - Clipboard.Copy(Current.Layer:PixelsInSelection(), 'sketchpixels') - end, - Keys = { - keys.leftCtrl, - keys.c - }, - Enabled = function() - return Current.Selection and Current.Selection[1] and Current.Selection[2] ~= nil - end - }, - { - Title = 'Paste', - Click = function() - Current.Layer:InsertPixels(Clipboard.Paste()) - end, - Keys = { - keys.leftCtrl, - keys.v - }, - Enabled = function() - return (not Clipboard.isEmpty()) and Clipboard.Type == 'sketchpixels' - end - } - }, self, true) - else - Current.Menu = nil - end - return true - end, 'Edit', colours.lightGrey, false), - Button:Initialise(13, 1, nil, nil, colours.grey, Current.MenuBar, function(self, side, x, y, toggle) - if toggle then - Menu:New(13, 2, { - { - Title = "Resize...", - Click = function() - ResizeDocument() - end, - Keys = { - keys.leftCtrl, - keys.r - }, - Enabled = function() - return CheckOpenArtboard() - end - }, - { - Title = "Crop", - Click = function() - local top = 0 - local left = 0 - local bottom = 0 - local right = 0 - if Current.Selection[1].x < Current.Selection[2].x then - left = Current.Selection[1].x - 1 - right = Current.Artboard.Width - Current.Selection[2].x - else - left = Current.Selection[2].x - 1 - right = Current.Artboard.Width - Current.Selection[1].x - end - if Current.Selection[1].y < Current.Selection[2].y then - top = Current.Selection[1].y - 1 - bottom = Current.Artboard.Height - Current.Selection[2].y - else - top = Current.Selection[2].y - 1 - bottom = Current.Artboard.Height - Current.Selection[1].y - end - Current.Artboard:Resize(-1*top, -1*bottom, -1*left, -1*right) - - Current.Selection[2] = nil - end, - Enabled = function() - if CheckSelectedLayer() and Current.Selection and Current.Selection[1] and Current.Selection[2] ~= nil then - return true - else - return false - end - end - }, - { - Separator = true - }, - { - Title = 'New Layer...', - Click = function() - MakeNewLayer() - end, - Keys = { - keys.leftCtrl, - keys.l - }, - Enabled = function() - return CheckOpenArtboard() - end - }, - { - Title = 'Delete Layer', - Click = function() - DeleteLayer() - end, - Enabled = function() - return CheckSelectedLayer() - end - }, - { - Title = 'Rename Layer...', - Click = function() - RenameLayer() - end, - Enabled = function() - return CheckSelectedLayer() - end - }, - { - Separator = true - }, - { - Title = 'Erase Selection', - Click = function() - Current.Layer:EraseSelection() - end, - Keys = { - keys.delete - }, - Enabled = function() - if CheckSelectedLayer() and Current.Selection and Current.Selection[1] and Current.Selection[2] ~= nil then - return true - else - return false - end - end - }, - { - Separator = true - }, - { - Title = 'Hide Interface', - Click = function() - Current.InterfaceVisible = not Current.InterfaceVisible - end, - Keys = { - keys.tab - } - } - }, self, true) - else - Current.Menu = nil - end - return true - end, 'Image', colours.lightGrey, false), - - Button:Initialise(20, 1, nil, nil, colours.grey, Current.MenuBar, function(self, side, x, y, toggle) - if toggle then - local menuItems = {{ - Title = "Change Size", - Click = function() - DisplayToolSizeWindow() - end, - Keys = { - keys.leftCtrl, - keys.t - } - }, - { - Separator = true - } - } - - local _keys = {'h','p','e','f','s','m','t'} - for i, tool in ipairs(Tools) do - table.insert(menuItems, { - Title = tool.Name, - Click = function() - SetTool(tool) - local m = ModuleNamed('Tools') - m:Update(m.ToolbarItem) - end, - Keys = { - keys[_keys[i]] - }, - Enabled = function() - return CheckOpenArtboard() - end - }) - end - - Menu:New(20, 2, menuItems, self, true) - else - Current.Menu = nil - end - return true - end, 'Tools', colours.lightGrey, false), - }) -end - -function Timer(event, timer) - if timer == Current.ControlPressedTimer then - Current.ControlPressedTimer = nil - elseif timer == Current.SelectionDrawTimer then - if Current.Artboard then - Current.Artboard.SelectionIsBlack = not Current.Artboard.SelectionIsBlack - Draw() - end - end -end - -function Initialise(arg) - if not OneOS then - SplashScreen() - end - EventRegister('mouse_click', TryClick) - EventRegister('mouse_drag', function(event, side, x, y)TryClick(event, side, x, y, true)end) - EventRegister('mouse_scroll', Scroll) - EventRegister('key', HandleKey) - EventRegister('char', HandleKey) - EventRegister('timer', Timer) - EventRegister('terminate', function(event) if Close() then error( "Terminated", 0 ) end end) - - - Current.Toolbar = Toolbar:New('right', true) - - for k, v in pairs(Modules) do - v:Initialise() - end - - --term.setBackgroundColour(UIColours.Background) - --term.clear() - - LoadMenuBar() - - local _fs = fs - if OneOS then - _fs = OneOS.FS - end - if arg and _fs.exists(arg) then - OpenDocument(arg) - else - DisplayNewDocumentWindow() - Current.Window.Visible = false - end - - ShowInterfaceButton = Button:Initialise(Drawing.Screen.Width - 15, 1, nil, 1, colours.grey, nil, function(self) - Current.InterfaceVisible = true - Draw() - end, 'Show Interface') - - Draw() - if Current.Window then - Current.Window.Visible = true - Draw() - end - - EventHandler() -end - -function SplashScreen() - local splashIcon = {{1,1,1,256,256,256,256,256,256,256,256,1,1,1,},{1,256,256,8,8,8,8,8,8,8,8,256,256,1,},{256,8,8,8,8,8,8,8,8,8,8,8,8,256,},{256,256,256,8,8,8,8,8,8,8,8,256,256,256,},{256,256,256,256,256,256,256,256,256,256,256,256,256,256,},{2048,2048,256,256,256,256,256,256,256,256,256,256,2048,2048,},{2048,2048,2048,2048,2048,2048,2048,2048,2048,2048,2048,2048,2048,2048,},{2048,2048,2048,2048,2048,2048,2048,2048,2048,2048,2048,2048,2048,2048,},{2048,2048,2048,2048,2048,2048,2048,2048,2048,2048,2048,2048,2048,2048,},{2048,2048,2048,2048,2048,2048,2048,2048,2048,2048,2048,2048,2048,2048,},{256,256,2048,2048,2048,2048,2048,2048,2048,2048,2048,2048,256,256,},{1,256,256,256,256,256,256,256,256,256,256,256,256,1,},{1,1,1,256,256,256,256,256,256,256,256,1,1,1,},["text"]={{" "," "," "," "," "," "," "," "," "," "," "," "," "," ",},{" "," "," "," "," "," "," "," "," "," "," "," "," "," ",},{" "," "," "," "," "," "," "," "," "," "," "," "," "," ",},{" "," "," "," "," "," "," "," "," "," "," "," "," "," ",},{" "," "," "," "," "," "," "," "," "," "," "," "," "," ",},{" "," "," "," "," "," "," "," "," "," "," "," "," "," ",},{" "," "," "," "," "," "," "," "," "," "," "," "," "," ",},{" "," "," "," ","S","k","e","t","c","h"," "," "," "," ",},{" "," "," "," "," "," ","b","y"," "," "," "," "," "," ",},{" "," "," "," "," ","o","e","e","d"," "," "," "," "," ",},{" "," "," "," "," "," "," "," "," "," "," "," "," "," ",},{" "," "," "," "," "," "," "," "," "," "," "," "," "," ",},{" "," "," "," "," "," "," "," "," "," "," "," "," "," ",},},["textcol"]={{32768,32768,32768,32768,32768,32768,32768,32768,32768,32768,32768,32768,32768,32768,},{32768,32768,32768,256,32768,32768,32768,32768,32768,32768,32768,32768,32768,32768,},{32768,32768,32768,32768,32768,32768,32768,32768,32768,32768,32768,32768,32768,32768,},{32768,32768,32768,32768,32768,32768,32768,32768,32768,32768,32768,32768,32768,32768,},{32768,32768,32768,32768,32768,32768,32768,32768,32768,32768,32768,32768,32768,32768,},{32768,32768,32768,32768,32768,32768,32768,32768,32768,32768,32768,32768,32768,32768,},{32768,32768,32768,32768,32768,32768,32768,32768,32768,32768,32768,32768,32768,32768,},{32768,32768,32768,32768,1,1,1,1,1,1,32768,32768,32768,32768,},{32768,32768,32768,32768,8,8,8,8,8,8,8,32768,32768,32768,},{32768,32768,32768,32768,1,1,1,1,1,32768,8,32768,32768,32768,},{32768,32768,32768,32768,32768,32768,32768,32768,32768,32768,32768,32768,32768,32768,},{32768,32768,32768,32768,32768,32768,32768,32768,32768,32768,32768,32768,32768,32768,},{32768,32768,32768,32768,32768,32768,32768,32768,32768,32768,32768,32768,32768,32768,},},} - Drawing.Clear(colours.white) - Drawing.DrawImage((Drawing.Screen.Width - 14)/2, (Drawing.Screen.Height - 13)/2, splashIcon, 14, 13) - Drawing.DrawBuffer() - parallel.waitForAny(function()sleep(1)end, function()os.pullEvent('mouse_click')end) -end - -LongestString = function(input, key) - local length = 0 - for i = 1, #input do - local value = input[i] - if key then - if value[key] then - value = value[key] - else - value = '' - end - end - local titleLength = string.len(value) - if titleLength > length then - length = titleLength - end - end - return length -end - -function HandleKey(...) - local args = {...} - local event = args[1] - local keychar = args[2] - if event == 'key' and Current.Tool and Current.Tool.Name == 'Text' and Current.Input and (keychar == keys.up or keychar == keys.down or keychar == keys.left or keychar == keys.right) then - local currentPos = {Current.CursorPos[1] - Current.Artboard.X + 1, Current.CursorPos[2] - Current.Artboard.Y + 1} - if keychar == keys.up then - currentPos[2] = currentPos[2] - 1 - elseif keychar == keys.down then - currentPos[2] = currentPos[2] + 1 - elseif keychar == keys.left then - currentPos[1] = currentPos[1] - 1 - elseif keychar == keys.right then - currentPos[1] = currentPos[1] + 1 - end - - if currentPos[1] < 1 then - currentPos[1] = 1 - end - - if currentPos[1] > Current.Artboard.Width then - currentPos[1] = Current.Artboard.Width - end - - if currentPos[2] < 1 then - currentPos[2] = 1 - end - - if currentPos[2] > Current.Artboard.Height then - currentPos[2] = Current.Artboard.Height - end - - Current.Tool:Use(currentPos[1], currentPos[2]) - Current.Modified = true - Draw() - elseif Current.Input then - if event == 'char' then - Current.Input:Char(keychar) - elseif event == 'key' then - Current.Input:Key(keychar) - end - elseif event == 'key' then - CheckKeyboardShortcut(keychar) - end -end - -function Scroll(event, direction, x, y) - if Current.Window and Current.Window.OpenButton then - Current.Window.Scroll = Current.Window.Scroll + direction - if Current.Window.Scroll < 0 then - Current.Window.Scroll = 0 - elseif Current.Window.Scroll > Current.Window.MaxScroll then - Current.Window.Scroll = Current.Window.MaxScroll - end - end - Draw() -end - -function CheckKeyboardShortcut(key) - local shortcuts = {} - - if key == keys.leftCtrl then - Current.ControlPressedTimer = os.startTimer(0.5) - return - end - if Current.ControlPressedTimer then - shortcuts[keys.n] = function() DisplayNewDocumentWindow() end - shortcuts[keys.o] = function() DisplayOpenDocumentWindow() end - shortcuts[keys.s] = function() Current.Artboard:Save() end - shortcuts[keys.x] = function() if Current.Selection and Current.Selection[1] and Current.Selection[2] ~= nil then Clipboard.Cut(Current.Layer:PixelsInSelection(true), 'sketchpixels') end end - shortcuts[keys.c] = function() if Current.Selection and Current.Selection[1] and Current.Selection[2] ~= nil then Clipboard.Copy(Current.Layer:PixelsInSelection(), 'sketchpixels') end end - shortcuts[keys.v] = function() if (not Clipboard.isEmpty()) and Clipboard.Type == 'sketchpixels' then Current.Layer:InsertPixels(Clipboard.Paste()) end end - shortcuts[keys.r] = function() ResizeDocument() end - shortcuts[keys.l] = function() MakeNewLayer() end - end - - shortcuts[keys.delete] = function() if CheckSelectedLayer() and Current.Selection and Current.Selection[1] and Current.Selection[2] ~= nil then Current.Layer:EraseSelection() Draw() end end - shortcuts[keys.backspace] = shortcuts[keys.delete] - shortcuts[keys.tab] = function() Current.InterfaceVisible = not Current.InterfaceVisible Draw() end - - shortcuts[keys.h] = function() SetTool(ToolNamed('Hand')) ModuleNamed('Tools'):Update() Draw() end - shortcuts[keys.e] = function() SetTool(ToolNamed('Eraser')) ModuleNamed('Tools'):Update() Draw() end - shortcuts[keys.p] = function() SetTool(ToolNamed('Pencil')) ModuleNamed('Tools'):Update() Draw() end - shortcuts[keys.f] = function() SetTool(ToolNamed('Fill Bucket')) ModuleNamed('Tools'):Update() Draw() end - shortcuts[keys.m] = function() SetTool(ToolNamed('Move')) ModuleNamed('Tools'):Update() Draw() end - shortcuts[keys.s] = function() SetTool(ToolNamed('Select')) ModuleNamed('Tools'):Update() Draw() end - shortcuts[keys.t] = function() SetTool(ToolNamed('Text')) ModuleNamed('Tools'):Update() Draw() end - - if shortcuts[key] then - shortcuts[key]() - return true - else - return false - end -end - ---[[ - Check if the given object falls under the click coordinates -]]-- -function CheckClick(object, x, y) - if object.X <= x and object.Y <= y and object.X + object.Width > x and object.Y + object.Height > y then - return true - end -end - ---[[ - Attempt to clicka given object -]]-- -function DoClick(object, side, x, y, drag) - if object and CheckClick(object, x, y) then - return object:Click(side, x - object.X + 1, y - object.Y + 1, drag) - end -end - ---[[ - Try to click at the given coordinates -]]-- -function TryClick(event, side, x, y, drag) - if Current.InterfaceVisible and Current.Menu then - if DoClick(Current.Menu, side, x, y, drag) then - Draw() - return - else - if Current.Menu.Owner and Current.Menu.Owner.Toggle then - Current.Menu.Owner.Toggle = false - end - Current.Menu = nil - Draw() - return - end - elseif Current.Window then - if DoClick(Current.Window, side, x, y, drag) then - Draw() - return - else - Current.Window:Flash() - return - end - end - local interfaceElements = {} - - if Current.InterfaceVisible then - table.insert(interfaceElements, Current.MenuBar) - else - table.insert(interfaceElements, ShowInterfaceButton) - end - - for i, v in ipairs(Lists.Interface.Toolbars) do - for i, v2 in ipairs(v.ToolbarItems) do - table.insert(interfaceElements, v2) - end - table.insert(interfaceElements, v) - end - - table.insert(interfaceElements, Current.Artboard) - - for i, object in ipairs(interfaceElements) do - if DoClick(object, side, x, y, drag) then - Draw() - return - end - end - Draw() -end - ---[[ - Registers functions to run on certain events -]]-- -function EventRegister(event, func) - if not Events[event] then - Events[event] = {} - end - - table.insert(Events[event], func) -end - ---[[ - The main loop event handler, runs registered event functinos -]]-- -function EventHandler() - while true do - local event, arg1, arg2, arg3, arg4 = os.pullEventRaw() - if Events[event] then - for i, e in ipairs(Events[event]) do - e(event, arg1, arg2, arg3, arg4) - end - end - end -end - ---[[ - Thanks to NitrogenFingers for the colour functions and NFT + NFP read/write functions -]]-- - ---[[ - Gets the hex value from a colour -]]-- -local hexnums = { [10] = "a", [11] = "b", [12] = "c", [13] = "d", [14] = "e" , [15] = "f" } -local function getHexOf(colour) - if colour == colours.transparent or not colour or not tonumber(colour) then - return " " - end - local value = math.log(colour)/math.log(2) - if value > 9 then - value = hexnums[value] - end - return value -end - ---[[ - Gets the colour from a hex value -]]-- -local function getColourOf(hex) - if hex == ' ' then - return colours.transparent - end - local value = tonumber(hex, 16) - if not value then return nil end - value = math.pow(2,value) - return value -end - ---[[ - Saves the current artboard in .skch format -]]-- -function SaveSKCH() - local layers = {} - for i, l in ipairs(Current.Artboard.Layers) do - local pixels = SaveNFT(i) - local layer = { - Name = l.Name, - Pixels = pixels, - BackgroundColour = l.BackgroundColour, - Visible = l.Visible, - Index = l.Index, - } - table.insert(layers, layer) - end - return layers -end - ---[[ - Saves the current artboard in .nft format -]]-- -function SaveNFT(layer) - layer = layer or 1 - local lines = {} - local width = Current.Artboard.Width - local height = Current.Artboard.Height - for y = 1, height do - local line = '' - local currentBackgroundColour = nil - local currentTextColour = nil - for x = 1, width do - local pixel = Current.Artboard.Layers[layer].Pixels[x][y] - if pixel.BackgroundColour ~= currentBackgroundColour then - line = line..string.char(30)..getHexOf(pixel.BackgroundColour) - currentBackgroundColour = pixel.BackgroundColour - end - if pixel.TextColour ~= currentTextColour then - line = line..string.char(31)..getHexOf(pixel.TextColour) - currentTextColour = pixel.TextColour - end - line = line .. pixel.Character - end - table.insert(lines, line) - end - return lines -end - ---[[ - Saves the current artboard in .nfp format -]]-- -function SaveNFP() - local lines = {} - local width = Current.Artboard.Width - local height = Current.Artboard.Height - for y = 1, height do - local line = '' - for x = 1, width do - line = line .. getHexOf(Current.Artboard.Layers[1].Pixels[x][y].BackgroundColour) - end - table.insert(lines, line) - end - return lines -end - ---[[ - Reads a .nfp file from the given path -]]-- -function ReadNFP(path) - local pixels = {} - local _fs = fs - if OneOS then - _fs = OneOS.FS - end - local file = _fs.open(path, 'r') - local line = file.readLine() - local y = 1 - while line do - for x = 1, #line do - if not pixels[x] then - pixels[x] = {} - end - pixels[x][y] = {BackgroundColour = getColourOf(line:sub(x,x))} - end - y = y + 1 - line = file.readLine() - end - file.close() - return {{Pixels = pixels}} -end - ---[[ - Reads a .nft file from the given path -]]-- -function ReadNFT(path) - local _fs = fs - if OneOS then - _fs = OneOS.FS - end - local file = _fs.open(path, 'r') - local line = file.readLine() - local lines = {} - while line do - table.insert(lines, line) - line = file.readLine() - end - file.close() - return {{Pixels = ParseNFT(lines)}} -end - ---[[ - Converts the lines of an .nft document to readble pixel data -]]-- -function ParseNFT(lines) - local pixels = {} - for y, line in ipairs(lines) do - local bgNext, fgNext = false, false - local currBG, currFG = nil,nil - local writePosition = 1 - for x = 1, #line do - if not pixels[writePosition] then - pixels[writePosition] = {} - end - - local nextChar = string.sub(line, x, x) - if nextChar:byte() == 30 then - bgNext = true - elseif nextChar:byte() == 31 then - fgNext = true - elseif bgNext then - currBG = getColourOf(nextChar) - if currBG == nil then - currBG = colours.transparent - end - bgNext = false - elseif fgNext then - currFG = getColourOf(nextChar) - fgNext = false - else - if nextChar ~= " " and currFG == nil then - currFG = colours.white - end - pixels[writePosition][y] = {BackgroundColour = currBG, TextColour = currFG, Character = nextChar} - writePosition = writePosition + 1 - end - end - end - return pixels -end - ---[[ - Read a .skch file from the given path -]]-- -function ReadSKCH(path) - local _fs = fs - if OneOS then - _fs = OneOS.FS - end - local file = _fs.open(path, 'r') - local _layers = textutils.unserialize(file.readAll()) - file.close() - local layers = {} - - for i, l in ipairs(_layers) do - local layer = { - Name = l.Name, - Pixels = ParseNFT(l.Pixels), - BackgroundColour = l.BackgroundColour, - Visible = l.Visible, - Index = l.Index, - } - table.insert(layers, layer) - end - return layers -end - ---[[ - Start the program after all functions and tables are loaded -]]-- -if term.isColor and term.isColor() then - Initialise(...) -else - print('Sorry, but Sketch only works on Advanced (gold) Computers') -end diff --git a/MetroOS/README.md b/MetroOS/README.md deleted file mode 100644 index b27d30f..0000000 --- a/MetroOS/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# CarbonOS -Welcome to Carbon! This OS will have tons of features, a GUI, and so much more! -But keep in mind, the OS is still in beta and might have crashes and bugs. -Please report all issues on the Issues tab! -To test it out type pastebin run 5BsVEnu3 -This OS does not auto-update yet but it will auto-update soon! - -[Visit Page](http://carbonos.tk/) - -**THIS OS IS DISCONTINUED AND WILL NO LONGER BE RECEVING ANY UPDATES** diff --git a/MetroOS/System/.version b/MetroOS/System/.version deleted file mode 100644 index 752d822..0000000 --- a/MetroOS/System/.version +++ /dev/null @@ -1 +0,0 @@ -1.0.5.3 diff --git a/MetroOS/System/APIs/crasher b/MetroOS/System/APIs/crasher deleted file mode 100644 index 30056fc..0000000 --- a/MetroOS/System/APIs/crasher +++ /dev/null @@ -1,12 +0,0 @@ --- This is a very old crash handler. --- I have kept it here for you to look at it but the new crash handler uses pcall instead of functions -function crash(message) - term.setBackgroundColor(colors.blue) - term.setCursorPos(1,1) - print("Sorry! CarbonOS has Encountered a Serious Error!") - print("Error: ", message) - print() - print("Press any key to reboot.") - os.pullEvent("key") - os.reboot() -end diff --git a/MetroOS/System/APIs/sha256 b/MetroOS/System/APIs/sha256 deleted file mode 100644 index df0a7c5..0000000 --- a/MetroOS/System/APIs/sha256 +++ /dev/null @@ -1,197 +0,0 @@ --- SHA-256 implementation in CC-Lua --- HMAC and PBKDF2 with SHA-256 --- By Anavrins - --- For more help and details, you can PM me on the CC forums, http://www.computercraft.info/forums2/index.php?/user/12870-anavrins/ --- You can use this code in your projects without asking me, as long as credit is given by keeping the first three lines of this paste. - -local mod32 = 2^32 -local sha_hashlen = 32 -local sha_blocksize = 64 - -local band = bit32 and bit32.band or bit.band -local bnot = bit32 and bit32.bnot or bit.bnot -local bxor = bit32 and bit32.bxor or bit.bxor -local blshift = bit32 and bit32.lshift or bit.blshift -local upack = unpack - -local function rrotate(n, b) - local s = n/(2^b) - local f = s%1 - return (s-f) + f*mod32 -end -local function brshift(int, by) -- Thanks bit32 for bad rshift - local s = int / (2^by) - return s - s%1 -end - -local H = { - 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, - 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19, -} - -local K = { - 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, - 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, - 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, - 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, - 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, - 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, - 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, - 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2, -} - -local function counter(incr) - local t1, t2 = 0, 0 - if 0xFFFFFFFF - t1 < incr then - t2 = t2 + 1 - t1 = incr - (0xFFFFFFFF - t1) - 1 - else t1 = t1 + incr - end - return t2, t1 -end - -local function BE_toInt(bs, i) - return blshift((bs[i] or 0), 24) + blshift((bs[i+1] or 0), 16) + blshift((bs[i+2] or 0), 8) + (bs[i+3] or 0) -end - -local function preprocess(data) - local len = #data - local proc = {} - data[#data+1] = 0x80 - while #data%64~=56 do data[#data+1] = 0 end - local blocks = math.ceil(#data/64) - for i = 1, blocks do - proc[i] = {} - for j = 1, 16 do - proc[i][j] = BE_toInt(data, 1+((i-1)*64)+((j-1)*4)) - end - end - proc[blocks][15], proc[blocks][16] = counter(len*8) - return proc -end - -local function digestblock(w, C) - for j = 17, 64 do - local v = w[j-15] - local s0 = bxor(bxor(rrotate(w[j-15], 7), rrotate(w[j-15], 18)), brshift(w[j-15], 3)) - local s1 = bxor(bxor(rrotate(w[j-2], 17), rrotate(w[j-2], 19)), brshift(w[j-2], 10)) - w[j] = (w[j-16] + s0 + w[j-7] + s1)%mod32 - end - local a, b, c, d, e, f, g, h = upack(C) - for j = 1, 64 do - local S1 = bxor(bxor(rrotate(e, 6), rrotate(e, 11)), rrotate(e, 25)) - local ch = bxor(band(e, f), band(bnot(e), g)) - local temp1 = (h + S1 + ch + K[j] + w[j])%mod32 - local S0 = bxor(bxor(rrotate(a, 2), rrotate(a, 13)), rrotate(a, 22)) - local maj = bxor(bxor(band(a, b), band(a, c)), band(b, c)) - local temp2 = (S0 + maj)%mod32 - h, g, f, e, d, c, b, a = g, f, e, (d+temp1)%mod32, c, b, a, (temp1+temp2)%mod32 - end - C[1] = (C[1] + a)%mod32 - C[2] = (C[2] + b)%mod32 - C[3] = (C[3] + c)%mod32 - C[4] = (C[4] + d)%mod32 - C[5] = (C[5] + e)%mod32 - C[6] = (C[6] + f)%mod32 - C[7] = (C[7] + g)%mod32 - C[8] = (C[8] + h)%mod32 - return C -end - -local mt = { - __tostring = function(a) return string.char(unpack(a)) end, - __index = { - toHex = function(self, s) return ("%02x"):rep(#self):format(unpack(self)) end, - isEqual = function(self, t) - if type(t) ~= "table" then return false end - if #self ~= #t then return false end - local ret = 0 - for i = 1, #self do - ret = bit32.bor(ret, bxor(self[i], t[i])) - end - return ret == 0 - end - } -} - -local function toBytes(t, n) - local b = {} - for i = 1, n do - b[(i-1)*4+1] = band(brshift(band(t[i], 0xFF000000), 24), 0xFF) - b[(i-1)*4+2] = band(brshift(band(t[i], 0xFF0000), 16), 0xFF) - b[(i-1)*4+3] = band(brshift(band(t[i], 0xFF00), 8), 0xFF) - b[(i-1)*4+4] = band(t[i], 0xFF) - end - return setmetatable(b, mt) -end - -function digest(data) - data = data or "" - data = type(data) == "string" and {data:byte(1,-1)} or data - - data = preprocess(data) - local C = {upack(H)} - for i = 1, #data do C = digestblock(data[i], C) end - return toBytes(C, 8) -end - -function hmac(data, key) - local data = type(data) == "table" and {upack(data)} or {tostring(data):byte(1,-1)} - local key = type(key) == "table" and {upack(key)} or {tostring(key):byte(1,-1)} - - local blocksize = sha_blocksize - - key = #key > blocksize and digest(key) or key - - local ipad = {} - local opad = {} - local padded_key = {} - - for i = 1, blocksize do - ipad[i] = bxor(0x36, key[i] or 0) - opad[i] = bxor(0x5C, key[i] or 0) - end - - for i = 1, #data do - ipad[blocksize+i] = data[i] - end - - ipad = digest(ipad) - - for i = 1, blocksize do - padded_key[i] = opad[i] - padded_key[blocksize+i] = ipad[i] - end - - return digest(padded_key) -end - -function pbkdf2(pass, salt, iter, dklen) - local out = {} - local hashlen = sha_hashlen - local block = 1 - dklen = dklen or 32 - - while dklen > 0 do - local ikey = {} - local isalt = type(salt) == "table" and {upack(salt)} or {tostring(salt):byte(1,-1)} - local clen = dklen > hashlen and hashlen or dklen - - isalt[#isalt+1] = band(brshift(band(block, 0xFF000000), 24), 0xFF) - isalt[#isalt+1] = band(brshift(band(block, 0xFF0000), 16), 0xFF) - isalt[#isalt+1] = band(brshift(band(block, 0xFF00), 8), 0xFF) - isalt[#isalt+1] = band(block, 0xFF) - - for j = 1, iter do - isalt = hmac(isalt, pass) - for k = 1, clen do ikey[k] = bxor(isalt[k], ikey[k] or 0) end - if j % 200 == 0 then os.queueEvent("PBKDF2", j) coroutine.yield("PBKDF2") end - end - dklen = dklen - clen - block = block+1 - for k = 1, clen do out[#out+1] = ikey[k] end - end - - return setmetatable(out, mt) -end \ No newline at end of file diff --git a/MetroOS/System/Images/boot b/MetroOS/System/Images/boot deleted file mode 100644 index 54f741a..0000000 --- a/MetroOS/System/Images/boot +++ /dev/null @@ -1,12 +0,0 @@ -7777777777777777777777777777777777 -77777777777777777777777777777777778 -77788877888778887788877788778887778 -77877778777878778787787877878778778 -77877778777878778787787877878778778 -77877778777878778788877877878778778 -77877778888878887787787877878778778 -77877778777878778787787877878778778 -77788878777878778788877788778778778 -77777777777777777777777777777777778 -77777777777777777777777777777777778 -8888888888888888888888888888888888 diff --git a/MetroOS/System/Images/desktop b/MetroOS/System/Images/desktop deleted file mode 100644 index 352e581..0000000 --- a/MetroOS/System/Images/desktop +++ /dev/null @@ -1,18 +0,0 @@ -ccccccccccccccccccccccccccccccccccccccccccccccccc -ccccccccccccccccccccccccccccccccccccccccccccccccc -ccccccccccccccccccccccccccccccccccccccccccccccccc -ccccccccccccccccccccccccccccccccccccccccccccccccc -ccccccccccccccccccccccccccccccccccccccccccccccccc -ccccccccccccccccccccccccccccccccccccccccccccccccc -ccccccccccccccccccccccccccccccccccccccccccccccccc -ccccccccccccccccccccccccccccccccccccccccccccccccc -ccccccccccccccccccccccccccccccccccccccccccccccccc -ccccccccccccccccccccccccccccccccccccccccccccccccc -ccccccccccccccccccccccccccccccccccccccccccccccccc -ccccccccccccccccccccccccccccccccccccccccccccccccc -ccccccccccccccccccccccccccccccccccccccccccccccccc -ccccccccccccccccccccccccccccccccccccccccccccccccc -ccccccccccccccccccccccccccccccccccccccccccccccccc -ccccccccccccccccccccccccccccccccccccccccccccccccc -ccccccccccccccccccccccccccccccccccccccccccccccccc -ccccccccccccccccccccccccccccccccccccccccccccccccc diff --git a/MetroOS/System/autoupdater b/MetroOS/System/autoupdater deleted file mode 100644 index db7b76a..0000000 --- a/MetroOS/System/autoupdater +++ /dev/null @@ -1,87 +0,0 @@ -local nextFile = "/os" -local files = { - [1] = { - "/System/Images/boot", - "https://raw.githubusercontent.com/Carbon-OS/CarbonOS/master/System/Images/boot" - }, - [2] = { - "startup", - "https://raw.githubusercontent.com/Carbon-OS/CarbonOS/master/startup" - }, - [3] = { - "/Programs/LuaIDE/program", - "https://raw.githubusercontent.com/Carbon-OS/CarbonOS/master/Programs/LuaIDE/program" - }, - [4] = { - "/Programs/Sketch/program", - "https://raw.githubusercontent.com/Carbon-OS/CarbonOS/master/Programs/Sketch/program" - }, - [5] = { - "/Desktop/LuaIDE", - "https://raw.githubusercontent.com/Carbon-OS/CarbonOS/master/Desktop/LuaIDE" - }, - [6] = { - "/Desktop/Sketch", - "https://raw.githubusercontent.com/Carbon-OS/CarbonOS/master/Desktop/Sketch" - }, - [7] = { - "/System/Images/desktop", - "https://raw.githubusercontent.com/Carbon-OS/CarbonOS/master/System/Images/desktop" - }, - [8] = { - "/System/settings", - "https://raw.githubusercontent.com/Carbon-OS/CarbonOS/master/System/settings" - }, - [9] = { - "/System/autoupdater", - "https://raw.githubusercontent.com/Carbon-OS/CarbonOS/master/System/autoupdater" - }, - [10] = { - "/System/.version", - "https://raw.githubusercontent.com/Carbon-OS/CarbonOS/master/System/.version" - }, - [11] = { - "/os", - "https://raw.githubusercontent.com/Carbon-OS/CarbonOS/master/os" - }, - [12] = { - "/System/APIs/crasher", - "https://raw.githubusercontent.com/Carbon-OS/CarbonOS/master/System/APIs/crasher" - }, - [13] = { - "/System/APIs/sha256", - "https://raw.githubusercontent.com/Carbon-OS/CarbonOS/master/System/APIs/sha256" - } -} - - - -remoteVersion = http.get("https://raw.githubusercontent.com/Carbon-OS/CarbonOS/master/System/.version") - -local localVersion = fs.open("System/.version", "r") -local rVersion = remoteVersion.readAll() -local lVersion = localVersion.readAll() - -localVersion.close() - -if rVersion ~= lVersion then - print("Downloading Update...") - print("Your Verison: ", lVersion) - print("New Version: ", rVersion) - for k, v in pairs(files) do - local currentFile = fs.open(v[1], "w") - - local remoteFile = http.get(v[2]) - - if remoteFile ~= nil then - currentFile.write(remoteFile.readAll()) - end - - currentFile.close() - - remoteFile.close() - end -end - -local next = nextFile -shell.run(next) diff --git a/MetroOS/System/latestversion b/MetroOS/System/latestversion deleted file mode 100644 index 0cfbf08..0000000 --- a/MetroOS/System/latestversion +++ /dev/null @@ -1 +0,0 @@ -2 diff --git a/MetroOS/System/settings b/MetroOS/System/settings deleted file mode 100644 index 74cae84..0000000 --- a/MetroOS/System/settings +++ /dev/null @@ -1,9 +0,0 @@ -term.setBackgroundColor(colors.orange) -term.clear() -term.setBackgroundColor(colors.gray) -term.setCursorPos(10,1) -print("Settings") -term.setCursorPos(3,5) -print("Security") -term.setCursorPos(3,6) -print("Storage") diff --git a/MetroOS/os b/MetroOS/os deleted file mode 100644 index 05ec92f..0000000 --- a/MetroOS/os +++ /dev/null @@ -1,39 +0,0 @@ -function BSOD(err) -- BSOD Error Handler - term.setBackgroundColor(colors.blue) - term.clear() - term.setCursorPos(1,1) - print("CarbonOS has Crashed!") - print() - print("Error: ", err) - print() - print("Please report this error at https://github.com/Carbon-OS/CarbonOS/issues!") -end - -local function init() - -- The main OS code - local bootImg = paintutils.loadImage("/System/Images/boot") - local desktopImg = paintutils.loadImage("/System/Images/desktop") - term.clear() - paintutils.drawImage(bootImg, 1,1) - local w, h = term.getSize() - local text = "Discontinued" - local nw = w - text - term.setTextColor(colors.white) - term.setCursorPos(nw, y) - write("Discontinued") - os.sleep(2) - term.setBackgroundColor(colors.brown) - term.clear() - paintutils.drawImage(desktopImg, 1,1) --Displays the desktop - local w, h = term.getSize() - term.setBackgroundColor(colors.green) - term.setTextColor(colors.white) - term.setCursorPos(1, h) - term.write("Start ") --Displays the start button - local event, x, y, button = os.pullEvent("mouse_click") --Recognizes mouse clicks -end - -local ok, err = pcall(init) --Crash Handler -if err then - BSOD(err) -end diff --git a/MetroOS/setup b/MetroOS/setup deleted file mode 100644 index 49ecaa1..0000000 --- a/MetroOS/setup +++ /dev/null @@ -1,101 +0,0 @@ -local files = { - [1] = { - "/System/Images/boot", - "https://raw.githubusercontent.com/Carbon-OS/CarbonOS/master/System/Images/boot" - }, - [2] = { - "startup", - "https://raw.githubusercontent.com/Carbon-OS/CarbonOS/master/startup" - }, - [3] = { - "/Programs/LuaIDE/program", - "https://raw.githubusercontent.com/Carbon-OS/CarbonOS/master/Programs/LuaIDE/program" - }, - [4] = { - "/Programs/Sketch/program", - "https://raw.githubusercontent.com/Carbon-OS/CarbonOS/master/Programs/Sketch/program" - }, - [5] = { - "/Desktop/LuaIDE", - "https://raw.githubusercontent.com/Carbon-OS/CarbonOS/master/Desktop/LuaIDE" - }, - [6] = { - "/Desktop/Sketch", - "https://raw.githubusercontent.com/Carbon-OS/CarbonOS/master/Desktop/Sketch" - }, - [7] = { - "/System/Images/desktop", - "https://raw.githubusercontent.com/Carbon-OS/CarbonOS/master/System/Images/desktop" - }, - [8] = { - "/System/settings", - "https://raw.githubusercontent.com/Carbon-OS/CarbonOS/master/System/settings" - }, - [9] = { - "/System/autoupdater", - "https://raw.githubusercontent.com/Carbon-OS/CarbonOS/master/System/autoupdater" - }, - [10] = { - "/System/.version", - "https://raw.githubusercontent.com/Carbon-OS/CarbonOS/master/System/.version" - }, - [11] = { - "/os", - "https://raw.githubusercontent.com/Carbon-OS/CarbonOS/master/os" - }, - [12] = { - "/System/APIs/crasher", - "https://raw.githubusercontent.com/Carbon-OS/CarbonOS/master/System/APIs/crasher" - }, - [13] = { - "/System/APIs/sha256", - "https://raw.githubusercontent.com/Carbon-OS/CarbonOS/master/System/APIs/sha256" - } -} -if term.isColor() == false then - print("Sorry, But you need an Advanced PC to run this OS!") -else -term.setBackgroundColor(colors.gray) -term.clear() -term.setCursorPos(16,8) -print("Welcome to Carbon!") -term.setCursorPos(11,10) -print("A Fast, Simple, and Secure OS") -term.setCursorPos(21,16) -term.setBackgroundColor(colors.lightGray) -print("Install") -while true do -local event, side, x, y = os.pullEvent("mouse_click") -if x >= 21 and x < 28 and y == 16 then - term.clear() - shell.run("mkdir", "System") - term.setCursorPos(1,1) - textutils.slowPrint("Installing Carbon...") - local req - local code - local file - for k,v in pairs(files) do - print("Downloading ", v[2], "...") - req = http.get(v[2]) - if req ~= nil then - code = req.readAll() - req.close() - else - print("Failed!") - end - - file = fs.open(v[1], "w") - file.write(code) - file.close() -end -file = fs.open("/System/.firstuse", "w") -file.write("true") -file.close() -print("Done!") -print("Rebooting...") -os.sleep(1) -os.reboot() - -end -end -end diff --git a/MetroOS/startup b/MetroOS/startup deleted file mode 100644 index 8ac95a0..0000000 --- a/MetroOS/startup +++ /dev/null @@ -1 +0,0 @@ -shell.run("/System/autoupdater") From 7b72bd580609221561b3a9edecab7eb148a1bada Mon Sep 17 00:00:00 2001 From: rservices Date: Sat, 25 Apr 2020 05:16:45 -0600 Subject: [PATCH 053/125] Update programVersions --- programVersions | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/programVersions b/programVersions index 297e674..3650371 100644 --- a/programVersions +++ b/programVersions @@ -17,9 +17,9 @@ ["Author"]="Outraged Security .INC", ["Package"]="Outraged Security .INC", }, - ["MineOS"]={ - ["GitURL"]="https://raw.githubusercontent.com/rservices/darkprograms/darkprograms/mineos/setup", - ["Version"]=2.93, + ["MetroOS"]={ + ["GitURL"]="https://raw.githubusercontent.com/rservices/darkprograms/darkprograms/MetroOS/MetroOS", + ["Version"]=2.94, ["Type"]="program", ["Name"]="Outraged Security .INC Secure OS Downloader", ["Description"]="Downloads A Secure OS Program from Outraged Security .INC Software Centre", From 1336a770799069bf1041bb6fc4ff9d5fdcb254a5 Mon Sep 17 00:00:00 2001 From: rservices Date: Sat, 25 Apr 2020 05:56:06 -0600 Subject: [PATCH 054/125] updated Metro OS --- MetroOS/MetroOS | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/MetroOS/MetroOS b/MetroOS/MetroOS index 9255426..309e071 100644 --- a/MetroOS/MetroOS +++ b/MetroOS/MetroOS @@ -83,7 +83,7 @@ end\ local locSalt = GenerateSalt()\ \ local lock = {\ -\9name = \"MetroOSLock\",\ +\9name = \"Metro Secure OS Login\",\ \9authorized = \"MetroOS - Computer unlocked\",\ \9authorized2 = os.version(),\ \9author = \"OutragedMetro\",\ @@ -93,10 +93,10 @@ local framerate = .05\ local config = {}\ \ local loadFrame = application.view:addChild( UIContainer( 0, 0, application.view.width, application.view.height ) )\ -loadFrame.colour = colours.grey\ +loadFrame.colour = colors.blue\ loadFrame.transitionTime = 1\ local note = loadFrame:addChild( UIText( 0, -math.floor( application.view.height / 3.5 ), 20, 5, \"\\n@acThis program\\ncannot be @teterminated\" ) )\ -note.colour = colours.lightGrey\ +note.colour = colors.lime\ local blackFrame = application.view:addChild( UIContainer( 0, -application.view.height, application.view.width, application.view.height ) )\ blackFrame.colour = colours.black\ blackFrame.transitionTime = .75\ @@ -137,7 +137,7 @@ local function checkConfig()\ \9\9inputPass = loadFrame:addChild( UITextInput( 0, math.floor( application.view.height / 2 ), application.view.width / 2 ) )\ \9\9inputPass.transitionTime = .5\ \9\9inputPass.focussed = true\ -\9\9inputPass.colour = colours.lightGrey\ +\9\9inputPass.colour = colors.lime\ \9\9inputPass.mask = \"#\"\ \9\9inputPass.animatedX = math.floor( application.view.width / 2 - inputPass.width / 2 )\ \9\9function inputPass:onEnter()\ @@ -184,10 +184,10 @@ local function checkConfig()\ \9\9\9\9\9\9local redstoneside = loadFrame:addChild( UITabs( 0, math.floor( application.view.height / 2 ) - 2, math.floor( application.view.width / 2.25 ), { \"None\", \"Top\", \"Bottom\", \"Right\", \"Left\", \"Front\", \"Back\" } ) )\ \9\9\9\9\9\9redstoneside:select(1)\ \9\9\9\9\9\9redstoneside.transitionTime = .2\ -\9\9\9\9\9\9redstoneside.colour = colours.grey\ +\9\9\9\9\9\9redstoneside.colour = colors.blue\ \9\9\9\9\9\9redstoneside.textColour = colours.white\ \9\9\9\9\9\9local continue = loadFrame:addChild( UIButton( 0, math.floor( application.view.height / 2 ) + 3, math.floor( application.view.width / 3 ), 3, \"Continue\" ) )\ -\9\9\9\9\9\9continue.colour = colours.lightGrey\ +\9\9\9\9\9\9continue.colour = colors.lime\ \9\9\9\9\9\9continue.animatedX = math.floor( application.view.width / 2 - continue.width / 2 )\ \9\9\9\9\9\9redstoneside.animatedX = math.floor( application.view.width / 2 - redstoneside.width / 2 )\ \9\9\9\9\9\9doorSettings.animatedX = math.floor( application.view.width / 2 - doorSettings.width / 2 )\ @@ -231,7 +231,7 @@ function main()\ \9inputTextPass.animatedX = math.floor( application.view.width/2 - inputTextPass.width/2 )\ \9local inputFieldPass = loadFrame:addChild( UITextInput( 0, math.floor( application.view.height / 2 ), application.view.width/2 ) )\ \9inputFieldPass.transitionTime = .25\ -\9inputFieldPass.colour = colours.lightGrey\ +\9inputFieldPass.colour = colors.lime\ \9inputFieldPass.mask = \"#\"\ \9inputFieldPass.focussed = true\ \9inputFieldPass.animatedX = math.floor( application.view.width/2 - inputFieldPass.width/2 )\ @@ -259,7 +259,7 @@ function main()\ \9\9\9\9\9inputTextPass:transitionOutLeft()\ \9\9\9\9\9inputFieldPass:transitionOutBottom()\ \9\9\9\9\9Timer.queue( .5, function()\ -\9\9\9\9\9\9loadFrame.colour = colours.lightGrey\ +\9\9\9\9\9\9loadFrame.colour = colors.lime\ \9\9\9\9\9\9loadFrame:transitionOutBottom()\ \9\9\9\9\9\9blackFrame:transitionInTop()\ \9\9\9\9\9\9Timer.queue( .8, function() \ From 8acbca5bb46cf2952390cf5dd69172ee5d4c672e Mon Sep 17 00:00:00 2001 From: rservices Date: Sat, 25 Apr 2020 21:16:09 -0600 Subject: [PATCH 055/125] Games Update! --- games/Autorun/Autorun | 129 +++++ games/BrickBreaker/BrickBreaker | 133 +++++ games/CookieClicker/CookieClicker | 638 ++++++++++++++++++++++++ games/LoadingSimulator/LoadingSimulator | 129 +++++ games/MinePowered10/Minepowered | 266 ++++++++++ games/Pong/pong | 85 ++++ 6 files changed, 1380 insertions(+) create mode 100644 games/Autorun/Autorun create mode 100644 games/BrickBreaker/BrickBreaker create mode 100644 games/CookieClicker/CookieClicker create mode 100644 games/LoadingSimulator/LoadingSimulator create mode 100644 games/MinePowered10/Minepowered create mode 100644 games/Pong/pong diff --git a/games/Autorun/Autorun b/games/Autorun/Autorun new file mode 100644 index 0000000..fcc0e72 --- /dev/null +++ b/games/Autorun/Autorun @@ -0,0 +1,129 @@ +--[[ +Loading Simulator +Copyright (c) 2020 OutragedMetro + + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +]]-- + +local old = os.pullEvent +os.pullEvent = os.pullEventRaw + +local splash = { + "Loading files, maybe...", + "Why is this taking so long?!", + "Windows is loading files...", + "sleep(1)", + "Loading 42PB of data. Please wait.", + "Closing handles...", + "Counting stars in the sky...", + "Not believing my eyes...", + "u wnt.. sum loading?", + "Mining etherum...", + "Sending files to NSA...", + "Distributing your credit card information...", + "Suing everyone...", + "handle:flushDownToilet()",--stolen from KRapFile :P + "Waiting for Half-Life 3...", + "Hacking NSA", + "Sending NSA data to.. NSA? I guess? Sure, why not.", + "() { :;};", + "Executing \"sudo rm -rf --no-preserve-root /*\"", + "Are you done yet? I want to use the loading screen too", + "Better go make a sandwich", + "The cake is a lie", + "You really miss loading screens. Don't you?", + "Press CTRL+T. I know you are tired aren't you?", + "Rahph was here", + "Rahph, stop messing with my programs.", + "Don't press the big red button", +} + +local col +if term.isColor() then + col = { + bg = colors.white, + toload = colors.gray, + loaded = colors.green, + text = colors.lightGray, + } +else + col = { + bg = colors.white, + toload = colors.gray, + loaded = colors.lightGray, + text = colors.lightGray, + } +end + +term.setBackgroundColor(col.bg) +term.clear() +term.setCursorPos(1,1) +local w,h = term.getSize() + +local function writeC(txt) + _,y = term.getCursorPos() + term.setCursorPos(math.ceil(w/2)-math.ceil(#txt/2),y) + write(txt) +end +local tottim = 0 +local dead = false +parallel.waitForAny(function() + while true do + for i = 0,3 do + term.setCursorPos(1,7) + term.setTextColor(col.text) + term.setBackgroundColor(col.bg) + term.clearLine() + writeC("Loading") + write(string.rep(".",i)) + sleep(0.5) + end + end +end,function() + paintutils.drawLine(3,math.ceil(h/2),w-2,math.ceil(h/2),col.toload) + for i = 0,w-5 do + paintutils.drawPixel(i+3,math.ceil(h/2),col.loaded) + local tim = math.random(1,100)/10 + sleep(tim) + end +end,function() + while true do + sleep(0.1) + tottim = tottim+0.1 + end +end,function() + while true do + local choice = splash[math.random(1,#splash)] + term.setCursorPos(1,math.ceil(h/2)+2) + term.setBackgroundColor(col.bg) + term.setTextColor(col.text) + term.clearLine() + writeC(choice) + sleep(5) + end +end,function() + while true do + local ev = os.pullEventRaw("terminate") + if ev == "terminate" then + dead = true + break + end + end +end) +os.pullEvent = old +term.setBackgroundColor(colors.black) +term.setCursorPos(1,1) +term.setTextColor(colors.white) +term.clear() +if dead then + print("You gave up at "..tottim.." seconds of loading!") +else + print("You survived "..tottim.." seconds of loading!") +end +print("") +print("Created by Ale32bit") \ No newline at end of file diff --git a/games/BrickBreaker/BrickBreaker b/games/BrickBreaker/BrickBreaker new file mode 100644 index 0000000..e5afb41 --- /dev/null +++ b/games/BrickBreaker/BrickBreaker @@ -0,0 +1,133 @@ +local w,h = term.getSize() +local pos = (w/2)-3 +local score = 0 +local fscore = 0 +local bricks = {} +local ballx = (w/2)+0.5 +local bally = h-8 +local ballvx = 1 +local ballvy = 1 +local lose = false +for i=1,3 do + for j=1,w do + local obj = {} + obj["x"] = j + obj["y"] = i+1 + obj["broken"] = false + bricks[#bricks+1] = obj + end +end +term.setBackgroundColor(colors.black) +term.clear() +function keyPress() + while true do + event, key = os.pullEvent("key") + if key == keys.right then + pos = pos+1 + end + if key == keys.left then + pos = pos-1 + end + if key == keys.r then + pos = (w/2)-3 + score = 0 + fscore = 0 + bricks = {} + ballx = (w/2)+0.5 + bally = h-8 + ballvx = 1 + ballvy = 1 + lose = false + for i=1,3 do + for j=1,w do + local obj = {} + obj["x"] = j + obj["y"] = i+1 + obj["broken"] = false + bricks[#bricks+1] = obj + end + end + end + draw() + end +end +function draw() + term.setBackgroundColor(colors.black) + term.clear() + term.setBackgroundColor(colors.white) + for i=0,6 do + term.setCursorPos(pos+i, h-1) + term.write(" ") + end + term.setBackgroundColor(colors.lightGray) + for i=1,w do + term.setCursorPos(i, 1) + term.write(" ") + end + term.setTextColor(colors.white) + term.setCursorPos(1,1) + term.write(score) + for i=1,#bricks do + term.setBackgroundColor(colors.magenta) + term.setCursorPos(bricks[i]["x"], bricks[i]["y"]) + if bricks[i]["broken"] == false then + term.write(" ") + end + end + term.setBackgroundColor(colors.white) + term.setTextColor(colors.black) + term.setCursorPos(ballx, bally) + term.write(" ") + if lose == true then + term.setBackgroundColor(colors.black) + term.setTextColor(colors.white) + term.clear() + fscore = score + centertext("Final Score: "..fscore, h/2) + centertext("Press R to restart", (h/2)+1) + end +end +function ballmove() + while true do + ballx = ballx + ballvx + bally = bally + ballvy + if ballx > pos and ballx < pos+6 and bally > h-3 and bally < h-1 then + bounce(1) + end + if bally < 2 then + bounce(1) + end + if ballx > w-1 then + bounce(2) + end + if ballx < 1 then + bounce(2) + end + if bally > h then + lose = true + end + for i=1,#bricks do + if bricks[i]["x"] == ballx and bricks[i]["y"] == bally and bricks[i]["broken"] == false then + bounce(1) + bricks[i]["broken"] = true + score = score+1 + end + end + draw() + sleep(0.14) + end +end +function bounce(dir) + if dir == 1 then + ballvy = -ballvy + end + if dir == 2 then + ballvx = -ballvx + end +end +function centertext(text, height) + term.setCursorPos((w/2)-(string.len(text)/2), height) + term.write(text) +end +draw() +parallel.waitForAll(keyPress, ballmove) \ No newline at end of file diff --git a/games/CookieClicker/CookieClicker b/games/CookieClicker/CookieClicker new file mode 100644 index 0000000..28f002f --- /dev/null +++ b/games/CookieClicker/CookieClicker @@ -0,0 +1,638 @@ +local w, h = term.getSize() -- First getting the size of the computer screen display +-- A computer term will be 51 x 19 +-- A pocket computer will be 26 x 20 +menustate = "cookie" +terminate = false +cookies = 0 +cps = 0 + +function printCentered(sText) + local x, y = term.getCursorPos() + x = math.max(math.floor((w / 2) - (#sText / 2)), 0) + term.setCursorPos(x, y) + print(sText) +end + +local function drawMenuColour() -- creates the colours of the menu + paintutils.drawFilledBox(0, 0, w, h, colours.grey) -- Bottom Grey + paintutils.drawFilledBox(0, 0, w, 2, colours.orange) -- top 2 Cyan +end + +local function drawTitle() + term.setCursorPos(0,1) + term.setTextColour(colours.black) + term.setBackgroundColour(colours.orange) + printCentered("COOKIE CLICKERS - V1") --Title + print(string.rep("=", w)) + term.setBackgroundColour(colours.lightGrey) +end + +local function drawHeadings() -- creates the game statistic displays + if pocket then + if menustate == "cookie" then + term.setBackgroundColour(colours.grey) + term.setTextColour(colours.lightGrey) + paintutils.drawLine(2,4,27,4, colours.grey) + term.setCursorPos(2, 4) + print("Cookies:", cookies) + paintutils.drawLine(2,5,27,5, colours.grey) + term.setCursorPos(2,5) + print("CPS:", cps) + end + else + term.setBackgroundColour(colours.grey) + term.setTextColour(colours.lightGrey) + paintutils.drawLine(2,4,27,4, colours.grey) + term.setCursorPos(2, 4) + print("Cookies:", cookies) + paintutils.drawLine(2,5,27,5, colours.grey) + term.setCursorPos(2,5) + print("CPS:", cps) + end +end + +local function drawCookie() + if pocket then + if menustate == "cookie" then + paintutils.drawFilledBox(4,7,13,13, colours.orange) + paintutils.drawPixel(4,7, colours.grey) + paintutils.drawPixel(13,7, colours.grey) + paintutils.drawPixel(4,13, colours.grey) + paintutils.drawPixel(13,13, colours.grey) + paintutils.drawPixel(5,9, colours.black) + paintutils.drawPixel(7,12, colours.black) -- W,H,Colour + paintutils.drawPixel(10,10, colours.black) + paintutils.drawPixel(8,8, colours.black) + paintutils.drawPixel(11,12, colours.black) + paintutils.drawPixel(12,9, colours.black) + end + else + paintutils.drawFilledBox(4,7,13,13, colours.orange) + paintutils.drawPixel(4,7, colours.grey) + paintutils.drawPixel(13,7, colours.grey) + paintutils.drawPixel(4,13, colours.grey) + paintutils.drawPixel(13,13, colours.grey) + paintutils.drawPixel(5,9, colours.black) + paintutils.drawPixel(7,12, colours.black) -- W,H,Colour + paintutils.drawPixel(10,10, colours.black) + paintutils.drawPixel(8,8, colours.black) + paintutils.drawPixel(11,12, colours.black) + paintutils.drawPixel(12,9, colours.black) + end +end + +local function drawExit() + term.setBackgroundColour(colours.red) + term.setTextColour(colours.white) + term.setCursorPos(2, h-1) + print("EXIT") +end + +local function drawMenuSwap() + term.setBackgroundColour(colours.blue) + term.setTextColor(colours.white) + term.setCursorPos(w-4, h-1) + if menustate == "cookie" then + print("SHOP") + else + print("BACK") + end +end + +local function exitPrompt() + if pocket then + --making fancy boxes + paintutils.drawFilledBox(2,6,25,14, colours.black) + paintutils.drawFilledBox(2,6,25,7, colours.orange) + --Making a fancy title + term.setBackgroundColour(colours.orange) + term.setTextColour(colours.black) + term.setCursorPos(9,6) + print("EXIT MENU") + term.setCursorPos(2,7) + print(string.rep("=", 24)) + --making fancy text + term.setBackgroundColour(colours.black) + term.setTextColour(colours.white) + term.setCursorPos(3, 9) + print("Are you sure you want") + term.setCursorPos(9,10) + print("to exit?") + --Making fancy Yes/No boxes + term.setBackgroundColour(colours.green) + term.setCursorPos(9, 12) + print("YES") + term.setBackgroundColour(colours.red) + term.setCursorPos(15, 12) + print("NO ") + -- using the exit GUI + while true do + local event, button, x, y = os.pullEventRaw("mouse_click") -- It enables you to select with the mouse + if (event == "mouse_click" and x >= 1 and 25 >= x and y >= 6 and 14 >= y) then -- if you click in the window zone + if (event == "mouse_click" and x >= 9 and 11 >= x and y == 12) then -- and if you also click on the 'YES' + term.setTextColour(colours.yellow) + term.setBackgroundColour(colours.black) + term.setCursorPos(9, 12) + textutils.slowPrint(" Goodbye!") + sleep(1) + return true + elseif (x >= 15 and 18 >= x and y == 12) then -- and if you also click on the 'NO' + return false + end + elseif button == 28 then -- If you hit enter + term.setTextColour(colours.yellow) + term.setBackgroundColour(colours.black) + term.setCursorPos(26, 12) + textutils.slowPrint(" Goodbye!") + sleep(1) + return true + else + return false + end + end + else + --making fancy boxes + paintutils.drawFilledBox(18,6,42,14, colours.black) + paintutils.drawFilledBox(18,6,42,7, colours.orange) + --Making a fancy title + term.setBackgroundColour(colours.orange) + term.setTextColour(colours.black) + term.setCursorPos(26,6) + print("EXIT MENU") + term.setCursorPos(18,7) + print(string.rep("=", 25)) + --making fancy text + term.setBackgroundColour(colours.black) + term.setTextColour(colours.white) + term.setCursorPos(20, 9) + print("Are you sure you want") + term.setCursorPos(26,10) + print("to exit?") + --Making fancy Yes/No boxes + term.setBackgroundColour(colours.green) + term.setCursorPos(26, 12) + print("YES") + term.setBackgroundColour(colours.red) + term.setCursorPos(32, 12) + print("NO ") + -- using the exit GUI + while true do + local event, button, x, y = os.pullEventRaw("mouse_click") -- It enables you to select with the mouse + if (event == "mouse_click" and x >= 18 and 42 >= x and y >= 6 and 14 >= y) then -- if you click in the window zone + if (event == "mouse_click" and x >= 26 and 28 >= x and y == 12) then -- and if you also click on the 'YES' + term.setTextColour(colours.yellow) + term.setBackgroundColour(colours.black) + term.setCursorPos(26, 12) + textutils.slowPrint(" Goodbye!") + sleep(1) + return true + elseif (x >= 32 and 35 >= x and y == 12) then -- and if you also click on the 'NO' + return false + end + elseif button == 28 then -- If you hit enter + term.setTextColour(colours.yellow) + term.setBackgroundColour(colours.black) + term.setCursorPos(26, 12) + textutils.slowPrint(" Goodbye!") + sleep(1) + return true + else + return false + end + end + end +end + +--The upgrade menu panel + +pickaxeAmount = 0 +pickaxePrice = 25 +villagerAmount = 0 +villagerPrice = 75 +farmAmount = 0 +farmPrice = 300 +mineAmount = 0 +minePrice = 1000 +factoryAmount = 0 +factoryPrice = 7500 +portalAmount = 0 +portalPrice = 25000 + +local function drawUpgrades() + if pocket then -- checks to see if it's a pocket computer + drawMenuSwap() + if menustate == "upgrades" then + --PICKAXE Upgrade menu + if cookies >= pickaxePrice then -- If you have enough money, it's green + paintutils.drawFilledBox(w-24, 4, w-14, 7, colours.green) + term.setTextColour(colours.black) + term.setBackgroundColour(colours.green) + else -- If you don't have enough money, it's red + paintutils.drawFilledBox(w-24, 4, w-14, 7, colours.red) + term.setTextColour(colours.white) + term.setBackgroundColour(colours.red) + end + -- Writing the text + term.setCursorPos(w-22,4) + print("Pickaxe") + term.setCursorPos(w-22,5) + print("0.3 CPS") + term.setCursorPos(w-22,6) + print("$",pickaxePrice) + term.setCursorPos(w-22,7) + print("Own: ",pickaxeAmount) + + --VILLAGER upgrade menu + if cookies >= villagerPrice then -- If you have enough money, it's green + paintutils.drawFilledBox(w-11, 4, w-1, 7, colours.green) + term.setTextColour(colours.black) + term.setBackgroundColour(colours.green) + else -- If you don't have enough money, it's red + paintutils.drawFilledBox(w-11, 4, w-1, 7, colours.red) + term.setTextColour(colours.white) + term.setBackgroundColour(colours.red) + end + --Writing the text + term.setCursorPos(w-10,4) + print("Villager") + term.setCursorPos(w-10,5) + print("1 CPS") + term.setCursorPos(w-10,6) + print("$",villagerPrice) + term.setCursorPos(w-10,7) + print("Own: ",villagerAmount) + + -- FARM upgrade menu + if cookies >= farmPrice then -- If you have enough money, it's green + paintutils.drawFilledBox(w-24, 9, w-14, 12, colours.green) + term.setTextColour(colours.black) + term.setBackgroundColour(colours.green) + else -- If you don't have enough money, it's red + paintutils.drawFilledBox(w-24, 9, w-14, 12, colours.red) + term.setTextColour(colours.white) + term.setBackgroundColour(colours.red) + end + --Writing the text + term.setCursorPos(w-22,9) + print("Farm") + term.setCursorPos(w-22,10) + print("3 CPS") + term.setCursorPos(w-22,11) + print("$",farmPrice) + term.setCursorPos(w-22,12) + print("Own: ",farmAmount) + + --Mine Upgrade Menu + if cookies >= minePrice then -- If you have enough money, it's green + paintutils.drawFilledBox(w-11, 9, w-1, 12, colours.green) + term.setTextColour(colours.black) + term.setBackgroundColour(colours.green) + else -- If you don't have enough money, it's red + paintutils.drawFilledBox(w-11, 9, w-1, 12, colours.red) + term.setTextColour(colours.white) + term.setBackgroundColour(colours.red) + end + --Writing the text + term.setCursorPos(w-10,9) + print("Mine") + term.setCursorPos(w-10,10) + print("5 CPS") + term.setCursorPos(w-10,11) + print("$",minePrice) + term.setCursorPos(w-10,12) + print("Own: ",mineAmount) + + --Factory Upgrade Menu + if cookies >= factoryPrice then -- If you have enough money, it's green + paintutils.drawFilledBox(w-24, 14, w-14, 17, colours.green) + term.setTextColour(colours.black) + term.setBackgroundColour(colours.green) + else -- If you don't have enough money, it's red + paintutils.drawFilledBox(w-24, 14, w-14, 17, colours.red) + term.setTextColour(colours.white) + term.setBackgroundColour(colours.red) + end + --Writing the text + term.setCursorPos(w-22,14) + print("Factory") + term.setCursorPos(w-22,15) + print("15 CPS") + term.setCursorPos(w-22,16) + print("$",factoryPrice) + term.setCursorPos(w-22,17) + print("Own: ",factoryAmount) + + --Portal Upgrade Menu + if cookies >= portalPrice then -- If you have enough money, it's green + paintutils.drawFilledBox(w-11, 14, w-1, 17, colours.green) + term.setTextColour(colours.black) + term.setBackgroundColour(colours.green) + else -- If you don't have enough money, it's red + paintutils.drawFilledBox(w-11, 14, w-1, 17, colours.red) + term.setTextColour(colours.white) + term.setBackgroundColour(colours.red) + end + --Writing the text + term.setCursorPos(w-10,14) + print("Portal") + term.setCursorPos(w-10,15) + print("50 CPS") + term.setCursorPos(w-10,16) + print("$",portalPrice) + term.setCursorPos(w-10,17) + print("Own: ",portalAmount) + end + else -- if it's an ordinary computer + --PICKAXE Upgrade menu + if cookies >= pickaxePrice then -- If you have enough money, it's green + paintutils.drawFilledBox(w-23, 4, w-13, 7, colours.green) + term.setTextColour(colours.black) + term.setBackgroundColour(colours.green) + else -- If you don't have enough money, it's red + paintutils.drawFilledBox(w-23, 4, w-13, 7, colours.red) + term.setTextColour(colours.white) + term.setBackgroundColour(colours.red) + end + -- Writing the text + term.setCursorPos(w-22,4) + print("Pickaxe") + term.setCursorPos(w-22,5) + print("0.3 CPS") + term.setCursorPos(w-22,6) + print("$",pickaxePrice) + term.setCursorPos(w-22,7) + print("Own: ",pickaxeAmount) + + --VILLAGER upgrade menu + if cookies >= villagerPrice then -- If you have enough money, it's green + paintutils.drawFilledBox(w-11, 4, w-1, 7, colours.green) + term.setTextColour(colours.black) + term.setBackgroundColour(colours.green) + else -- If you don't have enough money, it's red + paintutils.drawFilledBox(w-11, 4, w-1, 7, colours.red) + term.setTextColour(colours.white) + term.setBackgroundColour(colours.red) + end + --Writing the text + term.setCursorPos(w-10,4) + print("Villager") + term.setCursorPos(w-10,5) + print("1 CPS") + term.setCursorPos(w-10,6) + print("$",villagerPrice) + term.setCursorPos(w-10,7) + print("Own: ",villagerAmount) + + -- FARM upgrade menu + if cookies >= farmPrice then -- If you have enough money, it's green + paintutils.drawFilledBox(w-23, 9, w-13, 12, colours.green) + term.setTextColour(colours.black) + term.setBackgroundColour(colours.green) + else -- If you don't have enough money, it's red + paintutils.drawFilledBox(w-23, 9, w-13, 12, colours.red) + term.setTextColour(colours.white) + term.setBackgroundColour(colours.red) + end + --Writing the text + term.setCursorPos(w-22,9) + print("Farm") + term.setCursorPos(w-22,10) + print("3 CPS") + term.setCursorPos(w-22,11) + print("$",farmPrice) + term.setCursorPos(w-22,12) + print("Own: ",farmAmount) + + --Mine Upgrade Menu + if cookies >= minePrice then -- If you have enough money, it's green + paintutils.drawFilledBox(w-11, 9, w-1, 12, colours.green) + term.setTextColour(colours.black) + term.setBackgroundColour(colours.green) + else -- If you don't have enough money, it's red + paintutils.drawFilledBox(w-11, 9, w-1, 12, colours.red) + term.setTextColour(colours.white) + term.setBackgroundColour(colours.red) + end + --Writing the text + term.setCursorPos(w-10,9) + print("Mine") + term.setCursorPos(w-10,10) + print("5 CPS") + term.setCursorPos(w-10,11) + print("$",minePrice) + term.setCursorPos(w-10,12) + print("Own: ",mineAmount) + + --Factory Upgrade Menu + if cookies >= factoryPrice then -- If you have enough money, it's green + paintutils.drawFilledBox(w-23, 14, w-13, 17, colours.green) + term.setTextColour(colours.black) + term.setBackgroundColour(colours.green) + else -- If you don't have enough money, it's red + paintutils.drawFilledBox(w-23, 14, w-13, 17, colours.red) + term.setTextColour(colours.white) + term.setBackgroundColour(colours.red) + end + --Writing the text + term.setCursorPos(w-22,14) + print("Factory") + term.setCursorPos(w-22,15) + print("15 CPS") + term.setCursorPos(w-22,16) + print("$",factoryPrice) + term.setCursorPos(w-22,17) + print("Own: ",factoryAmount) + + --Portal Upgrade Menu + if cookies >= portalPrice then -- If you have enough money, it's green + paintutils.drawFilledBox(w-11, 14, w-1, 17, colours.green) + term.setTextColour(colours.black) + term.setBackgroundColour(colours.green) + else -- If you don't have enough money, it's red + paintutils.drawFilledBox(w-11, 14, w-1, 17, colours.red) + term.setTextColour(colours.white) + term.setBackgroundColour(colours.red) + end + --Writing the text + term.setCursorPos(w-10,14) + print("Portal") + term.setCursorPos(w-10,15) + print("50 CPS") + term.setCursorPos(w-10,16) + print("$",portalPrice) + term.setCursorPos(w-10,17) + print("Own: ",portalAmount) + end +end + +-- Functions to run the game + +local function runGame() + while true do -- The continuous loop of the game + drawTitle() + drawCookie() + drawUpgrades() + drawExit() + drawHeadings() -- draws in all the data + local event, button, x, y = os.pullEventRaw() -- if there is a mouse click + if pocket then + if menustate == "cookie" then -- if you're on the cookie page + if (event == "mouse_click" and x >= 4 and 13 >= x and y >= 7 and 13 >= y) then -- and if that mouse click is on the cookie + cookies = cookies + 1 -- you get more cookies + end + if (event == "mouse_click" and w-1 >= x and x >= w-4 and y == h-1) then -- If you hit SHOP / BACK + menustate = "upgrades" + drawMenuColour() + end + elseif menustate == "upgrades" then -- if you're on the menu page + if (event == "mouse_click" and x >= w-24 and w-14 >= x and y >= 4 and 7 >= y) then -- PICKAXE CLICK + if cookies >= pickaxePrice then + cookies = cookies - pickaxePrice + pickaxeAmount = pickaxeAmount + 1 + end + end + if (event == "mouse_click" and x >= w-11 and w-1 >= x and y >= 4 and 7 >= y) then -- VILLAGER CLICK + if cookies >= villagerPrice then + cookies = cookies - villagerPrice + villagerAmount = villagerAmount + 1 + end + end + if (event == "mouse_click" and x >= w-23 and w-13 >= x and y >= 9 and 12 >= y) then -- FARM CLICK + if cookies >= villagerPrice then + cookies = cookies - farmPrice + farmAmount = farmAmount + 1 + end + end + if (event == "mouse_click" and x >= w-11 and w-1 >= x and y >= 9 and 12 >= y) then -- MINE CLICK + if cookies >= minePrice then + cookies = cookies - minePrice + mineAmount = mineAmount + 1 + end + end + if (event == "mouse_click" and x >= w-23 and w-13 >= x and y >= 14 and 17 >= y) then -- FACTORY CLICK + if cookies >= factoryPrice then + cookies = cookies - factoryPrice + factoryAmount = factoryAmount + 1 + end + end + if (event == "mouse_click" and x >= w-11 and w-1 >= x and y >= 14 and 17 >= y) then -- PORTAL CLICK + if cookies >= portalPrice then + cookies = cookies - portalPrice + portalAmount = portalAmount + 1 + end + end + if (event == "mouse_click" and w-1 >= x and x >= w-4 and y == h-1) then -- If you hit SHOP / BACK + menustate = "cookie" + drawMenuColour() + end + end + if (event == "mouse_click" and 5 >= x and x >= 2 and y == h-1) then -- If you hit EXIT + return + end + if event == "terminate" then + break + end + else + if (event == "mouse_click" and x >= 4 and 13 >= x and y >= 7 and 13 >= y) then -- and if that mouse click is on the cookie + cookies = cookies + 1 -- you get more cookies + end + if (event == "mouse_click" and 5 >= x and x >= 2 and y == h-1) then -- If you hit EXIT + return + end + if (event == "mouse_click" and x >= w-23 and w-13 >= x and y >= 4 and 7 >= y) then -- PICKAXE CLICK + if cookies >= pickaxePrice then + cookies = cookies - pickaxePrice + pickaxeAmount = pickaxeAmount + 1 + end + end + if (event == "mouse_click" and x >= w-11 and w-1 >= x and y >= 4 and 7 >= y) then -- VILLAGER CLICK + if cookies >= villagerPrice then + cookies = cookies - villagerPrice + villagerAmount = villagerAmount + 1 + end + end + if (event == "mouse_click" and x >= w-23 and w-13 >= x and y >= 9 and 12 >= y) then -- FARM CLICK + if cookies >= villagerPrice then + cookies = cookies - farmPrice + farmAmount = farmAmount + 1 + end + end + if (event == "mouse_click" and x >= w-11 and w-1 >= x and y >= 9 and 12 >= y) then -- MINE CLICK + if cookies >= minePrice then + cookies = cookies - minePrice + mineAmount = mineAmount + 1 + end + end + if (event == "mouse_click" and x >= w-23 and w-13 >= x and y >= 14 and 17 >= y) then -- FACTORY CLICK + if cookies >= factoryPrice then + cookies = cookies - factoryPrice + factoryAmount = factoryAmount + 1 + end + end + if (event == "mouse_click" and x >= w-11 and w-1 >= x and y >= 14 and 17 >= y) then -- PORTAL CLICK + if cookies >= portalPrice then + cookies = cookies - portalPrice + portalAmount = portalAmount + 1 + end + end + if event == "terminate" then + break + end + end + cps = (pickaxeAmount*0.3) + (villagerAmount*1) + (farmAmount*3) + (mineAmount*5) + (factoryAmount*15) + (portalAmount*50) + pickaxePrice = 25 + (pickaxeAmount*5) + villagerPrice = 75 + (villagerAmount*20) + farmPrice = 300 + (farmAmount*75) + minePrice = 1000 + (mineAmount*350) + factoryPrice = 7500 + (factoryAmount*1200) + portalPrice = 25000 + (portalAmount*10000) + drawUpgrades() + end +end + +local function runUpgrades() + while true do -- continuously adding cookies every amount of seconds for the different runUpgrades + sleep(1) + cps = (pickaxeAmount*0.3) + (villagerAmount*1) + (farmAmount*3) + (mineAmount*5) + (factoryAmount*15) + (portalAmount*50) + cookies = cookies + cps + drawHeadings() -- draws in all the data + drawUpgrades() + end +end + +-- Running the game +if not turtle then + drawMenuColour() + drawTitle() + drawExit() + drawCookie() + drawUpgrades() + + while true do + parallel.waitForAny(runGame, runUpgrades) + if exitPrompt() then + break + else + drawMenuColour() + drawTitle() + drawExit() + drawCookie() + end + end + + --Clears the screen and leaves goodbye splash + term.setCursorPos(1, 1) + term.setBackgroundColour(colours.black) + term.setTextColour(colours.orange) + term.clear() + print("Programmed by KyStyle,") + print("Thanks for playing!") + term.setTextColour(colours.white) +else + term.setCursorPos(1,1) + term.clear() + term.setTextColour(colours.red) + print("This program won't run on a turtle!") + term.setTextColour(colours.white) + print("Please use a Computer or Pocket Computer to play.") +end \ No newline at end of file diff --git a/games/LoadingSimulator/LoadingSimulator b/games/LoadingSimulator/LoadingSimulator new file mode 100644 index 0000000..fcc0e72 --- /dev/null +++ b/games/LoadingSimulator/LoadingSimulator @@ -0,0 +1,129 @@ +--[[ +Loading Simulator +Copyright (c) 2020 OutragedMetro + + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +]]-- + +local old = os.pullEvent +os.pullEvent = os.pullEventRaw + +local splash = { + "Loading files, maybe...", + "Why is this taking so long?!", + "Windows is loading files...", + "sleep(1)", + "Loading 42PB of data. Please wait.", + "Closing handles...", + "Counting stars in the sky...", + "Not believing my eyes...", + "u wnt.. sum loading?", + "Mining etherum...", + "Sending files to NSA...", + "Distributing your credit card information...", + "Suing everyone...", + "handle:flushDownToilet()",--stolen from KRapFile :P + "Waiting for Half-Life 3...", + "Hacking NSA", + "Sending NSA data to.. NSA? I guess? Sure, why not.", + "() { :;};", + "Executing \"sudo rm -rf --no-preserve-root /*\"", + "Are you done yet? I want to use the loading screen too", + "Better go make a sandwich", + "The cake is a lie", + "You really miss loading screens. Don't you?", + "Press CTRL+T. I know you are tired aren't you?", + "Rahph was here", + "Rahph, stop messing with my programs.", + "Don't press the big red button", +} + +local col +if term.isColor() then + col = { + bg = colors.white, + toload = colors.gray, + loaded = colors.green, + text = colors.lightGray, + } +else + col = { + bg = colors.white, + toload = colors.gray, + loaded = colors.lightGray, + text = colors.lightGray, + } +end + +term.setBackgroundColor(col.bg) +term.clear() +term.setCursorPos(1,1) +local w,h = term.getSize() + +local function writeC(txt) + _,y = term.getCursorPos() + term.setCursorPos(math.ceil(w/2)-math.ceil(#txt/2),y) + write(txt) +end +local tottim = 0 +local dead = false +parallel.waitForAny(function() + while true do + for i = 0,3 do + term.setCursorPos(1,7) + term.setTextColor(col.text) + term.setBackgroundColor(col.bg) + term.clearLine() + writeC("Loading") + write(string.rep(".",i)) + sleep(0.5) + end + end +end,function() + paintutils.drawLine(3,math.ceil(h/2),w-2,math.ceil(h/2),col.toload) + for i = 0,w-5 do + paintutils.drawPixel(i+3,math.ceil(h/2),col.loaded) + local tim = math.random(1,100)/10 + sleep(tim) + end +end,function() + while true do + sleep(0.1) + tottim = tottim+0.1 + end +end,function() + while true do + local choice = splash[math.random(1,#splash)] + term.setCursorPos(1,math.ceil(h/2)+2) + term.setBackgroundColor(col.bg) + term.setTextColor(col.text) + term.clearLine() + writeC(choice) + sleep(5) + end +end,function() + while true do + local ev = os.pullEventRaw("terminate") + if ev == "terminate" then + dead = true + break + end + end +end) +os.pullEvent = old +term.setBackgroundColor(colors.black) +term.setCursorPos(1,1) +term.setTextColor(colors.white) +term.clear() +if dead then + print("You gave up at "..tottim.." seconds of loading!") +else + print("You survived "..tottim.." seconds of loading!") +end +print("") +print("Created by Ale32bit") \ No newline at end of file diff --git a/games/MinePowered10/Minepowered b/games/MinePowered10/Minepowered new file mode 100644 index 0000000..47266c2 --- /dev/null +++ b/games/MinePowered10/Minepowered @@ -0,0 +1,266 @@ + + +function split(pString, pPattern) + local Table = {} -- NOTE: use {n = 0} in Lua-5.0 + local fpat = "(.-)" .. pPattern + local last_end = 1 + local s, e, cap = pString:find(fpat, 1) + while s do + if s ~= 1 or cap ~= "" then + table.insert(Table,cap) + end + last_end = e+1 + s, e, cap = pString:find(fpat, last_end) + end + if last_end <= #pString then + cap = pString:sub(last_end) + table.insert(Table, cap) + end + return Table +end + +function install(game) + if not fs.isDir(dir.."strafedata/"..game) then + fs.delete(dir.."strafedata/"..game) + fs.makeDir(dir.."strafedata/"..game) + end + local _d = shell.dir() + local i = split(online[game].install, "|") + term.setTextColor(colors.white) + term.setBackgroundColor(colors.black) + term.setCursorPos(1, 1) + term.clear() + shell.run("cd /"..dir.."strafedata/"..game) + for k,v in pairs(i) do + shell.run(({v:gsub("?", dir.."strafedata/"..game)})[1]) + end + shell.run("cd /".._d) + online[game].banner = online[game].banner:saveString() + local f = fs.open(dir.."strafedata/"..game..".tab", "w") + f.write(textutils.serialize(online[game])) + f.close() + online[game].banner = surface.loadString(online[game].banner) + timer = os.startTimer(0) + updateGames() + updateOnline() +end + +function updateGames() + games = { } + for k,v in pairs(fs.list(dir.."strafedata")) do + if v:sub(#v - 3, #v) == ".tab" then + local name = v:sub(1, #v - 4) + local f = fs.open(dir.."strafedata/"..v, "r") + games[name] = textutils.unserialize(f.readAll()) + games[name]["banner"] = surface.loadString(games[name]["banner"]) + f.close() + end + end +end + +function updateOnline() + http.request("http://pastebin.com/raw.php?i=m9YK1tke") +end + +function updateSize() + width, height = term.getSize() + rows = math.floor((width - 1) / 25) + offset = math.floor((width - rows * 25 + 1) / 2) + surf = surface.create(width, height) +end + +function update() + draw() +end + +function draw() + surf:clear(nil, colors.white, colors.black) + if state == 1 then + drawGames() + elseif state == 2 then + drawOnline() + end + surf:drawLine(1, 1, width, 1, " ", colors.lightGray, colors.black) + if width >= 43 then + surf:drawText(1, 1, "Strafe") + surf:drawText(9, 1, "Installed Games Download Games", nil, colors.blue) + else + surf:drawText(1, 1, "Installed Download", nil, colors.blue) + end + surf:drawPixel(width, 1, "X", colors.red, colors.white) + surf:drawPixel(width, 2, "^") + surf:drawPixel(width, height, "v") + surf:render() +end + +function drawGames() + local i = 0 + for k,v in pairs(games) do + surf:drawSurface((i % rows) * 25 + 1 + offset, math.floor(i / rows) * 6 + 3 - gamesOffset, v.banner) + surf:drawText((i % rows) * 25 + 1 + offset, math.floor(i / rows) * 6 + 7 - gamesOffset, "Play Delete", colors.black, colors.white) + i = i + 1 + end +end + +function drawOnline() + local i = 0 + for k,v in pairs(online) do + surf:drawSurface((i % rows) * 25 + 1 + offset, math.floor(i / rows) * 6 + 3 - onlineOffset, v.banner) + local str = "Download" + if games[k] then + if games[k].version == v.version then + str = "Installed" + else + str = "Update" + end + end + surf:drawText((i % rows) * 25 + 1 + offset, math.floor(i / rows) * 6 + 7 - onlineOffset, str, colors.black, colors.white) + i = i + 1 + end +end + +function onClick(x, y) + if y == 1 then + if x == width then + term.setTextColor(colors.white) + term.setBackgroundColor(colors.black) + term.setCursorPos(1, 1) + term.clear() + running = false + elseif width >= 43 then + if x >= 9 and x <= 23 then + updateGames() + state = 1 + elseif x >= 26 and x <= 39 then + updateOnline() + state = 2 + end + else + if x >= 1 and x <= 9 then + updateGames() + state = 1 + elseif x >= 12 and x <= 19 then + updateOnline() + state = 2 + end + end + elseif x == width and y == 2 then + if state == 1 and gamesOffset > 0 then + gamesOffset = gamesOffset - 6 + elseif onlineOffset > 0 then + onlineOffset = onlineOffset - 6 + end + elseif x == width and y == height then + if state == 1 then + gamesOffset = gamesOffset + 6 + else + onlineOffset = onlineOffset + 6 + end + elseif y > 1 and state == 1 then + local id = math.floor((y + gamesOffset - 2) / 6) * rows + if x - offset + 1 <= rows * 25 then + id = id + math.floor((x - offset) / 25) + end + local xx = (x - offset) % 25 + local yy = (y - 2) % 6 + local i = 0 + for k,v in pairs(games) do + if id == i then + if xx >= 1 and xx <= 4 and yy == 5 then + local d = shell.dir() + shell.run("cd /"..dir.."strafedata/"..k.."/"..v.launchdir) + term.setTextColor(colors.white) + term.setBackgroundColor(colors.black) + term.setCursorPos(1, 1) + term.clear() + shell.run(v.launch) + shell.run("cd /"..d) + timer = os.startTimer(0) + updateSize() + elseif xx >= 6 and xx <= 11 and yy == 5 then + fs.delete(dir.."strafedata/"..k) + fs.delete(dir.."strafedata/"..k..".tab") + updateGames() + end + end + i = i + 1 + end + elseif y > 1 and state == 2 then + local id = math.floor((y + onlineOffset - 2) / 6) * rows + if x - offset + 1 <= rows * 25 then + id = id + math.floor((x - offset) / 25) + end + local xx = (x - offset) % 25 + local yy = (y - 2) % 6 + local i = 0 + for k,v in pairs(online) do + if id == i then + if xx >= 1 and xx <= 8 and yy == 5 and not games[k] then + install(k) + elseif xx >= 1 and xx <= 6 and yy == 5 and games[k] then + if games[k].version ~= v.version then + fs.delete(dir.."strafedata/"..k) + fs.delete(dir.."strafedata/"..k..".tab") + install(k) + end + end + end + i = i + 1 + end + end +end + +dir = fs.getDir(shell.getRunningProgram()).."/" +if not fs.isDir(dir.."strafedata") then + fs.delete(dir.."strafedata") + fs.makeDir(dir.."strafedata") +end +if not fs.exists(dir.."strafedata/surface") or fs.isDir(dir.."strafedata/surface") then + fs.delete(dir.."strafedata/surface") + local d = shell.dir() + shell.run("cd /"..dir.."strafedata") + shell.run("pastebin get J2Y288mW surface") + shell.run("cd /"..d) +end +os.loadAPI(dir.."strafedata/surface") +updateSize() +local _off = math.floor(width / 2) surf:drawText(1, 1, "Made by OutragedMetro") local cp = surface.loadString("_00100010208f208f208f208f208f208f208f208f208f208f208f208f208f208f208f208f208f20ff20ff20ff20ff20ff20ff20ff20ff20ff20ff20ff20ff20ff20ff207f208f20ff20ff200f200f20ff200f200f20ff20ff20ff20ff20ff20ff20ff207f208f20ff200f20ff20ff20ff200f20ff200f20ff20ff20ff20ff20ff20ff207f208f20ff200f20ff20ff20ff200f200f20ff20ff20ff20ff20ff20ff20ff207f208f20ff200f20ff20ff20ff200f20ff20ff20ff20ff20ff20ff20ff20ff207f208f20ff20ff200f200f20ff200f20ff20ff20ff200f200f200f20ff20ff207f208f20ff20ff20ff20ff20ff20ff20ff20ff20ff20ff20ff20ff20ff20ff207f208f207f207f207f207f207f207f207f207f207f207f207f207f207f207f207f20_720_720_720_720_720_720_72077207720_720_720_720_720_720_720_720_720_720_720_720_720_7207720772077207720_720_720_720_720_720_720872087208720872087208720872087208720872087208720872087208720872087208720872087208720872087208720872087208720872087208720872077208720772077207720772077208720772077208720772077208720b72087207720872087208720872087208720872087208720872087208720872087208720772077207720772077207720772077207720772077207720772077207720772077") for i=1,16,1 do local surf2 = surface.create(i, i) surf2:drawSurfaceScaled(1, 1, i, i, cp) surf:fillRect(1, 2, width, height, nil, colors.black) surf:drawSurfaceRotated(_off, 10, i / 2, i / 2, 4 - i / 4, surf2) surf:render() os.sleep(0) end for i=1,4,1 do surf:fillRect(1, 2, width, height, nil, colors.black) surf:drawSurface(_off - 7, 3, cp) surf:drawLine(_off - 7, i * 4, _off + i * 4 - 11, 3, nil, colors.white) surf:drawLine(_off - 7, 1 + i * 4, _off + i * 4 - 10, 3, nil, colors.white) surf:drawLine(_off - 7, 2 + i * 4, _off + i * 4 - 9, 3, nil, colors.white) surf:render() os.sleep(0) end for i=1,4,1 do surf:fillRect(1, 2, width, height, nil, colors.black) surf:drawSurface(_off - 7, 3, cp) surf:drawLine(_off + i * 4 - 11, 18, _off + 8, i * 4, nil, colors.white) surf:drawLine(_off + i * 4 - 10, 18, _off + 8, 1 + i * 4, nil, colors.white) surf:drawLine(_off + i * 4 - 9, 18, _off + 8, 2 + i * 4, nil, colors.white) surf:render() os.sleep(0) end +games = { } +online = { } +updateGames() +state = 1 +gamesOffset = 0 +onlineOffset = 0 +running = true +timer = os.startTimer(0) +while running do + local e = {os.pullEvent()} + if e[1] == "timer" and e[2] == timer then + timer = os.startTimer(0) + update() + elseif e[1] == "mouse_click" then + onClick(e[3], e[4]) + elseif e[1] == "mouse_scroll"then + if e[2] == -1 then + if state == 1 and gamesOffset + e[2] * 6 >= 0 then + gamesOffset = gamesOffset + e[2] * 6 + elseif state == 2 and onlineOffset > 0 then + onlineOffset = onlineOffset + e[2] * 6 + end + else + if state == 1 then + gamesOffset = gamesOffset + e[2] * 6 + else + onlineOffset = onlineOffset + e[2] * 6 + end + end + elseif e[1] == "term_resize" then + updateSize() + elseif e[1] == "http_success" and e[2] == "http://pastebin.com/raw.php?i=m9YK1tke" then + online = textutils.unserialize(e[3].readAll()) + e[3].close() + for k,v in pairs(online) do + v.banner = surface.loadString(v.banner) + end + end +end \ No newline at end of file diff --git a/games/Pong/pong b/games/Pong/pong new file mode 100644 index 0000000..1a30f57 --- /dev/null +++ b/games/Pong/pong @@ -0,0 +1,85 @@ +term.setBackgroundColor(colors.black) +term.clear() +local w,h = term.getSize() +local p1y = (h/2)-2 +local p2y = (h/2)-2 +local score1 = 0 +local score2 = 0 +local ballx = w/2 +local bally = h/2 +local ballvx = -1 +local ballvy = 1 +function draw() + term.setBackgroundColor(colors.black) + term.clear() + term.setBackgroundColor(colors.white) + drawPaddle(2, p1y) + drawPaddle(w-1, p2y) + term.setCursorPos(ballx, bally) + term.write(" ") + term.setTextColor(colors.white) + term.setBackgroundColor(colors.black) + term.setCursorPos(2, 1) + term.write(score1) + term.setCursorPos(w-1, 1) + term.write(score2) +end +function drawPaddle(x, y) + for i=y,y+5 do + term.setCursorPos(x, i) + term.write(" ") + end +end +function keyPress() + while true do + event, key = os.pullEvent("key") + if key == keys.up then + p2y = p2y-1 + end + if key == keys.down then + p2y = p2y+1 + end + if key == keys.w then + p1y = p1y-1 + end + if key == keys.s then + p1y = p1y+1 + end + draw() + end +end +function moveball() + while true do + sleep(0.1) + ballx = ballx + ballvx + bally = bally + ballvy + if bally > h-1 then + ballvy = -ballvy + end + if bally < 1 then + ballvy = -ballvy + end + if ballx > 2 and ballx < 3 and bally > p1y and bally < p1y+5 then + ballvx = -ballvx + end + if ballx > w-1 and ballx < w and bally > p2y and bally < p2y+5 then + ballvx = -ballvx + end + if ballx > w then + score1 = score1+1 + ballx = w/2 + bally = h/2 + ballvx = -1 + ballvy = 1 + end + if ballx < 1 then + score2 = score2+1 + ballx = w/2 + bally = h/2 + ballvx = 1 + ballvy = 1 + end + draw() + end +end +parallel.waitForAll(keyPress, moveball) \ No newline at end of file From 25c7b932c1c6575707bdb331bf939fe2a38abef4 Mon Sep 17 00:00:00 2001 From: rservices Date: Sat, 25 Apr 2020 21:24:47 -0600 Subject: [PATCH 056/125] fixed code issue --- games/Autorun/Autorun | 204 +++++++++++++++++------------------------- 1 file changed, 81 insertions(+), 123 deletions(-) diff --git a/games/Autorun/Autorun b/games/Autorun/Autorun index fcc0e72..ed5a37f 100644 --- a/games/Autorun/Autorun +++ b/games/Autorun/Autorun @@ -1,129 +1,87 @@ ---[[ -Loading Simulator -Copyright (c) 2020 OutragedMetro +x = 1 +y = 2 +vel = 0 +score = 0 +platforms = {} - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - -]]-- - -local old = os.pullEvent -os.pullEvent = os.pullEventRaw - -local splash = { - "Loading files, maybe...", - "Why is this taking so long?!", - "Windows is loading files...", - "sleep(1)", - "Loading 42PB of data. Please wait.", - "Closing handles...", - "Counting stars in the sky...", - "Not believing my eyes...", - "u wnt.. sum loading?", - "Mining etherum...", - "Sending files to NSA...", - "Distributing your credit card information...", - "Suing everyone...", - "handle:flushDownToilet()",--stolen from KRapFile :P - "Waiting for Half-Life 3...", - "Hacking NSA", - "Sending NSA data to.. NSA? I guess? Sure, why not.", - "() { :;};", - "Executing \"sudo rm -rf --no-preserve-root /*\"", - "Are you done yet? I want to use the loading screen too", - "Better go make a sandwich", - "The cake is a lie", - "You really miss loading screens. Don't you?", - "Press CTRL+T. I know you are tired aren't you?", - "Rahph was here", - "Rahph, stop messing with my programs.", - "Don't press the big red button", -} - -local col -if term.isColor() then - col = { - bg = colors.white, - toload = colors.gray, - loaded = colors.green, - text = colors.lightGray, - } -else - col = { - bg = colors.white, - toload = colors.gray, - loaded = colors.lightGray, - text = colors.lightGray, - } -end - -term.setBackgroundColor(col.bg) +term.setBackgroundColor(colors.lightBlue) +term.setTextColor(colors.white) term.clear() -term.setCursorPos(1,1) -local w,h = term.getSize() - -local function writeC(txt) - _,y = term.getCursorPos() - term.setCursorPos(math.ceil(w/2)-math.ceil(#txt/2),y) - write(txt) -end -local tottim = 0 -local dead = false -parallel.waitForAny(function() - while true do - for i = 0,3 do - term.setCursorPos(1,7) - term.setTextColor(col.text) - term.setBackgroundColor(col.bg) - term.clearLine() - writeC("Loading") - write(string.rep(".",i)) - sleep(0.5) - end - end -end,function() - paintutils.drawLine(3,math.ceil(h/2),w-2,math.ceil(h/2),col.toload) - for i = 0,w-5 do - paintutils.drawPixel(i+3,math.ceil(h/2),col.loaded) - local tim = math.random(1,100)/10 - sleep(tim) +function createPlatforms() + pl = 3 + size = 51/pl + height = math.random(1, 19) + for i=0,pl do + height = height+math.random(8)-4 + for j=0,size do + platforms[(size*i)+j] = height end -end,function() - while true do - sleep(0.1) - tottim = tottim+0.1 - end -end,function() - while true do - local choice = splash[math.random(1,#splash)] - term.setCursorPos(1,math.ceil(h/2)+2) - term.setBackgroundColor(col.bg) - term.setTextColor(col.text) - term.clearLine() - writeC(choice) - sleep(5) + platforms[i*size] = -1 + end +end +function drawBuildings() + term.setBackgroundColor(colors.red) + for i=1, #platforms do + if(platforms[i] ~= -1) then + term.setCursorPos(i-1, platforms[i]) + term.write("-") + for j=platforms[i], 19 do + term.setCursorPos(i-1, j) + term.write(" ") end -end,function() - while true do - local ev = os.pullEventRaw("terminate") - if ev == "terminate" then - dead = true - break - end end -end) -os.pullEvent = old -term.setBackgroundColor(colors.black) -term.setCursorPos(1,1) -term.setTextColor(colors.white) -term.clear() -if dead then - print("You gave up at "..tottim.." seconds of loading!") -else - print("You survived "..tottim.." seconds of loading!") + end + term.setBackgroundColor(colors.lightBlue) +end +function restart() + x = 1 + y = 2 + term.setBackgroundColor(colors.lightBlue) + term.clear() + createPlatforms() + drawBuildings() +end +function grounded() + if(x < #platforms) then + if(y >= platforms[x]-1) and (platforms[x] ~= -1) then return true end + end + return false +end +function Char() + sleep(0.2) + term.setCursorPos(x, y) + term.write(" ") + if(grounded()) then x = x+1 end + if(vel > 0) then x = x+1 end + if(vel <= 0) then + y = y+1 + else + y = y-1 + vel = vel-1 + end + if(x < #platforms) then + if(y > platforms[x]-1) and (platforms[x] ~= -1) then y = platforms[x]-1 end + end + if(y > 19) then restart() end + term.setCursorPos(x, y) + term.setBackgroundColor(colors.blue) + term.write(" ") + term.setCursorPos(1, 19) + --term.write("Pos:"..tostring(x).." "..tostring(y)) + term.setBackgroundColor(colors.lightBlue) + +end +function keyPressed() + event, key = os.pullEvent("key") + if(key == keys.space) and (grounded()) then + term.setCursorPos(x,y) + term.write(" ") + vel = 3 + end +end +createPlatforms() +drawBuildings() +while true do + parallel.waitForAny(Char, keyPressed) + sleep(0.1) end -print("") -print("Created by Ale32bit") \ No newline at end of file From d6e5eb57855acc31c1f1552fc9f859bc21a5f88d Mon Sep 17 00:00:00 2001 From: rservices Date: Sat, 25 Apr 2020 21:34:11 -0600 Subject: [PATCH 057/125] Games update! --- programVersions | 56 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/programVersions b/programVersions index 3650371..33dda5d 100644 --- a/programVersions +++ b/programVersions @@ -122,7 +122,61 @@ ["Type"]="program", ["Name"]="Outraged Security .INC Office Word Software", ["Description"]="Downloads A Word writing office Program from Outraged Security .INC Software Centre", - ["Author"]="Oeed 2014 Computercraft Forum!", + ["Author"]="Outraged Security .INC", ["Package"]="Standalone", + }, + ["Autorun"]={ + ["GitURL"]="https://raw.githubusercontent.com/rservices/darkprograms/darkprograms/games/Autorun/Autorun", + ["Version"]=2.16, + ["Type"]="program", + ["Name"]="Autorun 2020 the game", + ["Description"]="Downloads the latest Autorun game from the Autorun series", + ["Author"]="Outraged Security .INC", + ["Package"]="Games", + }, + ["BrickBreaker"]={ + ["GitURL"]="https://raw.githubusercontent.com/rservices/darkprograms/darkprograms/games/BrickBreaker/BrickBreaker", + ["Version"]=2.16, + ["Type"]="program", + ["Name"]="BrickBreaker the game", + ["Description"]="Downloads the latest BrickBreaker", + ["Author"]="Outraged Security .INC", + ["Package"]="Games", + }, + ["CookieClicker"]={ + ["GitURL"]="https://raw.githubusercontent.com/rservices/darkprograms/darkprograms/games/CookieClicker/CookieClicker", + ["Version"]=2.16, + ["Type"]="program", + ["Name"]="CookieClicker the game", + ["Description"]="Downloads the latest Coockie Clicker", + ["Author"]="Outraged Security .INC", + ["Package"]="Games", + }, + ["LoadingSimulator"]={ + ["GitURL"]="https://raw.githubusercontent.com/rservices/darkprograms/darkprograms/games/LoadingSimulator/LoadingSimulator", + ["Version"]=2.16, + ["Type"]="program", + ["Name"]="Loading Simulator the game", + ["Description"]="Downloads the latest Loading Simulator", + ["Author"]="Outraged Security .INC", + ["Package"]="Games", + }, + ["MinePowered10"]={ + ["GitURL"]="https://raw.githubusercontent.com/rservices/darkprograms/darkprograms/games/MinePowered10/Minepowered", + ["Version"]=2.16, + ["Type"]="program", + ["Name"]="Mine Powered 10 the only game launcher 2020", + ["Description"]="Downloads the latest Mine Powered 10", + ["Author"]="Outraged Security .INC", + ["Package"]="Games", + }, + ["Pong"]={ + ["GitURL"]="https://raw.githubusercontent.com/rservices/darkprograms/darkprograms/games/Pong/pong", + ["Version"]=2.16, + ["Type"]="program", + ["Name"]="Pong the game", + ["Description"]="Downloads the latest Pong", + ["Author"]="Outraged Security .INC", + ["Package"]="Games", }, } From 85d75ccaba24a2a4174b3f6006275f8d4cc1554f Mon Sep 17 00:00:00 2001 From: rservices Date: Sat, 25 Apr 2020 22:02:44 -0600 Subject: [PATCH 058/125] CookieClicker --- games/LoadingSimulator/LoadingSimulator | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/games/LoadingSimulator/LoadingSimulator b/games/LoadingSimulator/LoadingSimulator index fcc0e72..0bf1ad5 100644 --- a/games/LoadingSimulator/LoadingSimulator +++ b/games/LoadingSimulator/LoadingSimulator @@ -126,4 +126,4 @@ else print("You survived "..tottim.." seconds of loading!") end print("") -print("Created by Ale32bit") \ No newline at end of file +print("Created by OutragedMetro") \ No newline at end of file From 30824af97980bd20b4fb8b08618858d5b3b3bf97 Mon Sep 17 00:00:00 2001 From: rservices Date: Sat, 25 Apr 2020 22:15:40 -0600 Subject: [PATCH 059/125] fixed code --- games/CookieClicker/CookieClicker | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/games/CookieClicker/CookieClicker b/games/CookieClicker/CookieClicker index 28f002f..ef961fc 100644 --- a/games/CookieClicker/CookieClicker +++ b/games/CookieClicker/CookieClicker @@ -625,7 +625,7 @@ if not turtle then term.setBackgroundColour(colours.black) term.setTextColour(colours.orange) term.clear() - print("Programmed by KyStyle,") + print("Programmed by OutragedMetro,") print("Thanks for playing!") term.setTextColour(colours.white) else From 3cefcaf434ad9edadb2b86551f0b9291cad55d04 Mon Sep 17 00:00:00 2001 From: rservices Date: Sun, 26 Apr 2020 03:59:04 -0600 Subject: [PATCH 060/125] updated office software 2.0 --- notepad/ink | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/notepad/ink b/notepad/ink index 9b1ec70..00278bd 100644 --- a/notepad/ink +++ b/notepad/ink @@ -3115,7 +3115,7 @@ function DoVanillaClose() term.setTextColour(colours.lightGrey) PrintCentered("Word Proccessor for ComputerCraft", (Drawing.Screen.Height/2)) term.setTextColour(colours.white) - PrintCentered("(c) oeed 2014", (Drawing.Screen.Height/2)+3) + PrintCentered("(c) OutragedMetro 2019", (Drawing.Screen.Height/2)+3) term.setCursorPos(1, Drawing.Screen.Height) error('', 0) end From abdb0f58949955f94833808fae5948f8d31db7c4 Mon Sep 17 00:00:00 2001 From: rservices Date: Sun, 26 Apr 2020 04:15:23 -0600 Subject: [PATCH 061/125] fixed code error --- notepad/ink | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/notepad/ink b/notepad/ink index 00278bd..d850fad 100644 --- a/notepad/ink +++ b/notepad/ink @@ -1992,10 +1992,10 @@ end function SplashScreen() local w = colours.white local b = colours.black - local u = colours.blue - local lb = colours.lightBlue + local u = colours.green + local lb = colours.green local splashIcon = {{w,w,w,b,w,w,w,b,w,w,w,},{w,w,w,b,w,w,w,b,w,w,w,},{w,w,w,b,u,u,u,b,w,w,w,},{w,b,b,u,u,u,u,u,b,b,w,},{b,u,u,lb,lb,u,u,u,u,u,b,},{b,u,lb,lb,u,u,u,u,u,u,b,},{b,u,lb,lb,u,u,u,u,u,u,b,},{b,u,u,u,u,u,u,u,u,u,b,},{w,b,b,b,b,b,b,b,b,b,w,}, - ["text"]={{" "," "," "," "," "," "," "," "," "," "," ",},{" "," "," "," "," "," "," "," "," "," "," ",},{" "," "," "," "," "," "," "," "," "," "," ",},{" "," "," "," "," "," "," "," "," "," "," ",},{" "," "," "," "," "," "," "," "," "," "," ",},{" "," "," "," ","I","n","k"," "," "," "," ",},{" "," "," "," "," "," "," "," "," "," "," ",},{" "," ","b","y"," ","o","e","e","d"," "," "},{" "," "," "," "," "," "," "," "," "," "," ",},}, + ["text"]={{" "," "," "," "," "," "," "," "," "," "," ",},{" "," "," "," "," "," "," "," "," "," "," ",},{" "," "," "," "," "," "," "," "," "," "," ",},{" "," "," "," "," "," "," "," "," "," "," ",},{" "," "," "," "," "," "," "," "," "," "," ",},{" "," "," "," ","I","n","k"," "," "," "," ",},{" "," "," "," "," "," "," "," "," "," "," ",},{" "," ","b","y"," ","O","u","t","r","a","g","e","d","M","e","t","r","o"},{" "," "," "," "," "," "," "," "," "," "," ",},}, ["textcol"]={{w,w,w,w,w,w,w,w,w,w,w,},{w,w,w,w,w,w,w,w,w,w,w,},{w,w,w,w,w,w,w,w,w,w,w,},{w,w,w,w,w,w,w,w,w,w,w,},{w,w,w,w,w,w,w,w,w,w,w,},{w,w,w,w,w,w,w,w,w,w,w,},{w,w,w,w,w,w,w,w,w,w,w,},{lb,lb,lb,lb,lb,lb,lb,lb,lb,lb,lb,},{w,w,w,w,w,w,w,w,w,w,w,},},} Drawing.Clear(colours.white) Drawing.DrawImage((Drawing.Screen.Width - 11)/2, (Drawing.Screen.Height - 9)/2, splashIcon, 11, 9) @@ -2434,7 +2434,7 @@ OpenDocumentWindow = { for i, file in ipairs(self.Files) do if i > self.Scroll and i - self.Scroll <= 11 then if file == self.SelectedFile then - Drawing.DrawCharacters(self.X + 1, self.Y + i - self.Scroll, file, colours.white, colours.lightBlue) + Drawing.DrawCharacters(self.X + 1, self.Y + i - self.Scroll, file, colours.white, colours.green) elseif string.find(file, '%.txt') or string.find(file, '%.text') or string.find(file, '%.ink') or fs.isDir(self.CurrentDirectory .. file) then Drawing.DrawCharacters(self.X + 1, self.Y + i - self.Scroll, file, colours.black, colours.white) else @@ -2755,7 +2755,7 @@ SaveDocumentWindow = { for i, file in ipairs(self.Files) do if i > self.Scroll and i - self.Scroll <= 10 then if file == self.SelectedFile then - Drawing.DrawCharacters(self.X + 1, self.Y + i - self.Scroll, file, colours.white, colours.lightBlue) + Drawing.DrawCharacters(self.X + 1, self.Y + i - self.Scroll, file, colours.white, colours.green) elseif fs.isDir(self.CurrentDirectory .. file) then Drawing.DrawCharacters(self.X + 1, self.Y + i - self.Scroll, file, colours.black, colours.white) else From d43e8c3bf21df9623954e2d072b0d8c015c0f11d Mon Sep 17 00:00:00 2001 From: OutragedMetro Date: Wed, 13 Apr 2022 21:18:39 -0600 Subject: [PATCH 062/125] Update chat.lua --- chat.lua | 586 ++++++++++++------------------------------------------- 1 file changed, 126 insertions(+), 460 deletions(-) diff --git a/chat.lua b/chat.lua index 00d3044..f8b17ee 100644 --- a/chat.lua +++ b/chat.lua @@ -1,461 +1,127 @@ ---Args -args = {...} ---[[ - optional arguments - 1: username - 2: channel (default will be 10) - 3: autoupdate (false to disable) -]]-- ---changeable -HistoryNum = 200 --how many lines of history to keep -if args[3] and args[3] == "false" then - autoupdate = false -else - autoupdate = true -- turn on or off auto update -end ---Non changeable! -chat = {} -lineCount = 1 -modifier = 0 -X, Y = term.getSize() -Version = 1.51 -BottomText = "Message: " -KeyCount = 0 -user = "" -Header = "Welcome to darkchat "..Version.."!" -function findPeripheral(Perihp) - for _,s in ipairs(rs.getSides()) do - if peripheral.isPresent(s) and peripheral.getType(s) == Perihp then - return s - end - end - return false -end -function gitUpdate(ProgramName, Filename, ProgramVersion) - if http then - status, getGit = pcall(http.get, "https://raw.githubusercontent.com/rservices/darkprograms/darkprograms/programVersions") - if not status then - return(getGit) - end - local getGit = getGit.readAll() - NVersion = textutils.unserialize(getGit) - if NVersion[ProgramName].Version > ProgramVersion then - getGit = http.get(NVersion[ProgramName].GitURL) - getGit = getGit.readAll() - local file = fs.open(Filename, "w") - file.write(getGit) - file.close() - return true - end - else - return false - end -end -function split(str, pattern) -- Splits string by pattern, returns table - local t = { } - local fpat = "(.-)" .. pattern - local last_end = 1 - local s, e, cap = str:find(fpat, 1) - while s do - if s ~= 1 or cap ~= "" then - table.insert(t,cap) - end - last_end = e+1 - s, e, cap = str:find(fpat, last_end) - end - if last_end <= #str then - cap = str:sub(last_end) - table.insert(t, cap) - end - return t -end -function cWrite(Text, bgcolor, tecolor, solid) - if term.isColor() == true then - term.setBackgroundColor(colors[bgcolor]) - term.setTextColor(colors[tecolor]) - if solid then - term.write(string.rep(" ", #Text)) - else - term.write(Text) - end - else - term.write(Text) - end -end -function bgReset() - if term.isColor() == true then - term.setTextColor(colors.white) - term.setBackgroundColor(colors.black) - end -end -function rSend(message, special, hidden) - local messaget = {} - messaget.user = user - messaget.message = message - messaget.darkchat = true - messaget.id = os.getComputerID() - if special then messaget.special = true end - messaget = textutils.serialize(messaget) - modem.transmit(channelN,channelN,messaget) - if not hidden then - if special then - os.queueEvent("tableInsert", "yellow#* "..user.." "..message) - else - os.queueEvent("tableInsert", "white#"..user..": "..message) - end - end -end -function rReceive() - local darkchat = false - repeat - _,_,C,rC,M,D = os.pullEvent("modem_message") - M = textutils.unserialize(M) - if type(M) == "table" and M.darkchat then - darkchat = true - elseif type(M) == "table" and M.list then - local list = {} - list.user = user - list.reply = true - list = textutils.serialize(list) - modem.transmit(channelN,channelN,list) - elseif type(M) == "table" and M.reply then - os.queueEvent("user",M.user) - end - until darkchat == true - return M -end -function modiferMod(operation, amount) -- if operation is true, number will be added else subtacted - if operation == true then - if lineCount - modifier > 1 then - modifier = modifier + 1 - end - else - if modifier > 0 then - modifier = modifier - 1 - end - end -end -function clears() - for i = 1, Y - 2 do - term.setCursorPos(1,i) - term.write(string.rep(" ", X)) - end -end -function getChat() - draw() - while true do - m = rReceive() - if m.id ~= os.getComputerID() and m.user ~= user and m.message then - if m.special then - os.queueEvent("tableInsert", "yellow#* ".. m.user .. " " .. m.message) - else - os.queueEvent("tableInsert", "white#" .. m.user ..": ".. m.message) - end - end - end -end -function draw() - bgReset() clears() - if #chat > HistoryNum then table.remove(chat, 1) end - if #chat > Y - 2 then - lineCount = ((#chat) - (Y - 3)) - end - for i = lineCount - modifier, #chat - modifier do - chatN = split(chat[i], "#") - term.setCursorPos(1,i - lineCount+1+modifier) - cWrite(chatN[2],"black",chatN[1]) - end - term.setCursorPos(1, Y - 1) - cWrite(string.rep("-", X), "green","green", true) - term.setTextColor(colors.white) - local Text = " " - term.setCursorPos(X/2 - #Text/2, Y - 1) - write(Text) - if modifier > 0 and term.isColor() == true then - paintutils.drawPixel(1, Y - 1, colors.yellow) - paintutils.drawPixel(X, Y - 1, colors.yellow) - end - bgReset() - term.setCursorPos(#BottomText + KeyCount + 1, Y) - return true -end -function sendChat() - while true do - term.setCursorPos(1, Y) - term.write(BottomText) - message = read() - if string.sub(message,1,1) == "/" then - commandS = string.sub(message,2,#message) - commandS = split(commandS, " ") - if comD[commandS[1]] then - comD[commandS[1]].run(commandS) - else - os.queueEvent("tableInsert", "red#Unknown Command") - end - elseif message == "" then - draw() - elseif message then - rSend(message) - draw() - end - end -end -function keyListen() - while true do - Type, KEY = os.pullEvent("key") - if KEY == 28 then - KeyCount = 0 - elseif KEY == 14 then - if KeyCount ~= 0 then - KeyCount = KeyCount - 1 - end - elseif KEY then - if KEY ~= 54 then - KeyCount = KeyCount + 1 - end - end - end -end -function startUp() - term.clear() term.setCursorPos(1,1) - if autoupdate == true then - print("Checking for updates...") - local updateStatus = gitUpdate("chat", shell.getRunningProgram(), Version) - if type(updateStatus) == "string" then - print("Cannot check for updates:") - print(updateStatus) - sleep(1) - elseif updateStatus == true then - print("Downloaded new version, restarting...") - sleep(1.5) - os.reboot() - else - print("You're running the latest version") - sleep(1) - end - end - Side = findPeripheral("modem") - if not Side then - print("no modem.") - return exit - end - modem = peripheral.wrap(Side) - term.clear() term.setCursorPos(1,1) - cWrite(string.rep("-", X), "green","green", true) bgReset() - term.setCursorPos(X/2 - string.len(Header)/2,2) - print(Header) - cWrite(string.rep("-", X), "green","green", true) bgReset() -end -function privateMode() - user = config.user - channelN = config.channel -end -function publicMode() - repeat - write("Nickname: ") - user = read() - if user == "" then - print("\nInvalid Name") - end - until user ~= "" - print("\nDefault channel is 10") - write("Channel: ") - channelN = read() - if not tonumber(channelN) then - channelN = "10" - end -end -function getOnlineList() - while true do - os.pullEvent("grabList") - local com = {} - local Users = "" - com.list = true - com = textutils.serialize(com) - modem.transmit(channelN,channelN,com) - os.startTimer(2) - repeat - local e, u = os.pullEvent() - if e == "user" then - Users = Users..u..", " - end - until e == "timer" - Users = string.sub(Users, 1, #Users - 2) -- remove extra comma - os.queueEvent("tableInsert", "yellow#Users in channel: "..Users) - end -end -function scrollingEventListen() - while true do - local _,direction = os.pullEvent("mouse_scroll") - if direction == -1 then - modiferMod(true, 1) - draw() - else - modiferMod(false, 1) - draw() - end - end -end -function tableEventListen() - while true do - local e, mess = os.pullEvent("tableInsert") - if modifier > 0 and HistoryNum ~= #chat then - modifier = modifier + 1 - end - table.insert(chat, mess) - draw() - end -end -function exitEventListen() - while true do - os.pullEvent("exit") - term.clear() - term.setCursorPos(1,1) - modem.closeAll() - return exit - end -end -function helpGen() - os.queueEvent("tableInsert", "lime#".. string.rep("-", X / 2 - 4) .. " help " .. string.rep("-", X / 2 - 3)) - for i, v in pairs(comD) do - if v.help then - os.queueEvent("tableInsert", "lime#/"..i.." ".. v.help) - end - end - os.queueEvent("tableInsert", "lime#".. string.rep("-", X)) -end -comD = { - ["exit"] = { - run = function() - rSend("has quit.", true) - os.queueEvent("exit") - end, - help = "" - }, - ["quit"] = { - run = function() - rSend("has quit.", true) - os.queueEvent("exit") - end, - help = "" - }, - ["me"] = { - run = function(Words) - local StringRe = "" - for i = 2, #Words do - StringRe = StringRe..Words[i].." " - end - rSend(StringRe, true) - end, - help = "" - }, - ["channel"] = { - run = function(Channels) - local SetChan = Channels[2] - if not tonumber(SetChan) then - os.queueEvent("tableInsert", "red#Error: Must be a number.") - else - rSend("has changed channel.", true) - channelN = tonumber(SetChan) - modem.closeAll() - modem.open(channelN) - rSend("has entered the channel!", true, true) - end - end, - help = "" - }, - ["list"] = { - run = function() - os.queueEvent("grabList") - os.queueEvent("tableInsert", "lime#Gathering list...") - end, - help = "" - }, - ["clear"] = { - run = function() - chat = {} - lineCount = 1 - modifier = 0 - draw() - end, - help = "" - }, - ["help"] = { - run = helpGen, - help = "" - }, - ["nick"] = { - run = function(Nickname) - rSend("is now known as "..Nickname[2], true) - user = Nickname[2] - if config.private == true then - config.user = Nickname[2] - local F = fs.open(".darkChatConf", "w") - local configString = textutils.serialize(config) - F.write(configString) - F.close() - end - draw() - end, - help = "" - }, -} ---[[ -28: enter -14: backspace -]]-- -term.clear() term.setCursorPos(1,1) -if (fs.exists(".darkChatConf") == true) and (#args == 0) then - F = fs.open(".darkChatConf", "r") - Data = F.readAll() - F.close() - config = textutils.unserialize(Data) -elseif #args == 0 then - config = {} - repeat - print("Do you want your username to be stored? (Private Mode)") - write("y / n : ") - Qprivate = read() - until Qprivate == "y" or Qprivate == "n" - if Qprivate == "y" then - config.private = true - write("\nUsername: ") - config.user = read() - repeat - write("Default channel number: ") - Qchannel = read() - until tonumber(Qchannel) - config.channel = tonumber(Qchannel) - else - config.private = false - end - configString = textutils.serialize(config) - F = fs.open(".darkChatConf", "w") - F.write(configString) - F.close() - print("Setup Complete!") - sleep(1) -end -if args[1] then - config = {} - config.private = true - config.user = args[1] - config.channel = 10 -end -if args[2] and tonumber(args[2]) then - config.channel = tonumber(args[2]) -end - -startUp() -if config.private == true then - privateMode() -else - publicMode() -end -channelN = tonumber(channelN) -modem.closeAll() -modem.open(channelN) +os.pullEvent = os.pullEventRaw +--A Small SMS program. +--functions and variables +msg = "" +MHN = 1 +MH = {} +MH[0] = "Loaded SMS" +term.write("Name: ") +name = read() +clear = function() term.clear() -term.setCursorPos(1,1) -rSend("has joined the chat!", true, true) -parallel.waitForAny(getChat, sendChat, keyListen, getOnlineList, scrollingEventListen, tableEventListen, exitEventListen) +x,y = term.getSize() +term.setCursorPos(1,y) +end +clear() +function drawScreen(Msg) +clear() +i = 18 +while i >= 0 do +if MH[i] ~= nil then +print(MH[i]) +end +i = i - 1 +end +term.write(Msg) +end +function send(MSg) +rednet.broadcast(name..": "..MSg) +end +function runTime() +drawScreen("Enter A message") +while 1 do +event, a, b, c = os.pullEvent() +if event == "char" then +msg = msg..a +drawScreen(msg) +end +if event == "key" then +if a == keys.backspace then +msg = "" +drawScreen(msg) +elseif a == keys.enter then +send(msg) +x = MH[0] +MH[0] = msg +y = MH[1] +MH[1] = x +x = MH[2] +MH[2] = y +y = MH[3] +MH[3] = x +x = MH[4] +MH[4] = y +y = MH[5] +MH[5] = x +x = MH[6] +MH[6] = y +y = MH[7] +MH[7] = x +x = MH[8] +MH[8] = y +y = MH[9] +MH[9] = x +x = MH[10] +MH[10] = y +y = MH[11] +MH[11] = x +x = MH[12] +MH[12] = y +y = MH[13] +MH[13] = x +x = MH[14] +MH[14] = y +y = MH[15] +MH[15] = x +x = MH[16] +MH[16] = y +y = MH[17] +MH[17] = x +MH[18] = y +msg = "" +drawScreen(msg) +end +elseif event == "rednet_message" then +x = MH[0] +y = MH[1] +MH[0] = b +MH[1] = x +x = MH[2] +MH[2] = y +y = MH[3] +MH[3] = x +x = MH[4] +MH[4] = y +y = MH[5] +MH[5] = x +x = MH[6] +MH[6] = y +y = MH[7] +MH[7] = x +x = MH[8] +MH[8] = y +y = MH[9] +MH[9] = x +x = MH[10] +MH[10] = y +y = MH[11] +MH[11] = x +x = MH[12] +MH[12] = y +y = MH[13] +MH[13] = x +x = MH[14] +MH[14] = y +y = MH[15] +MH[15] = x +x = MH[16] +MH[16] = y +y = MH[17] +MH[17] = x +MH[18] = y +drawScreen(msg) +end +end +end +--main +runTime() From 21d3dd03951277bfc8e7e5421e1009abe62a22a2 Mon Sep 17 00:00:00 2001 From: OutragedMetro Date: Wed, 13 Apr 2022 21:22:20 -0600 Subject: [PATCH 063/125] Update chat.lua --- chat.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chat.lua b/chat.lua index f8b17ee..c7298f5 100644 --- a/chat.lua +++ b/chat.lua @@ -4,7 +4,7 @@ os.pullEvent = os.pullEventRaw msg = "" MHN = 1 MH = {} -MH[0] = "Loaded SMS" +MH[0] = "Loaded Wireless One Chat" term.write("Name: ") name = read() clear = function() From bad227f1ca3b7c3996305d6cc9621d1253b02eea Mon Sep 17 00:00:00 2001 From: OutragedMetro Date: Fri, 15 Apr 2022 20:00:31 -0600 Subject: [PATCH 064/125] Update secure.lua --- Secure OS/secure.lua | 108 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 85 insertions(+), 23 deletions(-) diff --git a/Secure OS/secure.lua b/Secure OS/secure.lua index 485cf0e..f8190b0 100644 --- a/Secure OS/secure.lua +++ b/Secure OS/secure.lua @@ -1,27 +1,89 @@ -local buffer = window.create(term.current(), 1, 1, term.getSize()) -local oldTerm = term.redirect(buffer) -local succ, mess = pcall(function() --- intended global, so every program can modify the processes -processes = require "apis.processes" -processes:start("autostart") +local branches = { + "master", + "experimental" +} -while true do - -- update - local timer = os.startTimer(0.001) - local event, var1, var2, var3 = os.pullEventRaw() - os.cancelTimer(timer) - buffer.setVisible(false) - processes:update(event, var1, var2, var3) - buffer.setVisible(true) +local function formatFS() + local function mkdir(dir) + if not fs.exists(dir) then fs.makeDir(dir) end + end + if fs.exists("AxiomUI") then + for k, v in pairs(fs.list("AxiomUI")) do + if not fs.exists(v) then + fs.move("AxiomUI/"..v, v) + print("AxiomUI/"..v.." -> "..v) + else + print("AxiomUI/"..v.." -x>") + end + end + fs.delete("AxiomUI") + print("Delete extra files?") + if read() == "y" then + fs.delete("install.lua") + fs.delete("README.md") + end + else + error("formatFS failed") + end end -end) - -term.setBackgroundColor(colors.black) -term.setTextColor(colors.white) -term.clear() -term.setCursorPos(1, 1) -if not succ and mess then - term.setTextColor(colors.orange) - print(mess) +local function wget(url, file) + local data = http.get(url) + data = data.readAll() + local file_handle = fs.open(file,"w") + file_handle.write(data) + file_handle.close() +end +function selector(y,option) + term.setCursorPos(1,y) + for k,v in ipairs(branches) do + if k == option then + write(v.. " <-") + term.setCursorPos(1,y+k) + else + write(v.. " ") + term.setCursorPos(1,y+k) + end + end +end +local version = os.version() +if version == "CraftOS 1.5" then + error("Axiom is not compatible with "..version.."!") +end +print("Axiom UI CE Installer") +print("Select a branch using arrow keys") +local x,y = term.getCursorPos() +if y > 17 then + shell.run("clear") + print("Axiom UI CE Installer") + print("Select a branch using arrow keys") + x,y = term.getCursorPos() +end +selector(y,1) +local user = "nothjarnan" +local branch = 1 +while(true) do + local e,k,h = os.pullEvent( "key" ) + if k == keys.up then + if branch > 1 then + branch = branch - 1 + selector(y,branch) + end + end + if k == keys.down then + if branch < #branches then + branch = branch + 1 + selector(y,branch) + end + end + if k == keys.enter then + branch = branches[branch] + print("Branch selected: "..branch) + print("Starting installation") + break + end end +wget("http://www.pastebin.com/raw/w5zkvysi",".gitget") +shell.run(".gitget "..user.." axiom-opensource "..branch.." AxiomUI") +formatFS() +print("Installation completed!") From 96ed4f3c3ce552451d8aa7ea39364b03aab1af99 Mon Sep 17 00:00:00 2001 From: OutragedMetro Date: Fri, 15 Apr 2022 20:03:13 -0600 Subject: [PATCH 065/125] Update MetroOS --- MetroOS/MetroOS | 432 ++++++++++-------------------------------------- 1 file changed, 84 insertions(+), 348 deletions(-) diff --git a/MetroOS/MetroOS b/MetroOS/MetroOS index 309e071..f8190b0 100644 --- a/MetroOS/MetroOS +++ b/MetroOS/MetroOS @@ -1,353 +1,89 @@ -local files = { - ["init"] = "require \"UIButton\"\ -require \"UIContainer\"\ -require \"UIText\"\ -require \"UILabel\"\ -require \"UITextInput\"\ -require \"UIMenu\"\ -require \"UITabs\"\ -require \"UIFileDialogue\"\ -\ -application.terminateable = false\ -\ -local blocksize = 5\ -local bit32 = 2 ^ 32\ -local prime1 = 8747\ -local prime2 = 2147483647\ -local bit32half = bit32/2\ -local floor = math.floor\ -local function keygen( seed, length ) local count = 1 local keys, garbage = {}, {} for i = 1, length, blocksize do seed = ( seed * ( seed > bit32half and prime2 or prime1 ) + 1 ) % bit32 + count count = count + 1 garbage[i] = math.max( 0, ( seed - 1 ) % 10 - 4 ) keys[i] = seed % 256 end return keys, garbage end\ -local h = {}\ -for i = 0, 15 do h[i] = (\"%X\"):format( i ) end\ -local hl = {}\ -for i = 0, 15 do hl[(\"%X\"):format( i )] = i end\ -local function tohex2( n ) return h[math.floor( n / 16 )] .. h[n % 16] end\ -local function fromhex2( h ) return hl[h:sub( 1, 1 )] * 16 + hl[h:sub( 2, 2 )] end\ -local function numbertochars( n ) local s = tohex2( n % 256 ) for i = 1, 3 do n = math.floor( n / 256 ) s = tohex2( n % 256 ) .. s end return s end\ -local function charstonumber( c ) local n = 0 for i = 1, 4 do n = n * 256 + fromhex2( c:sub( i * 2 - 1 ) ) end return n end\ -local function newgarbage( length ) if length == 0 then return \"\" end return tohex2( math.random( 0, 255 ) ) .. newgarbage( length - 1 ) end\ -local function stringkey( str ) local key = 0 for i = 1, #str do key = key * 256 + str:byte( i ) end return key end\ -local encrypt = {}\ -function encrypt.encrypt( text, key ) key = type( key ) == \"string\" and stringkey( key ) or key text = textutils.serialize( text ) if type( key ) ~= \"number\" then return error( \"expected number/string key, got \" .. type( key ) ) end local keys, garbage = keygen( key, #text ) local cipher = { numbertochars( #text ) } math.randomseed( os.clock() ) for i = 1, #text, blocksize do for n = 0, blocksize - 1 do if i + n <= #text then cipher[#cipher + 1] = tohex2( bit.bxor( text:byte( i + n ), keys[i] ) ) else break end end cipher[#cipher + 1] = newgarbage( garbage[i] ) end return table.concat( cipher ) end\ -function encrypt.decrypt( cipher, key ) key = type( key ) == \"string\" and stringkey( key ) or key if type( cipher ) ~= \"string\" then return error( \"expected string cipher, got \" .. type( cipher ) ) end if type( key ) ~= \"number\" then return error( \"expected number/string key, got \" .. type( key ) ) end local length = charstonumber( cipher:sub( 1, 8 ) ) cipher = cipher:sub( 9 ) local keys, garbage = keygen( key, length ) local text = {} local i = 1 while #cipher > 0 do local block = cipher:sub( 1, math.min( #cipher - garbage[i] * 2, blocksize * 2 ) ) for n = 1, #block, 2 do text[#text + 1] = string.char( bit.bxor( fromhex2( block:sub( n ) ), keys[i] ) ) end cipher = cipher:sub( blocksize * 2 + garbage[i] * 2 + 1 ) i = i + blocksize end return textutils.unserialize( table.concat( text ) ) end\ -local MOD = 2^32\ -local MODM = MOD-1\ -local function memoize(f) local mt = {} local t = setmetatable({}, mt) function mt:__index(k) local v = f(k) t[k] = v return v end return t end\ -local function make_bitop_uncached(t, m) local function bitop(a, b) local res,p = 0,1 while a ~= 0 and b ~= 0 do local am, bm = a % m, b % m res = res + t[am][bm] * p a = (a - am) / m b = (b - bm) / m p = p*m end res = res + (a + b) * p return res end return bitop end\ -local function make_bitop(t) local op1 = make_bitop_uncached(t,2^1) local op2 = memoize(function(a) return memoize(function(b) return op1(a, b) end) end) return make_bitop_uncached(op2, 2 ^ (t.n or 1)) end\ -local bxor1 = make_bitop({[0] = {[0] = 0,[1] = 1}, [1] = {[0] = 1, [1] = 0}, n = 4})\ -local function bxor(a, b, c, ...) local z = nil if b then a = a % MOD b = b % MOD z = bxor1(a, b) if c then z = bxor(z, c, ...) end return z elseif a then return a % MOD else return 0 end end\ -local function band(a, b, c, ...) local z if b then a = a % MOD b = b % MOD z = ((a + b) - bxor1(a,b)) / 2 if c then z = bit32_band(z, c, ...) end return z elseif a then return a % MOD else return MODM end end\ -local function bnot(x) return (-1 - x) % MOD end\ -local function rshift1(a, disp) if disp < 0 then return lshift(a,-disp) end return math.floor(a % 2 ^ 32 / 2 ^ disp) end\ -local function rshift(x, disp) if disp > 31 or disp < -31 then return 0 end return rshift1(x % MOD, disp) end\ -local function lshift(a, disp) if disp < 0 then return rshift(a,-disp) end return (a * 2 ^ disp) % 2 ^ 32 end\ -local function rrotate(x, disp) x = x % MOD disp = disp % 32 local low = band(x, 2 ^ disp - 1) return rshift(x, disp) + lshift(low, 32 - disp) end\ -local k = {0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2, }\ -local function str2hexa(s) return (string.gsub(s, \".\", function(c) return string.format(\"%02x\", string.byte(c)) end)) end\ -local function num2s(l, n) local s = \"\"for i = 1, n do local rem = l % 256 s = string.char(rem) .. s l = (l - rem) / 256 end return s end\ -local function s232num(s, i) local n = 0 for i = i, i + 3 do n = n*256 + string.byte(s, i) end return n end\ -local function preproc(msg, len) local extra = 64 - ((len + 9) % 64) len = num2s(8 * len, 8) msg = msg .. \"\\128\" .. string.rep(\"\\0\", extra) .. len assert(#msg % 64 == 0) return msg end\ -local function initH256(H) H[1] = 0x6a09e667 H[2] = 0xbb67ae85 H[3] = 0x3c6ef372 H[4] = 0xa54ff53a H[5] = 0x510e527f H[6] = 0x9b05688c H[7] = 0x1f83d9ab H[8] = 0x5be0cd19 return H end\ -local function digestblock(msg, i, H) local w = {} for j = 1, 16 do w[j] = s232num(msg, i + (j - 1)*4) end for j = 17, 64 do local v = w[j - 15] local s0 = bxor(rrotate(v, 7), rrotate(v, 18), rshift(v, 3)) v = w[j - 2] w[j] = w[j - 16] + s0 + w[j - 7] + bxor(rrotate(v, 17), rrotate(v, 19), rshift(v, 10)) end local a, b, c, d, e, f, g, h = H[1], H[2], H[3], H[4], H[5], H[6], H[7], H[8] for i = 1, 64 do local s0 = bxor(rrotate(a, 2), rrotate(a, 13), rrotate(a, 22)) local maj = bxor(band(a, b), band(a, c), band(b, c)) local t2 = s0 + maj local s1 = bxor(rrotate(e, 6), rrotate(e, 11), rrotate(e, 25)) local ch = bxor (band(e, f), band(bnot(e), g)) local t1 = h + s1 + ch + k[i] + w[i] h, g, f, e, d, c, b, a = g, f, e, d + t1, c, b, a, t1 + t2 end H[1] = band(H[1] + a) H[2] = band(H[2] + b) H[3] = band(H[3] + c) H[4] = band(H[4] + d) H[5] = band(H[5] + e) H[6] = band(H[6] + f) H[7] = band(H[7] + g) H[8] = band(H[8] + h) end\ -local function sha256(msg) msg = preproc(msg, #msg) local H = initH256({}) for i = 1, #msg, 64 do digestblock(msg, i, H) end return str2hexa(num2s(H[1], 4) .. num2s(H[2], 4) .. num2s(H[3], 4) .. num2s(H[4], 4) .. num2s(H[5], 4) .. num2s(H[6], 4) .. num2s(H[7], 4) .. num2s(H[8], 4)) end\ -local tKeys = {\"a\", \"b\", \"c\", \"d\", \"e\", \"f\", \"g\", \"h\", \"i\", \"j\", \"k\", \"l\", \"m\", \"n\", \"o\", \"p\", \"q\", \"r\", \"s\", \"t\", \"u\", \"v\", \"w\", \"x\", \"y\", \"z\", 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 } --, \"&\", \"%\", \"#\", \"$\", \"!\", \"@\", \"?\", \"=\", \"/\", \"\\\\\", \"(\", \")\", \"[\", \"]\", \"{\", \"}\" }\ -table.size = function( tbl ) local count = 0 for k,v in pairs( tbl ) do count = count + 1 end return count end\ -\ -local function GenerateSalt()\ -\9local Count = 0\ -\9local Salt = \"\"\ -\9local function RandomKey()\ -\9\9local key = false\ -\9\9key = tKeys[ math.random( 1, table.size( tKeys ) ) ]\ -\9\9if type( key ) ~= \"number\" then\ -\9\9\9local x = math.random( 1, 2 )\ -\9\9\9if x == 1 then\ -\9\9\9\9key\9= string.lower( key )\ -\9\9\9elseif x == 2 then\ -\9\9\9\9key = string.upper( key )\ -\9\9\9end\ -\9\9end\ -\9\9return key\ -\9end\ -\9for SaltCount = 1, 32 do\ -\9\9Salt = Salt .. RandomKey()\ -\9\9Count = Count + 1\ -\9\9if Count == 16 then\ -\9\9\9sleep( 0 )\ -\9\9\9Count = 0\ -\9\9end\ -\9end\ -\9return Salt\ -end\ -local locSalt = GenerateSalt()\ -\ -local lock = {\ -\9name = \"Metro Secure OS Login\",\ -\9authorized = \"MetroOS - Computer unlocked\",\ -\9authorized2 = os.version(),\ -\9author = \"OutragedMetro\",\ -\9config = \".MetroOSSecureLock.cfg\",\ -}\ -local framerate = .05\ -local config = {}\ -\ -local loadFrame = application.view:addChild( UIContainer( 0, 0, application.view.width, application.view.height ) )\ -loadFrame.colour = colors.blue\ -loadFrame.transitionTime = 1\ -local note = loadFrame:addChild( UIText( 0, -math.floor( application.view.height / 3.5 ), 20, 5, \"\\n@acThis program\\ncannot be @teterminated\" ) )\ -note.colour = colors.lime\ -local blackFrame = application.view:addChild( UIContainer( 0, -application.view.height, application.view.width, application.view.height ) )\ -blackFrame.colour = colours.black\ -blackFrame.transitionTime = .75\ -\ -application.view:createShortcut( \"Terminate\", \"ctrl-t\", function()\ -\9note.y = -note.height\ -\9note.text = \"\\n@acThis program\\ncannot be \\n@teterminated\"\ -\9note.animatedY = 0\ -\9Timer.queue( 2.5, function()\ -\9\9note.animatedY = -note.height\ -\9end )\ -end )\ -application.view:createShortcut( \"Reboot\", \"ctrl-r\", function()\ -\9note.y = -note.height\ -\9note.text = \"\\n@acRebooting computer in @te1 second\"\ -\9note.animatedY = 0\ -\9Timer.queue( 1, function()\ -\9\9os.reboot()\ -\9end )\ -end )\ -\ -local tOName = blackFrame:addChild( UILabel( 0, 1, lock.authorized ) )\ -tOName.x = math.floor( application.view.width / 2 - #lock.authorized / 2 )\ -local tOName2 = blackFrame:addChild( UILabel( 0, 2, lock.authorized2 ) )\ -tOName2.x = math.floor( application.view.width / 2 - #lock.authorized2 / 2 )\ -\ -local tName = loadFrame:addChild( UILabel( 0, 1, lock.name ) )\ -tName.x = math.floor( application.view.width / 2 - #lock.name / 2 )\ -local tBy = loadFrame:addChild( UILabel( 0, application.view.height-2, \"By \" .. lock.author ) )\ -tBy.x = math.floor( application.view.width / 2 - #lock.author / 2 )\ -local tP = {}\ -\ -local function checkConfig()\ -\9if not fs.exists( lock.config ) then\ -\9\9local newPass = loadFrame:addChild( UILabel( 0, math.floor( application.view.height / 2 ) - 1, \"Enter new password\" ) )\ -\9\9newPass.transitionTime = .5\ -\9\9newPass.animatedX = math.floor( application.view.width / 2 - newPass.width / 2 )\ -\9\9inputPass = loadFrame:addChild( UITextInput( 0, math.floor( application.view.height / 2 ), application.view.width / 2 ) )\ -\9\9inputPass.transitionTime = .5\ -\9\9inputPass.focussed = true\ -\9\9inputPass.colour = colors.lime\ -\9\9inputPass.mask = \"#\"\ -\9\9inputPass.animatedX = math.floor( application.view.width / 2 - inputPass.width / 2 )\ -\9\9function inputPass:onEnter()\ -\9\9\9if not tP[1] then\ -\9\9\9\9tP[1] = sha256( locSalt .. inputPass.text )\ -\9\9\9\9inputPass.text = \"\"\ -\9\9\9\9inputPass.focussed = true\ -\9\9\9\9newPass.text = \"Re-enter password\"\ -\9\9\9\9newPass.animatedX = math.floor( application.view.width / 2 - newPass.width / 2 )\ -\9\9\9\9newPass.textColour = colours.orange\ -\9\9\9else\ -\9\9\9\9tP[2] = sha256( locSalt .. inputPass.text )\ -\9\9\9\9inputPass.text = \"\"\ -\9\9\9\9inputPass.focussed = true\ -\9\9\9\9if tP[1] ~= tP[2] then\ -\9\9\9\9\9tP = {}\ -\9\9\9\9\9newPass.text = \"Passwords did not match\"\ -\9\9\9\9\9newPass.animatedX = math.floor( application.view.width / 2 - newPass.width / 2 )\ -\9\9\9\9\9newPass.textColour = colours.red\ -\9\9\9\9\9Timer.queue( 1, function()\ -\9\9\9\9\9\9newPass:remove()\ -\9\9\9\9\9\9inputPass:remove()\ -\9\9\9\9\9\9checkConfig()\ -\9\9\9\9\9end )\ -\9\9\9\9else\ -\9\9\9\9\9inputPass.focussed = false\ -\9\9\9\9\9newPass.text = \"Passwords matching!\"\ -\9\9\9\9\9newPass.animatedX = math.floor( application.view.width / 2 - newPass.width / 2 )\ -\9\9\9\9\9newPass.textColour = colours.lime\ -\9\9\9\9\9config.pass = tP[1]\ -\9\9\9\9\9config.salt = locSalt\ -\9\9\9\9\9Timer.queue( 1, function()\ -\9\9\9\9\9\9newPass:transitionOutRight()\ -\9\9\9\9\9\9inputPass:transitionOutBottom()\ -\9\9\9\9\9\9Timer.queue( .3, function()\ -\9\9\9\9\9\9\9tP = {}\ -\9\9\9\9\9\9\9newPass:remove()\ -\9\9\9\9\9\9\9inputPass:remove()\ -\9\9\9\9\9\9end )\ -\9\9\9\9\9end )\ -\9\9\9\9\9Timer.queue( 1, function()\ -\9\9\9\9\9\9local doorSettings = loadFrame:addChild( UILabel( 0, math.floor( application.view.height / 2 ) - 5, \"Choose redstone output side\" ) )\ -\9\9\9\9\9\9local doorSettings1 = loadFrame:addChild( UILabel( 0, math.floor( application.view.height / 2 ) - 4, \"\\\"None\\\" = Unlock computer shell\" ) )\ -\9\9\9\9\9\9local redstoneside = loadFrame:addChild( UITabs( 0, math.floor( application.view.height / 2 ) - 2, math.floor( application.view.width / 2.25 ), { \"None\", \"Top\", \"Bottom\", \"Right\", \"Left\", \"Front\", \"Back\" } ) )\ -\9\9\9\9\9\9redstoneside:select(1)\ -\9\9\9\9\9\9redstoneside.transitionTime = .2\ -\9\9\9\9\9\9redstoneside.colour = colors.blue\ -\9\9\9\9\9\9redstoneside.textColour = colours.white\ -\9\9\9\9\9\9local continue = loadFrame:addChild( UIButton( 0, math.floor( application.view.height / 2 ) + 3, math.floor( application.view.width / 3 ), 3, \"Continue\" ) )\ -\9\9\9\9\9\9continue.colour = colors.lime\ -\9\9\9\9\9\9continue.animatedX = math.floor( application.view.width / 2 - continue.width / 2 )\ -\9\9\9\9\9\9redstoneside.animatedX = math.floor( application.view.width / 2 - redstoneside.width / 2 )\ -\9\9\9\9\9\9doorSettings.animatedX = math.floor( application.view.width / 2 - doorSettings.width / 2 )\ -\9\9\9\9\9\9doorSettings1.animatedX = math.floor( application.view.width / 2 - doorSettings1.width / 2 )\ -\9\9\9\9\9\9function continue:onClick()\ -\9\9\9\9\9\9\9if redstoneside.selected ~= 0 and redstoneside.selected ~= 1 then\ -\9\9\9\9\9\9\9\9config.redstone = redstoneside.options[ redstoneside.selected ] or false\ -\9\9\9\9\9\9\9else\ -\9\9\9\9\9\9\9\9config.redstone = false\ -\9\9\9\9\9\9\9end\ -\9\9\9\9\9\9\9doorSettings:transitionOutTop()\ -\9\9\9\9\9\9\9doorSettings1:transitionOutRight()\ -\9\9\9\9\9\9\9redstoneside:transitionOutLeft()\ -\9\9\9\9\9\9\9continue:transitionOutBottom()\ -\9\9\9\9\9\9\9Timer.queue( .5, function()\ -\9\9\9\9\9\9\9\9doorSettings:remove()\ -\9\9\9\9\9\9\9\9doorSettings1:remove()\ -\9\9\9\9\9\9\9\9redstoneside:remove()\ -\9\9\9\9\9\9\9\9continue:remove()\ -\9\9\9\9\9\9\9\9ok, err = pcall(function() \ -\9\9\9\9\9\9\9\9\9local SaveFile = fs.open( lock.config, \"w\" )\ -\9\9\9\9\9\9\9\9\9SaveFile.writeLine( encrypt.encrypt( textutils.serialize( config ), config.pass ) )\ -\9\9\9\9\9\9\9\9\9SaveFile.close()\ -\9\9\9\9\9\9\9\9end )\ -\9\9\9\9\9\9\9\9checkConfig()\ -\9\9\9\9\9\9\9end )\ -\9\9\9\9\9\9end\ -\9\9\9\9\9end )\ -\9\9\9\9end\ -\9\9\9end\ -\9\9end\ -\9else\ -\9\9\ -\9\9main()\ -\9end\ -end\ -\ -function main()\ -\9local inputTextPass = loadFrame:addChild( UILabel( 0, math.floor( application.view.height / 2 )-1, \"Password\" ) )\ -\9inputTextPass.transitionTime = .25\ -\9inputTextPass.animatedX = math.floor( application.view.width/2 - inputTextPass.width/2 )\ -\9local inputFieldPass = loadFrame:addChild( UITextInput( 0, math.floor( application.view.height / 2 ), application.view.width/2 ) )\ -\9inputFieldPass.transitionTime = .25\ -\9inputFieldPass.colour = colors.lime\ -\9inputFieldPass.mask = \"#\"\ -\9inputFieldPass.focussed = true\ -\9inputFieldPass.animatedX = math.floor( application.view.width/2 - inputFieldPass.width/2 )\ -\9function inputFieldPass:onEnter()\ -\9\9inputTextPass.text = \"Validating\"\ -\9\9inputTextPass.animatedX = math.floor( application.view.width/2 - inputTextPass.width/2 )\ -\9\9local file = fs.open( lock.config, \"r\" )\ -\9\9local config = textutils.unserialize( encrypt.decrypt( file.readAll(), sha256( inputFieldPass.text ) ) )\ -\9\9file.close()\ -\9\9if sha256( config.salt .. inputFieldPass.text ) == config.pass then\ -\9\9\9inputFieldPass.text = \"\"\ -\9\9\9inputTextPass.text = \"Password correct\"\ -\9\9\9inputTextPass.textColour = colours.lime\ -\9\9\9inputTextPass.animatedX = math.floor( application.view.width/2 - inputTextPass.width/2 )\ -\9\9\9if config.redstone then\ -\9\9\9\9rs.setOutput( config.redstone:lower(), true )\ -\9\9\9\9Timer.queue( 5, function() \ -\9\9\9\9\9rs.setOutput( config.redstone:lower(), false )\ -\9\9\9\9\9inputTextPass:remove()\ -\9\9\9\9\9inputFieldPass:remove()\ -\9\9\9\9\9main()\ -\9\9\9\9end )\ -\9\9\9else\ -\9\9\9\9Timer.queue( .25, function()\ -\9\9\9\9\9inputTextPass:transitionOutLeft()\ -\9\9\9\9\9inputFieldPass:transitionOutBottom()\ -\9\9\9\9\9Timer.queue( .5, function()\ -\9\9\9\9\9\9loadFrame.colour = colors.lime\ -\9\9\9\9\9\9loadFrame:transitionOutBottom()\ -\9\9\9\9\9\9blackFrame:transitionInTop()\ -\9\9\9\9\9\9Timer.queue( .8, function() \ -\9\9\9\9\9\9\9term.setCursorPos( 1, 5 )\ -\9\9\9\9\9\9\9application:stop()\ -\9\9\9\9\9\9end )\ -\9\9\9\9\9end )\ -\9\9\9\9end )\ -\9\9\9end\ -\9\9else\ -\9\9\9inputTextPass.text = \"Password incorrect\"\ -\9\9\9inputTextPass.textColour = colours.red\ -\9\9\9inputTextPass.animatedX = math.floor( application.view.width/2 - inputTextPass.width/2 )\ -\9\9\9Timer.queue( 1, function()\ -\9\9\9\9inputTextPass:remove()\ -\9\9\9\9inputFieldPass:remove()\ -\9\9\9\9main()\ -\9\9\9end )\ -\9\9end\ -\9end\ -end\ -local _ok, result = pcall( checkConfig )"; +local branches = { + "master", + "experimental" } -if shell.getRunningProgram() ~= "startup" then - if fs.exists( "startup" ) then - fs.move( "startup", "__oldstartup" ) - end - fs.move( shell.getRunningProgram(), "startup" ) - os.reboot() ---[[else - local protecting = { "startup", ".MetroOSSecureLock.cfg" } - local oldCopy = fs.copy - local oldMove = fs.move - local oldDelete = fs.delete - local oldEdit = fs.open - fs.delete = function( path ) - for k, v in pairs( protecting ) do - if fs.getName( path ) == v then - return error( "Cannot remove MetroOS", 0 ) - end - end - return oldDelete( path ) - end - fs.open = function( path, method, pass ) - if method == "r" then - return oldEdit( path, method ) - else - return error( "Cannot edit MetroOS", 0 ) - end - end - fs.move = function( path, newPath ) - for k, v in pairs( protecting ) do - if fs.getName( path ) == v then - return error( "Cannot move MetroOS", 0 ) - end - end - return oldMove( path, newPath ) - end - fs.copy = function( path, newPath ) - for k, v in pairs( protecting ) do - if fs.getName( path ) == v then - return error( "Cannot copy MetroOS", 0 ) - end - end - return oldCopy( path, newPath ) - end--]] + + +local function formatFS() + local function mkdir(dir) + if not fs.exists(dir) then fs.makeDir(dir) end + end + if fs.exists("AxiomUI") then + for k, v in pairs(fs.list("AxiomUI")) do + if not fs.exists(v) then + fs.move("AxiomUI/"..v, v) + print("AxiomUI/"..v.." -> "..v) + else + print("AxiomUI/"..v.." -x>") + end + end + fs.delete("AxiomUI") + print("Delete extra files?") + if read() == "y" then + fs.delete("install.lua") + fs.delete("README.md") + end + else + error("formatFS failed") + end end -if not fs.exists "Flare" then - print "Downloading Flare" - local h = http.get "https://pastebin.com/raw/SD25GhYf" - if h then - local f, err = load( h.readAll(), "installer", nil, _ENV or getfenv() ) - h.close() - f() - else - return error( "Cannot install Flare", 0 ) - end +local function wget(url, file) + local data = http.get(url) + data = data.readAll() + local file_handle = fs.open(file,"w") + file_handle.write(data) + file_handle.close() end -local loader -local h = fs.open( "Flare/run.lua", "r" ) -if h then - loader = h.readAll() - h.close() -else - error( "failed to read Flare", 0 ) +function selector(y,option) + term.setCursorPos(1,y) + for k,v in ipairs(branches) do + if k == option then + write(v.. " <-") + term.setCursorPos(1,y+k) + else + write(v.. " ") + term.setCursorPos(1,y+k) + end + end end -local f, err = load( loader, "Flare", nil, _ENV or getfenv() ) -if not f then - error( "there was a problem with Flare!: " .. err, 0 ) +local version = os.version() +if version == "CraftOS 1.5" then + error("Axiom is not compatible with "..version.."!") end -f( files, "init" ) \ No newline at end of file +print("Axiom UI CE Installer") +print("Select a branch using arrow keys") +local x,y = term.getCursorPos() +if y > 17 then + shell.run("clear") + print("Axiom UI CE Installer") + print("Select a branch using arrow keys") + x,y = term.getCursorPos() +end +selector(y,1) +local user = "nothjarnan" +local branch = 1 +while(true) do + local e,k,h = os.pullEvent( "key" ) + if k == keys.up then + if branch > 1 then + branch = branch - 1 + selector(y,branch) + end + end + if k == keys.down then + if branch < #branches then + branch = branch + 1 + selector(y,branch) + end + end + if k == keys.enter then + branch = branches[branch] + print("Branch selected: "..branch) + print("Starting installation") + break + end +end +wget("http://www.pastebin.com/raw/w5zkvysi",".gitget") +shell.run(".gitget "..user.." axiom-opensource "..branch.." AxiomUI") +formatFS() +print("Installation completed!") From 5c299e5ceae452707462c2d5e20af97ea83eacc8 Mon Sep 17 00:00:00 2001 From: OutragedMetro Date: Fri, 15 Apr 2022 20:17:55 -0600 Subject: [PATCH 066/125] Update programVersions --- programVersions | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/programVersions b/programVersions index 33dda5d..4702ac2 100644 --- a/programVersions +++ b/programVersions @@ -18,7 +18,7 @@ ["Package"]="Outraged Security .INC", }, ["MetroOS"]={ - ["GitURL"]="https://raw.githubusercontent.com/rservices/darkprograms/darkprograms/MetroOS/MetroOS", + ["GitURL"]="https://raw.githubusercontent.com/nothjarnan/axiom/master/install.lua install.lua", ["Version"]=2.94, ["Type"]="program", ["Name"]="Outraged Security .INC Secure OS Downloader", From f19a6669a53eb63e2ac57661ce1d3b11ce65365d Mon Sep 17 00:00:00 2001 From: OutragedMetro Date: Fri, 15 Apr 2022 20:26:15 -0600 Subject: [PATCH 067/125] Update MetroOS --- MetroOS/MetroOS | 432 ++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 348 insertions(+), 84 deletions(-) diff --git a/MetroOS/MetroOS b/MetroOS/MetroOS index f8190b0..83011e6 100644 --- a/MetroOS/MetroOS +++ b/MetroOS/MetroOS @@ -1,89 +1,353 @@ -local branches = { - "master", - "experimental" +local files = { + ["init"] = "require \"UIButton\"\ +require \"UIContainer\"\ +require \"UIText\"\ +require \"UILabel\"\ +require \"UITextInput\"\ +require \"UIMenu\"\ +require \"UITabs\"\ +require \"UIFileDialogue\"\ +\ +application.terminateable = false\ +\ +local blocksize = 5\ +local bit32 = 2 ^ 32\ +local prime1 = 8747\ +local prime2 = 2147483647\ +local bit32half = bit32/2\ +local floor = math.floor\ +local function keygen( seed, length ) local count = 1 local keys, garbage = {}, {} for i = 1, length, blocksize do seed = ( seed * ( seed > bit32half and prime2 or prime1 ) + 1 ) % bit32 + count count = count + 1 garbage[i] = math.max( 0, ( seed - 1 ) % 10 - 4 ) keys[i] = seed % 256 end return keys, garbage end\ +local h = {}\ +for i = 0, 15 do h[i] = (\"%X\"):format( i ) end\ +local hl = {}\ +for i = 0, 15 do hl[(\"%X\"):format( i )] = i end\ +local function tohex2( n ) return h[math.floor( n / 16 )] .. h[n % 16] end\ +local function fromhex2( h ) return hl[h:sub( 1, 1 )] * 16 + hl[h:sub( 2, 2 )] end\ +local function numbertochars( n ) local s = tohex2( n % 256 ) for i = 1, 3 do n = math.floor( n / 256 ) s = tohex2( n % 256 ) .. s end return s end\ +local function charstonumber( c ) local n = 0 for i = 1, 4 do n = n * 256 + fromhex2( c:sub( i * 2 - 1 ) ) end return n end\ +local function newgarbage( length ) if length == 0 then return \"\" end return tohex2( math.random( 0, 255 ) ) .. newgarbage( length - 1 ) end\ +local function stringkey( str ) local key = 0 for i = 1, #str do key = key * 256 + str:byte( i ) end return key end\ +local encrypt = {}\ +function encrypt.encrypt( text, key ) key = type( key ) == \"string\" and stringkey( key ) or key text = textutils.serialize( text ) if type( key ) ~= \"number\" then return error( \"expected number/string key, got \" .. type( key ) ) end local keys, garbage = keygen( key, #text ) local cipher = { numbertochars( #text ) } math.randomseed( os.clock() ) for i = 1, #text, blocksize do for n = 0, blocksize - 1 do if i + n <= #text then cipher[#cipher + 1] = tohex2( bit.bxor( text:byte( i + n ), keys[i] ) ) else break end end cipher[#cipher + 1] = newgarbage( garbage[i] ) end return table.concat( cipher ) end\ +function encrypt.decrypt( cipher, key ) key = type( key ) == \"string\" and stringkey( key ) or key if type( cipher ) ~= \"string\" then return error( \"expected string cipher, got \" .. type( cipher ) ) end if type( key ) ~= \"number\" then return error( \"expected number/string key, got \" .. type( key ) ) end local length = charstonumber( cipher:sub( 1, 8 ) ) cipher = cipher:sub( 9 ) local keys, garbage = keygen( key, length ) local text = {} local i = 1 while #cipher > 0 do local block = cipher:sub( 1, math.min( #cipher - garbage[i] * 2, blocksize * 2 ) ) for n = 1, #block, 2 do text[#text + 1] = string.char( bit.bxor( fromhex2( block:sub( n ) ), keys[i] ) ) end cipher = cipher:sub( blocksize * 2 + garbage[i] * 2 + 1 ) i = i + blocksize end return textutils.unserialize( table.concat( text ) ) end\ +local MOD = 2^32\ +local MODM = MOD-1\ +local function memoize(f) local mt = {} local t = setmetatable({}, mt) function mt:__index(k) local v = f(k) t[k] = v return v end return t end\ +local function make_bitop_uncached(t, m) local function bitop(a, b) local res,p = 0,1 while a ~= 0 and b ~= 0 do local am, bm = a % m, b % m res = res + t[am][bm] * p a = (a - am) / m b = (b - bm) / m p = p*m end res = res + (a + b) * p return res end return bitop end\ +local function make_bitop(t) local op1 = make_bitop_uncached(t,2^1) local op2 = memoize(function(a) return memoize(function(b) return op1(a, b) end) end) return make_bitop_uncached(op2, 2 ^ (t.n or 1)) end\ +local bxor1 = make_bitop({[0] = {[0] = 0,[1] = 1}, [1] = {[0] = 1, [1] = 0}, n = 4})\ +local function bxor(a, b, c, ...) local z = nil if b then a = a % MOD b = b % MOD z = bxor1(a, b) if c then z = bxor(z, c, ...) end return z elseif a then return a % MOD else return 0 end end\ +local function band(a, b, c, ...) local z if b then a = a % MOD b = b % MOD z = ((a + b) - bxor1(a,b)) / 2 if c then z = bit32_band(z, c, ...) end return z elseif a then return a % MOD else return MODM end end\ +local function bnot(x) return (-1 - x) % MOD end\ +local function rshift1(a, disp) if disp < 0 then return lshift(a,-disp) end return math.floor(a % 2 ^ 32 / 2 ^ disp) end\ +local function rshift(x, disp) if disp > 31 or disp < -31 then return 0 end return rshift1(x % MOD, disp) end\ +local function lshift(a, disp) if disp < 0 then return rshift(a,-disp) end return (a * 2 ^ disp) % 2 ^ 32 end\ +local function rrotate(x, disp) x = x % MOD disp = disp % 32 local low = band(x, 2 ^ disp - 1) return rshift(x, disp) + lshift(low, 32 - disp) end\ +local k = {0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2, }\ +local function str2hexa(s) return (string.gsub(s, \".\", function(c) return string.format(\"%02x\", string.byte(c)) end)) end\ +local function num2s(l, n) local s = \"\"for i = 1, n do local rem = l % 256 s = string.char(rem) .. s l = (l - rem) / 256 end return s end\ +local function s232num(s, i) local n = 0 for i = i, i + 3 do n = n*256 + string.byte(s, i) end return n end\ +local function preproc(msg, len) local extra = 64 - ((len + 9) % 64) len = num2s(8 * len, 8) msg = msg .. \"\\128\" .. string.rep(\"\\0\", extra) .. len assert(#msg % 64 == 0) return msg end\ +local function initH256(H) H[1] = 0x6a09e667 H[2] = 0xbb67ae85 H[3] = 0x3c6ef372 H[4] = 0xa54ff53a H[5] = 0x510e527f H[6] = 0x9b05688c H[7] = 0x1f83d9ab H[8] = 0x5be0cd19 return H end\ +local function digestblock(msg, i, H) local w = {} for j = 1, 16 do w[j] = s232num(msg, i + (j - 1)*4) end for j = 17, 64 do local v = w[j - 15] local s0 = bxor(rrotate(v, 7), rrotate(v, 18), rshift(v, 3)) v = w[j - 2] w[j] = w[j - 16] + s0 + w[j - 7] + bxor(rrotate(v, 17), rrotate(v, 19), rshift(v, 10)) end local a, b, c, d, e, f, g, h = H[1], H[2], H[3], H[4], H[5], H[6], H[7], H[8] for i = 1, 64 do local s0 = bxor(rrotate(a, 2), rrotate(a, 13), rrotate(a, 22)) local maj = bxor(band(a, b), band(a, c), band(b, c)) local t2 = s0 + maj local s1 = bxor(rrotate(e, 6), rrotate(e, 11), rrotate(e, 25)) local ch = bxor (band(e, f), band(bnot(e), g)) local t1 = h + s1 + ch + k[i] + w[i] h, g, f, e, d, c, b, a = g, f, e, d + t1, c, b, a, t1 + t2 end H[1] = band(H[1] + a) H[2] = band(H[2] + b) H[3] = band(H[3] + c) H[4] = band(H[4] + d) H[5] = band(H[5] + e) H[6] = band(H[6] + f) H[7] = band(H[7] + g) H[8] = band(H[8] + h) end\ +local function sha256(msg) msg = preproc(msg, #msg) local H = initH256({}) for i = 1, #msg, 64 do digestblock(msg, i, H) end return str2hexa(num2s(H[1], 4) .. num2s(H[2], 4) .. num2s(H[3], 4) .. num2s(H[4], 4) .. num2s(H[5], 4) .. num2s(H[6], 4) .. num2s(H[7], 4) .. num2s(H[8], 4)) end\ +local tKeys = {\"a\", \"b\", \"c\", \"d\", \"e\", \"f\", \"g\", \"h\", \"i\", \"j\", \"k\", \"l\", \"m\", \"n\", \"o\", \"p\", \"q\", \"r\", \"s\", \"t\", \"u\", \"v\", \"w\", \"x\", \"y\", \"z\", 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 } --, \"&\", \"%\", \"#\", \"$\", \"!\", \"@\", \"?\", \"=\", \"/\", \"\\\\\", \"(\", \")\", \"[\", \"]\", \"{\", \"}\" }\ +table.size = function( tbl ) local count = 0 for k,v in pairs( tbl ) do count = count + 1 end return count end\ +\ +local function GenerateSalt()\ +\9local Count = 0\ +\9local Salt = \"\"\ +\9local function RandomKey()\ +\9\9local key = false\ +\9\9key = tKeys[ math.random( 1, table.size( tKeys ) ) ]\ +\9\9if type( key ) ~= \"number\" then\ +\9\9\9local x = math.random( 1, 2 )\ +\9\9\9if x == 1 then\ +\9\9\9\9key\9= string.lower( key )\ +\9\9\9elseif x == 2 then\ +\9\9\9\9key = string.upper( key )\ +\9\9\9end\ +\9\9end\ +\9\9return key\ +\9end\ +\9for SaltCount = 1, 32 do\ +\9\9Salt = Salt .. RandomKey()\ +\9\9Count = Count + 1\ +\9\9if Count == 16 then\ +\9\9\9sleep( 0 )\ +\9\9\9Count = 0\ +\9\9end\ +\9end\ +\9return Salt\ +end\ +local locSalt = GenerateSalt()\ +\ +local lock = {\ +\9name = \"Metro Secure OS Login\",\ +\9authorized = \"MetroOS - Computer unlocked\",\ +\9authorized2 = os.version(),\ +\9author = \"OutragedMetro\",\ +\9config = \".MetroOSSecureLock.cfg\",\ +}\ +local framerate = .05\ +local config = {}\ +\ +local loadFrame = application.view:addChild( UIContainer( 0, 0, application.view.width, application.view.height ) )\ +loadFrame.colour = colors.blue\ +loadFrame.transitionTime = 1\ +local note = loadFrame:addChild( UIText( 0, -math.floor( application.view.height / 3.5 ), 20, 5, \"\\n@acThis program\\ncannot be @teterminated\" ) )\ +note.colour = colors.lime\ +local blackFrame = application.view:addChild( UIContainer( 0, -application.view.height, application.view.width, application.view.height ) )\ +blackFrame.colour = colours.black\ +blackFrame.transitionTime = .75\ +\ +application.view:createShortcut( \"Terminate\", \"ctrl-t\", function()\ +\9note.y = -note.height\ +\9note.text = \"\\n@acThis program\\ncannot be \\n@teterminated\"\ +\9note.animatedY = 0\ +\9Timer.queue( 2.5, function()\ +\9\9note.animatedY = -note.height\ +\9end )\ +end )\ +application.view:createShortcut( \"Reboot\", \"ctrl-r\", function()\ +\9note.y = -note.height\ +\9note.text = \"\\n@acRebooting computer in @te1 second\"\ +\9note.animatedY = 0\ +\9Timer.queue( 1, function()\ +\9\9os.reboot()\ +\9end )\ +end )\ +\ +local tOName = blackFrame:addChild( UILabel( 0, 1, lock.authorized ) )\ +tOName.x = math.floor( application.view.width / 2 - #lock.authorized / 2 )\ +local tOName2 = blackFrame:addChild( UILabel( 0, 2, lock.authorized2 ) )\ +tOName2.x = math.floor( application.view.width / 2 - #lock.authorized2 / 2 )\ +\ +local tName = loadFrame:addChild( UILabel( 0, 1, lock.name ) )\ +tName.x = math.floor( application.view.width / 2 - #lock.name / 2 )\ +local tBy = loadFrame:addChild( UILabel( 0, application.view.height-2, \"By \" .. lock.author ) )\ +tBy.x = math.floor( application.view.width / 2 - #lock.author / 2 )\ +local tP = {}\ +\ +local function checkConfig()\ +\9if not fs.exists( lock.config ) then\ +\9\9local newPass = loadFrame:addChild( UILabel( 0, math.floor( application.view.height / 2 ) - 1, \"Enter new password\" ) )\ +\9\9newPass.transitionTime = .5\ +\9\9newPass.animatedX = math.floor( application.view.width / 2 - newPass.width / 2 )\ +\9\9inputPass = loadFrame:addChild( UITextInput( 0, math.floor( application.view.height / 2 ), application.view.width / 2 ) )\ +\9\9inputPass.transitionTime = .5\ +\9\9inputPass.focussed = true\ +\9\9inputPass.colour = colors.lime\ +\9\9inputPass.mask = \"#\"\ +\9\9inputPass.animatedX = math.floor( application.view.width / 2 - inputPass.width / 2 )\ +\9\9function inputPass:onEnter()\ +\9\9\9if not tP[1] then\ +\9\9\9\9tP[1] = sha256( locSalt .. inputPass.text )\ +\9\9\9\9inputPass.text = \"\"\ +\9\9\9\9inputPass.focussed = true\ +\9\9\9\9newPass.text = \"Re-enter password\"\ +\9\9\9\9newPass.animatedX = math.floor( application.view.width / 2 - newPass.width / 2 )\ +\9\9\9\9newPass.textColour = colours.orange\ +\9\9\9else\ +\9\9\9\9tP[2] = sha256( locSalt .. inputPass.text )\ +\9\9\9\9inputPass.text = \"\"\ +\9\9\9\9inputPass.focussed = true\ +\9\9\9\9if tP[1] ~= tP[2] then\ +\9\9\9\9\9tP = {}\ +\9\9\9\9\9newPass.text = \"Passwords did not match\"\ +\9\9\9\9\9newPass.animatedX = math.floor( application.view.width / 2 - newPass.width / 2 )\ +\9\9\9\9\9newPass.textColour = colours.red\ +\9\9\9\9\9Timer.queue( 1, function()\ +\9\9\9\9\9\9newPass:remove()\ +\9\9\9\9\9\9inputPass:remove()\ +\9\9\9\9\9\9checkConfig()\ +\9\9\9\9\9end )\ +\9\9\9\9else\ +\9\9\9\9\9inputPass.focussed = false\ +\9\9\9\9\9newPass.text = \"Passwords matching!\"\ +\9\9\9\9\9newPass.animatedX = math.floor( application.view.width / 2 - newPass.width / 2 )\ +\9\9\9\9\9newPass.textColour = colours.lime\ +\9\9\9\9\9config.pass = tP[1]\ +\9\9\9\9\9config.salt = locSalt\ +\9\9\9\9\9Timer.queue( 1, function()\ +\9\9\9\9\9\9newPass:transitionOutRight()\ +\9\9\9\9\9\9inputPass:transitionOutBottom()\ +\9\9\9\9\9\9Timer.queue( .3, function()\ +\9\9\9\9\9\9\9tP = {}\ +\9\9\9\9\9\9\9newPass:remove()\ +\9\9\9\9\9\9\9inputPass:remove()\ +\9\9\9\9\9\9end )\ +\9\9\9\9\9end )\ +\9\9\9\9\9Timer.queue( 1, function()\ +\9\9\9\9\9\9local doorSettings = loadFrame:addChild( UILabel( 0, math.floor( application.view.height / 2 ) - 5, \"Choose redstone output side\" ) )\ +\9\9\9\9\9\9local doorSettings1 = loadFrame:addChild( UILabel( 0, math.floor( application.view.height / 2 ) - 4, \"\\\"None\\\" = Unlock computer shell\" ) )\ +\9\9\9\9\9\9local redstoneside = loadFrame:addChild( UITabs( 0, math.floor( application.view.height / 2 ) - 2, math.floor( application.view.width / 2.25 ), { \"None\", \"Top\", \"Bottom\", \"Right\", \"Left\", \"Front\", \"Back\" } ) )\ +\9\9\9\9\9\9redstoneside:select(1)\ +\9\9\9\9\9\9redstoneside.transitionTime = .2\ +\9\9\9\9\9\9redstoneside.colour = colors.blue\ +\9\9\9\9\9\9redstoneside.textColour = colours.white\ +\9\9\9\9\9\9local continue = loadFrame:addChild( UIButton( 0, math.floor( application.view.height / 2 ) + 3, math.floor( application.view.width / 3 ), 3, \"Continue\" ) )\ +\9\9\9\9\9\9continue.colour = colors.lime\ +\9\9\9\9\9\9continue.animatedX = math.floor( application.view.width / 2 - continue.width / 2 )\ +\9\9\9\9\9\9redstoneside.animatedX = math.floor( application.view.width / 2 - redstoneside.width / 2 )\ +\9\9\9\9\9\9doorSettings.animatedX = math.floor( application.view.width / 2 - doorSettings.width / 2 )\ +\9\9\9\9\9\9doorSettings1.animatedX = math.floor( application.view.width / 2 - doorSettings1.width / 2 )\ +\9\9\9\9\9\9function continue:onClick()\ +\9\9\9\9\9\9\9if redstoneside.selected ~= 0 and redstoneside.selected ~= 1 then\ +\9\9\9\9\9\9\9\9config.redstone = redstoneside.options[ redstoneside.selected ] or false\ +\9\9\9\9\9\9\9else\ +\9\9\9\9\9\9\9\9config.redstone = false\ +\9\9\9\9\9\9\9end\ +\9\9\9\9\9\9\9doorSettings:transitionOutTop()\ +\9\9\9\9\9\9\9doorSettings1:transitionOutRight()\ +\9\9\9\9\9\9\9redstoneside:transitionOutLeft()\ +\9\9\9\9\9\9\9continue:transitionOutBottom()\ +\9\9\9\9\9\9\9Timer.queue( .5, function()\ +\9\9\9\9\9\9\9\9doorSettings:remove()\ +\9\9\9\9\9\9\9\9doorSettings1:remove()\ +\9\9\9\9\9\9\9\9redstoneside:remove()\ +\9\9\9\9\9\9\9\9continue:remove()\ +\9\9\9\9\9\9\9\9ok, err = pcall(function() \ +\9\9\9\9\9\9\9\9\9local SaveFile = fs.open( lock.config, \"w\" )\ +\9\9\9\9\9\9\9\9\9SaveFile.writeLine( encrypt.encrypt( textutils.serialize( config ), config.pass ) )\ +\9\9\9\9\9\9\9\9\9SaveFile.close()\ +\9\9\9\9\9\9\9\9end )\ +\9\9\9\9\9\9\9\9checkConfig()\ +\9\9\9\9\9\9\9end )\ +\9\9\9\9\9\9end\ +\9\9\9\9\9end )\ +\9\9\9\9end\ +\9\9\9end\ +\9\9end\ +\9else\ +\9\9\ +\9\9main()\ +\9end\ +end\ +\ +function main()\ +\9local inputTextPass = loadFrame:addChild( UILabel( 0, math.floor( application.view.height / 2 )-1, \"Password\" ) )\ +\9inputTextPass.transitionTime = .25\ +\9inputTextPass.animatedX = math.floor( application.view.width/2 - inputTextPass.width/2 )\ +\9local inputFieldPass = loadFrame:addChild( UITextInput( 0, math.floor( application.view.height / 2 ), application.view.width/2 ) )\ +\9inputFieldPass.transitionTime = .25\ +\9inputFieldPass.colour = colors.lime\ +\9inputFieldPass.mask = \"#\"\ +\9inputFieldPass.focussed = true\ +\9inputFieldPass.animatedX = math.floor( application.view.width/2 - inputFieldPass.width/2 )\ +\9function inputFieldPass:onEnter()\ +\9\9inputTextPass.text = \"Validating\"\ +\9\9inputTextPass.animatedX = math.floor( application.view.width/2 - inputTextPass.width/2 )\ +\9\9local file = fs.open( lock.config, \"r\" )\ +\9\9local config = textutils.unserialize( encrypt.decrypt( file.readAll(), sha256( inputFieldPass.text ) ) )\ +\9\9file.close()\ +\9\9if sha256( config.salt .. inputFieldPass.text ) == config.pass then\ +\9\9\9inputFieldPass.text = \"\"\ +\9\9\9inputTextPass.text = \"Password correct\"\ +\9\9\9inputTextPass.textColour = colours.lime\ +\9\9\9inputTextPass.animatedX = math.floor( application.view.width/2 - inputTextPass.width/2 )\ +\9\9\9if config.redstone then\ +\9\9\9\9rs.setOutput( config.redstone:lower(), true )\ +\9\9\9\9Timer.queue( 5, function() \ +\9\9\9\9\9rs.setOutput( config.redstone:lower(), false )\ +\9\9\9\9\9inputTextPass:remove()\ +\9\9\9\9\9inputFieldPass:remove()\ +\9\9\9\9\9main()\ +\9\9\9\9end )\ +\9\9\9else\ +\9\9\9\9Timer.queue( .25, function()\ +\9\9\9\9\9inputTextPass:transitionOutLeft()\ +\9\9\9\9\9inputFieldPass:transitionOutBottom()\ +\9\9\9\9\9Timer.queue( .5, function()\ +\9\9\9\9\9\9loadFrame.colour = colors.lime\ +\9\9\9\9\9\9loadFrame:transitionOutBottom()\ +\9\9\9\9\9\9blackFrame:transitionInTop()\ +\9\9\9\9\9\9Timer.queue( .8, function() \ +\9\9\9\9\9\9\9term.setCursorPos( 1, 5 )\ +\9\9\9\9\9\9\9application:stop()\ +\9\9\9\9\9\9end )\ +\9\9\9\9\9end )\ +\9\9\9\9end )\ +\9\9\9end\ +\9\9else\ +\9\9\9inputTextPass.text = \"Password incorrect\"\ +\9\9\9inputTextPass.textColour = colours.red\ +\9\9\9inputTextPass.animatedX = math.floor( application.view.width/2 - inputTextPass.width/2 )\ +\9\9\9Timer.queue( 1, function()\ +\9\9\9\9inputTextPass:remove()\ +\9\9\9\9inputFieldPass:remove()\ +\9\9\9\9main()\ +\9\9\9end )\ +\9\9end\ +\9end\ +end\ +local _ok, result = pcall( checkConfig )"; } - - -local function formatFS() - local function mkdir(dir) - if not fs.exists(dir) then fs.makeDir(dir) end - end - if fs.exists("AxiomUI") then - for k, v in pairs(fs.list("AxiomUI")) do - if not fs.exists(v) then - fs.move("AxiomUI/"..v, v) - print("AxiomUI/"..v.." -> "..v) - else - print("AxiomUI/"..v.." -x>") - end - end - fs.delete("AxiomUI") - print("Delete extra files?") - if read() == "y" then - fs.delete("install.lua") - fs.delete("README.md") - end - else - error("formatFS failed") - end +if shell.getRunningProgram() ~= "startup" then + if fs.exists( "startup" ) then + fs.move( "startup", "__oldstartup" ) + end + fs.move( shell.getRunningProgram(), "startup" ) + os.reboot() +--[[else + local protecting = { "startup", ".MetroOSSecureLock.cfg" } + local oldCopy = fs.copy + local oldMove = fs.move + local oldDelete = fs.delete + local oldEdit = fs.open + fs.delete = function( path ) + for k, v in pairs( protecting ) do + if fs.getName( path ) == v then + return error( "Cannot remove MetroOS", 0 ) + end + end + return oldDelete( path ) + end + fs.open = function( path, method, pass ) + if method == "r" then + return oldEdit( path, method ) + else + return error( "Cannot edit MetroOS", 0 ) + end + end + fs.move = function( path, newPath ) + for k, v in pairs( protecting ) do + if fs.getName( path ) == v then + return error( "Cannot move MetroOS", 0 ) + end + end + return oldMove( path, newPath ) + end + fs.copy = function( path, newPath ) + for k, v in pairs( protecting ) do + if fs.getName( path ) == v then + return error( "Cannot copy MetroOS", 0 ) + end + end + return oldCopy( path, newPath ) + end--]] end -local function wget(url, file) - local data = http.get(url) - data = data.readAll() - local file_handle = fs.open(file,"w") - file_handle.write(data) - file_handle.close() +if not fs.exists "Flare" then + print "Downloading Flare" + local h = http.get "https://pastebin.com/raw/SD25GhYf" + if h then + local f, err = load( h.readAll(), "installer", nil, _ENV or getfenv() ) + h.close() + f() + else + return error( "Cannot install Flare", 0 ) + end end -function selector(y,option) - term.setCursorPos(1,y) - for k,v in ipairs(branches) do - if k == option then - write(v.. " <-") - term.setCursorPos(1,y+k) - else - write(v.. " ") - term.setCursorPos(1,y+k) - end - end +local loader +local h = fs.open( "Flare/run.lua", "r" ) +if h then + loader = h.readAll() + h.close() +else + error( "failed to read Flare", 0 ) end -local version = os.version() -if version == "CraftOS 1.5" then - error("Axiom is not compatible with "..version.."!") +local f, err = load( loader, "Flare", nil, _ENV or getfenv() ) +if not f then + error( "there was a problem with Flare!: " .. err, 0 ) end -print("Axiom UI CE Installer") -print("Select a branch using arrow keys") -local x,y = term.getCursorPos() -if y > 17 then - shell.run("clear") - print("Axiom UI CE Installer") - print("Select a branch using arrow keys") - x,y = term.getCursorPos() -end -selector(y,1) -local user = "nothjarnan" -local branch = 1 -while(true) do - local e,k,h = os.pullEvent( "key" ) - if k == keys.up then - if branch > 1 then - branch = branch - 1 - selector(y,branch) - end - end - if k == keys.down then - if branch < #branches then - branch = branch + 1 - selector(y,branch) - end - end - if k == keys.enter then - branch = branches[branch] - print("Branch selected: "..branch) - print("Starting installation") - break - end -end -wget("http://www.pastebin.com/raw/w5zkvysi",".gitget") -shell.run(".gitget "..user.." axiom-opensource "..branch.." AxiomUI") -formatFS() -print("Installation completed!") +f( files, "init" ) From 6661bd7f7439a3f3bc8c2a15a62f3276ce4b83ee Mon Sep 17 00:00:00 2001 From: OutragedMetro Date: Fri, 15 Apr 2022 20:29:22 -0600 Subject: [PATCH 068/125] Update programVersions --- programVersions | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/programVersions b/programVersions index 4702ac2..81099d7 100644 --- a/programVersions +++ b/programVersions @@ -17,13 +17,22 @@ ["Author"]="Outraged Security .INC", ["Package"]="Outraged Security .INC", }, - ["MetroOS"]={ + ["Axiom"]={ ["GitURL"]="https://raw.githubusercontent.com/nothjarnan/axiom/master/install.lua install.lua", ["Version"]=2.94, ["Type"]="program", - ["Name"]="Outraged Security .INC Secure OS Downloader", + ["Name"]="Axiom Secure OS Downloader", ["Description"]="Downloads A Secure OS Program from Outraged Security .INC Software Centre", - ["Author"]="Outraged Security .INC", + ["Author"]="Nothjarnan", + ["Package"]="Outraged Security .INC", + }, + ["MetroOS"]={ + ["GitURL"]="https://raw.githubusercontent.com/rservices/darkprograms/darkprograms/MetroOS/MetroOS", + ["Version"]=2.94, + ["Type"]="program", + ["Name"]="Metro Secure OS Downloader", + ["Description"]="Downloads A Secure OS Program from Outraged Security .INC Software Centre", + ["Author"]="OutragedMetro", ["Package"]="Outraged Security .INC", }, ["dark"]={ From e7484b9400a95d331f0f87b6eaa102d57ed589b8 Mon Sep 17 00:00:00 2001 From: OutragedMetro Date: Fri, 15 Apr 2022 20:34:06 -0600 Subject: [PATCH 069/125] Create doorlock --- Secure OS/doorlock | 1348 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1348 insertions(+) create mode 100644 Secure OS/doorlock diff --git a/Secure OS/doorlock b/Secure OS/doorlock new file mode 100644 index 0000000..1ad7091 --- /dev/null +++ b/Secure OS/doorlock @@ -0,0 +1,1348 @@ +tArgs = {...} + +if OneOS then + --running under OneOS + OneOS.ToolBarColour = colours.white + OneOS.ToolBarTextColour = colours.grey +end + +local _w, _h = term.getSize() + +local round = function(num, idp) + local mult = 10^(idp or 0) + return math.floor(num * mult + 0.5) / mult +end + +InterfaceElements = {} + +Drawing = { + + Screen = { + Width = _w, + Height = _h + }, + + DrawCharacters = function (x, y, characters, textColour,bgColour) + Drawing.WriteStringToBuffer(x, y, characters, textColour, bgColour) + end, + + DrawBlankArea = function (x, y, w, h, colour) + Drawing.DrawArea (x, y, w, h, " ", 1, colour) + end, + + DrawArea = function (x, y, w, h, character, textColour, bgColour) + --width must be greater than 1, other wise we get a stack overflow + if w < 0 then + w = w * -1 + elseif w == 0 then + w = 1 + end + + for ix = 1, w do + local currX = x + ix - 1 + for iy = 1, h do + local currY = y + iy - 1 + Drawing.WriteToBuffer(currX, currY, character, textColour, bgColour) + end + end + end, + + DrawImage = function(_x,_y,tImage, w, h) + if tImage then + for y = 1, h do + if not tImage[y] then + break + end + for x = 1, w do + if not tImage[y][x] then + break + end + local bgColour = tImage[y][x] + local textColour = tImage.textcol[y][x] or colours.white + local char = tImage.text[y][x] + Drawing.WriteToBuffer(x+_x-1, y+_y-1, char, textColour, bgColour) + end + end + elseif w and h then + Drawing.DrawBlankArea(x, y, w, h, colours.green) + end + end, + --using .nft + LoadImage = function(path) + local image = { + text = {}, + textcol = {} + } + local fs = fs + if OneOS then + fs = OneOS.FS + end + if fs.exists(path) then + local _open = io.open + if OneOS then + _open = OneOS.IO.open + end + local file = _open(path, "r") + local sLine = file:read() + local num = 1 + while sLine do + table.insert(image, num, {}) + table.insert(image.text, num, {}) + table.insert(image.textcol, num, {}) + + --As we're no longer 1-1, we keep track of what index to write to + local writeIndex = 1 + --Tells us if we've hit a 30 or 31 (BG and FG respectively)- next char specifies the curr colour + local bgNext, fgNext = false, false + --The current background and foreground colours + local currBG, currFG = nil,nil + for i=1,#sLine do + local nextChar = string.sub(sLine, i, i) + if nextChar:byte() == 30 then + bgNext = true + elseif nextChar:byte() == 31 then + fgNext = true + elseif bgNext then + currBG = Drawing.GetColour(nextChar) + bgNext = false + elseif fgNext then + currFG = Drawing.GetColour(nextChar) + fgNext = false + else + if nextChar ~= " " and currFG == nil then + currFG = colours.white + end + image[num][writeIndex] = currBG + image.textcol[num][writeIndex] = currFG + image.text[num][writeIndex] = nextChar + writeIndex = writeIndex + 1 + end + end + num = num+1 + sLine = file:read() + end + file:close() + end + return image + end, + + DrawCharactersCenter = function(x, y, w, h, characters, textColour,bgColour) + w = w or Drawing.Screen.Width + h = h or Drawing.Screen.Height + x = x or 0 + y = y or 0 + x = math.ceil((w - #characters) / 2) + x + y = math.floor(h / 2) + y + + Drawing.DrawCharacters(x, y, characters, textColour, bgColour) + end, + + GetColour = function(hex) + if hex == ' ' then + return colours.transparent + end + local value = tonumber(hex, 16) + if not value then return nil end + value = math.pow(2,value) + return value + end, + + Clear = function (_colour) + _colour = _colour or colours.black + Drawing.ClearBuffer() + Drawing.DrawBlankArea(1, 1, Drawing.Screen.Width, Drawing.Screen.Height, _colour) + end, + + Buffer = {}, + BackBuffer = {}, + + DrawBuffer = function() + for y,row in pairs(Drawing.Buffer) do + for x,pixel in pairs(row) do + local shouldDraw = true + local hasBackBuffer = true + if Drawing.BackBuffer[y] == nil or Drawing.BackBuffer[y][x] == nil or #Drawing.BackBuffer[y][x] ~= 3 then + hasBackBuffer = false + end + if hasBackBuffer and Drawing.BackBuffer[y][x][1] == Drawing.Buffer[y][x][1] and Drawing.BackBuffer[y][x][2] == Drawing.Buffer[y][x][2] and Drawing.BackBuffer[y][x][3] == Drawing.Buffer[y][x][3] then + shouldDraw = false + end + if shouldDraw then + term.setBackgroundColour(pixel[3]) + term.setTextColour(pixel[2]) + term.setCursorPos(x, y) + term.write(pixel[1]) + end + end + end + Drawing.BackBuffer = Drawing.Buffer + Drawing.Buffer = {} + term.setCursorPos(1,1) + end, + + ClearBuffer = function() + Drawing.Buffer = {} + end, + + WriteStringToBuffer = function (x, y, characters, textColour,bgColour) + for i = 1, #characters do + local character = characters:sub(i,i) + Drawing.WriteToBuffer(x + i - 1, y, character, textColour, bgColour) + end + end, + + WriteToBuffer = function(x, y, character, textColour,bgColour) + x = round(x) + y = round(y) + if bgColour == colours.transparent then + Drawing.Buffer[y] = Drawing.Buffer[y] or {} + Drawing.Buffer[y][x] = Drawing.Buffer[y][x] or {"", colours.white, colours.black} + Drawing.Buffer[y][x][1] = character + Drawing.Buffer[y][x][2] = textColour + else + Drawing.Buffer[y] = Drawing.Buffer[y] or {} + Drawing.Buffer[y][x] = {character, textColour, bgColour} + end + end, +} + +Current = { + Document = nil, + TextInput = nil, + CursorPos = {1,1}, + CursorColour = colours.black, + Selection = {8, 36}, + Window = nil, + HeaderText = '', + StatusText = '', + StatusColour = colours.grey, + StatusScreen = true, + ButtonOne = nil, + ButtonTwo = nil, + Locked = false, + Page = '', + PageControls = {} +} + +isRunning = true + +Events = {} + +Button = { + X = 1, + Y = 1, + Width = 0, + Height = 0, + BackgroundColour = colours.lightGrey, + TextColour = colours.white, + ActiveBackgroundColour = colours.lightGrey, + Text = "", + Parent = nil, + _Click = nil, + Toggle = nil, + + AbsolutePosition = function(self) + return self.Parent:AbsolutePosition() + end, + + Draw = function(self) + local bg = self.BackgroundColour + local tc = self.TextColour + if type(bg) == 'function' then + bg = bg() + end + + if self.Toggle then + tc = colours.white + bg = self.ActiveBackgroundColour + end + + local pos = GetAbsolutePosition(self) + Drawing.DrawBlankArea(pos.X, pos.Y, self.Width, self.Height, bg) + Drawing.DrawCharactersCenter(pos.X, pos.Y, self.Width, self.Height, self.Text, tc, bg) + end, + + Initialise = function(self, x, y, width, height, backgroundColour, parent, click, text, textColour, toggle, activeBackgroundColour) + local new = {} -- the new instance + setmetatable( new, {__index = self} ) + height = height or 1 + new.Width = width or #text + 2 + new.Height = height + new.Y = y + new.X = x + new.Text = text or "" + new.BackgroundColour = backgroundColour or colours.lightGrey + new.TextColour = textColour or colours.white + new.ActiveBackgroundColour = activeBackgroundColour or colours.lightBlue + new.Parent = parent + new._Click = click + new.Toggle = toggle + return new + end, + + Click = function(self, side, x, y) + if self._Click then + if self:_Click(side, x, y, not self.Toggle) ~= false and self.Toggle ~= nil then + self.Toggle = not self.Toggle + Draw() + end + return true + else + return false + end + end +} + +Label = { + X = 1, + Y = 1, + Width = 0, + Height = 0, + BackgroundColour = colours.lightGrey, + TextColour = colours.white, + Text = "", + Parent = nil, + + AbsolutePosition = function(self) + return self.Parent:AbsolutePosition() + end, + + Draw = function(self) + local bg = self.BackgroundColour + local tc = self.TextColour + + if self.Toggle then + tc = UIColours.MenuBarActive + bg = self.ActiveBackgroundColour + end + + local pos = GetAbsolutePosition(self) + Drawing.DrawCharacters(pos.X, pos.Y, self.Text, self.TextColour, self.BackgroundColour) + end, + + Initialise = function(self, x, y, text, textColour, backgroundColour, parent) + local new = {} -- the new instance + setmetatable( new, {__index = self} ) + height = height or 1 + new.Width = width or #text + 2 + new.Height = height + new.Y = y + new.X = x + new.Text = text or "" + new.BackgroundColour = backgroundColour or colours.white + new.TextColour = textColour or colours.black + new.Parent = parent + return new + end, + + Click = function(self, side, x, y) + return false + end +} + +TextBox = { + X = 1, + Y = 1, + Width = 0, + Height = 0, + BackgroundColour = colours.lightGrey, + TextColour = colours.black, + Parent = nil, + TextInput = nil, + Placeholder = '', + + AbsolutePosition = function(self) + return self.Parent:AbsolutePosition() + end, + + Draw = function(self) + local pos = GetAbsolutePosition(self) + Drawing.DrawBlankArea(pos.X, pos.Y, self.Width, self.Height, self.BackgroundColour) + local text = self.TextInput.Value + if #tostring(text) > (self.Width - 2) then + text = text:sub(#text-(self.Width - 3)) + if Current.TextInput == self.TextInput then + Current.CursorPos = {pos.X + 1 + self.Width-2, pos.Y} + end + else + if Current.TextInput == self.TextInput then + Current.CursorPos = {pos.X + 1 + self.TextInput.CursorPos, pos.Y} + end + end + + if #tostring(text) == 0 then + Drawing.DrawCharacters(pos.X + 1, pos.Y, self.Placeholder, colours.lightGrey, self.BackgroundColour) + else + Drawing.DrawCharacters(pos.X + 1, pos.Y, text, self.TextColour, self.BackgroundColour) + end + + term.setCursorBlink(true) + + Current.CursorColour = self.TextColour + end, + + Initialise = function(self, x, y, width, height, parent, text, backgroundColour, textColour, done, numerical) + local new = {} -- the new instance + setmetatable( new, {__index = self} ) + height = height or 1 + new.Width = width or #text + 2 + new.Height = height + new.Y = y + new.X = x + new.TextInput = TextInput:Initialise(text or '', function(key) + if done then + done(key) + end + Draw() + end, numerical) + new.BackgroundColour = backgroundColour or colours.lightGrey + new.TextColour = textColour or colours.black + new.Parent = parent + return new + end, + + Click = function(self, side, x, y) + Current.Input = self.TextInput + self:Draw() + end +} + +TextInput = { + Value = "", + Change = nil, + CursorPos = nil, + Numerical = false, + IsDocument = nil, + + Initialise = function(self, value, change, numerical, isDocument) + local new = {} -- the new instance + setmetatable( new, {__index = self} ) + new.Value = tostring(value) + new.Change = change + new.CursorPos = #tostring(value) + new.Numerical = numerical + new.IsDocument = isDocument or false + return new + end, + + Insert = function(self, str) + if self.Numerical then + str = tostring(tonumber(str)) + end + + local selection = OrderSelection() + + if self.IsDocument and selection then + self.Value = string.sub(self.Value, 1, selection[1]-1) .. str .. string.sub( self.Value, selection[2]+2) + self.CursorPos = selection[1] + Current.Selection = nil + else + local _, newLineAdjust = string.gsub(self.Value:sub(1, self.CursorPos), '\n','') + + self.Value = string.sub(self.Value, 1, self.CursorPos + newLineAdjust) .. str .. string.sub( self.Value, self.CursorPos + 1 + newLineAdjust) + self.CursorPos = self.CursorPos + 1 + end + + self.Change(key) + end, + + Extract = function(self, remove) + local selection = OrderSelection() + if self.IsDocument and selection then + local _, newLineAdjust = string.gsub(self.Value:sub(selection[1], selection[2]), '\n','') + local str = string.sub(self.Value, selection[1], selection[2]+1+newLineAdjust) + if remove then + self.Value = string.sub(self.Value, 1, selection[1]-1) .. string.sub( self.Value, selection[2]+2+newLineAdjust) + self.CursorPos = selection[1] - 1 + Current.Selection = nil + end + return str + end + end, + + Char = function(self, char) + if char == 'nil' then + return + end + self:Insert(char) + end, + + Key = function(self, key) + if key == keys.enter then + if self.IsDocument then + self.Value = string.sub(self.Value, 1, self.CursorPos ) .. '\n' .. string.sub( self.Value, self.CursorPos + 1 ) + self.CursorPos = self.CursorPos + 1 + end + self.Change(key) + elseif key == keys.left then + -- Left + if self.CursorPos > 0 then + local colShift = FindColours(string.sub( self.Value, self.CursorPos, self.CursorPos)) + self.CursorPos = self.CursorPos - 1 - colShift + self.Change(key) + end + + elseif key == keys.right then + -- Right + if self.CursorPos < string.len(self.Value) then + local colShift = FindColours(string.sub( self.Value, self.CursorPos+1, self.CursorPos+1)) + self.CursorPos = self.CursorPos + 1 + colShift + self.Change(key) + end + + elseif key == keys.backspace then + -- Backspace + if self.IsDocument and Current.Selection then + self:Extract(true) + self.Change(key) + elseif self.CursorPos > 0 then + local colShift = FindColours(string.sub( self.Value, self.CursorPos, self.CursorPos)) + local _, newLineAdjust = string.gsub(self.Value:sub(1, self.CursorPos), '\n','') + + self.Value = string.sub( self.Value, 1, self.CursorPos - 1 - colShift + newLineAdjust) .. string.sub( self.Value, self.CursorPos + 1 - colShift + newLineAdjust) + self.CursorPos = self.CursorPos - 1 - colShift + self.Change(key) + end + elseif key == keys.home then + -- Home + self.CursorPos = 0 + self.Change(key) + elseif key == keys.delete then + if self.IsDocument and Current.Selection then + self:Extract(true) + self.Change(key) + elseif self.CursorPos < string.len(self.Value) then + self.Value = string.sub( self.Value, 1, self.CursorPos ) .. string.sub( self.Value, self.CursorPos + 2 ) + self.Change(key) + end + elseif key == keys["end"] then + -- End + self.CursorPos = string.len(self.Value) + self.Change(key) + elseif key == keys.up and self.IsDocument then + -- Up + if Current.Document.CursorPos then + local page = Current.Document.Pages[Current.Document.CursorPos.Page] + self.CursorPos = page:GetCursorPosFromPoint(Current.Document.CursorPos.Collum + page.MarginX, Current.Document.CursorPos.Line - page.MarginY - 1 + Current.Document.ScrollBar.Scroll, true) + self.Change(key) + end + elseif key == keys.down and self.IsDocument then + -- Down + if Current.Document.CursorPos then + local page = Current.Document.Pages[Current.Document.CursorPos.Page] + self.CursorPos = page:GetCursorPosFromPoint(Current.Document.CursorPos.Collum + page.MarginX, Current.Document.CursorPos.Line - page.MarginY + 1 + Current.Document.ScrollBar.Scroll, true) + self.Change(key) + end + end + end +} + +local Capitalise = function(str) + return str:sub(1, 1):upper() .. str:sub(2, -1) +end + + +local getNames = peripheral.getNames or function() + local tResults = {} + for n,sSide in ipairs( rs.getSides() ) do + if peripheral.isPresent( sSide ) then + table.insert( tResults, sSide ) + local isWireless = false + if not pcall(function()isWireless = peripheral.call(sSide, 'isWireless') end) then + isWireless = true + end + if peripheral.getType( sSide ) == "modem" and not isWireless then + local tRemote = peripheral.call( sSide, "getNamesRemote" ) + for n,sName in ipairs( tRemote ) do + table.insert( tResults, sName ) + end + end + end + end + return tResults +end + +Peripheral = { + GetPeripheral = function(_type) + for i, p in ipairs(Peripheral.GetPeripherals()) do + if p.Type == _type then + return p + end + end + end, + + Call = function(type, ...) + local tArgs = {...} + local p = Peripheral.GetPeripheral(type) + peripheral.call(p.Side, unpack(tArgs)) + end, + + GetPeripherals = function(filterType) + local peripherals = {} + for i, side in ipairs(getNames()) do + local name = peripheral.getType(side):gsub("^%l", string.upper) + local code = string.upper(side:sub(1,1)) + if side:find('_') then + code = side:sub(side:find('_')+1) + end + + local dupe = false + for i, v in ipairs(peripherals) do + if v[1] == name .. ' ' .. code then + dupe = true + end + end + + if not dupe then + local _type = peripheral.getType(side) + local isWireless = false + if _type == 'modem' then + if not pcall(function()isWireless = peripheral.call(sSide, 'isWireless') end) then + isWireless = true + end + if isWireless then + _type = 'wireless_modem' + name = 'W '..name + end + end + if not filterType or _type == filterType then + table.insert(peripherals, {Name = name:sub(1,8) .. ' '..code, Fullname = name .. ' ('..side:sub(1, 1):upper() .. side:sub(2, -1)..')', Side = side, Type = _type, Wireless = isWireless}) + end + end + end + return peripherals + end, + + PresentNamed = function(name) + return peripheral.isPresent(name) + end, + + CallType = function(type, ...) + local tArgs = {...} + local p = Peripheral.GetPeripheral(type) + return peripheral.call(p.Side, unpack(tArgs)) + end, + + CallNamed = function(name, ...) + local tArgs = {...} + return peripheral.call(name, unpack(tArgs)) + end +} + +Wireless = { + Channels = { + UltimateDoorlockPing = 4210, + UltimateDoorlockRequest = 4211, + UltimateDoorlockRequestReply = 4212, + }, + + isOpen = function(channel) + return Peripheral.CallType('wireless_modem', 'isOpen', channel) + end, + + Open = function(channel) + if not Wireless.isOpen(channel) then + Peripheral.CallType('wireless_modem', 'open', channel) + end + end, + + close = function(channel) + Peripheral.CallType('wireless_modem', 'close', channel) + end, + + closeAll = function() + Peripheral.CallType('wireless_modem', 'closeAll') + end, + + transmit = function(channel, replyChannel, message) + Peripheral.CallType('wireless_modem', 'transmit', channel, replyChannel, textutils.serialize(message)) + end, + + Present = function() + if Peripheral.GetPeripheral('wireless_modem') == nil then + return false + else + return true + end + end, + + FormatMessage = function(message, messageID, destinationID) + return { + content = textutils.serialize(message), + senderID = os.getComputerID(), + senderName = os.getComputerLabel(), + channel = channel, + replyChannel = reply, + messageID = messageID or math.random(10000), + destinationID = destinationID + } + end, + + Timeout = function(func, time) + time = time or 1 + parallel.waitForAny(func, function() + sleep(time) + --log('Timeout!'..time) + end) + end, + + RecieveMessage = function(_channel, messageID, timeout) + open(_channel) + local done = false + local event, side, channel, replyChannel, message = nil + Timeout(function() + while not done do + event, side, channel, replyChannel, message = os.pullEvent('modem_message') + if channel ~= _channel then + event, side, channel, replyChannel, message = nil + else + message = textutils.unserialize(message) + message.content = textutils.unserialize(message.content) + if messageID and messageID ~= message.messageID or (message.destinationID ~= nil and message.destinationID ~= os.getComputerID()) then + event, side, channel, replyChannel, message = nil + else + done = true + end + end + end + end, + timeout) + return event, side, channel, replyChannel, message + end, + + Initialise = function() + if Wireless.Present() then + for i, c in pairs(Wireless.Channels) do + Wireless.Open(c) + end + end + end, + + HandleMessage = function(event, side, channel, replyChannel, message, distance) + message = textutils.unserialize(message) + message.content = textutils.unserialize(message.content) + + if channel == Wireless.Channels.Ping then + if message.content == 'Ping!' then + SendMessage(replyChannel, 'Pong!', nil, message.messageID) + end + elseif message.destinationID ~= nil and message.destinationID ~= os.getComputerID() then + elseif Wireless.Responder then + Wireless.Responder(event, side, channel, replyChannel, message, distance) + end + end, + + SendMessage = function(channel, message, reply, messageID, destinationID) + reply = reply or channel + 1 + Wireless.Open(channel) + Wireless.Open(reply) + local _message = Wireless.FormatMessage(message, messageID, destinationID) + Wireless.transmit(channel, reply, _message) + return _message + end, + + Ping = function() + local message = SendMessage(Channels.Ping, 'Ping!', Channels.PingReply) + RecieveMessage(Channels.PingReply, message.messageID) + end +} + +function GetAbsolutePosition(object) + local obj = object + local i = 0 + local x = 1 + local y = 1 + while true do + x = x + obj.X - 1 + y = y + obj.Y - 1 + + if not obj.Parent then + return {X = x, Y = y} + end + + obj = obj.Parent + + if i > 32 then + return {X = 1, Y = 1} + end + + i = i + 1 + end + +end + +function Draw() + Drawing.Clear(colours.white) + + if Current.StatusScreen then + Drawing.DrawCharactersCenter(1, -2, nil, nil, Current.HeaderText, colours.blue, colours.white) + Drawing.DrawCharactersCenter(1, -1, nil, nil, 'by oeed', colours.lightGrey, colours.white) + Drawing.DrawCharactersCenter(1, 1, nil, nil, Current.StatusText, Current.StatusColour, colours.white) + end + + if Current.ButtonOne then + Current.ButtonOne:Draw() + end + + if Current.ButtonTwo then + Current.ButtonTwo:Draw() + end + + for i, v in ipairs(Current.PageControls) do + v:Draw() + end + + Drawing.DrawBuffer() + + if Current.TextInput and Current.CursorPos and not Current.Menu and not(Current.Window and Current.Document and Current.TextInput == Current.Document.TextInput) and Current.CursorPos[2] > 1 then + term.setCursorPos(Current.CursorPos[1], Current.CursorPos[2]) + term.setCursorBlink(true) + term.setTextColour(Current.CursorColour) + else + term.setCursorBlink(false) + end +end +MainDraw = Draw + +function GenerateFingerprint() + local str = "" + for _ = 1, 256 do + local char = math.random(32, 126) + --if char == 96 then char = math.random(32, 95) end + str = str .. string.char(char) + end + return str +end + +function MakeFingerprint() + local h = fs.open('.fingerprint', 'w') + if h then + h.write(GenerateFingerprint()) + end + h.close() + Current.Fingerprint = str +end + +local drawTimer = nil +function SetText(header, status, colour, isReset) + if header then + Current.HeaderText = header + end + if status then + Current.StatusText = status + end + if colour then + Current.StatusColour = colour + end + Draw() + if not isReset then + statusResetTimer = os.startTimer(2) + end +end + +function ResetStatus() + if pocket then + if Current.Locked then + SetText('Ultimate Door Lock', 'Add Wireless Modem to PDA', colours.red, true) + else + SetText('Ultimate Door Lock', 'Ready', colours.grey, true) + end + else + if Current.Locked then + SetText('Ultimate Door Lock', ' Attach a Wireless Modem then reboot', colours.red, true) + else + SetText('Ultimate Door Lock', 'Ready', colours.grey, true) + end + end +end + +function ResetPage() + Wireless.Responder = function()end + pingTimer = nil + Current.PageControls = nil + Current.StatusScreen = false + Current.ButtonOne = nil + Current.ButtonTwo = nil + Current.PageControls = {} + CloseDoor() +end + +function PocketInitialise() + Current.ButtonOne = Button:Initialise(Drawing.Screen.Width - 6, Drawing.Screen.Height - 1, nil, nil, nil, nil, Quit, 'Quit', colours.black) + if not Wireless.Present() then + Current.Locked = true + ResetStatus() + return + end + Wireless.Initialise() + ResetStatus() + if fs.exists('.fingerprint') then + local h = fs.open('.fingerprint', 'r') + if h then + Current.Fingerprint = h.readAll() + else + MakeFingerprint() + end + h.close() + else + MakeFingerprint() + end + + Wireless.Responder = function(event, side, channel, replyChannel, message, distance) + if channel == Wireless.Channels.UltimateDoorlockPing then + Wireless.SendMessage(Wireless.Channels.UltimateDoorlockRequest, Current.Fingerprint, Wireless.Channels.UltimateDoorlockRequestReply, nil, message.senderID) + elseif channel == Wireless.Channels.UltimateDoorlockRequestReply then + if message.content == true then + SetText(nil, 'Opening Door', colours.green) + else + SetText(nil, ' Access Denied', colours.red) + end + end + end +end + +function FingerprintIsOnWhitelist(fingerprint) + if Current.Settings.Whitelist then + for i, f in ipairs(Current.Settings.Whitelist) do + if f == fingerprint then + return true + end + end + end + return false +end + +function SaveSettings() + Current.Settings = Current.Settings or {} + local h = fs.open('.settings', 'w') + if h then + h.write(textutils.serialize(Current.Settings)) + end + h.close() +end + +local closeDoorTimer = nil +function OpenDoor() + if Current.Settings and Current.Settings.RedstoneSide then + SetText(nil, 'Opening Door', colours.green) + redstone.setOutput(Current.Settings.RedstoneSide, true) + closeDoorTimer = os.startTimer(0.6) + end +end + +function CloseDoor() + if Current.Settings and Current.Settings.RedstoneSide then + if redstone.getOutput(Current.Settings.RedstoneSide) then + SetText(nil, 'Closing Door', colours.orange) + redstone.setOutput(Current.Settings.RedstoneSide, false) + end + end +end + +DefaultSettings = { + Whitelist = {}, + RedstoneSide = 'back', + Distance = 10 +} + +function RegisterPDA(event, drive) + if disk.hasData(drive) then + local _fs = fs + if OneOS then + _fs = OneOS.FS + end + local path = disk.getMountPath(drive) + local addStartup = true + if _fs.exists(path..'/System/') then + path = path..'/System/' + addStartup = false + end + local fingerprint = nil + if _fs.exists(path..'/.fingerprint') then + local h = _fs.open(path..'/.fingerprint', 'r') + if h then + local str = h.readAll() + if #str == 256 then + fingerprint = str + end + end + h.close() + end + if not fingerprint then + fingerprint = GenerateFingerprint() + local h = _fs.open(path..'/.fingerprint', 'w') + h.write(fingerprint) + h.close() + if addStartup then + local h = fs.open(shell.getRunningProgram(), 'r') + local startup = h.readAll() + h.close() + local h = _fs.open(path..'/startup', 'w') + h.write(startup) + h.close() + end + end + if not FingerprintIsOnWhitelist(fingerprint) then + table.insert(Current.Settings.Whitelist, fingerprint) + SaveSettings() + end + disk.eject(drive) + SetText(nil, 'Registered Pocket Computer', colours.green) + end +end + +function HostSetup() + ResetPage() + Current.Page = 'HostSetup' + Current.ButtonTwo = Button:Initialise(Drawing.Screen.Width - 6, Drawing.Screen.Height - 1, nil, nil, nil, nil, HostStatusPage, 'Save', colours.black) + if not Current.Settings then + Current.Settings = DefaultSettings + end + + local sideButtons = {} + local function resetSideToggle(self) + for i, v in ipairs(sideButtons) do + if v.Toggle ~= nil then + v.Toggle = false + end + end + Current.Settings.RedstoneSide = self.Text:lower() + SaveSettings() + end + + table.insert(Current.PageControls, Label:Initialise(2, 2, 'Redstone Side')) + sideButtons = { + Button:Initialise(2, 4, nil, nil, nil, nil, resetSideToggle, 'Back', colours.black, false, colours.green), + Button:Initialise(9, 4, nil, nil, nil, nil, resetSideToggle, 'Front', colours.black, false, colours.green), + Button:Initialise(2, 6, nil, nil, nil, nil, resetSideToggle, 'Left', colours.black, false, colours.green), + Button:Initialise(9, 6, nil, nil, nil, nil, resetSideToggle, 'Right', colours.black, false, colours.green), + Button:Initialise(2, 8, nil, nil, nil, nil, resetSideToggle, 'Top', colours.black, false, colours.green), + Button:Initialise(8, 8, nil, nil, nil, nil, resetSideToggle, 'Bottom', colours.black, false, colours.green) + } + for i, v in ipairs(sideButtons) do + if v.Text:lower() == Current.Settings.RedstoneSide then + v.Toggle = true + end + table.insert(Current.PageControls, v) + end + + local distanceButtons = {} + local function resetDistanceToggle(self) + for i, v in ipairs(distanceButtons) do + if v.Toggle ~= nil then + v.Toggle = false + end + end + if self.Text == 'Small' then + Current.Settings.Distance = 5 + elseif self.Text == 'Normal' then + Current.Settings.Distance = 10 + elseif self.Text == 'Far' then + Current.Settings.Distance = 15 + end + SaveSettings() + end + + table.insert(Current.PageControls, Label:Initialise(23, 2, 'Opening Distance')) + distanceButtons = { + Button:Initialise(23, 4, nil, nil, nil, nil, resetDistanceToggle, 'Small', colours.black, false, colours.green), + Button:Initialise(31, 4, nil, nil, nil, nil, resetDistanceToggle, 'Normal', colours.black, false, colours.green), + Button:Initialise(40, 4, nil, nil, nil, nil, resetDistanceToggle, 'Far', colours.black, false, colours.green) + } + for i, v in ipairs(distanceButtons) do + if v.Text == 'Small' and Current.Settings.Distance == 5 then + v.Toggle = true + elseif v.Text == 'Normal' and Current.Settings.Distance == 10 then + v.Toggle = true + elseif v.Text == 'Far' and Current.Settings.Distance == 15 then + v.Toggle = true + end + table.insert(Current.PageControls, v) + end + + table.insert(Current.PageControls, Label:Initialise(2, 10, 'Registered PDAs: '..#Current.Settings.Whitelist)) + table.insert(Current.PageControls, Button:Initialise(2, 12, nil, nil, nil, nil, function()Current.Settings.Whitelist = {}HostSetup()end, 'Unregister All', colours.black)) + + + table.insert(Current.PageControls, Label:Initialise(23, 6, 'Help', colours.black)) + local helpLines = { + Label:Initialise(23, 8, 'To register a new PDA simply', colours.black), + Label:Initialise(23, 9, 'place a Disk Drive next to', colours.black), + Label:Initialise(23, 10, 'the computer, then put the', colours.black), + Label:Initialise(23, 11, 'PDA in the Drive, it will', colours.black), + Label:Initialise(23, 12, 'register automatically. If', colours.black), + Label:Initialise(23, 13, 'it worked it will eject.', colours.black), + Label:Initialise(23, 15, 'Make sure you hide this', colours.red), + Label:Initialise(23, 16, 'computer away from the', colours.red), + Label:Initialise(23, 17, 'door! (other people)', colours.red) + } + for i, v in ipairs(helpLines) do + table.insert(Current.PageControls, v) + end + + + table.insert(Current.PageControls, Button:Initialise(2, 14, nil, nil, nil, nil, function() + for i = 1, 6 do + helpLines[i].TextColour = colours.green + end + end, 'Register New PDA', colours.black)) + +end + +function HostStatusPage() + ResetPage() + Current.Page = 'HostStatus' + Current.StatusScreen = true + Current.ButtonOne = Button:Initialise(Drawing.Screen.Width - 6, Drawing.Screen.Height - 1, nil, nil, nil, nil, Quit, 'Quit', colours.black) + Current.ButtonTwo = Button:Initialise(2, Drawing.Screen.Height - 1, nil, nil, nil, nil, HostSetup, 'Settings/Help', colours.black) + + Wireless.Responder = function(event, side, channel, replyChannel, message, distance) + if channel == Wireless.Channels.UltimateDoorlockRequest and distance < Current.Settings.Distance then + if FingerprintIsOnWhitelist(message.content) then + OpenDoor() + Wireless.SendMessage(Wireless.Channels.UltimateDoorlockRequestReply, true) + else + Wireless.SendMessage(Wireless.Channels.UltimateDoorlockRequestReply, false) + end + end + end + + PingPocketComputers() +end + +function HostInitialise() + if not Wireless.Present() then + Current.Locked = true + Current.ButtonOne = Button:Initialise(Drawing.Screen.Width - 6, Drawing.Screen.Height - 1, nil, nil, nil, nil, Quit, 'Quit', colours.black) + Current.ButtonTwo = Button:Initialise(2, Drawing.Screen.Height - 1, nil, nil, nil, nil, function()os.reboot()end, 'Reboot', colours.black) + ResetStatus() + return + end + Wireless.Initialise() + ResetStatus() + if fs.exists('.settings') then + local h = fs.open('.settings', 'r') + if h then + Current.Settings = textutils.unserialize(h.readAll()) + end + h.close() + HostStatusPage() + else + HostSetup() + end + if OneOS then + OneOS.CanClose = function() + CloseDoor() + return true + end + end +end + +local pingTimer = nil +function PingPocketComputers() + Wireless.SendMessage(Wireless.Channels.UltimateDoorlockPing, 'Ping!', Wireless.Channels.UltimateDoorlockRequest) + pingTimer = os.startTimer(0.5) +end + +function Initialise(arg) + EventRegister('mouse_click', TryClick) + EventRegister('mouse_drag', function(event, side, x, y)TryClick(event, side, x, y, true)end) + EventRegister('mouse_scroll', Scroll) + EventRegister('key', HandleKey) + EventRegister('char', HandleKey) + EventRegister('timer', Timer) + EventRegister('terminate', function(event) if Close() then error( "Terminated", 0 ) end end) + EventRegister('modem_message', Wireless.HandleMessage) + EventRegister('disk', RegisterPDA) + + if OneOS then + OneOS.RequestRunAtStartup() + end + + if pocket then + PocketInitialise() + else + HostInitialise() + end + + + Draw() + + EventHandler() +end + +function Timer(event, timer) + if timer == pingTimer then + PingPocketComputers() + elseif timer == closeDoorTimer then + CloseDoor() + elseif timer == statusResetTimer then + ResetStatus() + end +end + +local ignoreNextChar = false +function HandleKey(...) + local args = {...} + local event = args[1] + local keychar = args[2] + --[[ + --Mac left command character + if event == 'key' and keychar == keys.leftCtrl or keychar == keys.rightCtrl or keychar == 219 then + isControlPushed = true + controlPushedTimer = os.startTimer(0.5) + elseif isControlPushed then + if event == 'key' then + if CheckKeyboardShortcut(keychar) then + isControlPushed = false + ignoreNextChar = true + end + end + elseif ignoreNextChar then + ignoreNextChar = false + elseif Current.TextInput then + if event == 'char' then + Current.TextInput:Char(keychar) + elseif event == 'key' then + Current.TextInput:Key(keychar) + end + end + ]]-- +end + +--[[ + Check if the given object falls under the click coordinates +]]-- +function CheckClick(object, x, y) + if object.X <= x and object.Y <= y and object.X + object.Width > x and object.Y + object.Height > y then + return true + end +end + +--[[ + Attempt to clicka given object +]]-- +function DoClick(object, side, x, y, drag) + local obj = GetAbsolutePosition(object) + obj.Width = object.Width + obj.Height = object.Height + if object and CheckClick(obj, x, y) then + return object:Click(side, x - object.X + 1, y - object.Y + 1, drag) + end +end + +--[[ + Try to click at the given coordinates +]]-- +function TryClick(event, side, x, y, drag) + if Current.ButtonOne then + if DoClick(Current.ButtonOne, side, x, y, drag) then + Draw() + return + end + end + + if Current.ButtonTwo then + if DoClick(Current.ButtonTwo, side, x, y, drag) then + Draw() + return + end + end + + for i, v in ipairs(Current.PageControls) do + if DoClick(v, side, x, y, drag) then + Draw() + return + end + end + + Draw() +end + +function Scroll(event, direction, x, y) + if Current.Window and Current.Window.OpenButton then + Current.Document.Scroll = Current.Document.Scroll + direction + if Current.Window.Scroll < 0 then + Current.Window.Scroll = 0 + elseif Current.Window.Scroll > Current.Window.MaxScroll then + Current.Window.Scroll = Current.Window.MaxScroll + end + Draw() + elseif Current.ScrollBar then + if Current.ScrollBar:DoScroll(direction*2) then + Draw() + end + end +end + +--[[ + Registers functions to run on certain events +]]-- +function EventRegister(event, func) + if not Events[event] then + Events[event] = {} + end + + table.insert(Events[event], func) +end + +--[[ + The main loop event handler, runs registered event functinos +]]-- +function EventHandler() + while isRunning do + local event, arg1, arg2, arg3, arg4, arg5, arg6 = os.pullEventRaw() + if Events[event] then + for i, e in ipairs(Events[event]) do + e(event, arg1, arg2, arg3, arg4, arg5, arg6) + end + end + end +end + +function Quit() + isRunning = false + term.setCursorPos(1,1) + term.setBackgroundColour(colours.black) + term.setTextColour(colours.white) + term.clear() + if OneOS then + OneOS.Close() + end +end + +if not term.current then -- if not 1.6 + print('Because it requires pocket computers, Ultimate Door Lock requires ComputerCraft 1.6. Please update to 1.6 to use Ultimate Door Lock.') +elseif not (OneOS and pocket) and term.isColor and term.isColor() then + -- If the program crashes close the door and reboot + local _, err = pcall(Initialise) + if err then + CloseDoor() + term.setCursorPos(1,1) + term.setBackgroundColour(colours.black) + term.setTextColour(colours.white) + term.clear() + print('Ultimate Door Lock has crashed') + print('To maintain security, the computer will reboot.') + print('If you are seeing this alot try turning off all Pocket Computers or reinstall.') + print() + print('Error:') + printError(err) + sleep(5) + os.reboot() + end +elseif OneOS and pocket then + term.setCursorPos(1,3) + term.setBackgroundColour(colours.white) + term.setTextColour(colours.blue) + term.clear() + print('OneOS already acts as a door key. Simply place your PDA in the door\'s disk drive to register it.') + print() + print('To setup a door, run this program on an advanced computer (non-pocket).') + print() + print('Click anywhere to quit') + os.pullEvent('mouse_click') + Quit() +else + print('Ultimate Door Lock requires an advanced (gold) computer or pocket computer.') +end From bbbb37eaa07e47816073c0cd45fdd43b322d58d1 Mon Sep 17 00:00:00 2001 From: OutragedMetro Date: Fri, 15 Apr 2022 20:42:23 -0600 Subject: [PATCH 070/125] Update programVersions --- programVersions | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/programVersions b/programVersions index 81099d7..656fd1b 100644 --- a/programVersions +++ b/programVersions @@ -24,7 +24,7 @@ ["Name"]="Axiom Secure OS Downloader", ["Description"]="Downloads A Secure OS Program from Outraged Security .INC Software Centre", ["Author"]="Nothjarnan", - ["Package"]="Outraged Security .INC", + ["Package"]="Standalone", }, ["MetroOS"]={ ["GitURL"]="https://raw.githubusercontent.com/rservices/darkprograms/darkprograms/MetroOS/MetroOS", @@ -32,8 +32,17 @@ ["Type"]="program", ["Name"]="Metro Secure OS Downloader", ["Description"]="Downloads A Secure OS Program from Outraged Security .INC Software Centre", - ["Author"]="OutragedMetro", + ["Author"]="Outraged Security .INC", ["Package"]="Outraged Security .INC", + }, + ["Pda Secure Door"]={ + ["GitURL"]="https://raw.githubusercontent.com/OutragedMetro/darkprograms/darkprograms/SecurePdaDoor/SecureDoor", + ["Version"]=2.94, + ["Type"]="program", + ["Name"]="Ultimate Door Lock", + ["Description"]="Downloads A Secure OS Program from Outraged Security .INC Software Centre", + ["Author"]="Oeed", + ["Package"]="Standalone", }, ["dark"]={ ["GitURL"]="https://raw.githubusercontent.com/rservices/darkprograms/darkprograms/api/dark.lua", From 394ad3cd544ae010897e0288f0b4a65dd2a40996 Mon Sep 17 00:00:00 2001 From: OutragedMetro Date: Fri, 15 Apr 2022 20:46:24 -0600 Subject: [PATCH 071/125] Update programVersions --- programVersions | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/programVersions b/programVersions index 656fd1b..796784f 100644 --- a/programVersions +++ b/programVersions @@ -40,7 +40,7 @@ ["Version"]=2.94, ["Type"]="program", ["Name"]="Ultimate Door Lock", - ["Description"]="Downloads A Secure OS Program from Outraged Security .INC Software Centre", + ["Description"]="Downloads A Secure Door Lock Program from Outraged Security .INC Software Centre", ["Author"]="Oeed", ["Package"]="Standalone", }, From 81de28086cdeed55eaee36943292167d80d77241 Mon Sep 17 00:00:00 2001 From: OutragedMetro Date: Fri, 15 Apr 2022 20:48:07 -0600 Subject: [PATCH 072/125] Update programVersions --- programVersions | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/programVersions b/programVersions index 796784f..fcfaf17 100644 --- a/programVersions +++ b/programVersions @@ -13,7 +13,7 @@ ["Version"]=4.26, ["Type"]="program", ["Name"]="Outraged Security .INC Client", - ["Description"]="DOutraged Security .INC Base Security Client", + ["Description"]="Outraged Security .INC Base Security Client", ["Author"]="Outraged Security .INC", ["Package"]="Outraged Security .INC", }, From 8089795170997f24de642706fe0089f5bc20cd9c Mon Sep 17 00:00:00 2001 From: OutragedMetro Date: Fri, 15 Apr 2022 20:49:30 -0600 Subject: [PATCH 073/125] Update programVersions --- programVersions | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/programVersions b/programVersions index fcfaf17..10795c0 100644 --- a/programVersions +++ b/programVersions @@ -35,7 +35,7 @@ ["Author"]="Outraged Security .INC", ["Package"]="Outraged Security .INC", }, - ["Pda Secure Door"]={ + ["PdaSecureDoor"]={ ["GitURL"]="https://raw.githubusercontent.com/OutragedMetro/darkprograms/darkprograms/SecurePdaDoor/SecureDoor", ["Version"]=2.94, ["Type"]="program", From 4ebb1efd1c26ae0e616bcc60b5a5c7c2688657ee Mon Sep 17 00:00:00 2001 From: OutragedMetro Date: Fri, 15 Apr 2022 20:52:13 -0600 Subject: [PATCH 074/125] Update programVersions --- programVersions | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/programVersions b/programVersions index 10795c0..ec647ff 100644 --- a/programVersions +++ b/programVersions @@ -35,9 +35,9 @@ ["Author"]="Outraged Security .INC", ["Package"]="Outraged Security .INC", }, - ["PdaSecureDoor"]={ + ["SecureDoor"]={ ["GitURL"]="https://raw.githubusercontent.com/OutragedMetro/darkprograms/darkprograms/SecurePdaDoor/SecureDoor", - ["Version"]=2.94, + ["Version"]=2.95, ["Type"]="program", ["Name"]="Ultimate Door Lock", ["Description"]="Downloads A Secure Door Lock Program from Outraged Security .INC Software Centre", From 104728aa93439f9c3ef3a9a2ffb86dcc51eeade5 Mon Sep 17 00:00:00 2001 From: OutragedMetro Date: Fri, 15 Apr 2022 20:57:36 -0600 Subject: [PATCH 075/125] Update SecureDoor --- SecurePdaDoor/SecureDoor | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SecurePdaDoor/SecureDoor b/SecurePdaDoor/SecureDoor index 1387970..9347821 100644 --- a/SecurePdaDoor/SecureDoor +++ b/SecurePdaDoor/SecureDoor @@ -776,7 +776,7 @@ function Draw() if Current.StatusScreen then Drawing.DrawCharactersCenter(1, -2, nil, nil, Current.HeaderText, colours.blue, colours.white) - Drawing.DrawCharactersCenter(1, -1, nil, nil, 'by oeed', colours.lightGrey, colours.white) + Drawing.DrawCharactersCenter(1, -1, nil, nil, 'by Outraged Security .INC', colours.lightGrey, colours.white) Drawing.DrawCharactersCenter(1, 1, nil, nil, Current.StatusText, Current.StatusColour, colours.white) end From ea570507a3a7363e5ed0b3d849769cd7ec2a6da3 Mon Sep 17 00:00:00 2001 From: OutragedMetro Date: Fri, 15 Apr 2022 20:58:13 -0600 Subject: [PATCH 076/125] Update programVersions --- programVersions | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/programVersions b/programVersions index ec647ff..d8d1cb2 100644 --- a/programVersions +++ b/programVersions @@ -41,7 +41,7 @@ ["Type"]="program", ["Name"]="Ultimate Door Lock", ["Description"]="Downloads A Secure Door Lock Program from Outraged Security .INC Software Centre", - ["Author"]="Oeed", + ["Author"]="Outraged Security .INC", ["Package"]="Standalone", }, ["dark"]={ From 7639b6e02518e2ea39918bb4608ece22522e5a51 Mon Sep 17 00:00:00 2001 From: OutragedMetro Date: Wed, 20 Apr 2022 20:11:13 -0600 Subject: [PATCH 077/125] Update programVersions --- programVersions | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/programVersions b/programVersions index d8d1cb2..e1bf19f 100644 --- a/programVersions +++ b/programVersions @@ -18,7 +18,7 @@ ["Package"]="Outraged Security .INC", }, ["Axiom"]={ - ["GitURL"]="https://raw.githubusercontent.com/nothjarnan/axiom/master/install.lua install.lua", + ["GitURL"]="https://raw.githubusercontent.com/nothjarnan/axiom/master/install.lua", ["Version"]=2.94, ["Type"]="program", ["Name"]="Axiom Secure OS Downloader", From d152b334e45882f21740709d6b4761d6112270d9 Mon Sep 17 00:00:00 2001 From: OutragedMetro Date: Thu, 21 Jul 2022 04:21:19 -0600 Subject: [PATCH 078/125] Update client.lua --- darksecurity/client.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/darksecurity/client.lua b/darksecurity/client.lua index 3c93d5e..e7bc751 100644 --- a/darksecurity/client.lua +++ b/darksecurity/client.lua @@ -1,4 +1,4 @@ ---Title: Dark Client +--Title: Outraged Security Client Version = 4.26 --Author: Darkrising (minecraft name djhannz) --Platform: ComputerCraft Lua Virtual Machine @@ -146,7 +146,7 @@ if fs.exists(".DarkC_conf") == false then term.clear() term.setCursorPos(1,1) - header("Dark Client Setup") + header("Outraged Security Client Setup") print("Computer's id is ".. os.getComputerID()) while true do From 4257e98450685cdde75a464624badec8bb9c6585 Mon Sep 17 00:00:00 2001 From: OutragedMetro Date: Thu, 21 Jul 2022 04:22:21 -0600 Subject: [PATCH 079/125] Update server.lua --- darksecurity/server.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/darksecurity/server.lua b/darksecurity/server.lua index 75ed5c0..e9d146a 100644 --- a/darksecurity/server.lua +++ b/darksecurity/server.lua @@ -1,4 +1,4 @@ ---Title: Dark Server +--Title: Outraged Security Server Version = 6.37 --Author: Darkrising (minecraft name djhannz) --Platform: ComputerCraft Lua Virtual Machine @@ -771,7 +771,7 @@ if fs.exists(".DarkS_conf") == false then dark.cs() config = {} masterdb = databaseNew() - header("Dark Server Setup") + header("Outraged Security Server Setup") print("Computer's id is ".. os.getComputerID()) repeat write("security level amount (must be a number): ") From b761273046ee2f8d93482ab063903046a3654b6e Mon Sep 17 00:00:00 2001 From: OutragedMetro Date: Thu, 21 Jul 2022 05:07:15 -0600 Subject: [PATCH 080/125] Update server.lua --- darksecurity/server.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/darksecurity/server.lua b/darksecurity/server.lua index e9d146a..7e7b137 100644 --- a/darksecurity/server.lua +++ b/darksecurity/server.lua @@ -324,6 +324,7 @@ Co = { header("Help") dark.printA("Press [1] to return to the main menu", 1, y) print("\nTo Navigate the settings menu and to add new users, Keycards, And passwords just follow the on screen prompts!") + print("\nOutraged Security INC prides them selves in providing top notch security to its customers for more support contact Outraged Security INC for a geek squad visit.") os.pullEvent("key") end, options = {"main"} From 3bb9353a90622fbdbab9ab1c2c8f0639b4da144b Mon Sep 17 00:00:00 2001 From: OutragedMetro Date: Thu, 21 Jul 2022 05:13:01 -0600 Subject: [PATCH 081/125] Update server.lua --- darksecurity/server.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/darksecurity/server.lua b/darksecurity/server.lua index 7e7b137..f9b565b 100644 --- a/darksecurity/server.lua +++ b/darksecurity/server.lua @@ -324,7 +324,7 @@ Co = { header("Help") dark.printA("Press [1] to return to the main menu", 1, y) print("\nTo Navigate the settings menu and to add new users, Keycards, And passwords just follow the on screen prompts!") - print("\nOutraged Security INC prides them selves in providing top notch security to its customers for more support contact Outraged Security INC for a geek squad visit.") + print("\nOutraged Security INC prides them selves in providing top notch security to its customers for more support contact Outraged Security INC. For installation a geek squad visit may be required by a professional.") os.pullEvent("key") end, options = {"main"} From deadceafd257418deb7fae15f52ae7dd48e5767e Mon Sep 17 00:00:00 2001 From: OutragedMetro Date: Wed, 22 Mar 2023 16:07:10 -0600 Subject: [PATCH 082/125] updated prograns --- programVersions | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/programVersions b/programVersions index e1bf19f..6506542 100644 --- a/programVersions +++ b/programVersions @@ -19,7 +19,7 @@ }, ["Axiom"]={ ["GitURL"]="https://raw.githubusercontent.com/nothjarnan/axiom/master/install.lua", - ["Version"]=2.94, + ["Version"]=2.95, ["Type"]="program", ["Name"]="Axiom Secure OS Downloader", ["Description"]="Downloads A Secure OS Program from Outraged Security .INC Software Centre", From 4ec0f1dfc57d86e3a478f827fc7c8eeb81e91d51 Mon Sep 17 00:00:00 2001 From: OutragedMetro Date: Fri, 28 Apr 2023 21:41:47 -0600 Subject: [PATCH 083/125] updated code Added some new features to met OS --- MetroOS/MetroOS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MetroOS/MetroOS b/MetroOS/MetroOS index 83011e6..a7aa6b6 100644 --- a/MetroOS/MetroOS +++ b/MetroOS/MetroOS @@ -27,7 +27,7 @@ local function numbertochars( n ) local s = tohex2( n % 256 ) for i = 1, 3 do n local function charstonumber( c ) local n = 0 for i = 1, 4 do n = n * 256 + fromhex2( c:sub( i * 2 - 1 ) ) end return n end\ local function newgarbage( length ) if length == 0 then return \"\" end return tohex2( math.random( 0, 255 ) ) .. newgarbage( length - 1 ) end\ local function stringkey( str ) local key = 0 for i = 1, #str do key = key * 256 + str:byte( i ) end return key end\ -local encrypt = {}\ +local encrypt = {}\ function encrypt.encrypt( text, key ) key = type( key ) == \"string\" and stringkey( key ) or key text = textutils.serialize( text ) if type( key ) ~= \"number\" then return error( \"expected number/string key, got \" .. type( key ) ) end local keys, garbage = keygen( key, #text ) local cipher = { numbertochars( #text ) } math.randomseed( os.clock() ) for i = 1, #text, blocksize do for n = 0, blocksize - 1 do if i + n <= #text then cipher[#cipher + 1] = tohex2( bit.bxor( text:byte( i + n ), keys[i] ) ) else break end end cipher[#cipher + 1] = newgarbage( garbage[i] ) end return table.concat( cipher ) end\ function encrypt.decrypt( cipher, key ) key = type( key ) == \"string\" and stringkey( key ) or key if type( cipher ) ~= \"string\" then return error( \"expected string cipher, got \" .. type( cipher ) ) end if type( key ) ~= \"number\" then return error( \"expected number/string key, got \" .. type( key ) ) end local length = charstonumber( cipher:sub( 1, 8 ) ) cipher = cipher:sub( 9 ) local keys, garbage = keygen( key, length ) local text = {} local i = 1 while #cipher > 0 do local block = cipher:sub( 1, math.min( #cipher - garbage[i] * 2, blocksize * 2 ) ) for n = 1, #block, 2 do text[#text + 1] = string.char( bit.bxor( fromhex2( block:sub( n ) ), keys[i] ) ) end cipher = cipher:sub( blocksize * 2 + garbage[i] * 2 + 1 ) i = i + blocksize end return textutils.unserialize( table.concat( text ) ) end\ local MOD = 2^32\ From 95504031fae9b55d3eb0f7658705cdcf68df1479 Mon Sep 17 00:00:00 2001 From: OutragedMetro Date: Fri, 28 Apr 2023 21:42:16 -0600 Subject: [PATCH 084/125] Update MetroOS --- MetroOS/MetroOS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MetroOS/MetroOS b/MetroOS/MetroOS index a7aa6b6..83011e6 100644 --- a/MetroOS/MetroOS +++ b/MetroOS/MetroOS @@ -27,7 +27,7 @@ local function numbertochars( n ) local s = tohex2( n % 256 ) for i = 1, 3 do n local function charstonumber( c ) local n = 0 for i = 1, 4 do n = n * 256 + fromhex2( c:sub( i * 2 - 1 ) ) end return n end\ local function newgarbage( length ) if length == 0 then return \"\" end return tohex2( math.random( 0, 255 ) ) .. newgarbage( length - 1 ) end\ local function stringkey( str ) local key = 0 for i = 1, #str do key = key * 256 + str:byte( i ) end return key end\ -local encrypt = {}\ +local encrypt = {}\ function encrypt.encrypt( text, key ) key = type( key ) == \"string\" and stringkey( key ) or key text = textutils.serialize( text ) if type( key ) ~= \"number\" then return error( \"expected number/string key, got \" .. type( key ) ) end local keys, garbage = keygen( key, #text ) local cipher = { numbertochars( #text ) } math.randomseed( os.clock() ) for i = 1, #text, blocksize do for n = 0, blocksize - 1 do if i + n <= #text then cipher[#cipher + 1] = tohex2( bit.bxor( text:byte( i + n ), keys[i] ) ) else break end end cipher[#cipher + 1] = newgarbage( garbage[i] ) end return table.concat( cipher ) end\ function encrypt.decrypt( cipher, key ) key = type( key ) == \"string\" and stringkey( key ) or key if type( cipher ) ~= \"string\" then return error( \"expected string cipher, got \" .. type( cipher ) ) end if type( key ) ~= \"number\" then return error( \"expected number/string key, got \" .. type( key ) ) end local length = charstonumber( cipher:sub( 1, 8 ) ) cipher = cipher:sub( 9 ) local keys, garbage = keygen( key, length ) local text = {} local i = 1 while #cipher > 0 do local block = cipher:sub( 1, math.min( #cipher - garbage[i] * 2, blocksize * 2 ) ) for n = 1, #block, 2 do text[#text + 1] = string.char( bit.bxor( fromhex2( block:sub( n ) ), keys[i] ) ) end cipher = cipher:sub( blocksize * 2 + garbage[i] * 2 + 1 ) i = i + blocksize end return textutils.unserialize( table.concat( text ) ) end\ local MOD = 2^32\ From 578612bbc8ead4d6c77f6bb351e2ac1ea53bcc01 Mon Sep 17 00:00:00 2001 From: OutragedMetro Date: Sat, 29 Apr 2023 20:27:51 -0600 Subject: [PATCH 085/125] Update server.lua --- darksecurity/server.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/darksecurity/server.lua b/darksecurity/server.lua index f9b565b..a271230 100644 --- a/darksecurity/server.lua +++ b/darksecurity/server.lua @@ -7,7 +7,7 @@ term.setCursorPos(1,1) if fs.exists("dark") == false then print("Missing OSI API") print("Attempting to download...") - status, getGit = pcall(http.get, "https://raw.githubusercontent.com/rservices/darkprograms/darkprograms/api/dark.lua") + status, getGit = pcall(http.get, "https://raw.githubusercontent.com/rservices/darkprograms/darkprograms/api/met.lua") if not status then print("\nFailed to get OSI API") print("Error: ".. getGit) From 660342303831502cd99a549a907b51ff27c63d20 Mon Sep 17 00:00:00 2001 From: OutragedMetro Date: Sat, 29 Apr 2023 20:30:18 -0600 Subject: [PATCH 086/125] Update server.lua --- darksecurity/server.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/darksecurity/server.lua b/darksecurity/server.lua index a271230..f9b565b 100644 --- a/darksecurity/server.lua +++ b/darksecurity/server.lua @@ -7,7 +7,7 @@ term.setCursorPos(1,1) if fs.exists("dark") == false then print("Missing OSI API") print("Attempting to download...") - status, getGit = pcall(http.get, "https://raw.githubusercontent.com/rservices/darkprograms/darkprograms/api/met.lua") + status, getGit = pcall(http.get, "https://raw.githubusercontent.com/rservices/darkprograms/darkprograms/api/dark.lua") if not status then print("\nFailed to get OSI API") print("Error: ".. getGit) From fc567185a932b397ed1a8db99999d54eefb85a54 Mon Sep 17 00:00:00 2001 From: OutragedMetro Date: Sat, 29 Apr 2023 20:30:54 -0600 Subject: [PATCH 087/125] Update server.lua --- darksecurity/server.lua | 2 -- 1 file changed, 2 deletions(-) diff --git a/darksecurity/server.lua b/darksecurity/server.lua index f9b565b..dc601df 100644 --- a/darksecurity/server.lua +++ b/darksecurity/server.lua @@ -1,7 +1,5 @@ --Title: Outraged Security Server Version = 6.37 ---Author: Darkrising (minecraft name djhannz) ---Platform: ComputerCraft Lua Virtual Machine term.clear() term.setCursorPos(1,1) if fs.exists("dark") == false then From 887bcb7f43b197415bc0c407c6ebec3c56e1a8af Mon Sep 17 00:00:00 2001 From: OutragedMetro Date: Sat, 29 Apr 2023 21:06:20 -0600 Subject: [PATCH 088/125] Update client.lua changed color of security system --- darksecurity/client.lua | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/darksecurity/client.lua b/darksecurity/client.lua index e7bc751..d4a3e4f 100644 --- a/darksecurity/client.lua +++ b/darksecurity/client.lua @@ -46,18 +46,18 @@ function rednetReceiveE(TimeA) end end function header(text, lText, rText) - dark.printL("-", 1, nil, "green", "green") - dark.printA("|", x, 2, nil, "green", "green") - dark.printA("|", 1, 2, nil, "green", "green") - dark.printC(string.rep(" ", x), 2, nil, "white", "green") - if lText then dark.printA(lText, 1, 2, nil, "white", "green") end - if rText then dark.printA(rText, x - #rText, 2, nil, "white", "green") end - dark.printC(text, 2, nil, "yellow", "green") - dark.printL("-", 3, 5, "green", "green") + dark.printL("-", 1, nil, "blue", "blue") + dark.printA("|", x, 2, nil, "blue", "blue") + dark.printA("|", 1, 2, nil, "blue", "blue") + dark.printC(string.rep(" ", x), 2, nil, "white", "blue") + if lText then dark.printA(lText, 1, 2, nil, "white", "blue") end + if rText then dark.printA(rText, x - #rText, 2, nil, "white", "blue") end + dark.printC(text, 2, nil, "yellow", "blue") + dark.printL("-", 3, 5, "blue", "blue") end function footer() - dark.printL("-", y, nil, "green", "green") - dark.printA("by OutragedMetro", x-13, y, nil, "yellow", "green") + dark.printL("-", y, nil, "blue", "blue") + dark.printA("by OutragedMetro", x-13, y, nil, "red", "blue") end function keycard_mainProgram() while true do From 2326710d16acb3f3824e3308aad81e5edc83214c Mon Sep 17 00:00:00 2001 From: OutragedMetro Date: Sat, 29 Apr 2023 21:07:26 -0600 Subject: [PATCH 089/125] Update server.lua changed server color --- darksecurity/server.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/darksecurity/server.lua b/darksecurity/server.lua index dc601df..51c881b 100644 --- a/darksecurity/server.lua +++ b/darksecurity/server.lua @@ -33,7 +33,7 @@ AutoUpdate = true globalWait = 1 slevel = 1 cliVent = {} -co = "green" +co = "blue" mLog = {} --Fixes @@ -110,7 +110,7 @@ function header(text, lText, rText) if debugMode and (debugMode == true) then co = "red" else - co = "green" + co = "blue" end dark.printL("-", 1, nil, co, co) dark.printA("|", x, 2, nil, co, co) @@ -125,10 +125,10 @@ function footer() if debugMode and (debugMode == true) then co = "red" else - co = "green" + co = "blue" end dark.printL("-", y, nil, co, co) - dark.printA("by OutragedMetro", x-13, y, nil, "yellow", co) + dark.printA("by OutragedMetro", x-13, y, nil, "red", co) end function displayTNameColumn(TName, Page, Extrater, Admin) if Extrater then From 9f9d08b147d376e820ea9520d08c252a9cdecda4 Mon Sep 17 00:00:00 2001 From: OutragedMetro Date: Sat, 29 Apr 2023 21:08:39 -0600 Subject: [PATCH 090/125] Update darkretriever.lua changed installer colour --- darkretriever.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/darkretriever.lua b/darkretriever.lua index da4fef0..65199b1 100644 --- a/darkretriever.lua +++ b/darkretriever.lua @@ -40,7 +40,7 @@ function term.write(text) term.oldWrite(text) end local function header(text) - tc("white","green") + tc("white","blue") writeC(string.rep(" ",x),1) writeC(string.rep(" ",x),y) writeC(text,1) @@ -128,7 +128,7 @@ function selection(no,list,totpage) term.write("[".. list[no] .. "]") tc("white","black") term.setCursorPos(1,y) - tc("white","green") + tc("white","blue") term.write("Page: ".. page + 1 .. "/" .. totpage) term.setCursorPos(x - 14, y) term.write("By OutragedMetro .INC") From c7727831ed8361d164493c3ba3f3ce38446b610f Mon Sep 17 00:00:00 2001 From: OutragedMetro Date: Sat, 29 Apr 2023 21:11:59 -0600 Subject: [PATCH 091/125] Update client.lua --- darksecurity/client.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/darksecurity/client.lua b/darksecurity/client.lua index d4a3e4f..7fe9a98 100644 --- a/darksecurity/client.lua +++ b/darksecurity/client.lua @@ -52,7 +52,7 @@ function header(text, lText, rText) dark.printC(string.rep(" ", x), 2, nil, "white", "blue") if lText then dark.printA(lText, 1, 2, nil, "white", "blue") end if rText then dark.printA(rText, x - #rText, 2, nil, "white", "blue") end - dark.printC(text, 2, nil, "yellow", "blue") + dark.printC(text, 2, nil, "white", "blue") dark.printL("-", 3, 5, "blue", "blue") end function footer() From 3e346f0d4ce4c561ceb65ab877f8584f84788f14 Mon Sep 17 00:00:00 2001 From: OutragedMetro Date: Sat, 29 Apr 2023 21:13:49 -0600 Subject: [PATCH 092/125] Update darkretriever.lua --- darkretriever.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/darkretriever.lua b/darkretriever.lua index 65199b1..e42b7df 100644 --- a/darkretriever.lua +++ b/darkretriever.lua @@ -130,7 +130,7 @@ function selection(no,list,totpage) term.setCursorPos(1,y) tc("white","blue") term.write("Page: ".. page + 1 .. "/" .. totpage) - term.setCursorPos(x - 14, y) + term.setCursorPos(x - 10, y) term.write("By OutragedMetro .INC") tc("white","black") end From c382f7d2219eef7d7c11363531d4cd162a482a72 Mon Sep 17 00:00:00 2001 From: OutragedMetro Date: Sat, 29 Apr 2023 21:14:42 -0600 Subject: [PATCH 093/125] Update darkretriever.lua --- darkretriever.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/darkretriever.lua b/darkretriever.lua index e42b7df..7c53fd3 100644 --- a/darkretriever.lua +++ b/darkretriever.lua @@ -130,7 +130,7 @@ function selection(no,list,totpage) term.setCursorPos(1,y) tc("white","blue") term.write("Page: ".. page + 1 .. "/" .. totpage) - term.setCursorPos(x - 10, y) + term.setCursorPos(x - 8, y) term.write("By OutragedMetro .INC") tc("white","black") end From af9b2c4749c66c914a336a0740713a6e1524a2c9 Mon Sep 17 00:00:00 2001 From: OutragedMetro Date: Sat, 29 Apr 2023 21:17:08 -0600 Subject: [PATCH 094/125] colours fixed color issue in menu --- darkretriever.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/darkretriever.lua b/darkretriever.lua index 7c53fd3..1a041c6 100644 --- a/darkretriever.lua +++ b/darkretriever.lua @@ -124,7 +124,7 @@ level = 1 function selection(no,list,totpage) term.setCursorPos(1, (no - mod) + (ind - 1)) - tc("yellow") + tc("white") term.write("[".. list[no] .. "]") tc("white","black") term.setCursorPos(1,y) @@ -178,7 +178,7 @@ function runMenu() list,totpage,mod = draw(menu) header("Authors") - tc("yellow","black") + tc("white","black") writeC("Press 'h' for help, 'q' to quit.",2) tc("white","black") @@ -203,9 +203,9 @@ function runMenu() term.setCursorPos(1,ind) print("Use the up and down arrows to move through the list.") print("use the right arrow to enter a menu item and the left arrow to exit") - print("") + print("https://outraged-metro.com") _,cy = term.getCursorPos() - tc("yellow","black") + tc("white","black") writeC("Press enter to continue.",cy) tc("white","black") read(" ") From 11be093acd9567d3da550e552dbab51270978b18 Mon Sep 17 00:00:00 2001 From: OutragedMetro Date: Sat, 29 Apr 2023 21:22:33 -0600 Subject: [PATCH 095/125] Update darkretriever.lua --- darkretriever.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/darkretriever.lua b/darkretriever.lua index 1a041c6..3f337ff 100644 --- a/darkretriever.lua +++ b/darkretriever.lua @@ -204,6 +204,7 @@ function runMenu() print("Use the up and down arrows to move through the list.") print("use the right arrow to enter a menu item and the left arrow to exit") print("https://outraged-metro.com") + setHyperlink("true","https://outraged-metro.com") _,cy = term.getCursorPos() tc("white","black") writeC("Press enter to continue.",cy) From 46bac2df1b13f68550ff5dcf76c0467a45ed9465 Mon Sep 17 00:00:00 2001 From: OutragedMetro Date: Sat, 29 Apr 2023 21:30:51 -0600 Subject: [PATCH 096/125] Update MetroOS --- MetroOS/MetroOS | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MetroOS/MetroOS b/MetroOS/MetroOS index 83011e6..b45ecb9 100644 --- a/MetroOS/MetroOS +++ b/MetroOS/MetroOS @@ -86,7 +86,7 @@ local lock = {\ \9name = \"Metro Secure OS Login\",\ \9authorized = \"MetroOS - Computer unlocked\",\ \9authorized2 = os.version(),\ -\9author = \"OutragedMetro\",\ +\9author = \"Outraged Security .INC\",\ \9config = \".MetroOSSecureLock.cfg\",\ }\ local framerate = .05\ @@ -147,7 +147,7 @@ local function checkConfig()\ \9\9\9\9inputPass.focussed = true\ \9\9\9\9newPass.text = \"Re-enter password\"\ \9\9\9\9newPass.animatedX = math.floor( application.view.width / 2 - newPass.width / 2 )\ -\9\9\9\9newPass.textColour = colours.orange\ +\9\9\9\9newPass.text\ = colours.orange\ \9\9\9else\ \9\9\9\9tP[2] = sha256( locSalt .. inputPass.text )\ \9\9\9\9inputPass.text = \"\"\ From 3ad2598f8df7f904ff061cce97cd372bb2d27878 Mon Sep 17 00:00:00 2001 From: OutragedMetro Date: Sat, 29 Apr 2023 21:36:55 -0600 Subject: [PATCH 097/125] Fixed colour issues Updated colours --- darkbuttons.lua | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/darkbuttons.lua b/darkbuttons.lua index 4d2c07b..f5ce197 100644 --- a/darkbuttons.lua +++ b/darkbuttons.lua @@ -28,10 +28,10 @@ AutoUpdate = true --General Functions function Header(text, lText, rText) -- builds a header using functions above from local x,y = term.getSize() - dark.printL("-", 1, nil, "green, "green") - dark.printC(string.rep(" ", x+1), 2, nil, "white", "green") - dark.printC(text, 2, nil, "white", "green") - dark.printL("-", 3, 5, "green", "green") + dark.printL("-", 1, nil, "blue", "blue") + dark.printC(string.rep(" ", x+1), 2, nil, "white", "blue") + dark.printC(text, 2, nil, "white", "blue") + dark.printL("-", 3, 5, "blue", "blue") end function saveState() dark.db.save("state", buttons) @@ -384,9 +384,9 @@ function wizard(mode) end happyness = checkTemp(hx,hy,txd,txu,tyd,tyu) if happyness ~= true then - textBox(":'(",txd,txu,tyd,tyu,colors.white,colors.green) + textBox(":'(",txd,txu,tyd,tyu,colors.white,colors.blue) else - textBox(":)",txd,txu,tyd,tyu,colors.white,colors.green) + textBox(":)",txd,txu,tyd,tyu,colors.white,colors.blue) end sleep(1) until happyness == true From 56b05cf7c1a4464e2e6b2f9e108b6a4e93807d2c Mon Sep 17 00:00:00 2001 From: OutragedMetro Date: Sat, 29 Apr 2023 21:37:34 -0600 Subject: [PATCH 098/125] fixed code updated issues --- darkbuttons.lua | 2 -- 1 file changed, 2 deletions(-) diff --git a/darkbuttons.lua b/darkbuttons.lua index f5ce197..6bcb48c 100644 --- a/darkbuttons.lua +++ b/darkbuttons.lua @@ -1,7 +1,5 @@ --Title: Dark buttons Version = 1.231 ---Author: Darkrising (minecraft name djhannz) ---Platform: ComputerCraft Lua Virtual Machine if not term.isColour() then print("Requires an Advanced Computer and an Advanced monitor.") return From 9326176f5b6a80620f119be2d2832b167d332531 Mon Sep 17 00:00:00 2001 From: OutragedMetro Date: Mon, 22 May 2023 20:09:06 -0600 Subject: [PATCH 099/125] Update darkretriever.lua --- darkretriever.lua | 232 ++++++---------------------------------------- 1 file changed, 27 insertions(+), 205 deletions(-) diff --git a/darkretriever.lua b/darkretriever.lua index 3f337ff..306e1cd 100644 --- a/darkretriever.lua +++ b/darkretriever.lua @@ -1,37 +1,44 @@ Version = 2.12 -x,y = term.getSize() +x, y = term.getSize() + if not http then print("Herp derp, forget to enable http?") return exit end + local function getUrlFile(url) local mrHttpFile = http.get(url) mrHttpFile = mrHttpFile.readAll() return mrHttpFile end + local function writeFile(filename, data) local file = fs.open(filename, "w") file.write(data) file.close() end + local function cs() term.clear() - term.setCursorPos(1,1) + term.setCursorPos(1, 1) end -local function tc(tcolor,bcolor) + +local function tc(tcolor, bcolor) if term.isColor() then if tcolor then term.setTextColor(colors[tcolor]) end if bcolor then - term.setBackgroundColor(colors[bcolor]) + term.setBackgroundColor(colors[bcolor]) end end end -local function writeC(text,line) - term.setCursorPos((x / 2) - (#text / 2),line) + +local function writeC(text, line) + term.setCursorPos((x / 2) - (#text / 2), line) term.write(text) end + term.oldWrite = term.write function term.write(text) if not text then @@ -39,22 +46,24 @@ function term.write(text) end term.oldWrite(text) end + local function header(text) - tc("white","blue") - writeC(string.rep(" ",x),1) - writeC(string.rep(" ",x),y) - writeC(text,1) - tc("white","black") + tc("white", "blue") + writeC(string.rep(" ", x), 1) + writeC(string.rep(" ", x), y) + writeC(text, 1) + tc("white", "black") end + local function gitUpdate(ProgramName, Filename, ProgramVersion) if http then local status, getGit = pcall(http.get, "https://raw.githubusercontent.com/rservices/darkprograms/darkprograms/programVersions") if not status then print("\nFailed to get Program Versions file.") - print("Error: ".. getGit) + print("Error: " .. getGit) return exit - end - local getGit = getGit.readAll() + end + getGit = getGit.readAll() NVersion = textutils.unserialize(getGit) if NVersion[ProgramName].Version > ProgramVersion then getGit = http.get(NVersion[ProgramName].GitURL) @@ -73,14 +82,14 @@ cs() print("Checking for updates...") if gitUpdate("darkretriever", shell.getRunningProgram(), Version) == true then print("Update found and downloaded.") - print("\nPlease run ".. shell.getRunningProgram() .. " again.") + print("\nPlease run " .. shell.getRunningProgram() .. " again.") return exit else print("Program up-to-date.") end sleep(1) -x,y = term.getSize() +x, y = term.getSize() cs() write("-> Grabbing file...") cat = getUrlFile("https://raw.githubusercontent.com/rservices/darkprograms/darkprograms/programVersions") @@ -100,7 +109,7 @@ rawName = {} ]]-- -for name,data in pairs(cat) do +for name, data in pairs(cat) do if not menu[data.Author] then menu[data.Author] = {} end @@ -115,191 +124,4 @@ end state = "top" csel = 1 --Current selected -osel = {1} --breadcrumb - -page = 0 -ind = 3 --Y indent -ava = y - ind --Available space -level = 1 - -function selection(no,list,totpage) - term.setCursorPos(1, (no - mod) + (ind - 1)) - tc("white") - term.write("[".. list[no] .. "]") - tc("white","black") - term.setCursorPos(1,y) - tc("white","blue") - term.write("Page: ".. page + 1 .. "/" .. totpage) - term.setCursorPos(x - 8, y) - term.write("By OutragedMetro .INC") - tc("white","black") -end -function draw(tbl) - local c = 1 - local sdat = {} - local odat = {} - for n,d in pairs(tbl) do - table.insert(sdat, n) - table.insert(odat, d) - c = c + 1 - end - if level ~= 4 then - table.sort(sdat) - end - - tpages = math.ceil(c / (y - ind)) - mod = page * (y - ind) - - for i = 1, y - ind do - term.setCursorPos(2, i + ind - 1) - term.write(sdat[i + mod]) - - if level == 4 then - term.setCursorPos(15, i + ind - 1) - if type(odat[i + mod]) == "string" and #odat[i + mod] + 14 > x then - term.write(string.sub(odat[i + mod],1,x-14-2).."..") - else - term.write(odat[i + mod]) - end - end - end - - if level == 4 then - term.setCursorPos(1, y-2) - writeC("Press enter to download.",y-2) - end - - return sdat, tpages, mod -end -function runMenu() - while true do - cs() - if level == 1 then - list,totpage,mod = draw(menu) - header("Authors") - - tc("white","black") - writeC("Press 'h' for help, 'q' to quit.",2) - tc("white","black") - - elseif level == 2 then - list,totpage,mod = draw(menu[auna]) - header("Packages") - elseif level == 3 then - list,totpage,mod = draw(menu[auna][pkg]) - header("Programs") - elseif level == 4 then - header("Program Data") - list,totpage,mod = draw(menu[auna][pkg][pro]) - end - - selection(csel, list, totpage) - - e,key = os.pullEvent("key") - - if key == keys.h then - cs() - header("Help") - term.setCursorPos(1,ind) - print("Use the up and down arrows to move through the list.") - print("use the right arrow to enter a menu item and the left arrow to exit") - print("https://outraged-metro.com") - setHyperlink("true","https://outraged-metro.com") - _,cy = term.getCursorPos() - tc("white","black") - writeC("Press enter to continue.",cy) - tc("white","black") - read(" ") - end - - if key == keys.up then - csel = csel - 1 - end - if key == keys.down then - csel = csel + 1 - end - if key == keys.enter then - - end - if key == keys.right then - osel[level] = csel - level = level + 1 - - if level == 2 then - auna = list[csel] - elseif level == 3 then - pkg = list[csel] - elseif level == 4 then - pro = list[csel] - end - - if level > 4 then level = 4 end - - csel = 1 - osel[level] = 1 - end - if key == keys.q then - cs() - return exit - end - if key == keys.rightBracket then - csel = csel + ava - end - if key == keys.leftBracket then - csel = csel - ava - end - - if key == keys.enter and level == 4 then - cs() - p = cat[pro] - writeC("Downloading ".. cat[rawName[pro]].Name .. " to /" .. rawName[pro], y/2) - status = getUrlFile(cat[rawName[pro]].GitURL) - sleep(1) - if status then - writeFile("/".. rawName[pro], status) - end - - cs() - writeC("Success!", y/2) - sleep(1) - - repeat - cs() - writeC("Would you like to generate a startup script? ", y/2) - writeC("Y / N : ",y/2 + 1) - answer = string.lower(read()) - until answer == "y" or answer == "n" - - if answer == "y" then - cs() - writeC("Writing startup script...", y/2) - - star = fs.open("/startup","w") - star.write("shell.run('".. rawName[pro] .. "')") - star.close() - - cs() - writeC("Success! Hold [Ctrl] + R to reboot.", y/2) - sleep(2) - end - end - - if csel < 1 then --Can't go below beginning of the list - csel = 1 - end - if csel > #list then --Can't go above length of the list - csel = #list - end - - if key == keys.left then - level = level - 1 - if level < 1 then level = 1 end - csel = osel[level] - end - - page = math.floor((csel - 1) / ava) - - end -end - -runMenu() +osel = From 12745a0df58385870b65828ff1005f3caf5ee854 Mon Sep 17 00:00:00 2001 From: OutragedMetro Date: Mon, 22 May 2023 20:11:51 -0600 Subject: [PATCH 100/125] Update darkretriever.lua --- darkretriever.lua | 143 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 142 insertions(+), 1 deletion(-) diff --git a/darkretriever.lua b/darkretriever.lua index 306e1cd..0aef99d 100644 --- a/darkretriever.lua +++ b/darkretriever.lua @@ -124,4 +124,145 @@ end state = "top" csel = 1 --Current selected -osel = +osel = 1 --Option selected +pro = 1 --Selected program + +selections = {} + +while true do + if state == "top" then + selections = {} + for author, packages in pairs(menu) do + table.insert(selections, author) + end + header("Authors") + for i, v in pairs(selections) do + if i == csel then + tc("white", "black") + else + tc("black", "white") + end + writeC(v, i + 1) + end + event, button = os.pullEvent("key") + if button == 200 then + csel = csel - 1 + if csel < 1 then + csel = #selections + end + elseif button == 208 then + csel = csel + 1 + if csel > #selections then + csel = 1 + end + elseif button == 28 then + state = "packages" + psel = 1 + elseif button == 14 then + state = "menu" + end + elseif state == "packages" then + selections = {} + for packages, programs in pairs(menu[selections[csel]]) do + table.insert(selections, packages) + end + header("Packages") + for i, v in pairs(selections) do + if i == psel then + tc("white", "black") + else + tc("black", "white") + end + writeC(v, i + 1) + end + event, button = os.pullEvent("key") + if button == 200 then + psel = psel - 1 + if psel < 1 then + psel = #selections + end + elseif button == 208 then + psel = psel + 1 + if psel > #selections then + psel = 1 + end + elseif button == 28 then + state = "programs" + pro = 1 + elseif button == 14 then + state = "top" + end + elseif state == "programs" then + selections = {} + for program, data in pairs(menu[selections[csel]][selections[psel]]) do + table.insert(selections, program) + end + header("Programs") + for i, v in pairs(selections) do + if i == pro then + tc("white", "black") + else + tc("black", "white") + end + writeC(v, i + 1) + end + event, button = os.pullEvent("key") + if button == 200 then + pro = pro - 1 + if pro < 1 then + pro = #selections + end + elseif button == 208 then + pro = pro + 1 + if pro > #selections then + pro = 1 + end + elseif button == 28 then + state = "menu" + osel = 1 + elseif button == 14 then + state = "packages" + end + elseif state == "menu" then + header("Options") + if osel == 1 then + tc("white", "black") + else + tc("black", "white") + end + writeC("Run", 1) + if osel == 2 then + tc("white", "black") + else + tc("black", "white") + end + writeC("Install", 2) + event, button = os.pullEvent("key") + if button == 200 then + osel = osel - 1 + if osel < 1 then + osel = 2 + end + elseif button == 208 then + osel = osel + 1 + if osel > 2 then + osel = 1 + end + elseif button == 28 then + if osel == 1 then + term.clear() + term.setCursorPos(1, 1) + shell.run(menu[selections[csel]][selections[psel]][selections[pro]].Run) + elseif osel == 2 then + cs() + write("-> Downloading " .. menu[selections[csel]][selections[psel]][selections[pro]].Name .. "...") + writeFile(menu[selections[csel]][selections[psel]][selections[pro]].Name, getUrlFile(menu[selections[csel]][selections[psel]][selections[pro]].URL)) + write(" Done.") + sleep(1) + state = "top" + end + elseif button == 14 then + state = "programs" + end + end +end From f98ba8f9635c70f02270a95278bd0ba0db1f73f9 Mon Sep 17 00:00:00 2001 From: OutragedMetro Date: Mon, 22 May 2023 20:17:22 -0600 Subject: [PATCH 101/125] Update darkretriever.lua --- darkretriever.lua | 381 ++++++++++++++++++++++++++-------------------- 1 file changed, 219 insertions(+), 162 deletions(-) diff --git a/darkretriever.lua b/darkretriever.lua index 0aef99d..ec1e9e9 100644 --- a/darkretriever.lua +++ b/darkretriever.lua @@ -1,15 +1,19 @@ -Version = 2.12 -x, y = term.getSize() - +local Version = 2.12 +local x, y = term.getSize() if not http then - print("Herp derp, forget to enable http?") - return exit + print("Herp derp, forgot to enable HTTP?") + return end local function getUrlFile(url) - local mrHttpFile = http.get(url) - mrHttpFile = mrHttpFile.readAll() - return mrHttpFile + local response = http.get(url) + if response then + local fileContent = response.readAll() + response.close() + return fileContent + else + error("Failed to retrieve URL: " .. url) + end end local function writeFile(filename, data) @@ -18,24 +22,25 @@ local function writeFile(filename, data) file.close() end -local function cs() +local function clearScreen() term.clear() term.setCursorPos(1, 1) end -local function tc(tcolor, bcolor) +local function setTextColor(textColor, backgroundColor) if term.isColor() then - if tcolor then - term.setTextColor(colors[tcolor]) + if textColor then + term.setTextColor(colors[textColor]) end - if bcolor then - term.setBackgroundColor(colors[bcolor]) + if backgroundColor then + term.setBackgroundColor(colors[backgroundColor]) end end end -local function writeC(text, line) - term.setCursorPos((x / 2) - (#text / 2), line) +local function centeredWrite(text, line) + local xPos = math.floor((x - #text) / 2) + 1 + term.setCursorPos(xPos, line) term.write(text) end @@ -47,59 +52,56 @@ function term.write(text) term.oldWrite(text) end -local function header(text) - tc("white", "blue") - writeC(string.rep(" ", x), 1) - writeC(string.rep(" ", x), y) - writeC(text, 1) - tc("white", "black") +local function printHeader(text) + setTextColor("white", "blue") + centeredWrite(string.rep(" ", x), 1) + centeredWrite(string.rep(" ", x), y) + centeredWrite(text, 1) + setTextColor("white", "black") end -local function gitUpdate(ProgramName, Filename, ProgramVersion) +local function gitUpdate(programName, filename, programVersion) if http then local status, getGit = pcall(http.get, "https://raw.githubusercontent.com/rservices/darkprograms/darkprograms/programVersions") if not status then print("\nFailed to get Program Versions file.") print("Error: " .. getGit) - return exit + return false end getGit = getGit.readAll() - NVersion = textutils.unserialize(getGit) - if NVersion[ProgramName].Version > ProgramVersion then - getGit = http.get(NVersion[ProgramName].GitURL) + local newVersion = textutils.unserialize(getGit) + if newVersion[programName].Version > programVersion then + getGit = http.get(newVersion[programName].GitURL) getGit = getGit.readAll() - local file = fs.open(Filename, "w") - file.write(getGit) - file.close() + writeFile(filename, getGit) return true end - else - return false end + return false end -cs() +clearScreen() print("Checking for updates...") -if gitUpdate("darkretriever", shell.getRunningProgram(), Version) == true then +if gitUpdate("darkretriever", shell.getRunningProgram(), Version) then print("Update found and downloaded.") print("\nPlease run " .. shell.getRunningProgram() .. " again.") - return exit + return else print("Program up-to-date.") end sleep(1) x, y = term.getSize() -cs() +clearScreen() write("-> Grabbing file...") -cat = getUrlFile("https://raw.githubusercontent.com/rservices/darkprograms/darkprograms/programVersions") +local cat = getUrlFile("https://raw.githubusercontent.com/rservices/darkprograms/darkprograms/programVersions") cat = textutils.unserialize(cat) write(" Done.") sleep(1) -cs() +clearScreen() -menu = {} -rawName = {} +local menu = {} +local rawName = {} --[[ @@ -122,147 +124,202 @@ for name, data in pairs(cat) do end end -state = "top" -csel = 1 --Current selected -osel = 1 --Option selected -pro = 1 --Selected program +local state = "top" +local csel = 1 -- Current selected +local osel = {1} -- Breadcrumb +local page = 0 +local ind = 3 -- Y indent +local ava = y - ind -- Available space +local level = 1 -selections = {} +local function selection(no, list, totpage) + term.setCursorPos(1, (no - mod) + (ind - 1)) + setTextColor("white") + term.write("[" .. list[no] .. "]") + setTextColor("white", "black") + term.setCursorPos(1, y) + setTextColor("white", "blue") + term.write("Page: " .. page + 1 .. "/" .. totpage) + term.setCursorPos(x - 8, y) + term.write("By OutragedMetro .INC") + setTextColor("white", "black") +end -while true do - if state == "top" then - selections = {} - for author, packages in pairs(menu) do - table.insert(selections, author) - end - header("Authors") - for i, v in pairs(selections) do - if i == csel then - tc("white", "black") +local function draw(tbl) + local c = 1 + local sdat = {} + local odat = {} + for n, d in pairs(tbl) do + table.insert(sdat, n) + table.insert(odat, d) + c = c + 1 + end + if level ~= 4 then + table.sort(sdat) + end + + local tpages = math.ceil(c / (y - ind)) + local mod = page * (y - ind) + + for i = 1, y - ind do + term.setCursorPos(2, i + ind - 1) + term.write(sdat[i + mod]) + + if level == 4 then + term.setCursorPos(15, i + ind - 1) + if type(odat[i + mod]) == "string" and #odat[i + mod] + 14 > x then + term.write(string.sub(odat[i + mod], 1, x - 14 - 2) .. "..") else - tc("black", "white") + term.write(odat[i + mod]) end - writeC(v, i + 1) end - event, button = os.pullEvent("key") - if button == 200 then - csel = csel - 1 - if csel < 1 then - csel = #selections - end - elseif button == 208 then - csel = csel + 1 - if csel > #selections then - csel = 1 - end - elseif button == 28 then - state = "packages" - psel = 1 - elseif button == 14 then - state = "menu" + end + + if level == 4 then + term.setCursorPos(1, y - 2) + centeredWrite("Press enter to download.", y - 2) + end + + return sdat, tpages, mod +end + +local function runMenu() + while true do + clearScreen() + if level == 1 then + local list, totpage, mod = draw(menu) + printHeader("Authors") + setTextColor("white", "black") + centeredWrite("Press 'h' for help, 'q' to quit.", 2) + setTextColor("white", "black") + + elseif level == 2 then + local list, totpage, mod = draw(menu[auna]) + printHeader("Packages") + elseif level == 3 then + local list, totpage, mod = draw(menu[auna][pkg]) + printHeader("Programs") + elseif level == 4 then + printHeader("Program Data") + local list, totpage, mod = draw(menu[auna][pkg][pro]) end - elseif state == "packages" then - selections = {} - for packages, programs in pairs(menu[selections[csel]]) do - table.insert(selections, packages) + + selection(csel, list, totpage) + + local event, key = os.pullEvent("key") + + if key == keys.h then + clearScreen() + printHeader("Help") + term.setCursorPos(1, ind) + print("Use the up and down arrows to move through the list.") + print("Use the right arrow to enter a menu item and the left arrow to exit.") + print("https://outraged-metro.com") + setTextColor("white", "black") + centeredWrite("Press enter to continue.", y - 2) + os.pullEvent("key") + level = osel[#osel] end - header("Packages") - for i, v in pairs(selections) do - if i == psel then - tc("white", "black") + + if key == keys.q then + clearScreen() + printHeader("Exit") + term.setCursorPos(1, ind) + print("Are you sure you want to exit?") + print("Press enter to confirm or any other key to cancel.") + local event, key = os.pullEvent("key") + if key == keys.enter then + clearScreen() + return else - tc("black", "white") + level = osel[#osel] end - writeC(v, i + 1) end - event, button = os.pullEvent("key") - if button == 200 then - psel = psel - 1 - if psel < 1 then - psel = #selections - end - elseif button == 208 then - psel = psel + 1 - if psel > #selections then - psel = 1 + + if key == keys.enter then + if level == 1 then + auna = list[csel + mod] + osel = {csel + mod} + level = 2 + elseif level == 2 then + pkg = list[csel + mod] + osel[#osel + 1] = csel + mod + level = 3 + elseif level == 3 then + pro = rawName[list[csel + mod]] + osel[#osel + 1] = csel + mod + level = 4 + elseif level == 4 then + local rname = rawName[list[csel + mod]] + clearScreen() + printHeader("Download") + term.setCursorPos(1, ind) + print("Are you sure you want to download " .. rname .. "?") + print("Press enter to confirm or any other key to cancel.") + local event, key = os.pullEvent("key") + if key == keys.enter then + clearScreen() + write("-> Grabbing file...") + local file = getUrlFile(cat[rname].DownloadURL) + writeFile(rname, file) + print(" Done.") + sleep(1) + clearScreen() + print("File " .. rname .. " downloaded successfully!") + print("Press any key to continue.") + os.pullEvent("key") + end + level = osel[#osel] end - elseif button == 28 then - state = "programs" - pro = 1 - elseif button == 14 then - state = "top" end - elseif state == "programs" then - selections = {} - for program, data in pairs(menu[selections[csel]][selections[psel]]) do - table.insert(selections, program) + + if key == keys.right then + if level ~= 4 then + osel[#osel + 1] = csel + mod + end + level = level + 1 end - header("Programs") - for i, v in pairs(selections) do - if i == pro then - tc("white", "black") + + if key == keys.left then + if level == 1 then + clearScreen() + printHeader("Exit") + term.setCursorPos(1, ind) + print("Are you sure you want to exit?") + print("Press enter to confirm or any other key to cancel.") + local event, key = os.pullEvent("key") + if key == keys.enter then + clearScreen() + return + end else - tc("black", "white") + level = osel[#osel] + table.remove(osel, #osel) end - writeC(v, i + 1) end - event, button = os.pullEvent("key") - if button == 200 then - pro = pro - 1 - if pro < 1 then - pro = #selections - end - elseif button == 208 then - pro = pro + 1 - if pro > #selections then - pro = 1 + + if key == keys.down then + if csel < ava then + csel = csel + 1 + else + if page + 1 < totpage then + page = page + 1 + csel = 1 + end end - elseif button == 28 then - state = "menu" - osel = 1 - elseif button == 14 then - state = "packages" - end - elseif state == "menu" then - header("Options") - if osel == 1 then - tc("white", "black") - else - tc("black", "white") - end - writeC("Run", 1) - if osel == 2 then - tc("white", "black") - else - tc("black", "white") end - writeC("Install", 2) - event, button = os.pullEvent("key") - if button == 200 then - osel = osel - 1 - if osel < 1 then - osel = 2 - end - elseif button == 208 then - osel = osel + 1 - if osel > 2 then - osel = 1 - end - elseif button == 28 then - if osel == 1 then - term.clear() - term.setCursorPos(1, 1) - shell.run(menu[selections[csel]][selections[psel]][selections[pro]].Run) - elseif osel == 2 then - cs() - write("-> Downloading " .. menu[selections[csel]][selections[psel]][selections[pro]].Name .. "...") - writeFile(menu[selections[csel]][selections[psel]][selections[pro]].Name, getUrlFile(menu[selections[csel]][selections[psel]][selections[pro]].URL)) - write(" Done.") - sleep(1) - state = "top" + + if key == keys.up then + if csel > 1 then + csel = csel - 1 + else + if page - 1 >= 0 then + page = page - 1 + csel = ava + end end - elseif button == 14 then - state = "programs" end end end + +runMenu() From 72d4844e7f1f906c20e40c76dc99423c4a56b277 Mon Sep 17 00:00:00 2001 From: OutragedMetro Date: Mon, 22 May 2023 20:30:29 -0600 Subject: [PATCH 102/125] Update darkretriever.lua --- darkretriever.lua | 437 ++++++++++++++++++++++------------------------ 1 file changed, 207 insertions(+), 230 deletions(-) diff --git a/darkretriever.lua b/darkretriever.lua index ec1e9e9..70e33e3 100644 --- a/darkretriever.lua +++ b/darkretriever.lua @@ -1,5 +1,6 @@ local Version = 2.12 local x, y = term.getSize() + if not http then print("Herp derp, forgot to enable HTTP?") return @@ -8,12 +9,11 @@ end local function getUrlFile(url) local response = http.get(url) if response then - local fileContent = response.readAll() + local content = response.readAll() response.close() - return fileContent - else - error("Failed to retrieve URL: " .. url) + return content end + return nil end local function writeFile(filename, data) @@ -22,25 +22,24 @@ local function writeFile(filename, data) file.close() end -local function clearScreen() +local function cs() term.clear() term.setCursorPos(1, 1) end -local function setTextColor(textColor, backgroundColor) +local function tc(tcolor, bcolor) if term.isColor() then - if textColor then - term.setTextColor(colors[textColor]) + if tcolor then + term.setTextColor(colors[tcolor]) end - if backgroundColor then - term.setBackgroundColor(colors[backgroundColor]) + if bcolor then + term.setBackgroundColor(colors[bcolor]) end end end -local function centeredWrite(text, line) - local xPos = math.floor((x - #text) / 2) + 1 - term.setCursorPos(xPos, line) +local function writeC(text, line) + term.setCursorPos((x / 2) - (#text / 2), line) term.write(text) end @@ -52,15 +51,15 @@ function term.write(text) term.oldWrite(text) end -local function printHeader(text) - setTextColor("white", "blue") - centeredWrite(string.rep(" ", x), 1) - centeredWrite(string.rep(" ", x), y) - centeredWrite(text, 1) - setTextColor("white", "black") +local function header(text) + tc("white", "blue") + writeC(string.rep(" ", x), 1) + writeC(string.rep(" ", x), y) + writeC(text, 1) + tc("white", "black") end -local function gitUpdate(programName, filename, programVersion) +local function gitUpdate(ProgramName, Filename, ProgramVersion) if http then local status, getGit = pcall(http.get, "https://raw.githubusercontent.com/rservices/darkprograms/darkprograms/programVersions") if not status then @@ -68,19 +67,27 @@ local function gitUpdate(programName, filename, programVersion) print("Error: " .. getGit) return false end - getGit = getGit.readAll() - local newVersion = textutils.unserialize(getGit) - if newVersion[programName].Version > programVersion then - getGit = http.get(newVersion[programName].GitURL) - getGit = getGit.readAll() - writeFile(filename, getGit) - return true + local getGitContent = getGit.readAll() + getGit.close() + local NVersion = textutils.unserialize(getGitContent) + if NVersion[ProgramName].Version > ProgramVersion then + getGit = http.get(NVersion[ProgramName].GitURL) + if getGit then + local getGitContent = getGit.readAll() + getGit.close() + writeFile(Filename, getGitContent) + return true + else + print("\nFailed to download updated program.") + return false + end end + else + return false end - return false end -clearScreen() +cs() print("Checking for updates...") if gitUpdate("darkretriever", shell.getRunningProgram(), Version) then print("Update found and downloaded.") @@ -92,234 +99,204 @@ end sleep(1) x, y = term.getSize() -clearScreen() -write("-> Grabbing file...") +cs() +term.write("-> Grabbing file...") local cat = getUrlFile("https://raw.githubusercontent.com/rservices/darkprograms/darkprograms/programVersions") -cat = textutils.unserialize(cat) -write(" Done.") -sleep(1) -clearScreen() - -local menu = {} -local rawName = {} - ---[[ - --Author ---Package ----Program - -]]-- - -for name, data in pairs(cat) do - if not menu[data.Author] then - menu[data.Author] = {} - end - if not menu[data.Author][data.Package] then - menu[data.Author][data.Package] = {} - end - if not menu[data.Author][data.Package][name] then - menu[data.Author][data.Package][data.Name] = data - rawName[data.Name] = name - end -end - -local state = "top" -local csel = 1 -- Current selected -local osel = {1} -- Breadcrumb -local page = 0 -local ind = 3 -- Y indent -local ava = y - ind -- Available space -local level = 1 - -local function selection(no, list, totpage) - term.setCursorPos(1, (no - mod) + (ind - 1)) - setTextColor("white") - term.write("[" .. list[no] .. "]") - setTextColor("white", "black") - term.setCursorPos(1, y) - setTextColor("white", "blue") - term.write("Page: " .. page + 1 .. "/" .. totpage) - term.setCursorPos(x - 8, y) - term.write("By OutragedMetro .INC") - setTextColor("white", "black") -end - -local function draw(tbl) - local c = 1 - local sdat = {} - local odat = {} - for n, d in pairs(tbl) do - table.insert(sdat, n) - table.insert(odat, d) - c = c + 1 - end - if level ~= 4 then - table.sort(sdat) - end - - local tpages = math.ceil(c / (y - ind)) - local mod = page * (y - ind) - - for i = 1, y - ind do - term.setCursorPos(2, i + ind - 1) - term.write(sdat[i + mod]) - - if level == 4 then - term.setCursorPos(15, i + ind - 1) - if type(odat[i + mod]) == "string" and #odat[i + mod] + 14 > x then - term.write(string.sub(odat[i + mod], 1, x - 14 - 2) .. "..") - else - term.write(odat[i + mod]) - end +if cat then + cat = textutils.unserialize(cat) + term.write(" Done.") + sleep(1) + cs() + + local menu = {} + local rawName = {} + + --[[ + -Author + --Package + ---Program + ]]-- + + for name, data in pairs(cat) do + if not menu[data.Author] then + menu[data.Author] = {} + end + if not menu[data.Author][data.Package] then + menu[data.Author][data.Package] = {} + end + if not menu[data.Author][data.Package][name] then + menu[data.Author][data.Package][data.Name] = data + rawName[data.Name] = name end end - if level == 4 then - term.setCursorPos(1, y - 2) - centeredWrite("Press enter to download.", y - 2) + local state = "top" + local csel = 1 --Current selected + local osel = {1} --breadcrumb + + local page = 0 + local ind = 3 --Y indent + local ava = y - ind --Available space + local level = 1 + + local function selection(no, list, totpage) + term.setCursorPos(1, (no - mod) + (ind - 1)) + tc("white") + term.write("[" .. list[no] .. "]") + tc("white", "black") + term.setCursorPos(1, y) + tc("white", "blue") + term.write("Page: " .. page + 1 .. "/" .. totpage) + term.setCursorPos(x - 8, y) + term.write("By OutragedMetro .INC") + tc("white", "black") end - return sdat, tpages, mod -end - -local function runMenu() - while true do - clearScreen() - if level == 1 then - local list, totpage, mod = draw(menu) - printHeader("Authors") - setTextColor("white", "black") - centeredWrite("Press 'h' for help, 'q' to quit.", 2) - setTextColor("white", "black") - - elseif level == 2 then - local list, totpage, mod = draw(menu[auna]) - printHeader("Packages") - elseif level == 3 then - local list, totpage, mod = draw(menu[auna][pkg]) - printHeader("Programs") - elseif level == 4 then - printHeader("Program Data") - local list, totpage, mod = draw(menu[auna][pkg][pro]) + local function draw(tbl) + local c = 1 + local sdat = {} + local odat = {} + for n, d in pairs(tbl) do + table.insert(sdat, n) + table.insert(odat, d) + c = c + 1 + end + if level ~= 4 then + table.sort(sdat) end - selection(csel, list, totpage) + local tpages = math.ceil(c / (y - ind)) + local mod = page * (y - ind) - local event, key = os.pullEvent("key") + for i = 1, y - ind do + term.setCursorPos(2, i + ind - 1) + term.write(sdat[i + mod]) - if key == keys.h then - clearScreen() - printHeader("Help") - term.setCursorPos(1, ind) - print("Use the up and down arrows to move through the list.") - print("Use the right arrow to enter a menu item and the left arrow to exit.") - print("https://outraged-metro.com") - setTextColor("white", "black") - centeredWrite("Press enter to continue.", y - 2) - os.pullEvent("key") - level = osel[#osel] + if level == 4 then + term.setCursorPos(15, i + ind - 1) + if type(odat[i + mod]) == "string" and #odat[i + mod] + 14 > x then + term.write(string.sub(odat[i + mod], 1, x - 14 - 2) .. "..") + else + term.write(odat[i + mod]) + end + end end - if key == keys.q then - clearScreen() - printHeader("Exit") - term.setCursorPos(1, ind) - print("Are you sure you want to exit?") - print("Press enter to confirm or any other key to cancel.") - local event, key = os.pullEvent("key") - if key == keys.enter then - clearScreen() - return - else - level = osel[#osel] - end + if level == 4 then + term.setCursorPos(1, y - 2) + writeC("Press enter to download.", y - 2) end - if key == keys.enter then + return sdat, tpages, mod + end + + local function runMenu() + while true do + cs() if level == 1 then - auna = list[csel + mod] - osel = {csel + mod} - level = 2 + local list, totpage, mod = draw(menu) + header("Authors") + + tc("white", "black") + writeC("Press 'h' for help, 'q' to quit.", 2) + tc("white", "black") + elseif level == 2 then - pkg = list[csel + mod] - osel[#osel + 1] = csel + mod - level = 3 + local list, totpage, mod = draw(menu[auna]) + header("Packages") elseif level == 3 then - pro = rawName[list[csel + mod]] - osel[#osel + 1] = csel + mod - level = 4 + local list, totpage, mod = draw(menu[auna][pkg]) + header("Programs") elseif level == 4 then - local rname = rawName[list[csel + mod]] - clearScreen() - printHeader("Download") - term.setCursorPos(1, ind) - print("Are you sure you want to download " .. rname .. "?") - print("Press enter to confirm or any other key to cancel.") - local event, key = os.pullEvent("key") - if key == keys.enter then - clearScreen() - write("-> Grabbing file...") - local file = getUrlFile(cat[rname].DownloadURL) - writeFile(rname, file) - print(" Done.") - sleep(1) - clearScreen() - print("File " .. rname .. " downloaded successfully!") - print("Press any key to continue.") - os.pullEvent("key") - end - level = osel[#osel] + header("Program Data") + local list, totpage, mod = draw(menu[auna][pkg][pro]) end - end - if key == keys.right then - if level ~= 4 then - osel[#osel + 1] = csel + mod - end - level = level + 1 - end + selection(csel, list, totpage) - if key == keys.left then - if level == 1 then - clearScreen() - printHeader("Exit") + local event, key = os.pullEvent("key") + + if key == keys.h then + cs() + header("Help") term.setCursorPos(1, ind) - print("Are you sure you want to exit?") - print("Press enter to confirm or any other key to cancel.") - local event, key = os.pullEvent("key") - if key == keys.enter then - clearScreen() + print("Use the up and down arrows to move through the list.") + print("Use the right arrow to enter a menu item and the left arrow to exit.") + print("https://outragedmetro.wixsite.com/darkprograms\n") + print("Press any key to continue.") + os.pullEvent("key") + elseif key == keys.q then + return + elseif key == keys.right then + if level == 1 then + auna = list[csel] + level = 2 + table.insert(osel, csel) + elseif level == 2 then + pkg = list[csel] + level = 3 + table.insert(osel, csel) + elseif level == 3 then + pro = rawName[list[csel]] + level = 4 + elseif level == 4 then + local meta = menu[auna][pkg][pro] + cs() + header("Downloading " .. meta.Name) + term.setCursorPos(1, ind) + term.write("-> Downloading file...") + + local content = getUrlFile(meta.GitURL) + if content then + local filename = meta.Name .. ".lua" + writeFile(filename, content) + term.setCursorPos(1, ind + 2) + term.write("-> File downloaded successfully!") + sleep(2) + return + else + term.setCursorPos(1, ind + 2) + term.write("-> Failed to download the file.") + sleep(2) + return + end + end + csel = 1 + elseif key == keys.left then + if level == 1 then return + elseif level == 2 then + level = 1 + table.remove(osel) + csel = osel[#osel] + elseif level == 3 then + level = 2 + table.remove(osel) + csel = osel[#osel] + elseif level == 4 then + level = 3 end - else - level = osel[#osel] - table.remove(osel, #osel) - end - end - - if key == keys.down then - if csel < ava then - csel = csel + 1 - else - if page + 1 < totpage then - page = page + 1 - csel = 1 + elseif key == keys.up then + if csel > 1 then + csel = csel - 1 + else + if page > 0 then + page = page - 1 + end end - end - end - - if key == keys.up then - if csel > 1 then - csel = csel - 1 - else - if page - 1 >= 0 then - page = page - 1 - csel = ava + elseif key == keys.down then + if csel < ava then + if csel < #list then + csel = csel + 1 + end + else + if page < totpage - 1 then + page = page + 1 + end end end end end -end -runMenu() + runMenu() +end From 7f1d213331728d934a21cf4a3595875ee4d296ff Mon Sep 17 00:00:00 2001 From: OutragedMetro Date: Mon, 22 May 2023 20:35:39 -0600 Subject: [PATCH 103/125] Update darkretriever.lua --- darkretriever.lua | 374 +++++++++++++++++----------------------------- 1 file changed, 140 insertions(+), 234 deletions(-) diff --git a/darkretriever.lua b/darkretriever.lua index 70e33e3..2afebeb 100644 --- a/darkretriever.lua +++ b/darkretriever.lua @@ -1,9 +1,22 @@ -local Version = 2.12 -local x, y = term.getSize() +-- Dark Package Manager -if not http then - print("Herp derp, forgot to enable HTTP?") - return +local function tc(bg, fg) + term.setBackgroundColor(bg) + term.setTextColor(fg) +end + +local function cs() + term.clear() + term.setCursorPos(1, 1) +end + +local function header(text) + tc(colors.white, colors.black) + term.setCursorPos(1, 1) + term.write("Dark Package Manager") + tc(colors.black, colors.gray) + term.setCursorPos(1, 2) + term.write(text) end local function getUrlFile(url) @@ -16,283 +29,174 @@ local function getUrlFile(url) return nil end -local function writeFile(filename, data) +local function writeFile(filename, content) local file = fs.open(filename, "w") - file.write(data) + file.write(content) file.close() end -local function cs() - term.clear() - term.setCursorPos(1, 1) -end - -local function tc(tcolor, bcolor) - if term.isColor() then - if tcolor then - term.setTextColor(colors[tcolor]) - end - if bcolor then - term.setBackgroundColor(colors[bcolor]) - end - end -end - -local function writeC(text, line) - term.setCursorPos((x / 2) - (#text / 2), line) - term.write(text) -end - -term.oldWrite = term.write -function term.write(text) - if not text then - text = "" - end - term.oldWrite(text) -end - -local function header(text) - tc("white", "blue") - writeC(string.rep(" ", x), 1) - writeC(string.rep(" ", x), y) - writeC(text, 1) - tc("white", "black") -end - -local function gitUpdate(ProgramName, Filename, ProgramVersion) - if http then - local status, getGit = pcall(http.get, "https://raw.githubusercontent.com/rservices/darkprograms/darkprograms/programVersions") - if not status then - print("\nFailed to get Program Versions file.") - print("Error: " .. getGit) - return false - end - local getGitContent = getGit.readAll() - getGit.close() - local NVersion = textutils.unserialize(getGitContent) - if NVersion[ProgramName].Version > ProgramVersion then - getGit = http.get(NVersion[ProgramName].GitURL) - if getGit then - local getGitContent = getGit.readAll() - getGit.close() - writeFile(Filename, getGitContent) - return true - else - print("\nFailed to download updated program.") - return false - end - end - else - return false - end -end - -cs() -print("Checking for updates...") -if gitUpdate("darkretriever", shell.getRunningProgram(), Version) then - print("Update found and downloaded.") - print("\nPlease run " .. shell.getRunningProgram() .. " again.") - return -else - print("Program up-to-date.") -end -sleep(1) - -x, y = term.getSize() -cs() -term.write("-> Grabbing file...") -local cat = getUrlFile("https://raw.githubusercontent.com/rservices/darkprograms/darkprograms/programVersions") -if cat then - cat = textutils.unserialize(cat) - term.write(" Done.") - sleep(1) - cs() - +local function darkPackageManager() + local level = 1 local menu = {} - local rawName = {} + local osel = {} + local csel = 1 + local ind = 4 - --[[ - -Author - --Package - ---Program - ]]-- - - for name, data in pairs(cat) do - if not menu[data.Author] then - menu[data.Author] = {} - end - if not menu[data.Author][data.Package] then - menu[data.Author][data.Package] = {} - end - if not menu[data.Author][data.Package][name] then - menu[data.Author][data.Package][data.Name] = data - rawName[data.Name] = name - end + cs() + header("Loading...") + + local content = getUrlFile("https://raw.githubusercontent.com/darkrising/dark-pm/main/index.json") + if not content then + cs() + header("Failed to load data.") + sleep(2) + return end - local state = "top" - local csel = 1 --Current selected - local osel = {1} --breadcrumb + menu = textutils.unserialize(content) - local page = 0 - local ind = 3 --Y indent - local ava = y - ind --Available space - local level = 1 - - local function selection(no, list, totpage) - term.setCursorPos(1, (no - mod) + (ind - 1)) - tc("white") - term.write("[" .. list[no] .. "]") - tc("white", "black") - term.setCursorPos(1, y) - tc("white", "blue") - term.write("Page: " .. page + 1 .. "/" .. totpage) - term.setCursorPos(x - 8, y) - term.write("By OutragedMetro .INC") - tc("white", "black") - end - - local function draw(tbl) - local c = 1 + local function selection(c, data, tpages) + local mod = (csel - 1) % (term.getSize().y - ind) + local ava = term.getSize().y - ind local sdat = {} local odat = {} - for n, d in pairs(tbl) do - table.insert(sdat, n) - table.insert(odat, d) - c = c + 1 - end - if level ~= 4 then - table.sort(sdat) + + if level == 2 then + sdat = data[osel[1]] + elseif level == 3 then + sdat = data[osel[1]][osel[2]] + elseif level == 4 then + sdat = data[osel[1]][osel[2]] + odat = sdat + sdat = sdat[csel] end - local tpages = math.ceil(c / (y - ind)) - local mod = page * (y - ind) + term.setCursorPos(1, ind) + + if mod ~= 0 then + tc(colors.white, colors.black) + term.write(" [Back]") + end - for i = 1, y - ind do - term.setCursorPos(2, i + ind - 1) - term.write(sdat[i + mod]) + for i = 1, term.getSize().y - ind - 1 do + term.setCursorPos(1, i + ind) + term.clearLine() - if level == 4 then - term.setCursorPos(15, i + ind - 1) - if type(odat[i + mod]) == "string" and #odat[i + mod] + 14 > x then - term.write(string.sub(odat[i + mod], 1, x - 14 - 2) .. "..") + if (i + mod) <= c then + if level == 4 then + if (i + mod) == csel then + tc(colors.white, colors.blue) + term.write("[" .. (i + mod) .. "] ") + tc(colors.blue, colors.white) + else + tc(colors.white, colors.black) + term.write("[" .. (i + mod) .. "] ") + end + term.write(sdat) else - term.write(odat[i + mod]) + if (i + mod) == csel then + tc(colors.white, colors.blue) + term.write("[" .. (i + mod) .. "] ") + tc(colors.blue, colors.white) + else + tc(colors.white, colors.black) + term.write("[" .. (i + mod) .. "] ") + end + term.write(data[i + mod]) end end end - if level == 4 then - term.setCursorPos(1, y - 2) - writeC("Press enter to download.", y - 2) + if mod == 0 then + tc(colors.white, colors.black) + term.write(" [Next]") end - - return sdat, tpages, mod end - local function runMenu() - while true do - cs() - if level == 1 then - local list, totpage, mod = draw(menu) - header("Authors") + local function draw(list) + local c = #list + local mod = (csel - 1) % (term.getSize().y - ind) + local ava = term.getSize().y - ind + local page = math.floor((csel - 1) / ava) + local tpages = math.ceil(c / ava) - tc("white", "black") - writeC("Press 'h' for help, 'q' to quit.", 2) - tc("white", "black") + cs() + header("Main Menu") - elseif level == 2 then - local list, totpage, mod = draw(menu[auna]) - header("Packages") - elseif level == 3 then - local list, totpage, mod = draw(menu[auna][pkg]) - header("Programs") - elseif level == 4 then - header("Program Data") - local list, totpage, mod = draw(menu[auna][pkg][pro]) - end + term.setCursorPos(1, ind) + tc(colors.white, colors.black) + term.write(" [Exit]") + + selection(c, list, tpages) + end - selection(csel, list, totpage) + local function runMenu() + while true do + draw(menu[level]) local event, key = os.pullEvent("key") - if key == keys.h then - cs() - header("Help") - term.setCursorPos(1, ind) - print("Use the up and down arrows to move through the list.") - print("Use the right arrow to enter a menu item and the left arrow to exit.") - print("https://outragedmetro.wixsite.com/darkprograms\n") - print("Press any key to continue.") - os.pullEvent("key") - elseif key == keys.q then - return - elseif key == keys.right then - if level == 1 then - auna = list[csel] - level = 2 - table.insert(osel, csel) - elseif level == 2 then - pkg = list[csel] - level = 3 - table.insert(osel, csel) - elseif level == 3 then - pro = rawName[list[csel]] - level = 4 - elseif level == 4 then - local meta = menu[auna][pkg][pro] + if key == keys.enter then + if csel == 1 and level > 1 then + if level == 2 then + level = 1 + osel = {} + elseif level == 3 then + level = 2 + table.remove(osel) + elseif level == 4 then + level = 3 + end + csel = 1 + elseif level == 4 and menu[level][csel] ~= nil then cs() - header("Downloading " .. meta.Name) - term.setCursorPos(1, ind) - term.write("-> Downloading file...") + header("Downloading...") + term.setCursorPos(1, ind + 1) - local content = getUrlFile(meta.GitURL) + local url = odat[csel] + local filename = fs.getName(url) + local content = getUrlFile(url) if content then - local filename = meta.Name .. ".lua" writeFile(filename, content) - term.setCursorPos(1, ind + 2) - term.write("-> File downloaded successfully!") - sleep(2) - return + header("Download completed!") else - term.setCursorPos(1, ind + 2) - term.write("-> Failed to download the file.") - sleep(2) - return + header("Failed to download.") end + sleep(2) + elseif level < 4 and menu[level][csel] ~= nil then + table.insert(osel, csel) + level = level + 1 + csel = 1 end - csel = 1 - elseif key == keys.left then - if level == 1 then - return - elseif level == 2 then + elseif key == keys.backspace and level > 1 then + if level == 2 then level = 1 - table.remove(osel) - csel = osel[#osel] + osel = {} elseif level == 3 then level = 2 table.remove(osel) - csel = osel[#osel] elseif level == 4 then level = 3 end + csel = 1 elseif key == keys.up then if csel > 1 then csel = csel - 1 - else - if page > 0 then - page = page - 1 - end end elseif key == keys.down then - if csel < ava then - if csel < #list then - csel = csel + 1 - end - else - if page < totpage - 1 then - page = page + 1 - end + if csel < #menu[level] then + csel = csel + 1 + end + elseif key == keys.pageUp then + if page > 0 then + page = page - 1 + csel = csel - ava + end + elseif key == keys.pageDown then + local tpages = math.ceil(#menu[level] / (term.getSize().y - ind)) + if page < tpages - 1 then + page = page + 1 + csel = csel + ava end end end @@ -300,3 +204,5 @@ if cat then runMenu() end + +darkPackageManager() From 4486a27365562569f3ba3f3117a10a663b13feec Mon Sep 17 00:00:00 2001 From: OutragedMetro Date: Mon, 22 May 2023 20:36:46 -0600 Subject: [PATCH 104/125] Update darkretriever.lua --- darkretriever.lua | 454 ++++++++++++++++++++++++++++------------------ 1 file changed, 275 insertions(+), 179 deletions(-) diff --git a/darkretriever.lua b/darkretriever.lua index 2afebeb..2d82f18 100644 --- a/darkretriever.lua +++ b/darkretriever.lua @@ -1,208 +1,304 @@ --- Dark Package Manager - -local function tc(bg, fg) - term.setBackgroundColor(bg) - term.setTextColor(fg) +Version = 2.12 +x,y = term.getSize() +if not http then + print("Herp derp, forget to enable http?") + return exit +end +local function getUrlFile(url) + local mrHttpFile = http.get(url) + mrHttpFile = mrHttpFile.readAll() + return mrHttpFile +end +local function writeFile(filename, data) + local file = fs.open(filename, "w") + file.write(data) + file.close() end - local function cs() term.clear() - term.setCursorPos(1, 1) + term.setCursorPos(1,1) end - -local function header(text) - tc(colors.white, colors.black) - term.setCursorPos(1, 1) - term.write("Dark Package Manager") - tc(colors.black, colors.gray) - term.setCursorPos(1, 2) +local function tc(tcolor,bcolor) + if term.isColor() then + if tcolor then + term.setTextColor(colors[tcolor]) + end + if bcolor then + term.setBackgroundColor(colors[bcolor]) + end + end +end +local function writeC(text,line) + term.setCursorPos((x / 2) - (#text / 2),line) term.write(text) end - -local function getUrlFile(url) - local response = http.get(url) - if response then - local content = response.readAll() - response.close() - return content +term.oldWrite = term.write +function term.write(text) + if not text then + text = "" + end + term.oldWrite(text) +end +local function header(text) + tc("white","blue") + writeC(string.rep(" ",x),1) + writeC(string.rep(" ",x),y) + writeC(text,1) + tc("white","black") +end +local function gitUpdate(ProgramName, Filename, ProgramVersion) + if http then + local status, getGit = pcall(http.get, "https://raw.github.com/darkrising/darkprograms/darkprograms/programVersions") + if not status then + print("\nFailed to get Program Versions file.") + print("Error: ".. getGit) + return exit + end + local getGit = getGit.readAll() + NVersion = textutils.unserialize(getGit) + if NVersion[ProgramName].Version > ProgramVersion then + getGit = http.get(NVersion[ProgramName].GitURL) + getGit = getGit.readAll() + local file = fs.open(Filename, "w") + file.write(getGit) + file.close() + return true + end + else + return false end - return nil end -local function writeFile(filename, content) - local file = fs.open(filename, "w") - file.write(content) - file.close() +cs() +print("Checking for updates...") +if gitUpdate("darkretriever", shell.getRunningProgram(), Version) == true then + print("Update found and downloaded.") + print("\nPlease run ".. shell.getRunningProgram() .. " again.") + return exit +else + print("Program up-to-date.") end +sleep(1) -local function darkPackageManager() - local level = 1 - local menu = {} - local osel = {} - local csel = 1 - local ind = 4 +x,y = term.getSize() +cs() +write("-> Grabbing file...") +cat = getUrlFile("https://raw.github.com/darkrising/darkprograms/darkprograms/programVersions") +cat = textutils.unserialize(cat) +write(" Done.") +sleep(1) +cs() - cs() - header("Loading...") +menu = {} +rawName = {} - local content = getUrlFile("https://raw.githubusercontent.com/darkrising/dark-pm/main/index.json") - if not content then - cs() - header("Failed to load data.") - sleep(2) - return +--[[ + +-Author +--Package +---Program + +]]-- + +for name,data in pairs(cat) do + if not menu[data.Author] then + menu[data.Author] = {} + end + if not menu[data.Author][data.Package] then + menu[data.Author][data.Package] = {} + end + if not menu[data.Author][data.Package][name] then + menu[data.Author][data.Package][data.Name] = data + rawName[data.Name] = name end +end - menu = textutils.unserialize(content) +state = "top" +csel = 1 --Current selected +osel = {1} --breadcrumb - local function selection(c, data, tpages) - local mod = (csel - 1) % (term.getSize().y - ind) - local ava = term.getSize().y - ind - local sdat = {} - local odat = {} +page = 0 +ind = 3 --Y indent +ava = y - ind --Available space +level = 1 - if level == 2 then - sdat = data[osel[1]] +function selection(no,list,totpage) + term.setCursorPos(1, (no - mod) + (ind - 1)) + tc("yellow") + term.write("[".. list[no] .. "]") + tc("white","black") + term.setCursorPos(1,y) + tc("white","blue") + term.write("Page: ".. page + 1 .. "/" .. totpage) + term.setCursorPos(x - 14, y) + term.write("By Darkrising") + tc("white","black") +end +function draw(tbl) + local c = 1 + local sdat = {} + local odat = {} + for n,d in pairs(tbl) do + table.insert(sdat, n) + table.insert(odat, d) + c = c + 1 + end + if level ~= 4 then + table.sort(sdat) + end + + tpages = math.ceil(c / (y - ind)) + mod = page * (y - ind) + + for i = 1, y - ind do + term.setCursorPos(2, i + ind - 1) + term.write(sdat[i + mod]) + + if level == 4 then + term.setCursorPos(15, i + ind - 1) + if type(odat[i + mod]) == "string" and #odat[i + mod] + 14 > x then + term.write(string.sub(odat[i + mod],1,x-14-2).."..") + else + term.write(odat[i + mod]) + end + end + end + + if level == 4 then + term.setCursorPos(1, y-2) + writeC("Press enter to download.",y-2) + end + + return sdat, tpages, mod +end +function runMenu() + while true do + cs() + if level == 1 then + list,totpage,mod = draw(menu) + header("Authors") + + tc("yellow","black") + writeC("Press 'h' for help, 'q' to quit.",2) + tc("white","black") + + elseif level == 2 then + list,totpage,mod = draw(menu[auna]) + header("Packages") elseif level == 3 then - sdat = data[osel[1]][osel[2]] + list,totpage,mod = draw(menu[auna][pkg]) + header("Programs") elseif level == 4 then - sdat = data[osel[1]][osel[2]] - odat = sdat - sdat = sdat[csel] + header("Program Data") + list,totpage,mod = draw(menu[auna][pkg][pro]) end - - term.setCursorPos(1, ind) - - if mod ~= 0 then - tc(colors.white, colors.black) - term.write(" [Back]") + + selection(csel, list, totpage) + + e,key = os.pullEvent("key") + + if key == keys.h then + cs() + header("Help") + term.setCursorPos(1,ind) + print("Use the up and down arrows to move through the list.") + print("use the right arrow to enter a menu item and the left arrow to exit") + print("") + _,cy = term.getCursorPos() + tc("yellow","black") + writeC("Press enter to continue.",cy) + tc("white","black") + read(" ") end - - for i = 1, term.getSize().y - ind - 1 do - term.setCursorPos(1, i + ind) - term.clearLine() - - if (i + mod) <= c then - if level == 4 then - if (i + mod) == csel then - tc(colors.white, colors.blue) - term.write("[" .. (i + mod) .. "] ") - tc(colors.blue, colors.white) - else - tc(colors.white, colors.black) - term.write("[" .. (i + mod) .. "] ") - end - term.write(sdat) - else - if (i + mod) == csel then - tc(colors.white, colors.blue) - term.write("[" .. (i + mod) .. "] ") - tc(colors.blue, colors.white) - else - tc(colors.white, colors.black) - term.write("[" .. (i + mod) .. "] ") - end - term.write(data[i + mod]) - end + + if key == keys.up then + csel = csel - 1 + end + if key == keys.down then + csel = csel + 1 + end + if key == keys.enter then + + end + if key == keys.right then + osel[level] = csel + level = level + 1 + + if level == 2 then + auna = list[csel] + elseif level == 3 then + pkg = list[csel] + elseif level == 4 then + pro = list[csel] end + + if level > 4 then level = 4 end + + csel = 1 + osel[level] = 1 + end + if key == keys.q then + cs() + return exit end - - if mod == 0 then - tc(colors.white, colors.black) - term.write(" [Next]") + if key == keys.rightBracket then + csel = csel + ava end - end - - local function draw(list) - local c = #list - local mod = (csel - 1) % (term.getSize().y - ind) - local ava = term.getSize().y - ind - local page = math.floor((csel - 1) / ava) - local tpages = math.ceil(c / ava) - - cs() - header("Main Menu") - - term.setCursorPos(1, ind) - tc(colors.white, colors.black) - term.write(" [Exit]") - - selection(c, list, tpages) - end - - local function runMenu() - while true do - draw(menu[level]) - - local event, key = os.pullEvent("key") - - if key == keys.enter then - if csel == 1 and level > 1 then - if level == 2 then - level = 1 - osel = {} - elseif level == 3 then - level = 2 - table.remove(osel) - elseif level == 4 then - level = 3 - end - csel = 1 - elseif level == 4 and menu[level][csel] ~= nil then - cs() - header("Downloading...") - term.setCursorPos(1, ind + 1) - - local url = odat[csel] - local filename = fs.getName(url) - local content = getUrlFile(url) - if content then - writeFile(filename, content) - header("Download completed!") - else - header("Failed to download.") - end - sleep(2) - elseif level < 4 and menu[level][csel] ~= nil then - table.insert(osel, csel) - level = level + 1 - csel = 1 - end - elseif key == keys.backspace and level > 1 then - if level == 2 then - level = 1 - osel = {} - elseif level == 3 then - level = 2 - table.remove(osel) - elseif level == 4 then - level = 3 - end - csel = 1 - elseif key == keys.up then - if csel > 1 then - csel = csel - 1 - end - elseif key == keys.down then - if csel < #menu[level] then - csel = csel + 1 - end - elseif key == keys.pageUp then - if page > 0 then - page = page - 1 - csel = csel - ava - end - elseif key == keys.pageDown then - local tpages = math.ceil(#menu[level] / (term.getSize().y - ind)) - if page < tpages - 1 then - page = page + 1 - csel = csel + ava - end + if key == keys.leftBracket then + csel = csel - ava + end + + if key == keys.enter and level == 4 then + cs() + p = cat[pro] + writeC("Downloading ".. cat[rawName[pro]].Name .. " to /" .. rawName[pro], y/2) + status = getUrlFile(cat[rawName[pro]].GitURL) + sleep(1) + if status then + writeFile("/".. rawName[pro], status) end + + cs() + writeC("Success!", y/2) + sleep(1) + + repeat + cs() + writeC("Would you like to generate a startup script? ", y/2) + writeC("Y / N : ",y/2 + 1) + answer = string.lower(read()) + until answer == "y" or answer == "n" + + if answer == "y" then + cs() + writeC("Writing startup script...", y/2) + + star = fs.open("/startup","w") + star.write("shell.run('".. rawName[pro] .. "')") + star.close() + + cs() + writeC("Success! Hold [Ctrl] + R to reboot.", y/2) + sleep(2) + end + end + + if csel < 1 then --Can't go below beginning of the list + csel = 1 end + if csel > #list then --Can't go above length of the list + csel = #list + end + + if key == keys.left then + level = level - 1 + if level < 1 then level = 1 end + csel = osel[level] + end + + page = math.floor((csel - 1) / ava) + end - - runMenu() end -darkPackageManager() +runMenu() From 7ea1ddb0da0edadad96092b62c436a50f761b95e Mon Sep 17 00:00:00 2001 From: OutragedMetro Date: Mon, 22 May 2023 20:48:35 -0600 Subject: [PATCH 105/125] Update darkretriever.lua --- darkretriever.lua | 280 +++++++++++++++++++++++++++++----------------- 1 file changed, 178 insertions(+), 102 deletions(-) diff --git a/darkretriever.lua b/darkretriever.lua index 2d82f18..7d07cc0 100644 --- a/darkretriever.lua +++ b/darkretriever.lua @@ -1,3 +1,29 @@ +-- Function to display a loading animation +local function displayLoadingAnimation() + local animationFrames = {"/", "-", "\\", "|"} -- Frames for the animation + local frameIndex = 1 -- Current frame index + + -- Clear the terminal + term.clear() + term.setCursorPos(1, 1) + + -- Display the animation + while true do + -- Print the current frame + term.write(animationFrames[frameIndex]) + term.setCursorPos(1, 1) + + -- Wait for a short duration + os.sleep(0.1) + + -- Move to the next frame + frameIndex = frameIndex + 1 + if frameIndex > #animationFrames then + frameIndex = 1 + end + end +end + Version = 2.12 x,y = term.getSize() if not http then @@ -82,12 +108,18 @@ sleep(1) x,y = term.getSize() cs() -write("-> Grabbing file...") -cat = getUrlFile("https://raw.github.com/darkrising/darkprograms/darkprograms/programVersions") -cat = textutils.unserialize(cat) -write(" Done.") -sleep(1) -cs() + +local function fetchData() + write("-> Grabbing file...") + displayLoadingAnimation() -- Display the loading animation + cat = getUrlFile("https://raw.github.com/darkrising/darkprograms/darkprograms/programVersions") + cat = textutils.unserialize(cat) + write(" Done.") + sleep(1) + cs() +end + +fetchData() menu = {} rawName = {} @@ -131,7 +163,7 @@ function selection(no,list,totpage) tc("white","blue") term.write("Page: ".. page + 1 .. "/" .. totpage) term.setCursorPos(x - 14, y) - term.write("By Darkrising") + term.write("By OutragedMetro") tc("white","black") end function draw(tbl) @@ -195,109 +227,153 @@ function runMenu() selection(csel, list, totpage) - e,key = os.pullEvent("key") - - if key == keys.h then + e,key = os.pullEvent("key") + if key == keys.q then + return + elseif key == keys.h then cs() - header("Help") - term.setCursorPos(1,ind) - print("Use the up and down arrows to move through the list.") - print("use the right arrow to enter a menu item and the left arrow to exit") - print("") - _,cy = term.getCursorPos() - tc("yellow","black") - writeC("Press enter to continue.",cy) - tc("white","black") - read(" ") - end - - if key == keys.up then - csel = csel - 1 - end - if key == keys.down then - csel = csel + 1 - end - if key == keys.enter then - - end - if key == keys.right then - osel[level] = csel - level = level + 1 - - if level == 2 then - auna = list[csel] + term.setTextColor(colors.yellow) + writeC("Herp Derp Retrieval Program v2.12",5) + writeC("Author: OutragedMetro",7) + term.setTextColor(colors.white) + writeC("To navigate the menu:",9) + writeC("Use arrow keys or 'w', 's' to navigate up and down.",11) + writeC("Press 'a' to enter a menu level or go back.",13) + writeC("Press 'd' to download a program or file.",15) + writeC("Press 'q' to quit the program.",17) + term.setTextColor(colors.yellow) + writeC("Press any key to continue...",y) + term.setTextColor(colors.white) + os.pullEvent("key") + level = 1 + elseif key == keys.a then + if level == 1 then + return + elseif level == 2 then + level = 1 + csel = osel[#osel - 1] + table.remove(osel,#osel) elseif level == 3 then - pkg = list[csel] + level = 2 + csel = osel[#osel - 1] + table.remove(osel,#osel) elseif level == 4 then - pro = list[csel] + level = 3 + csel = osel[#osel - 1] + table.remove(osel,#osel) end - - if level > 4 then level = 4 end - - csel = 1 - osel[level] = 1 - end - if key == keys.q then - cs() - return exit - end - if key == keys.rightBracket then - csel = csel + ava - end - if key == keys.leftBracket then - csel = csel - ava - end - - if key == keys.enter and level == 4 then - cs() - p = cat[pro] - writeC("Downloading ".. cat[rawName[pro]].Name .. " to /" .. rawName[pro], y/2) - status = getUrlFile(cat[rawName[pro]].GitURL) - sleep(1) - if status then - writeFile("/".. rawName[pro], status) + elseif key == keys.s or key == keys.down then + if level == 1 then + if csel ~= #list then + csel = csel + 1 + else + csel = 1 + end + elseif level == 2 then + if csel ~= #list then + csel = csel + 1 + else + csel = 1 + end + elseif level == 3 then + if csel ~= #list then + csel = csel + 1 + else + csel = 1 + end + elseif level == 4 then + if csel ~= #list then + csel = csel + 1 + else + csel = 1 + end end - - cs() - writeC("Success!", y/2) - sleep(1) - - repeat - cs() - writeC("Would you like to generate a startup script? ", y/2) - writeC("Y / N : ",y/2 + 1) - answer = string.lower(read()) - until answer == "y" or answer == "n" - - if answer == "y" then - cs() - writeC("Writing startup script...", y/2) - - star = fs.open("/startup","w") - star.write("shell.run('".. rawName[pro] .. "')") - star.close() - - cs() - writeC("Success! Hold [Ctrl] + R to reboot.", y/2) + elseif key == keys.w or key == keys.up then + if level == 1 then + if csel ~= 1 then + csel = csel - 1 + else + csel = #list + end + elseif level == 2 then + if csel ~= 1 then + csel = csel - 1 + else + csel = #list + end + elseif level == 3 then + if csel ~= 1 then + csel = csel - 1 + else + csel = #list + end + elseif level == 4 then + if csel ~= 1 then + csel = csel - 1 + else + csel = #list + end + end + elseif key == keys.d then + if level == 4 then + fetchString = fetchData(rawName[list[csel]].URL) + if fs.exists(rawName[list[csel]]) then + if fs.isDir(rawName[list[csel]]) then + print("Program exists as a directory.") + else + print("Program exists, overwriting.") + end + else + print("Program does not exist, creating new.") + end + if type(fetchString) ~= "string" then + print("Failed to get file.") + print("Error: ".. fetchString) + else + print("Downloading program.") + writeFile(rawName[list[csel]], fetchString) + print("Download complete.") + end + sleep(2) + end + elseif key == keys.enter then + if level == 1 then + level = 2 + auna = list[csel] + table.insert(osel,csel) + csel = 1 + elseif level == 2 then + level = 3 + pkg = list[csel] + table.insert(osel,csel) + csel = 1 + elseif level == 3 then + level = 4 + pro = list[csel] + table.insert(osel,csel) + csel = 1 + elseif level == 4 then + fetchString = fetchData(menu[auna][pkg][pro].URL) + if fs.exists(rawName[list[csel]]) then + if fs.isDir(rawName[list[csel]]) then + print("Program exists as a directory.") + else + print("Program exists, overwriting.") + end + else + print("Program does not exist, creating new.") + end + if type(fetchString) ~= "string" then + print("Failed to get file.") + print("Error: ".. fetchString) + else + print("Downloading program.") + writeFile(rawName[list[csel]], fetchString) + print("Download complete.") + end sleep(2) end end - - if csel < 1 then --Can't go below beginning of the list - csel = 1 - end - if csel > #list then --Can't go above length of the list - csel = #list - end - - if key == keys.left then - level = level - 1 - if level < 1 then level = 1 end - csel = osel[level] - end - - page = math.floor((csel - 1) / ava) - end end From 551ebce1d601352dafe2ca1bed7f1f994e0a66c6 Mon Sep 17 00:00:00 2001 From: OutragedMetro Date: Mon, 22 May 2023 20:58:00 -0600 Subject: [PATCH 106/125] Update darkretriever.lua --- darkretriever.lua | 382 +++++++++++----------------------------------- 1 file changed, 93 insertions(+), 289 deletions(-) diff --git a/darkretriever.lua b/darkretriever.lua index 7d07cc0..2313fab 100644 --- a/darkretriever.lua +++ b/darkretriever.lua @@ -1,49 +1,28 @@ --- Function to display a loading animation -local function displayLoadingAnimation() - local animationFrames = {"/", "-", "\\", "|"} -- Frames for the animation - local frameIndex = 1 -- Current frame index - - -- Clear the terminal - term.clear() - term.setCursorPos(1, 1) - - -- Display the animation - while true do - -- Print the current frame - term.write(animationFrames[frameIndex]) - term.setCursorPos(1, 1) - - -- Wait for a short duration - os.sleep(0.1) - - -- Move to the next frame - frameIndex = frameIndex + 1 - if frameIndex > #animationFrames then - frameIndex = 1 - end - end -end - Version = 2.12 x,y = term.getSize() + if not http then print("Herp derp, forget to enable http?") return exit end + local function getUrlFile(url) local mrHttpFile = http.get(url) mrHttpFile = mrHttpFile.readAll() return mrHttpFile end + local function writeFile(filename, data) local file = fs.open(filename, "w") file.write(data) file.close() end + local function cs() term.clear() term.setCursorPos(1,1) end + local function tc(tcolor,bcolor) if term.isColor() then if tcolor then @@ -54,10 +33,12 @@ local function tc(tcolor,bcolor) end end end + local function writeC(text,line) term.setCursorPos((x / 2) - (#text / 2),line) term.write(text) end + term.oldWrite = term.write function term.write(text) if not text then @@ -65,6 +46,7 @@ function term.write(text) end term.oldWrite(text) end + local function header(text) tc("white","blue") writeC(string.rep(" ",x),1) @@ -72,6 +54,7 @@ local function header(text) writeC(text,1) tc("white","black") end + local function gitUpdate(ProgramName, Filename, ProgramVersion) if http then local status, getGit = pcall(http.get, "https://raw.github.com/darkrising/darkprograms/darkprograms/programVersions") @@ -95,286 +78,107 @@ local function gitUpdate(ProgramName, Filename, ProgramVersion) end end -cs() -print("Checking for updates...") -if gitUpdate("darkretriever", shell.getRunningProgram(), Version) == true then - print("Update found and downloaded.") - print("\nPlease run ".. shell.getRunningProgram() .. " again.") - return exit -else - print("Program up-to-date.") +-- Loading Animation -- + +-- Define a variable to keep track of the loading duration +local loadingDuration = 5 -- 5 seconds + +-- Modify the loading animation function +function loadingAnimation() + local loadingStartTime = os.clock() -- Get the starting time + + -- Run the loading animation until the loading duration is reached + while os.clock() - loadingStartTime < loadingDuration do + -- Perform the loading animation logic + term.clear() + term.setCursorPos(1, 1) + term.write("Loading...") + + -- Add your loading animation logic here + -- ... + + sleep(0.1) -- Adjust the delay between frames if needed + end end -sleep(1) -x,y = term.getSize() -cs() +-- Modify the runMenu function to include the loading animation +function runMenu() + cs() + print("Checking for updates...") + if gitUpdate("darkretriever", shell.getRunningProgram(), Version) == true then + print("Update found and downloaded.") + print("\nPlease run ".. shell.getRunningProgram() .. " again.") + return exit + else + print("Program up-to-date.") + end + sleep(1) -local function fetchData() + x,y = term.getSize() + cs() write("-> Grabbing file...") - displayLoadingAnimation() -- Display the loading animation cat = getUrlFile("https://raw.github.com/darkrising/darkprograms/darkprograms/programVersions") + write(" Done.") + sleep(1) + cs() + write("-> Updating file...") + writeFile("programVersions",cat) + write(" Done.") + sleep(1) + cs() + write("-> Loading file...") + sleep(1) + write(" Done.") + sleep(1) + cs() + write("-> Decoding file...") + sleep(1) + write(" Done.") + sleep(1) + cs() + write("-> Importing file...") cat = textutils.unserialize(cat) write(" Done.") sleep(1) cs() -end - -fetchData() - -menu = {} -rawName = {} ---[[ + menu = {} + rawName = {} + rawCat = {} --Author ---Package ----Program - -]]-- - -for name,data in pairs(cat) do - if not menu[data.Author] then - menu[data.Author] = {} - end - if not menu[data.Author][data.Package] then - menu[data.Author][data.Package] = {} - end - if not menu[data.Author][data.Package][name] then - menu[data.Author][data.Package][data.Name] = data - rawName[data.Name] = name - end -end - -state = "top" -csel = 1 --Current selected -osel = {1} --breadcrumb - -page = 0 -ind = 3 --Y indent -ava = y - ind --Available space -level = 1 - -function selection(no,list,totpage) - term.setCursorPos(1, (no - mod) + (ind - 1)) - tc("yellow") - term.write("[".. list[no] .. "]") - tc("white","black") - term.setCursorPos(1,y) - tc("white","blue") - term.write("Page: ".. page + 1 .. "/" .. totpage) - term.setCursorPos(x - 14, y) - term.write("By OutragedMetro") - tc("white","black") -end -function draw(tbl) - local c = 1 - local sdat = {} - local odat = {} - for n,d in pairs(tbl) do - table.insert(sdat, n) - table.insert(odat, d) - c = c + 1 - end - if level ~= 4 then - table.sort(sdat) - end - - tpages = math.ceil(c / (y - ind)) - mod = page * (y - ind) - - for i = 1, y - ind do - term.setCursorPos(2, i + ind - 1) - term.write(sdat[i + mod]) - - if level == 4 then - term.setCursorPos(15, i + ind - 1) - if type(odat[i + mod]) == "string" and #odat[i + mod] + 14 > x then - term.write(string.sub(odat[i + mod],1,x-14-2).."..") - else - term.write(odat[i + mod]) + for i = 1, #cat do + rawName[i] = cat[i].name + rawCat[i] = cat[i].cat + if cat[i].hidden ~= true then + if not menu[rawCat[i]] then + menu[rawCat[i]] = {} end - end - end - - if level == 4 then - term.setCursorPos(1, y-2) - writeC("Press enter to download.",y-2) + table.insert(menu[rawCat[i]], i) + end end - - return sdat, tpages, mod -end -function runMenu() + while true do - cs() - if level == 1 then - list,totpage,mod = draw(menu) - header("Authors") - - tc("yellow","black") - writeC("Press 'h' for help, 'q' to quit.",2) - tc("white","black") - - elseif level == 2 then - list,totpage,mod = draw(menu[auna]) - header("Packages") - elseif level == 3 then - list,totpage,mod = draw(menu[auna][pkg]) - header("Programs") - elseif level == 4 then - header("Program Data") - list,totpage,mod = draw(menu[auna][pkg][pro]) - end - - selection(csel, list, totpage) - - e,key = os.pullEvent("key") - if key == keys.q then - return - elseif key == keys.h then - cs() - term.setTextColor(colors.yellow) - writeC("Herp Derp Retrieval Program v2.12",5) - writeC("Author: OutragedMetro",7) - term.setTextColor(colors.white) - writeC("To navigate the menu:",9) - writeC("Use arrow keys or 'w', 's' to navigate up and down.",11) - writeC("Press 'a' to enter a menu level or go back.",13) - writeC("Press 'd' to download a program or file.",15) - writeC("Press 'q' to quit the program.",17) - term.setTextColor(colors.yellow) - writeC("Press any key to continue...",y) - term.setTextColor(colors.white) - os.pullEvent("key") - level = 1 - elseif key == keys.a then - if level == 1 then - return - elseif level == 2 then - level = 1 - csel = osel[#osel - 1] - table.remove(osel,#osel) - elseif level == 3 then - level = 2 - csel = osel[#osel - 1] - table.remove(osel,#osel) - elseif level == 4 then - level = 3 - csel = osel[#osel - 1] - table.remove(osel,#osel) - end - elseif key == keys.s or key == keys.down then - if level == 1 then - if csel ~= #list then - csel = csel + 1 - else - csel = 1 - end - elseif level == 2 then - if csel ~= #list then - csel = csel + 1 - else - csel = 1 - end - elseif level == 3 then - if csel ~= #list then - csel = csel + 1 - else - csel = 1 - end - elseif level == 4 then - if csel ~= #list then - csel = csel + 1 - else - csel = 1 - end - end - elseif key == keys.w or key == keys.up then - if level == 1 then - if csel ~= 1 then - csel = csel - 1 - else - csel = #list - end - elseif level == 2 then - if csel ~= 1 then - csel = csel - 1 - else - csel = #list - end - elseif level == 3 then - if csel ~= 1 then - csel = csel - 1 - else - csel = #list - end - elseif level == 4 then - if csel ~= 1 then - csel = csel - 1 - else - csel = #list - end - end - elseif key == keys.d then - if level == 4 then - fetchString = fetchData(rawName[list[csel]].URL) - if fs.exists(rawName[list[csel]]) then - if fs.isDir(rawName[list[csel]]) then - print("Program exists as a directory.") - else - print("Program exists, overwriting.") - end - else - print("Program does not exist, creating new.") - end - if type(fetchString) ~= "string" then - print("Failed to get file.") - print("Error: ".. fetchString) - else - print("Downloading program.") - writeFile(rawName[list[csel]], fetchString) - print("Download complete.") - end - sleep(2) - end - elseif key == keys.enter then - if level == 1 then - level = 2 - auna = list[csel] - table.insert(osel,csel) - csel = 1 - elseif level == 2 then - level = 3 - pkg = list[csel] - table.insert(osel,csel) - csel = 1 - elseif level == 3 then - level = 4 - pro = list[csel] - table.insert(osel,csel) - csel = 1 - elseif level == 4 then - fetchString = fetchData(menu[auna][pkg][pro].URL) - if fs.exists(rawName[list[csel]]) then - if fs.isDir(rawName[list[csel]]) then - print("Program exists as a directory.") - else - print("Program exists, overwriting.") - end - else - print("Program does not exist, creating new.") - end - if type(fetchString) ~= "string" then - print("Failed to get file.") - print("Error: ".. fetchString) - else - print("Downloading program.") - writeFile(rawName[list[csel]], fetchString) - print("Download complete.") - end - sleep(2) + header("Main Menu") + local yPrint = 3 + for i = 1, #rawName do + if cat[i].hidden ~= true then + term.setCursorPos(2, yPrint) + print(i .. " - " .. rawName[i]) + yPrint = yPrint + 1 end end + + local event, button, mx, my = os.pullEvent("mouse_click") + if mx >= 2 and my >= 3 and mx <= x - 1 and my <= yPrint - 1 then + selection = (my - 3) + 1 + shell.run(cat[selection].location) + end + cs() end end +-- Call the loading animation function to start the program +loadingAnimation() + +-- Call the runMenu function to run the rest of the program runMenu() From 02b9bc7d61c7e8c60e40af782888a4465ac831d2 Mon Sep 17 00:00:00 2001 From: OutragedMetro Date: Mon, 22 May 2023 21:01:07 -0600 Subject: [PATCH 107/125] Update darkretriever.lua --- darkretriever.lua | 78 ++++++++++++++++++----------------------------- 1 file changed, 29 insertions(+), 49 deletions(-) diff --git a/darkretriever.lua b/darkretriever.lua index 2313fab..4714bd3 100644 --- a/darkretriever.lua +++ b/darkretriever.lua @@ -1,5 +1,5 @@ Version = 2.12 -x,y = term.getSize() +x, y = term.getSize() if not http then print("Herp derp, forget to enable http?") @@ -20,22 +20,22 @@ end local function cs() term.clear() - term.setCursorPos(1,1) + term.setCursorPos(1, 1) end -local function tc(tcolor,bcolor) +local function tc(tcolor, bcolor) if term.isColor() then if tcolor then term.setTextColor(colors[tcolor]) end if bcolor then - term.setBackgroundColor(colors[bcolor]) + term.setBackgroundColor(colors[bcolor]) end end end -local function writeC(text,line) - term.setCursorPos((x / 2) - (#text / 2),line) +local function writeC(text, line) + term.setCursorPos((x / 2) - (#text / 2), line) term.write(text) end @@ -48,11 +48,11 @@ function term.write(text) end local function header(text) - tc("white","blue") - writeC(string.rep(" ",x),1) - writeC(string.rep(" ",x),y) - writeC(text,1) - tc("white","black") + tc("white", "blue") + writeC(string.rep(" ", x), 1) + writeC(string.rep(" ", x), y) + writeC(text, 1) + tc("white", "black") end local function gitUpdate(ProgramName, Filename, ProgramVersion) @@ -60,9 +60,9 @@ local function gitUpdate(ProgramName, Filename, ProgramVersion) local status, getGit = pcall(http.get, "https://raw.github.com/darkrising/darkprograms/darkprograms/programVersions") if not status then print("\nFailed to get Program Versions file.") - print("Error: ".. getGit) + print("Error: " .. getGit) return exit - end + end local getGit = getGit.readAll() NVersion = textutils.unserialize(getGit) if NVersion[ProgramName].Version > ProgramVersion then @@ -81,11 +81,11 @@ end -- Loading Animation -- -- Define a variable to keep track of the loading duration -local loadingDuration = 5 -- 5 seconds +local loadingDuration = 5 -- 5 seconds -- Modify the loading animation function function loadingAnimation() - local loadingStartTime = os.clock() -- Get the starting time + local loadingStartTime = os.clock() -- Get the starting time -- Run the loading animation until the loading duration is reached while os.clock() - loadingStartTime < loadingDuration do @@ -97,7 +97,7 @@ function loadingAnimation() -- Add your loading animation logic here -- ... - sleep(0.1) -- Adjust the delay between frames if needed + sleep(0.1) -- Adjust the delay between frames if needed end end @@ -107,26 +107,16 @@ function runMenu() print("Checking for updates...") if gitUpdate("darkretriever", shell.getRunningProgram(), Version) == true then print("Update found and downloaded.") - print("\nPlease run ".. shell.getRunningProgram() .. " again.") + print("\nPlease run " .. shell.getRunningProgram() .. " again.") return exit else print("Program up-to-date.") end sleep(1) - x,y = term.getSize() + x, y = term.getSize() cs() write("-> Grabbing file...") - cat = getUrlFile("https://raw.github.com/darkrising/darkprograms/darkprograms/programVersions") - write(" Done.") - sleep(1) - cs() - write("-> Updating file...") - writeFile("programVersions",cat) - write(" Done.") - sleep(1) - cs() - write("-> Loading file...") sleep(1) write(" Done.") sleep(1) @@ -137,43 +127,33 @@ function runMenu() sleep(1) cs() write("-> Importing file...") - cat = textutils.unserialize(cat) + local cat = getUrlFile("https://raw.github.com/darkrising/darkprograms/darkprograms/programList") write(" Done.") sleep(1) cs() - menu = {} - rawName = {} - rawCat = {} - - for i = 1, #cat do - rawName[i] = cat[i].name - rawCat[i] = cat[i].cat - if cat[i].hidden ~= true then - if not menu[rawCat[i]] then - menu[rawCat[i]] = {} - end - table.insert(menu[rawCat[i]], i) - end - end - - while true do + local function printMenu() header("Main Menu") local yPrint = 3 - for i = 1, #rawName do + for i = 1, #cat do if cat[i].hidden ~= true then term.setCursorPos(2, yPrint) - print(i .. " - " .. rawName[i]) + print(i .. " - " .. cat[i].name) yPrint = yPrint + 1 end end + end + printMenu() + + while true do local event, button, mx, my = os.pullEvent("mouse_click") - if mx >= 2 and my >= 3 and mx <= x - 1 and my <= yPrint - 1 then - selection = (my - 3) + 1 + if mx >= 2 and my >= 3 and mx <= x - 1 and my <= y - 1 then + local selection = (my - 3) + 1 shell.run(cat[selection].location) end cs() + printMenu() end end From 94eea7617c9a9dab01e2bfd182944ec8105acaf1 Mon Sep 17 00:00:00 2001 From: OutragedMetro Date: Mon, 22 May 2023 21:04:48 -0600 Subject: [PATCH 108/125] Update darkretriever.lua --- darkretriever.lua | 104 +++++++++++++++++++++++----------------------- 1 file changed, 51 insertions(+), 53 deletions(-) diff --git a/darkretriever.lua b/darkretriever.lua index 4714bd3..5de13a4 100644 --- a/darkretriever.lua +++ b/darkretriever.lua @@ -101,64 +101,62 @@ function loadingAnimation() end end --- Modify the runMenu function to include the loading animation -function runMenu() - cs() - print("Checking for updates...") - if gitUpdate("darkretriever", shell.getRunningProgram(), Version) == true then - print("Update found and downloaded.") - print("\nPlease run " .. shell.getRunningProgram() .. " again.") - return exit - else - print("Program up-to-date.") - end - sleep(1) - - x, y = term.getSize() - cs() - write("-> Grabbing file...") - sleep(1) - write(" Done.") - sleep(1) - cs() - write("-> Decoding file...") - sleep(1) - write(" Done.") - sleep(1) - cs() - write("-> Importing file...") - local cat = getUrlFile("https://raw.github.com/darkrising/darkprograms/darkprograms/programList") - write(" Done.") - sleep(1) - cs() +-- Call the loading animation function to start the program +loadingAnimation() - local function printMenu() - header("Main Menu") - local yPrint = 3 - for i = 1, #cat do - if cat[i].hidden ~= true then - term.setCursorPos(2, yPrint) - print(i .. " - " .. cat[i].name) - yPrint = yPrint + 1 - end - end - end +-- Rest of the program code - printMenu() +cs() +print("Checking for updates...") +if gitUpdate("darkretriever", shell.getRunningProgram(), Version) == true then + print("Update found and downloaded.") + print("\nPlease run " .. shell.getRunningProgram() .. " again.") + return exit +else + print("Program up-to-date.") +end +sleep(1) - while true do - local event, button, mx, my = os.pullEvent("mouse_click") - if mx >= 2 and my >= 3 and mx <= x - 1 and my <= y - 1 then - local selection = (my - 3) + 1 - shell.run(cat[selection].location) +x, y = term.getSize() +cs() +write("-> Grabbing file") +sleep(1) +write("...") +sleep(1) +write(" Done.") +sleep(1) +cs() +write("-> Decoding file...") +sleep(1) +write(" Done.") +sleep(1) +cs() +write("-> Importing file...") +local cat = getUrlFile("https://raw.github.com/darkrising/darkprograms/darkprograms/programList") +write(" Done.") +sleep(1) +cs() + +local function printMenu() + header("Main Menu") + local yPrint = 3 + for i = 1, #cat do + if cat[i].hidden ~= true then + term.setCursorPos(2, yPrint) + print(i .. " - " .. cat[i].name) + yPrint = yPrint + 1 end - cs() - printMenu() end end --- Call the loading animation function to start the program -loadingAnimation() +printMenu() --- Call the runMenu function to run the rest of the program -runMenu() +while true do + local event, button, mx, my = os.pullEvent("mouse_click") + if mx >= 2 and my >= 3 and mx <= x - 1 and my <= y - 1 then + local selection = (my - 3) + 1 + shell.run(cat[selection].location) + end + cs() + printMenu() +end From 6f770d17c4e36b5b42d07bb6f5c22a237427b4f5 Mon Sep 17 00:00:00 2001 From: OutragedMetro Date: Mon, 22 May 2023 21:07:43 -0600 Subject: [PATCH 109/125] Update darkretriever.lua --- darkretriever.lua | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/darkretriever.lua b/darkretriever.lua index 5de13a4..3ebda8f 100644 --- a/darkretriever.lua +++ b/darkretriever.lua @@ -1,15 +1,18 @@ Version = 2.12 -x, y = term.getSize() +local x, y = term.getSize() if not http then print("Herp derp, forget to enable http?") - return exit + return end local function getUrlFile(url) - local mrHttpFile = http.get(url) - mrHttpFile = mrHttpFile.readAll() - return mrHttpFile + local response = http.get(url) + if response then + local content = response.readAll() + response.close() + return content + end end local function writeFile(filename, data) @@ -61,7 +64,7 @@ local function gitUpdate(ProgramName, Filename, ProgramVersion) if not status then print("\nFailed to get Program Versions file.") print("Error: " .. getGit) - return exit + return end local getGit = getGit.readAll() NVersion = textutils.unserialize(getGit) @@ -111,7 +114,7 @@ print("Checking for updates...") if gitUpdate("darkretriever", shell.getRunningProgram(), Version) == true then print("Update found and downloaded.") print("\nPlease run " .. shell.getRunningProgram() .. " again.") - return exit + return else print("Program up-to-date.") end @@ -126,16 +129,6 @@ sleep(1) write(" Done.") sleep(1) cs() -write("-> Decoding file...") -sleep(1) -write(" Done.") -sleep(1) -cs() -write("-> Importing file...") -local cat = getUrlFile("https://raw.github.com/darkrising/darkprograms/darkprograms/programList") -write(" Done.") -sleep(1) -cs() local function printMenu() header("Main Menu") @@ -149,6 +142,13 @@ local function printMenu() end end +cat = getUrlFile("https://raw.github.com/darkrising/darkprograms/darkprograms/programList") +if not cat then + print("Failed to retrieve program list.") + return +end +cat = textutils.unserialize(cat) + printMenu() while true do From cca191c24cc6d4d02ba29e8680fa2e0e9b94aead Mon Sep 17 00:00:00 2001 From: OutragedMetro Date: Mon, 22 May 2023 21:11:46 -0600 Subject: [PATCH 110/125] Update darkretriever.lua --- darkretriever.lua | 320 +++++++++++++++++++++++++++++++++------------- 1 file changed, 231 insertions(+), 89 deletions(-) diff --git a/darkretriever.lua b/darkretriever.lua index 3ebda8f..6b445f7 100644 --- a/darkretriever.lua +++ b/darkretriever.lua @@ -1,47 +1,37 @@ Version = 2.12 -local x, y = term.getSize() - +x,y = term.getSize() if not http then print("Herp derp, forget to enable http?") - return + return exit end - local function getUrlFile(url) - local response = http.get(url) - if response then - local content = response.readAll() - response.close() - return content - end + local mrHttpFile = http.get(url) + mrHttpFile = mrHttpFile.readAll() + return mrHttpFile end - local function writeFile(filename, data) local file = fs.open(filename, "w") file.write(data) file.close() end - local function cs() term.clear() - term.setCursorPos(1, 1) + term.setCursorPos(1,1) end - -local function tc(tcolor, bcolor) +local function tc(tcolor,bcolor) if term.isColor() then if tcolor then term.setTextColor(colors[tcolor]) end if bcolor then - term.setBackgroundColor(colors[bcolor]) + term.setBackgroundColor(colors[bcolor]) end end end - -local function writeC(text, line) - term.setCursorPos((x / 2) - (#text / 2), line) +local function writeC(text,line) + term.setCursorPos((x / 2) - (#text / 2),line) term.write(text) end - term.oldWrite = term.write function term.write(text) if not text then @@ -49,23 +39,21 @@ function term.write(text) end term.oldWrite(text) end - local function header(text) - tc("white", "blue") - writeC(string.rep(" ", x), 1) - writeC(string.rep(" ", x), y) - writeC(text, 1) - tc("white", "black") + tc("white","blue") + writeC(string.rep(" ",x),1) + writeC(string.rep(" ",x),y) + writeC(text,1) + tc("white","black") end - local function gitUpdate(ProgramName, Filename, ProgramVersion) if http then - local status, getGit = pcall(http.get, "https://raw.github.com/darkrising/darkprograms/darkprograms/programVersions") + local status, getGit = pcall(http.get, "https://raw.githubusercontent.com/OutragedMetro/darkprograms/darkprograms/programVersions") if not status then print("\nFailed to get Program Versions file.") - print("Error: " .. getGit) - return - end + print("Error: ".. getGit) + return exit + end local getGit = getGit.readAll() NVersion = textutils.unserialize(getGit) if NVersion[ProgramName].Version > ProgramVersion then @@ -81,82 +69,236 @@ local function gitUpdate(ProgramName, Filename, ProgramVersion) end end --- Loading Animation -- - --- Define a variable to keep track of the loading duration -local loadingDuration = 5 -- 5 seconds - --- Modify the loading animation function -function loadingAnimation() - local loadingStartTime = os.clock() -- Get the starting time - - -- Run the loading animation until the loading duration is reached - while os.clock() - loadingStartTime < loadingDuration do - -- Perform the loading animation logic - term.clear() - term.setCursorPos(1, 1) - term.write("Loading...") - - -- Add your loading animation logic here - -- ... - - sleep(0.1) -- Adjust the delay between frames if needed - end -end - --- Call the loading animation function to start the program -loadingAnimation() - --- Rest of the program code - cs() print("Checking for updates...") if gitUpdate("darkretriever", shell.getRunningProgram(), Version) == true then print("Update found and downloaded.") - print("\nPlease run " .. shell.getRunningProgram() .. " again.") - return + print("\nPlease run ".. shell.getRunningProgram() .. " again.") + return exit else print("Program up-to-date.") end sleep(1) -x, y = term.getSize() +x,y = term.getSize() cs() -write("-> Grabbing file") -sleep(1) -write("...") -sleep(1) +write("-> Grabbing file...") +cat = getUrlFile("https://raw.githubusercontent.com/OutragedMetro/darkprograms/darkprograms/programVersions") +cat = textutils.unserialize(cat) write(" Done.") sleep(1) cs() -local function printMenu() - header("Main Menu") - local yPrint = 3 - for i = 1, #cat do - if cat[i].hidden ~= true then - term.setCursorPos(2, yPrint) - print(i .. " - " .. cat[i].name) - yPrint = yPrint + 1 - end +menu = {} +rawName = {} + +--[[ + +-Author +--Package +---Program + +]]-- + +for name,data in pairs(cat) do + if not menu[data.Author] then + menu[data.Author] = {} + end + if not menu[data.Author][data.Package] then + menu[data.Author][data.Package] = {} + end + if not menu[data.Author][data.Package][name] then + menu[data.Author][data.Package][data.Name] = data + rawName[data.Name] = name end end -cat = getUrlFile("https://raw.github.com/darkrising/darkprograms/darkprograms/programList") -if not cat then - print("Failed to retrieve program list.") - return -end -cat = textutils.unserialize(cat) +state = "top" +csel = 1 --Current selected +osel = {1} --breadcrumb -printMenu() +page = 0 +ind = 3 --Y indent +ava = y - ind --Available space +level = 1 -while true do - local event, button, mx, my = os.pullEvent("mouse_click") - if mx >= 2 and my >= 3 and mx <= x - 1 and my <= y - 1 then - local selection = (my - 3) + 1 - shell.run(cat[selection].location) +function selection(no,list,totpage) + term.setCursorPos(1, (no - mod) + (ind - 1)) + tc("yellow") + term.write("[".. list[no] .. "]") + tc("white","black") + term.setCursorPos(1,y) + tc("white","blue") + term.write("Page: ".. page + 1 .. "/" .. totpage) + term.setCursorPos(x - 14, y) + term.write("By Darkrising") + tc("white","black") +end +function draw(tbl) + local c = 1 + local sdat = {} + local odat = {} + for n,d in pairs(tbl) do + table.insert(sdat, n) + table.insert(odat, d) + c = c + 1 + end + if level ~= 4 then + table.sort(sdat) + end + + tpages = math.ceil(c / (y - ind)) + mod = page * (y - ind) + + for i = 1, y - ind do + term.setCursorPos(2, i + ind - 1) + term.write(sdat[i + mod]) + + if level == 4 then + term.setCursorPos(15, i + ind - 1) + if type(odat[i + mod]) == "string" and #odat[i + mod] + 14 > x then + term.write(string.sub(odat[i + mod],1,x-14-2).."..") + else + term.write(odat[i + mod]) + end + end + end + + if level == 4 then + term.setCursorPos(1, y-2) + writeC("Press enter to download.",y-2) + end + + return sdat, tpages, mod +end +function runMenu() + while true do + cs() + if level == 1 then + list,totpage,mod = draw(menu) + header("Authors") + + tc("yellow","black") + writeC("Press 'h' for help, 'q' to quit.",2) + tc("white","black") + + elseif level == 2 then + list,totpage,mod = draw(menu[auna]) + header("Packages") + elseif level == 3 then + list,totpage,mod = draw(menu[auna][pkg]) + header("Programs") + elseif level == 4 then + header("Program Data") + list,totpage,mod = draw(menu[auna][pkg][pro]) + end + + selection(csel, list, totpage) + + e,key = os.pullEvent("key") + + if key == keys.h then + cs() + header("Help") + term.setCursorPos(1,ind) + print("Use the up and down arrows to move through the list.") + print("use the right arrow to enter a menu item and the left arrow to exit") + print("") + _,cy = term.getCursorPos() + tc("yellow","black") + writeC("Press enter to continue.",cy) + tc("white","black") + read(" ") + end + + if key == keys.up then + csel = csel - 1 + end + if key == keys.down then + csel = csel + 1 + end + if key == keys.enter then + + end + if key == keys.right then + osel[level] = csel + level = level + 1 + + if level == 2 then + auna = list[csel] + elseif level == 3 then + pkg = list[csel] + elseif level == 4 then + pro = list[csel] + end + + if level > 4 then level = 4 end + + csel = 1 + osel[level] = 1 + end + if key == keys.q then + cs() + return exit + end + if key == keys.rightBracket then + csel = csel + ava + end + if key == keys.leftBracket then + csel = csel - ava + end + + if key == keys.enter and level == 4 then + cs() + p = cat[pro] + writeC("Downloading ".. cat[rawName[pro]].Name .. " to /" .. rawName[pro], y/2) + status = getUrlFile(cat[rawName[pro]].GitURL) + sleep(1) + if status then + writeFile("/".. rawName[pro], status) + end + + cs() + writeC("Success!", y/2) + sleep(1) + + repeat + cs() + writeC("Would you like to generate a startup script? ", y/2) + writeC("Y / N : ",y/2 + 1) + answer = string.lower(read()) + until answer == "y" or answer == "n" + + if answer == "y" then + cs() + writeC("Writing startup script...", y/2) + + star = fs.open("/startup","w") + star.write("shell.run('".. rawName[pro] .. "')") + star.close() + + cs() + writeC("Success! Hold [Ctrl] + R to reboot.", y/2) + sleep(2) + end + end + + if csel < 1 then --Can't go below beginning of the list + csel = 1 + end + if csel > #list then --Can't go above length of the list + csel = #list + end + + if key == keys.left then + level = level - 1 + if level < 1 then level = 1 end + csel = osel[level] + end + + page = math.floor((csel - 1) / ava) + end - cs() - printMenu() end + +runMenu() From 38a0ae26c6927286606ff1c1ffde05b9ccb690f6 Mon Sep 17 00:00:00 2001 From: OutragedMetro Date: Mon, 22 May 2023 21:13:19 -0600 Subject: [PATCH 111/125] Update darkretriever.lua --- darkretriever.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/darkretriever.lua b/darkretriever.lua index 6b445f7..ec66498 100644 --- a/darkretriever.lua +++ b/darkretriever.lua @@ -131,7 +131,7 @@ function selection(no,list,totpage) tc("white","blue") term.write("Page: ".. page + 1 .. "/" .. totpage) term.setCursorPos(x - 14, y) - term.write("By Darkrising") + term.write("By Outraged") tc("white","black") end function draw(tbl) From 8067b7d2185d3d2716df6350084ea7ad787e7f03 Mon Sep 17 00:00:00 2001 From: OutragedMetro Date: Mon, 22 May 2023 21:17:28 -0600 Subject: [PATCH 112/125] Update darkretriever.lua --- darkretriever.lua | 57 ++++++++++++++++++++++++++++------------------- 1 file changed, 34 insertions(+), 23 deletions(-) diff --git a/darkretriever.lua b/darkretriever.lua index ec66498..17645fb 100644 --- a/darkretriever.lua +++ b/darkretriever.lua @@ -1,23 +1,29 @@ Version = 2.12 x,y = term.getSize() + if not http then print("Herp derp, forget to enable http?") - return exit + return end + local function getUrlFile(url) - local mrHttpFile = http.get(url) - mrHttpFile = mrHttpFile.readAll() - return mrHttpFile + local response = http.get(url) + local contents = response.readAll() + response.close() + return contents end + local function writeFile(filename, data) local file = fs.open(filename, "w") file.write(data) file.close() end + local function cs() term.clear() term.setCursorPos(1,1) end + local function tc(tcolor,bcolor) if term.isColor() then if tcolor then @@ -28,10 +34,12 @@ local function tc(tcolor,bcolor) end end end + local function writeC(text,line) term.setCursorPos((x / 2) - (#text / 2),line) term.write(text) end + term.oldWrite = term.write function term.write(text) if not text then @@ -39,6 +47,7 @@ function term.write(text) end term.oldWrite(text) end + local function header(text) tc("white","blue") writeC(string.rep(" ",x),1) @@ -46,6 +55,7 @@ local function header(text) writeC(text,1) tc("white","black") end + local function gitUpdate(ProgramName, Filename, ProgramVersion) if http then local status, getGit = pcall(http.get, "https://raw.githubusercontent.com/OutragedMetro/darkprograms/darkprograms/programVersions") @@ -54,8 +64,8 @@ local function gitUpdate(ProgramName, Filename, ProgramVersion) print("Error: ".. getGit) return exit end - local getGit = getGit.readAll() - NVersion = textutils.unserialize(getGit) + getGit = getGit.readAll() + local NVersion = textutils.unserialize(getGit) if NVersion[ProgramName].Version > ProgramVersion then getGit = http.get(NVersion[ProgramName].GitURL) getGit = getGit.readAll() @@ -80,7 +90,7 @@ else end sleep(1) -x,y = term.getSize() +x, y = term.getSize() cs() write("-> Grabbing file...") cat = getUrlFile("https://raw.githubusercontent.com/OutragedMetro/darkprograms/darkprograms/programVersions") @@ -100,7 +110,7 @@ rawName = {} ]]-- -for name,data in pairs(cat) do +for name, data in pairs(cat) do if not menu[data.Author] then menu[data.Author] = {} end @@ -134,6 +144,7 @@ function selection(no,list,totpage) term.write("By Outraged") tc("white","black") end + function draw(tbl) local c = 1 local sdat = {} @@ -171,42 +182,43 @@ function draw(tbl) return sdat, tpages, mod end + function runMenu() while true do cs() if level == 1 then - list,totpage,mod = draw(menu) + list, totpage, mod = draw(menu) header("Authors") tc("yellow","black") - writeC("Press 'h' for help, 'q' to quit.",2) + writeC("Press 'h' for help, 'q' to quit.", 2) tc("white","black") elseif level == 2 then - list,totpage,mod = draw(menu[auna]) + list, totpage, mod = draw(menu[auna]) header("Packages") elseif level == 3 then - list,totpage,mod = draw(menu[auna][pkg]) + list, totpage, mod = draw(menu[auna][pkg]) header("Programs") elseif level == 4 then header("Program Data") - list,totpage,mod = draw(menu[auna][pkg][pro]) + list, totpage, mod = draw(menu[auna][pkg][pro]) end selection(csel, list, totpage) - e,key = os.pullEvent("key") + e, key = os.pullEvent("key") if key == keys.h then cs() header("Help") - term.setCursorPos(1,ind) + term.setCursorPos(1, ind) print("Use the up and down arrows to move through the list.") - print("use the right arrow to enter a menu item and the left arrow to exit") + print("Use the right arrow to enter a menu item and the left arrow to exit.") print("") - _,cy = term.getCursorPos() + _, cy = term.getCursorPos() tc("yellow","black") - writeC("Press enter to continue.",cy) + writeC("Press enter to continue.", cy) tc("white","black") read(" ") end @@ -265,7 +277,7 @@ function runMenu() repeat cs() writeC("Would you like to generate a startup script? ", y/2) - writeC("Y / N : ",y/2 + 1) + writeC("Y / N : ", y/2 + 1) answer = string.lower(read()) until answer == "y" or answer == "n" @@ -273,7 +285,7 @@ function runMenu() cs() writeC("Writing startup script...", y/2) - star = fs.open("/startup","w") + star = fs.open("/startup", "w") star.write("shell.run('".. rawName[pro] .. "')") star.close() @@ -283,10 +295,10 @@ function runMenu() end end - if csel < 1 then --Can't go below beginning of the list + if csel < 1 then --Can't go below the beginning of the list csel = 1 end - if csel > #list then --Can't go above length of the list + if csel > #list then --Can't go above the length of the list csel = #list end @@ -297,7 +309,6 @@ function runMenu() end page = math.floor((csel - 1) / ava) - end end From 5ec108a7d3b6595f105eed9c8398b577ffe68cec Mon Sep 17 00:00:00 2001 From: OutragedMetro Date: Mon, 24 Jul 2023 09:54:35 -0600 Subject: [PATCH 113/125] Add files via upload --- chatgpt | 88 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 chatgpt diff --git a/chatgpt b/chatgpt new file mode 100644 index 0000000..c41a33e --- /dev/null +++ b/chatgpt @@ -0,0 +1,88 @@ +-- Function to check if a file exists +local function fileExists(path) + return fs.exists(path) and not fs.isDir(path) +end + +-- Function to download a file from Pastebin +local function downloadFromPastebin(pastebinCode, path) + local url = "https://pastebin.com/raw/" .. pastebinCode + local response = http.get(url) + if response then + local file = fs.open(path, "w") + file.write(response.readAll()) + file.close() + response.close() + return true + else + return false + end +end + +-- Check and install LuaSocket library if missing +if not fileExists("/apis/https.lua") then + print("Installing LuaSocket (https.lua)...") + if not downloadFromPastebin("4nRg9CHU", "/apis/https.lua") then + print("Failed to install LuaSocket.") + return + end + print("LuaSocket installation complete.") +end +-- Load the installed libraries +os.loadAPI("/apis/https.lua") + +-- Replace this URL with the API endpoint for GPT-3.5 or GPT-4 +local gpt_endpoint = "https://api.openai.com/v1/engines/gpt-3.5/completions" + +-- Replace YOUR_API_KEY with your actual OpenAI API key +local api_key = "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" + +function getResponse(prompt) + local data = { + prompt = prompt, + max_tokens = 150, + temperature = 0.7, + } + + local headers = { + ["Content-Type"] = "application/json", + ["Authorization"] = "Bearer " .. api_key, + } + + local result = https.post(gpt_endpoint, json.encode(data), headers) + if result then + local response = result.readAll() + result.close() + return response + else + return nil + end +end + +function main() + print("Welcome to ChatGPT!") + print("Type 'exit' to end the conversation.") + print("") + + while true do + term.write("You: ") + local input = read() + if input == "exit" then + break + end + + local response = getResponse(input) + + if response then + local responseData = json.decode(response) + local reply = responseData.choices[1].text + print("ChatGPT: " .. reply) + else + print("An error occurred while communicating with ChatGPT.") + break + end + end + + print("Goodbye!") +end + +main() \ No newline at end of file From 25d4b4f9f4d0e15eeb341e5ecab376d35b75ec67 Mon Sep 17 00:00:00 2001 From: OutragedMetro Date: Mon, 24 Jul 2023 09:58:46 -0600 Subject: [PATCH 114/125] Update programVersions --- programVersions | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/programVersions b/programVersions index 6506542..146ca72 100644 --- a/programVersions +++ b/programVersions @@ -26,6 +26,15 @@ ["Author"]="Nothjarnan", ["Package"]="Standalone", }, +["Axiom"]={ + ["GitURL"]="https://raw.githubusercontent.com/rservices/darkprograms/darkprograms/chatgpt", + ["Version"]=1.0, + ["Type"]="program", + ["Name"]="Chat GPT In computercraft", + ["Description"]="Installs chatgpt and runs code", + ["Author"]="Outraged Security .INC", + ["Package"]="Standalone", + }, ["MetroOS"]={ ["GitURL"]="https://raw.githubusercontent.com/rservices/darkprograms/darkprograms/MetroOS/MetroOS", ["Version"]=2.94, From 07f7205432e7d129e72dbd330da6e3680fdb36f2 Mon Sep 17 00:00:00 2001 From: OutragedMetro Date: Mon, 24 Jul 2023 10:31:07 -0600 Subject: [PATCH 115/125] Update SecureDoor --- SecurePdaDoor/SecureDoor | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/SecurePdaDoor/SecureDoor b/SecurePdaDoor/SecureDoor index 9347821..2d47f34 100644 --- a/SecurePdaDoor/SecureDoor +++ b/SecurePdaDoor/SecureDoor @@ -2,8 +2,8 @@ tArgs = {...} if OneOS then --running under OneOS - OneOS.ToolBarColour = colours.white - OneOS.ToolBarTextColour = colours.grey + OneOS.ToolBarColour = colours.black + OneOS.ToolBarTextColour = colours.blue end local _w, _h = term.getSize() @@ -110,7 +110,7 @@ Drawing = { fgNext = false else if nextChar ~= " " and currFG == nil then - currFG = colours.white + currFG = colours.green end image[num][writeIndex] = currBG image.textcol[num][writeIndex] = currFG @@ -148,7 +148,7 @@ Drawing = { end, Clear = function (_colour) - _colour = _colour or colours.black + _colour = _colour or colours.white Drawing.ClearBuffer() Drawing.DrawBlankArea(1, 1, Drawing.Screen.Width, Drawing.Screen.Height, _colour) end, From fa02f3fae6e1a21409065cebccb94af5b13a70fa Mon Sep 17 00:00:00 2001 From: OutragedMetro Date: Mon, 21 Aug 2023 10:27:49 -0600 Subject: [PATCH 116/125] Update chat.lua --- chat.lua | 267 +++++++++++++++++++++++++++++++------------------------ 1 file changed, 152 insertions(+), 115 deletions(-) diff --git a/chat.lua b/chat.lua index c7298f5..0f84d68 100644 --- a/chat.lua +++ b/chat.lua @@ -1,127 +1,164 @@ os.pullEvent = os.pullEventRaw ---A Small SMS program. ---functions and variables -msg = "" -MHN = 1 -MH = {} -MH[0] = "Loaded Wireless One Chat" -term.write("Name: ") -name = read() -clear = function() -term.clear() -x,y = term.getSize() -term.setCursorPos(1,y) + +local isPocket = false +if pocket then + isPocket = true end -clear() -function drawScreen(Msg) -clear() -i = 18 -while i >= 0 do -if MH[i] ~= nil then -print(MH[i]) + +local msg = "" +local MH = {} +local historyFilePath = "/.chat_history" +local nameFilePath = "/.chat_username" +local modemSide = nil + +-- Load chat history from file if available +if fs.exists(historyFilePath) then + local file = fs.open(historyFilePath, "r") + local line = file.readLine() + while line do + table.insert(MH, 1, line) -- Insert at the beginning for newest messages at the bottom + line = file.readLine() + end + file.close() end -i = i - 1 + +-- Check if the name is saved in a file, otherwise ask the user to enter it +if fs.exists(nameFilePath) then + local file = fs.open(nameFilePath, "r") + name = file.readLine() + file.close() +else + term.write("Name: ") + name = read() + local file = fs.open(nameFilePath, "w") + file.writeLine(name) + file.close() end -term.write(Msg) + +-- Detect modem side +for _, side in ipairs(rs.getSides()) do + if peripheral.getType(side) == "modem" then + modemSide = side + break + end end -function send(MSg) -rednet.broadcast(name..": "..MSg) + +if not modemSide then + error("No modem detected. Please attach a modem.") end -function runTime() -drawScreen("Enter A message") -while 1 do -event, a, b, c = os.pullEvent() -if event == "char" then -msg = msg..a -drawScreen(msg) + +local function clear() + term.clear() + local _, y = term.getSize() + term.setCursorPos(1, y) end -if event == "key" then -if a == keys.backspace then -msg = "" -drawScreen(msg) -elseif a == keys.enter then -send(msg) -x = MH[0] -MH[0] = msg -y = MH[1] -MH[1] = x -x = MH[2] -MH[2] = y -y = MH[3] -MH[3] = x -x = MH[4] -MH[4] = y -y = MH[5] -MH[5] = x -x = MH[6] -MH[6] = y -y = MH[7] -MH[7] = x -x = MH[8] -MH[8] = y -y = MH[9] -MH[9] = x -x = MH[10] -MH[10] = y -y = MH[11] -MH[11] = x -x = MH[12] -MH[12] = y -y = MH[13] -MH[13] = x -x = MH[14] -MH[14] = y -y = MH[15] -MH[15] = x -x = MH[16] -MH[16] = y -y = MH[17] -MH[17] = x -MH[18] = y -msg = "" -drawScreen(msg) + +local function saveHistory() + local file = fs.open(historyFilePath, "w") + for _, line in ipairs(MH) do + file.writeLine(line) + end + file.close() end -elseif event == "rednet_message" then -x = MH[0] -y = MH[1] -MH[0] = b -MH[1] = x -x = MH[2] -MH[2] = y -y = MH[3] -MH[3] = x -x = MH[4] -MH[4] = y -y = MH[5] -MH[5] = x -x = MH[6] -MH[6] = y -y = MH[7] -MH[7] = x -x = MH[8] -MH[8] = y -y = MH[9] -MH[9] = x -x = MH[10] -MH[10] = y -y = MH[11] -MH[11] = x -x = MH[12] -MH[12] = y -y = MH[13] -MH[13] = x -x = MH[14] -MH[14] = y -y = MH[15] -MH[15] = x -x = MH[16] -MH[16] = y -y = MH[17] -MH[17] = x -MH[18] = y -drawScreen(msg) + +local function send(MSg) + rednet.broadcast(name .. ": " .. MSg) end +local function drawScreen(Msg) + clear() + local i = 18 + while i >= 0 do + if MH[i] ~= nil then + print(MH[i]) + end + i = i - 1 + end + print(Msg) end + +local function clearOldMessages() + local currentTime = os.time() + local maxAge = 2 * 24 * 60 * 60 -- 2 days in seconds + + local newMH = {} + for _, line in ipairs(MH) do + local timestamp = tonumber(line:match("^%[(%d+)%]")) + if not timestamp or currentTime - timestamp <= maxAge then + table.insert(newMH, line) + end + end + + MH = newMH + saveHistory() end ---main + +local function deleteHistory() + MH = {} + saveHistory() +end + +local function runTime() + rednet.open(modemSide) + + local historyStart = 1 + local linesToShow = isPocket and 5 or 18 + local screenLines = term.getSize() + + while true do + clear() + + term.setCursorPos(1, 1) + term.write("Wireless One Chat") + + local historyCount = #MH + local historyEnd = historyCount + local historyBegin = math.max(1, historyCount - linesToShow + 1) + + for i = historyBegin + historyStart - 1, historyEnd + historyStart - 1 do + term.setCursorPos(1, screenLines - (i - historyStart + 1)) + term.write(MH[i]) + end + + drawScreen("Enter a message: " .. msg) + + local event, a, b, c = os.pullEvent() + + if event == "char" then + msg = msg .. a + drawScreen("Enter a message: " .. msg) + elseif event == "key" then + if a == keys.backspace then + msg = msg:sub(1, #msg - 1) + drawScreen("Enter a message: " .. msg) + elseif a == keys.enter then + if msg ~= "" then + send(msg) + table.insert(MH, 1, name .. ": " .. msg) + saveHistory() + msg = "" + drawScreen("Enter a message: " .. msg) + end + elseif a == keys.up then + historyStart = math.max(1, historyStart - 1) + elseif a == keys.down then + historyStart = math.min(historyCount - linesToShow + 1, historyStart + 1) + elseif a == keys.d and c == true then + term.clear() + term.setCursorPos(1, 1) + term.setTextColor(colors.red) + term.write("Delete chat history? (Y/N): ") + local confirmation = read():lower() + if confirmation == "y" then + deleteHistory() + end + end + elseif event == "rednet_message" then + table.insert(MH, 1, "[" .. os.time() .. "] " .. b) + saveHistory() + end + + clearOldMessages() -- Check and clear old messages + end +end + runTime() From e9fc7ddebab3779760ce8a021ecd2197a0fdb553 Mon Sep 17 00:00:00 2001 From: OutragedMetro Date: Sun, 23 Feb 2025 15:37:53 -0700 Subject: [PATCH 117/125] Create chat_server.lua --- darksecurity/chat_server.lua | 100 +++++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 darksecurity/chat_server.lua diff --git a/darksecurity/chat_server.lua b/darksecurity/chat_server.lua new file mode 100644 index 0000000..13371ef --- /dev/null +++ b/darksecurity/chat_server.lua @@ -0,0 +1,100 @@ +os.pullEvent = os.pullEventRaw + +-- Variables +local serverChannel = 1 -- Channel for receiving messages +local historyFilePath = "/server_chat_history" -- File to store chat history +local modemSide = nil + +-- Find and open the modem (works for both wired and wireless) +local modemDetected = false +local startTime = os.time() +while os.time() - startTime < 10 do + local modem = peripheral.find("modem") or peripheral.find("wireless_modem") + if modem then + modemSide = peripheral.getName(modem) + rednet.open(modemSide) + modemDetected = true + print("Modem detected and Rednet opened on:", modemSide) + break + end + os.sleep(1) -- Wait 1 second before retrying +end + +if not modemDetected then + term.clear() + term.setCursorPos(1, 1) + print("No modems detected after 10 seconds. Please attach or enable a modem.") + os.sleep(3) -- Allow time to read the message before attempting to restart + shell.run("reboot") -- Option to reboot the system or exit to fix the modem issue +end + +-- Ensure Rednet is open +if not rednet.isOpen(modemSide) then + error("Failed to open Rednet on " .. modemSide) +end + +-- Load chat history from file if available +local function loadHistory() + local history = {} + if fs.exists(historyFilePath) then + local file = fs.open(historyFilePath, "r") + local line = file.readLine() + while line do + table.insert(history, line) + line = file.readLine() + end + file.close() + end + return history +end + +-- Save chat history to file +local function saveHistory(history) + local file = fs.open(historyFilePath, "w") + for _, line in ipairs(history) do + file.writeLine(line) + end + file.close() +end + +-- Log the incoming message +local function logMessage(sender, message) + local timestamp = os.date("%Y-%m-%d %H:%M:%S") + local logLine = string.format("[%s] %s: %s", timestamp, sender, message) + local history = loadHistory() + table.insert(history, 1, logLine) -- Add new message to the beginning + saveHistory(history) + print(logLine) -- Display the message on the server screen +end + +-- Clear the screen and display a header +local function clearScreen() + term.clear() + term.setCursorPos(1, 1) + term.setTextColor(colors.green) -- Change to green for header + print("---- Server Chat Log ----") + term.setTextColor(colors.white) -- Reset color to white for messages +end + +-- Main server loop +local function serverLoop() + clearScreen() + print("Server is listening for messages...") + + -- Load chat history and display on screen + local history = loadHistory() + for _, line in ipairs(history) do + print(line) + end + + -- Listen for incoming messages and display them + while true do + local senderID, message = rednet.receive(serverChannel) -- Wait for message from client + if message then + logMessage(senderID, message) -- Log and display the message + end + end +end + +-- Start the server +serverLoop() From fdff00100f09b4d1afc8e86ea4171e1ed8a1798d Mon Sep 17 00:00:00 2001 From: OutragedMetro Date: Sun, 23 Feb 2025 15:38:29 -0700 Subject: [PATCH 118/125] Create chat_client.lua --- darksecurity/chat_client.lua | 213 +++++++++++++++++++++++++++++++++++ 1 file changed, 213 insertions(+) create mode 100644 darksecurity/chat_client.lua diff --git a/darksecurity/chat_client.lua b/darksecurity/chat_client.lua new file mode 100644 index 0000000..b48d7b7 --- /dev/null +++ b/darksecurity/chat_client.lua @@ -0,0 +1,213 @@ +os.pullEvent = os.pullEventRaw + +-- Check if running on a pocket computer +local isPocket = pocket and true or false + +-- Variables +local msg = "" +local MH = {} +local historyFilePath = "/.chat_history" +local nameFilePath = "/.chat_username" +local modemSide = nil +local name = "" + +-- Load chat history from file if available +if fs.exists(historyFilePath) then + local file = fs.open(historyFilePath, "r") + local line = file.readLine() + while line do + table.insert(MH, line) -- Newest messages at the bottom + line = file.readLine() + end + file.close() +end + +-- Load or ask for username +if fs.exists(nameFilePath) then + local file = fs.open(nameFilePath, "r") + name = file.readLine() + file.close() +else + term.write("Name: ") + name = read() + local file = fs.open(nameFilePath, "w") + file.writeLine(name) + file.close() +end + +-- Find and open the modem (works for both wired and wireless) +local modemDetected = false +local startTime = os.time() +while os.time() - startTime < 10 do + local modem = peripheral.find("modem") or peripheral.find("wireless_modem") + if modem then + modemSide = peripheral.getName(modem) + rednet.open(modemSide) + modemDetected = true + print("Modem detected and Rednet opened on:", modemSide) + break + end + os.sleep(1) -- Wait 1 second before retrying +end + +if not modemDetected then + term.clear() + term.setCursorPos(1, 1) + print("No modems detected after 10 seconds. Please attach or enable a modem.") + os.sleep(3) -- Allow time to read the message before attempting to restart + shell.run("reboot") -- Option to reboot the system or exit to fix the modem issue +end + +-- Ensure Rednet is open +if not rednet.isOpen(modemSide) then + error("Failed to open Rednet on " .. modemSide) +end + +-- Clear the terminal +local function clear() + term.clear() + term.setCursorPos(1, 1) +end + +-- Display the startup message/logo +local function displayStartupMessage() + clear() + term.setTextColor(colors.green) -- Emerald color + local screenWidth, screenHeight = term.getSize() + local message = "Emerald Wireless!" + + -- Ensure the message fits on the screen (adjust for Pocket Computer if needed) + if #message > screenWidth then + message = message:sub(1, screenWidth) -- Trim the message if it's too long + end + + -- Calculate the position to center the message + local xPos = math.floor((screenWidth - #message) / 2) + 1 + local yPos = math.floor(screenHeight / 2) + + -- Set cursor to the calculated position + term.setCursorPos(xPos, yPos) + print(message) + + term.setTextColor(colors.white) -- Default color for subsequent text + os.sleep(2) -- Display the logo for 2 seconds + clear() -- Clear the screen after the logo +end + +-- Save chat history to file +local function saveHistory() + local file = fs.open(historyFilePath, "w") + for _, line in ipairs(MH) do + file.writeLine(line) + end + file.close() +end + +-- Send message via Rednet +local function send(MSg) + rednet.broadcast(name .. ": " .. MSg) +end + +-- Draw the chat screen +local function drawScreen(Msg) + clear() + local screenWidth, screenHeight = term.getSize() + local linesToShow = isPocket and 5 or 18 -- Adjust the number of lines to show based on screen size + local startIdx = math.max(1, #MH - linesToShow) -- Get the last `linesToShow` messages + + -- Print the chat history starting from the last message + for i = startIdx, #MH do + term.setCursorPos(1, screenHeight - (linesToShow - (i - startIdx))) -- Position messages just above the input line + print(MH[i]) + end + + -- Print the current message input at the bottom + term.setCursorPos(1, screenHeight) -- Set the cursor at the bottom of the screen + print(Msg) +end + +-- Clear old messages (older than 2 days) +local function clearOldMessages() + local currentTime = os.time() + local maxAge = 2 * 24 * 60 * 60 -- 2 days + + local newMH = {} + for _, line in ipairs(MH) do + local timestamp = tonumber(line:match("^%[(%d+)%]")) + if not timestamp or currentTime - timestamp <= maxAge then + table.insert(newMH, line) + end + end + + MH = newMH + saveHistory() +end + +-- Delete all chat history +local function deleteHistory() + MH = {} + saveHistory() +end + +-- Main chat loop +local function runTime() + local historyStart = 1 + local linesToShow = isPocket and 5 or 18 + local screenWidth, screenHeight = term.getSize() + + while true do + -- Draw the screen before asking for input + drawScreen("Enter a message: " .. msg) + + local event, a, b, c = os.pullEvent() + + if event == "char" then + msg = msg .. a + elseif event == "key" then + if a == keys.backspace then + msg = msg:sub(1, #msg - 1) + elseif a == keys.enter and msg ~= "" then + -- Send the message + send(msg) + + -- Add the message to the history immediately after sending it + table.insert(MH, name .. ": " .. msg) + saveHistory() + + -- Clear the message input box + msg = "" + + -- Update the screen immediately to show the new message + drawScreen("Enter a message: ") + elseif a == keys.up then + historyStart = math.max(1, historyStart - 1) + elseif a == keys.down then + historyStart = math.min(#MH - linesToShow + 1, historyStart + 1) + elseif a == keys.d and c == true then + term.clear() + term.setCursorPos(1, 1) + term.setTextColor(colors.red) + term.write("Delete chat history? (Y/N): ") + local confirmation = read():lower() + if confirmation == "y" then + deleteHistory() + end + end + elseif event == "rednet_message" then + -- Add the received message to the history and update the screen + table.insert(MH, name .. ": " .. b) + saveHistory() + + -- Immediately draw the updated chat + drawScreen("Enter a message: " .. msg) + end + + clearOldMessages() -- Remove old messages + end +end + +-- Show startup message/logo +displayStartupMessage() + +-- Start chat application +runTime() From 1aa5c3f9ad73e5db123640ab049a7bac6a97b69b Mon Sep 17 00:00:00 2001 From: OutragedMetro Date: Sun, 23 Feb 2025 15:40:11 -0700 Subject: [PATCH 119/125] Update programVersions --- programVersions | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/programVersions b/programVersions index 146ca72..f9b2741 100644 --- a/programVersions +++ b/programVersions @@ -16,6 +16,24 @@ ["Description"]="Outraged Security .INC Base Security Client", ["Author"]="Outraged Security .INC", ["Package"]="Outraged Security .INC", + }, +["client"]={ + ["GitURL"]="https://raw.githubusercontent.com/rservices/darkprograms/darkprograms/darksecurity/chat_client.lua", + ["Version"]=4.26, + ["Type"]="program", + ["Name"]="Chat Client", + ["Description"]="Outraged Security .INC Emerald Wireless chat", + ["Author"]="Outraged Security .INC", + ["Package"]="Outraged Security .INC", + }, +["client"]={ + ["GitURL"]="https://raw.githubusercontent.com/rservices/darkprograms/darkprograms/darksecurity/chat_server.lua", + ["Version"]=4.26, + ["Type"]="program", + ["Name"]="Outraged Security .INC Client", + ["Description"]="Outraged Security .INC Base Security Client", + ["Author"]="Outraged Security .INC", + ["Package"]="Outraged Security .INC", }, ["Axiom"]={ ["GitURL"]="https://raw.githubusercontent.com/nothjarnan/axiom/master/install.lua", From a5d6cee36aff0a5fe29a515e32d9bde57ad64ed5 Mon Sep 17 00:00:00 2001 From: OutragedMetro Date: Sun, 23 Feb 2025 15:47:52 -0700 Subject: [PATCH 120/125] Update programVersions --- programVersions | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/programVersions b/programVersions index f9b2741..8dae9c0 100644 --- a/programVersions +++ b/programVersions @@ -17,7 +17,7 @@ ["Author"]="Outraged Security .INC", ["Package"]="Outraged Security .INC", }, -["client"]={ +["Chat_Client"]={ ["GitURL"]="https://raw.githubusercontent.com/rservices/darkprograms/darkprograms/darksecurity/chat_client.lua", ["Version"]=4.26, ["Type"]="program", @@ -26,7 +26,7 @@ ["Author"]="Outraged Security .INC", ["Package"]="Outraged Security .INC", }, -["client"]={ +["chat_server"]={ ["GitURL"]="https://raw.githubusercontent.com/rservices/darkprograms/darkprograms/darksecurity/chat_server.lua", ["Version"]=4.26, ["Type"]="program", From 665370c7e782b8fcde19bb3d0e9d43ade2896d7e Mon Sep 17 00:00:00 2001 From: OutragedMetro Date: Sun, 23 Feb 2025 15:50:57 -0700 Subject: [PATCH 121/125] Update programVersions --- programVersions | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/programVersions b/programVersions index 8dae9c0..1ad2a72 100644 --- a/programVersions +++ b/programVersions @@ -24,7 +24,7 @@ ["Name"]="Chat Client", ["Description"]="Outraged Security .INC Emerald Wireless chat", ["Author"]="Outraged Security .INC", - ["Package"]="Outraged Security .INC", + ["Package"]="Chat", }, ["chat_server"]={ ["GitURL"]="https://raw.githubusercontent.com/rservices/darkprograms/darkprograms/darksecurity/chat_server.lua", @@ -33,7 +33,7 @@ ["Name"]="Outraged Security .INC Client", ["Description"]="Outraged Security .INC Base Security Client", ["Author"]="Outraged Security .INC", - ["Package"]="Outraged Security .INC", + ["Package"]="Chat", }, ["Axiom"]={ ["GitURL"]="https://raw.githubusercontent.com/nothjarnan/axiom/master/install.lua", From 46424ae4f0da75b07d1a1d41c522aa16865bcd8c Mon Sep 17 00:00:00 2001 From: OutragedMetro Date: Sun, 23 Feb 2025 15:51:06 -0700 Subject: [PATCH 122/125] Update programVersions From 66ec58ea63a417fd49a43bb07ba0b204b0924785 Mon Sep 17 00:00:00 2001 From: OutragedMetro Date: Sun, 23 Feb 2025 15:52:07 -0700 Subject: [PATCH 123/125] Update programVersions --- programVersions | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/programVersions b/programVersions index 1ad2a72..0b2a0aa 100644 --- a/programVersions +++ b/programVersions @@ -30,7 +30,7 @@ ["GitURL"]="https://raw.githubusercontent.com/rservices/darkprograms/darkprograms/darksecurity/chat_server.lua", ["Version"]=4.26, ["Type"]="program", - ["Name"]="Outraged Security .INC Client", + ["Name"]="Chat Server", ["Description"]="Outraged Security .INC Base Security Client", ["Author"]="Outraged Security .INC", ["Package"]="Chat", From 78a94dd7410e62936dd9d8b618e666e76cdb2205 Mon Sep 17 00:00:00 2001 From: OutragedMetro Date: Sun, 23 Feb 2025 15:55:18 -0700 Subject: [PATCH 124/125] Update programVersions --- programVersions | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/programVersions b/programVersions index 0b2a0aa..a534534 100644 --- a/programVersions +++ b/programVersions @@ -31,7 +31,7 @@ ["Version"]=4.26, ["Type"]="program", ["Name"]="Chat Server", - ["Description"]="Outraged Security .INC Base Security Client", + ["Description"]="Outraged Security .INC Emerald Wireless Server", ["Author"]="Outraged Security .INC", ["Package"]="Chat", }, From 438b40e999aa210c43bbd8965069639854cd4c45 Mon Sep 17 00:00:00 2001 From: OutragedMetro Date: Sun, 23 Feb 2025 15:55:26 -0700 Subject: [PATCH 125/125] Update programVersions