1

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).

Promitheas Nikou
  • 511
  • 4
  • 14
  • and now you want us to debug your 480 line script? please narrow the problem down and reduce your code – Piglet Aug 12 '20 at 08:40
  • I know exactly where the problem is: it is on line 465 "break". My question is why does it only happen after you hit the close button – Promitheas Nikou Aug 12 '20 at 12:34
  • According to the manual the break error is raised if esc or on buttons are pressed during calcuation or program execution. any chance you end up in an infinite loop in your on.paint function? I'd assume that wouldn't be good in an event handler. – Piglet Aug 12 '20 at 15:04
  • i think you will find if no objects are in`drawObjects` `(focus >= 0)` is `true` and will not change which means the while loop will continue indefinitely – Nifim Aug 12 '20 at 15:04

1 Answers1

1

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)
Nifim
  • 4,758
  • 2
  • 12
  • 31
  • Wait, ipairs will only work if the first element isn't nil? Isn't ipairs supposed to return every non-nil element in order? Actually, I just checked it and ipairs is supposed to make the loop run for every element until it hits a nil one. The problem is that it doesn't start at the first non-nil one, it starts at element 1. Thanks. – Promitheas Nikou Aug 13 '20 at 08:04
  • Please read the Lua manual. "ipairs will iterate over the key–value pairs (1,t[1]), (2,t[2]), ..., up to the first nil value." note the 1 at the beginning. – Piglet Aug 13 '20 at 09:55