I was working on a script to make little windows that I could open and close on my TI Nspire CX CAS 2 calculator. Adding new windows worked just fine, but trying to remove them, would cause a my software to crash when the function that was supposed to remove the window finished. I also tried it on my calculator and it still got stuck but I was able to close the script and then reopen it. When I reopened it there was a 'break' error on line 465 a picture of my calculator with the error
screen = platform.window
screenx,screeny = screen.width(),screen.height()
-- system stuff
local password = nil
local passVerify = nil
local drawObjects = {}
local processes = {}
local rectangle = class()
local backgroundObj = class()
local taskbar = class()
local window = class()
local process = class()
local taskbarHeight = 20
local hasBeenSetup = false
local customSetupProcedures = {}
local isLogedIn = false
local draggingMode = {
move = 0,
left = 1,
top = 2,
right = 3,
bottom = 4
}
local readyToDraw = true
local currentDraggingMode = draggingMode.both
local selectedWindowForDragging = nil
local textures = {}
local processStatus = {
waiting = 0,
running = 1,
halted = 2,
dead = 3
}
local specialColors = {
taskbarColor = 0x00969E,
windowBackgroundColor = 0xF7F7F7,
windowTopBarColor = 0xA2A2A2,
windowCloseButtonColor = 0xFF5100,
windowMaximizeButtonColor = 0x00FF00,
windowMinimizeButtonColor = 0xFFFF00
}
function process:init(run,onKill)
self.status = processStatus.waiting
self.co = coroutine.create(run)
self.onKill = onKill
coroutine.resume(self.co,self)
end
function process:update()
if self.status == processStatus.running then
coroutine.resume(self.co)
end
end
function process:halt()
self.status = processStatus.halted
end
function process:resume()
self.status = processStatus.waiting
end
function process:kill()
readyToDraw = false
self.status = processStatus.dead
self.co = nil
self.onKill()
readyToDraw = true
end
function rectangle:init(x,y,width,height)
self.x = x
self.y = y
self.width = width
self.height = height
end
function rectangle:contains(x,y)
return self.x<x and x<self.x+self.width and self.y<y and y<self.y+self.height
end
function taskbar:init()
end
function taskbar:draw(gc)
gc:setColorRGB(specialColors.taskbarColor)
gc:fillRect(0,screeny-taskbarHeight,screenx,screeny)
--gc:drawImage(textures[1],0,screeny-20)
end
function taskbar:isWindow()
return false
end
function window:init(x,y,width,height)
self.x = x
self.y = y
self.draggedOnX = 0
self.draggedOnY = 0
self.width = width
self.height = height
self.icon = nil
self.components = {}
self.focusLevel = 0
newWindowFocused(#drawObjects+1)
self.resizable = true
self:subclassInit(x,y,width,height)
end
function window:subclassInit(x,y,width,height)
end
function window:getFocusLevel()
return self.focusLevel
end
function window:draw(gc)
gc:setColorRGB(specialColors.windowBackgroundColor)
gc:fillRect(self.x,self.y,self.width,self.height)
gc:setColorRGB(specialColors.windowTopBarColor)
gc:fillRect(self.x,self.y,self.width,10)
gc:setColorRGB(specialColors.windowMinimizeButtonColor)
gc:fillRect(self.x+self.width-29,self.y+1,8,8)
gc:setColorRGB(specialColors.windowMaximizeButtonColor)
gc:fillRect(self.x+self.width-19,self.y+1,8,8)
gc:setColorRGB(specialColors.windowCloseButtonColor)
gc:fillRect(self.x+self.width-9,self.y+1,8,8)
self:drawComponents(gc)
end
function window:drawComponents(gc)
for i in ipairs(self.components) do
self.components[i]:draw(gc,self)
end
end
function window:addComponent(component)
i = #self.components+1
self.components[i] = component
return i
end
function window:decreaseFocus()
self.focusLevel = self.focusLevel+1
end
function window:isWindow()
return true
end
function window:contains(x,y)
return self.x<x and self.y < y and self.width+self.x>x and self.height+self.y>y
end
function window:checkForMouse(x,y)
if y-self.y <= 3 and self.resizable then
cursor.set('resize row')
elseif y-self.y>=self.height-3 and self.resizable then
cursor.set('resize row')
elseif x-self.x<=3 and self.resizable then
cursor.set('resize column')
elseif x-self.x>=self.width-3 and self.resizable then
cursor.set('resize column')
end
end
function window:click(x,y,n)
self:focus(n)
if y-self.y <= 3 and self.resizable then
selectedWindowForDragging = self
currentDraggingMode = draggingMode.top
elseif y-self.y>=self.height-3 and self.resizable then
selectedWindowForDragging = self
currentDraggingMode = draggingMode.bottom
elseif x-self.x<=3 and self.resizable then
selectedWindowForDragging = self
currentDraggingMode = draggingMode.left
elseif x-self.x>=self.width-3 and self.resizable then
selectedWindowForDragging = self
currentDraggingMode = draggingMode.right
elseif y-self.y <= 10 then
if x>=self.x+self.width-10 then
self:close()
elseif x>=self.x+self.width-20 then
self:maximize()
elseif x>=self.x+self.width-30 then
self:minimize()
else
selectedWindowForDragging = self
currentDraggingMode = draggingMode.move
self.draggedOnX = x - self.x
self.draggedOnY = y - self.y
end
end
self:clickComponents(x,y)
end
function window:clickComponents(x,y)
end
function newWindowFocused(n)
for window in ipairs(drawObjects) do
window = drawObjects[window]
if window:isWindow() then
if window:getFocusLevel() < n then
window:decreaseFocus()
end
end
end
end
function window:focus(n)
self.focusLevel = -1
newWindowFocused(n)
end
function window:minimize()
end
function window:maximize()
end
function window:close()
processes[1]:kill()
end
function on.resize()
screenx = screen:width()
screeny = screen:height()
end
function on.mouseDown(x,y)
minFocus = 0
for i = 0,#drawObjects,1 do
for windown in ipairs(drawObjects) do
window=drawObjects[windown]
if window:isWindow() then
if window:getFocusLevel() == minFocus then
if window:contains(x,y) then
window:click(x,y,minFocus)
return
end
minFocus = minFocus+1
end
end
end
end
end
function on.mouseMove(x,y)
cursor.set('default')
if selectedWindowForDragging ~= nil then
if currentDraggingMode == draggingMode.move then
selectedWindowForDragging.x = x - selectedWindowForDragging.draggedOnX
selectedWindowForDragging.y = y - selectedWindowForDragging.draggedOnY
if selectedWindowForDragging.x+selectedWindowForDragging.draggedOnX<5 then selectedWindowForDragging.x = 5-selectedWindowForDragging.draggedOnX end
if selectedWindowForDragging.x+selectedWindowForDragging.draggedOnX>screenx-5 then selectedWindowForDragging.x = screenx-selectedWindowForDragging.draggedOnX-5 end
if selectedWindowForDragging.y<0 then selectedWindowForDragging.y=0 end
if selectedWindowForDragging.y>screeny-taskbarHeight-5 then selectedWindowForDragging.y = screeny-taskbarHeight-5 end
elseif currentDraggingMode == draggingMode.top then
i = selectedWindowForDragging.draggedOnY + y
selectedWindowForDragging.height = math.max(selectedWindowForDragging.height - selectedWindowForDragging.draggedOnY - y + selectedWindowForDragging.y,20)
selectedWindowForDragging.y = i
elseif currentDraggingMode == draggingMode.bottom then
selectedWindowForDragging.height = math.max(y - selectedWindowForDragging.y,20)
elseif currentDraggingMode == draggingMode.left then
i = selectedWindowForDragging.draggedOnX + x
selectedWindowForDragging.width = math.max(selectedWindowForDragging.width - selectedWindowForDragging.draggedOnX - x + selectedWindowForDragging.x,20)
selectedWindowForDragging.x = i
elseif currentDraggingMode == draggingMode.right then
selectedWindowForDragging.width = math.max(x - selectedWindowForDragging.x,20)
end
end
minFocus = 0
for i = 0,#drawObjects,1 do
for windown in ipairs(drawObjects) do
window=drawObjects[windown]
if window:isWindow() then
if window:getFocusLevel() == minFocus then
if window:contains(x,y) then
window:checkForMouse(x,y)
return
end
minFocus = minFocus+1
end
end
end
end
end
function on.mouseUp(x,y)
selectedWindowForDragging = nil
end
local calculator = class(window)
function calculator:subclassInit()
self.width = 60
self.height = 90
self.resizable = false
self.number = ''
print(self.number)
self.button1 = rectangle(5,22,13,10)
self.button2 = rectangle(24,22,14,10)
self.button3 = rectangle(44,22,13,10)
self.button4 = rectangle(5,34,13,10)
self.button5 = rectangle(24,34,14,10)
self.button6 = rectangle(44,34,13,10)
self.button7 = rectangle(5,45,13,10)
self.button8 = rectangle(24,45,14,10)
self.button9 = rectangle(44,45,13,10)
self.button0 = rectangle(24,57,14,10)
self.buttonC = rectangle(5,57,13,10)
self.buttonDot = rectangle(44,57,13,10)
self.buttonPlus = rectangle(6,70,7,7)
self.buttonMinus = rectangle(15,70,7,7)
self.buttonMultiply = rectangle(40,70,7,7)
self.buttonDivide = rectangle(49,70,7,7)
self.buttonEquals = rectangle(24,69,14,8)
end
function calculator:drawComponents(gc)
--gc:drawImage(textures[2],self.x,self.y+10)
gc:setColorRGB(0x000000)
gc:setFont('sansserif','r',6)
if #self.number < 10 then
gc:drawString(string.sub(self.number,0,#self.number),self.x+5,self.y+13)
else
gc:drawString(string.sub(self.number,#self.number-9,#self.number),self.x+5,self.y+13)
print(self.number)
end
end
function calculator:clickComponents(x,y)
x = x-self.x
y = y-self.y-10
if self.button1:contains(x,y) then self.number = self.number..'1' elseif
self.button2:contains(x,y) then self.number = self.number..'2' elseif
self.button3:contains(x,y) then self.number = self.number..'3' elseif
self.button4:contains(x,y) then self.number = self.number..'4' elseif
self.button5:contains(x,y) then self.number = self.number..'5' elseif
self.button6:contains(x,y) then self.number = self.number..'6' elseif
self.button7:contains(x,y) then self.number = self.number..'7' elseif
self.button8:contains(x,y) then self.number = self.number..'8' elseif
self.button9:contains(x,y) then self.number = self.number..'9' elseif
self.button0:contains(x,y) then self.number = self.number..'0' elseif
self.buttonPlus:contains(x,y) then self.number = self.number..'+' elseif
self.buttonMinus:contains(x,y) then self.number = self.number..'-' elseif
self.buttonMultiply:contains(x,y) then self.number = self.number..'*' elseif
self.buttonDivide:contains(x,y) then self.number = self.number..'/' elseif
self.buttonDot:contains(x,y) then self.number = self.number..'.' elseif
self.buttonEquals:contains(x,y) then
if not pcall(function()self.number = ''..math.eval(self.number)end) then self.number = 'error' end
elseif
self.buttonC:contains(x,y) then self.number = '' end
end
function backgroundObj:draw(gc)
gc:setColorRGB(0xC4C4C4)
gc:fillRect(0,0,screenx,screeny-taskbarHeight)
--gc:drawImage(textures[0],screenx/2-50,(screeny-taskbarHeight)/2-50)
end
function backgroundObj:isWindow()
return false
end
function loadTextures()
--textures[0]=image.new(_R.IMG.logo)
--textures[1]=image.new(_R.IMG.startMenuButton)
--textures[2]=image.new(_R.IMG.calculator)
end
function on.construction()
timer.start(1/30)
checkForSetup()
checkForLogin()
end
function on.timer()
screen:invalidate()
end
function encrypt(str,key)
result = ''
for i = 1,#str,1 do
result = result..string.char(math.abs(string.byte(str:sub(i,i))+key+i)%422)
end
return result
end
function decrypt(str,key)
result = ''
for i = 1,#str,1 do
result = result..string.char(math.abs((string.byte(str:sub(i,i))-key-i)%422))
end
return result
end
function checkForLogin()
end
function checkForSetup()
if not hasBeenSetup then
print('loading textures...')
loadTextures()
print('setting up graphics...')
setupGraphics()
print('executing default setup procedure...')
print('setting up password...')
setupPassword()
print('executing custom setup procedures...')
for funct in ipairs(customSetupProcedures) do
funct()
end
end
hasBeenSetup = true
end
function setupGraphics()
processes[1] = process(
function(proc)
drawObjects[1] = backgroundObj()
end,
function()
drawObjects[1] = nil
end)
drawObjects[2] = taskbar()
end
function setupPassword()
w1 = calculator(50,100,100,100)
drawObjects[3] = w1
w2 = window(0,50,100,100)
drawObjects[4] = w2
end
--system stuff
--graphics stuff
function on.paint(gc)
if readyToDraw then
if drawObjects[1]~=nil then
drawObjects[1]:draw(gc)
end
focus = 0
for i in ipairs(drawObjects) do
window = drawObjects[i]
if window:isWindow() then
focus = math.max(focus,window:getFocusLevel())
end
end
while focus>=0 do
for i in ipairs(drawObjects) do
window = drawObjects[i]
if window:isWindow() and window:getFocusLevel()==focus then
window:draw(gc)
focus = focus-1
break
end
end
end
drawObjects[2]:draw(gc)
end
end
--graphics stuff
--default programs
--default programs
Edit: I just found out that if you are running a script in the student software and it gets stuck in a loop, pressing F12 would break the loop (just like the home button on the actual calculator).
Your problem is you have a chance to enter an infinite loop in the on.paint
function.
You do a check here:
if drawObjects[1]~=nil then
drawObjects[1]:draw(gc)
end
if this check is false
it implies that the following for loop will run 0 times, as ipairs
wants to see index 1
first or bails out:
focus = 0
for i in ipairs(drawObjects) do -- Will run 0 times.
window = drawObjects[i]
if window:isWindow() then
focus = math.max(focus,window:getFocusLevel())
end
end
this mean your while loop condition of focus >= 0
will run indefinitely as the same issue with ipairs(drawObjects)
will happen in the loop body and focus
will never change from 0
while focus>=0 do
for i in ipairs(drawObjects) do -- Will run 0 times.
window = drawObjects[i]
if window:isWindow() and window:getFocusLevel()==focus then
window:draw(gc)
focus = focus-1
break
end
end
end
you can fix this by defaulting focus
to -1
additionally you would need to check if drawObjects[2]
is nil before using it.
or, what might be a better option, changing your check to
if drawObjects[1] == nil then
return -- bail out of function we dont have anything to draw
end
drawObjects[1]:draw(gc)