--------------------- -- "KhiCAS" : TI-Nspire Giac UI in Nspire-Lua -- Version 1.05 - 18/06/2014 -- GPL v3 License -- http://tiplanet.org/forum/viewtopic.php?f=43&t=14800 --------------------- -- Giac CAS engine by Bernard Parisse -- http://www-fourier.ujf-grenoble.fr/~parisse/ --------------------- -- The UI is highly based on Xavier 'critor' Andréani's "SuperSpire" -- See http://tiplanet.org/forum/viewtopic.php?t=13851&p=157015 ------------------- -- ETK GUI Lib by Jim Bauwens and Adrien "Adriweb" Bertrand -- Additions/mods by Xavier -- some more little changes by Adrien -- (borders, horiz. lines, readonly, copy/paste, errHandler etc.) ------------------- platform.apilevel = '2.0' hasGiac = pcall(nrequire, "luagiac") if not hasGiac then luagiac = { caseval = function(str) return math.evalStr(str) end } print("Giac module not loaded ! Fallback on Nspire's math engine.") end -- localized useful functions local mathmin = math.min local mathmax = math.max local mathrandom = math.random ts = 0.0 -- ETK Stuff --------------------------------------------------------------------- View: Widgets & Events manager defaultFocus = nil View = class() function View:init(window) self.window = window self.widgetList = {} self.focusList = {} self.currentFocus = 0 self.currentCursor = "default" -- Previous location of mouse pointer self.prev_mousex = 0 self.prev_mousey = 0 end function View:invalidate() self.window:invalidate() end function View:setCursor(cursor) if cursor ~= self.currentCursor then self.currentCursor = cursor self:invalidate() end end function View:add(o) table.insert(self.widgetList, o) self:repos(o) if o.acceptsFocus then table.insert(self.focusList, 1, o) if self.currentFocus > 0 then self.currentFocus = self.currentFocus + 1 end end return o end function View:remove(o) if self:getFocus() == o then o:releaseFocus() end local i = 1 local f = 0 local oldf while i <= #self.focusList do if self.focusList[i] == o then f = i end i = i + 1 end if f > 0 then if self:getFocus() == o then self:tabForward() end table.remove(self.focusList, f) if self.currentFocus > f then self.currentFocus = self.currentFocus - 1 end end f = 0 i = 1 while i <= #self.widgetList do if self.widgetList[i] == o then f = i end i = i + 1 end if f > 0 then table.remove(self.widgetList, f) end -- table.remove(self.widgetList, o) -- table.remove(self.focusList, o) end function View:repos(o) local x = o.x local y = o.y local w = o.w local h = o.h if o.hConstraint == "right" then x = scrWidth - o.w - o.dx1 elseif o.hConstraint == "center" then x = (scrWidth - o.w + o.dx1) / 2 elseif o.hConstraint == "justify" then w = scrWidth - o.x - o.dx1 end if o.vConstraint == "bottom" then y = scrHeight - o.h - o.dy1 elseif o.vConstraint == "middle" then y = (scrHeight - o.h + o.dy1) / 2 elseif o.vConstraint == "justify" then h = scrHeight - o.y - o.dy1 end o:repos(x, y) o:resize(w, h) end function View:resize() for _, o in ipairs(self.widgetList) do self:repos(o) end end function View:hide(o) if o.visible then o.visible = false self:releaseFocus(o) if o:contains(self.prev_mousex, self.prev_mousey) then o:onMouseLeave(o.x - 1, o.y - 1) end self:invalidate() end end function View:show(o) if not o.visible then o.visible = true if o:contains(self.prev_mousex, self.prev_mousey) then o:onMouseEnter(self.prev_mousex, self.prev_mousey) end self:invalidate() end end function View:getFocus() if self.currentFocus == 0 then return nil end return self.focusList[self.currentFocus] end function View:setFocus(obj) if self.currentFocus ~= 0 then if self.focusList[self.currentFocus] == obj then return end self.focusList[self.currentFocus]:releaseFocus() end self.currentFocus = 0 for i = 1, #self.focusList do if self.focusList[i] == obj then self.currentFocus = i obj:setFocus() self:invalidate() break end end end function View:releaseFocus(obj) if self.currentFocus ~= 0 then if self.focusList[self.currentFocus] == obj then self.currentFocus = 0 obj:releaseFocus() self:invalidate() end end end function View:sendStringToFocus(str) local o = self:getFocus() if not o then o = defaultFocus self:setFocus(o) end if o then if o.visible then if o:addString(str) then self:invalidate() else o = nil end end end if not o then -- look for a default handler for _, o in ipairs(self.focusList) do if o.visible then if o:addString(str) then self:setFocus(o) self:invalidate() break end end end end end function View:backSpaceHandler() -- Does the focused widget accept BackSpace? local o = self:getFocus() if o then if o.visible and o.acceptsBackSpace then o:backSpaceHandler() self:setFocus(o) self:invalidate() else o = nil end end if not o then -- look for a default handler for _, o in ipairs(self.focusList) do if o.visible and o.acceptsBackSpace then o:backSpaceHandler() self:setFocus(o) self:invalidate() break; end end end end function View:tabForward() local nextFocus = self.currentFocus + 1 if nextFocus > #self.focusList then nextFocus = 1 end self:setFocus(self.focusList[nextFocus]) if self:getFocus() then if not self:getFocus().visible then self:tabForward() end end self:invalidate() end function View:tabBackward() local nextFocus = self.currentFocus - 1 if nextFocus < 1 then nextFocus = #self.focusList end self:setFocus(self.focusList[nextFocus]) if not self:getFocus().visible then self:tabBackward() end self:invalidate() end function View:onMouseDown(x, y) -- Find a widget that has a mouse down handler and bounds the click point for _, o in ipairs(self.widgetList) do if o.visible and o.acceptsFocus and o:contains(x, y) then self.mouseCaptured = o o:onMouseDown(o, window, x - o.x, y - o.y) self:setFocus(o) self:invalidate() return end end if self:getFocus() then self:setFocus(nil) self:invalidate() end end function View:onMouseMove(x, y) local prev_mousex = self.prev_mousex local prev_mousey = self.prev_mousey for _, o in ipairs(self.widgetList) do local xyin = o:contains(x, y) local prev_xyin = o:contains(prev_mousex, prev_mousey) if xyin and not prev_xyin and o.visible then -- Mouse entered widget o:onMouseEnter(x, y) self:invalidate() elseif prev_xyin and (not xyin or not o.visible) then -- Mouse left widget o:onMouseLeave(x, y) self:invalidate() end end self.prev_mousex = x self.prev_mousey = y end function View:onMouseUp(x, y) local mc = self.mouseCaptured if mc then self.mouseCaptured = nil if mc:contains(x, y) then mc:onMouseUp(x - mc.x, y - mc.y) else -- mc:cancelClick() end end end function View:enterHandler() -- Does the focused widget accept Enter? local o = self:getFocus() if o then if o.visible and o.acceptsEnter then o:enterHandler() self:setFocus(o) self:invalidate() else o = nil end end if not o then -- look for a default handler for _, o in ipairs(self.focusList) do if o.visible and o.acceptsEnter then o:enterHandler() self:setFocus(o) self:invalidate() break; end end end end function View:arrowLeftHandler() -- Does the focused widget accept ArrowLeft? local o = self:getFocus() if o then if o.visible and o.acceptsArrowLeft then o:arrowLeftHandler() self:setFocus(o) self:invalidate() else o = nil end end if not o then -- look for a default handler for _, o in ipairs(self.focusList) do if o.visible and o.acceptsArrowLeft then o:arrowLeftHandler() self:setFocus(o) self:invalidate() break; end end end end function View:arrowRightHandler() -- Does the focused widget accept ArrowRight? local o = self:getFocus() if o then if o.visible and o.acceptsArrowRight then o:arrowRightHandler() self:setFocus(o) self:invalidate() else o = nil end end if not o then -- look for a default handler for _, o in ipairs(self.focusList) do if o.visible and o.acceptsArrowRight then o:arrowRightHandler() self:setFocus(o) self:invalidate() break; end end end end function View:arrowUpHandler() -- Does the focused widget accept ArrowUp? local o = self:getFocus() if o then if o.visible and o.acceptsArrowUp then o:arrowUpHandler() self:setFocus(o) self:invalidate() else o = nil end end if not o then -- look for a default handler for _, o in ipairs(self.focusList) do if o.visible and o.acceptsArrowUp then o:arrowUpHandler() self:setFocus(o) self:invalidate() break; end end end end function View:arrowDownHandler() -- Does the focused widget accept ArrowDown? local o = self:getFocus() if o then if o.visible and o.acceptsArrowDown then o:arrowDownHandler() self:setFocus(o) self:invalidate() else o = nil end end if not o then -- look for a default handler for _, o in ipairs(self.focusList) do if o.visible and o.acceptsArrowDown then o:arrowDownHandler() self:setFocus(o) self:invalidate() break; end end end end function View:paint(gc) local fo = self:getFocus() for _, o in ipairs(self.widgetList) do if o.visible then o:paint(gc, fo == o) if fo == o then gc:setColorRGB(100, 150, 255) gc:drawRect(o.x - 1, o.y - 1, o.w + 1, o.h + 1) gc:setPen("thin", "smooth") gc:setColorRGB(0) end end end cursor.set(self.currentCursor) end theView = nil --------------------------------------------------------------------- Widget Widget = class() function Widget:setHConstraints(hConstraint, dx1) self.hConstraint = hConstraint self.dx1 = dx1 end function Widget:setVConstraints(vConstraint, dy1) self.vConstraint = vConstraint self.dy1 = dy1 end function Widget:init(view, x, y, w, h) self.xOrig = x self.yOrig = y self.view = view self.x = x self.y = y self.w = w self.h = h self.acceptsFocus = false self.visible = true self.acceptsEnter = false self.acceptsEscape = false self.acceptsTab = false self.acceptsDelete = false self.acceptsBackSpace = false self.acceptsReturn = false self.acceptsArrowUp = false self.acceptsArrowDown = false self.acceptsArrowLeft = false self.acceptsArrowRight = false self.hConstraint = "left" self.vConstraint = "top" end function Widget:repos(x, y) self.x = x self.y = y end function Widget:resize(w, h) self.w = w self.h = h end function Widget:setFocus() end function Widget:releaseFocus() end function Widget:contains(x, y) return x >= self.x and x <= self.x + self.w and y >= self.y and y <= self.y + self.h end function Widget:onMouseEnter(x, y) -- Implemented in subclasses end function Widget:onMouseLeave(x, y) -- Implemented in subclasses end function Widget:paint(gc, focused) -- Implemented in subclasses end function Widget:enterHandler() end function Widget:escapeHandler() end function Widget:tabHandler() end function Widget:deleteHandler() end function Widget:backSpaceHandler() end function Widget:returnHandler() end function Widget:arrowUpHandler() end function Widget:arrowDownHandler() end function Widget:arrowLeftHandler() end function Widget:arrowRightHandler() end function Widget:onMouseDown(x, y) end function Widget:onMouseUp(x, y) end --------------------------------------------------------------------- Button widget Button = class(Widget) function Button:init(view, x, y, w, h, default, command, shortcut) Widget.init(self, view, x, y, w, h) -- Button configuration self.acceptsFocus = true self.acceptsBackspace = false self.command = command or function() end -- what to do when pressed self.default = default -- is default button when ENTER is pressed self.shortcut = shortcut -- Current button state self.clicked = false self.highlighted = false self.acceptsEnter = true end -- Act on key press on button function Button:enterHandler() if self.acceptsEnter then self:command() end end function Button:escapeHandler() if self.acceptsEscape then self:command() end end function Button:tabHandler() if self.acceptsTab then self:command() end end function Button:deleteHandler() if self.acceptsDelete then self:command() end end function Button:backSpaceHandler() if self.acceptsBackSpace then self:command() end end function Button:returnHandler() if self.acceptsReturn then self:command() end end function Button:arrowUpHandler() if self.acceptsArrowUp then self:command() end end function Button:arrowDownHandler() if self.acceptsArrowDown then self:command() end end function Button:arrowLeftHandler() if self.acceptsArrowLeft then self:command() end end function Button:arrowRightHandler() if self.acceptsArrowRight then self:command() end end function Button:arrowUpHandler() if self.acceptsArrowUp then self:command() end end function Button:arrowDownHandler() if self.acceptsArrowDown then self:command() end end function Button:onMouseDown(x, y) self.clicked = true self.highlighted = true end function Button:onMouseEnter(x, y) theView:setCursor("hand pointer") if self.clicked and not self.highlighted then self.highlighted = true end end function Button:onMouseLeave(x, y) theView:setCursor("default") if self.clicked and self.highlighted then self.highlighted = false end end function Button:cancelClick() if self.clicked then self.highlighted = false self.clicked = false end end function Button:onMouseUp(x, y) self:cancelClick() self:command() end function Button:addString(str) if str == " " or str == self.shortcut then self:command() return true end return false end --------------------------------------------------------------------- ImgLabel widget ImgLabel = class(Widget) function ImgLabel:init(view, x, y, img) self.img = image.new(img) self.w = image.width(self.img) self.h = image.height(self.img) Widget.init(self, view, x, y, self.w, self.h, false, command, shortcut) end function ImgLabel:paint(gc, focused) gc:drawImage(self.img, self.x, self.y) end --------------------------------------------------------------------- ImgButton widget ImgButton = class(Button) function ImgButton:init(view, x, y, img, command, shortcut) self.img = image.new(img) self.w = image.width(self.img) self.h = image.height(self.img) Button.init(self, view, x, y, self.w, self.h, false, command, shortcut) end function ImgButton:paint(gc, focused) gc:drawImage(self.img, self.x, self.y) end --------------------------------------------------------------------- TextButton widget TextButton = class(Button) function TextButton:init(view, x, y, text, command, shortcut) self.textid = text self.text = getLocaleText(text) self:resize(0, 0) Button.init(self, view, x, y, self.w, self.h, false, command, shortcut) end function TextButton:resize(w, h) self.text = getLocaleText(self.textid) self.w = getStringWidth(self.text) + 5 self.h = getStringHeight(self.text) + 5 end function TextButton:paint(gc, focused) gc:setColorRGB(223, 223, 223) gc:drawRect(self.x + 1, self.y + 1, self.w - 2, self.h - 2) gc:setColorRGB(191, 191, 191) gc:fillRect(self.x + 1, self.y + 1, self.w - 3, self.h - 3) gc:setColorRGB(223, 223, 223) gc:drawString(self.text, self.x + 3, self.y + 3, "top") gc:setColorRGB(0) gc:drawString(self.text, self.x + 2, self.y + 2, "top") gc:drawRect(self.x, self.y, self.w - 2, self.h - 2) end --------------------------------------------------------------------- vertical scroll bar VScrollBar = class(Widget) function VScrollBar:init(view, x, y, w, h) self.pos = 10 self.siz = 10 Widget.init(self, view, x, y, w, h, false) end function VScrollBar:paint(gc, focused) gc:setColorRGB(0) gc:drawRect(self.x, self.y, self.w, self.h) gc:fillRect(self.x + 2, self.y + self.h - (self.h - 4) * (self.pos + self.siz) / 100 - 2, self.w - 3, mathmax(1, (self.h - 4) * self.siz / 100 + 1)) end --------------------------------------------------------------------- Text widget TextLabel = class(Widget) function TextLabel:init(view, x, y, text) self:setText(text) Widget.init(self, view, x, y, self.w, self.h, false) end function TextLabel:resize(w, h) self.text = getLocaleText(self.textid) self.w = getStringWidth(self.text) self.h = getStringHeight(self.text) end function TextLabel:setText(text) self.textid = text self.text = getLocaleText(text) self:resize(0, 0) end function TextLabel:getText() return self.text end function TextLabel:paint(gc, focused) gc:setColorRGB(0) gc:drawString(self.text, self.x, self.y, "top") end --------------------------------------------------------------------- editable RichText widget RichTextEditor = class(Widget) function RichTextEditor:init(view, x, y, w, h, text) self.editor = D2Editor.newRichText() self.readOnly = false self:repos(x, y) self.editor:setFontSize(fsize) self.editor:setFocus(false) self.text = text self:resize(w, h) Widget.init(self, view, x, y, self.w, self.h, true) self.acceptsFocus = true self.editor:setExpression(text) self.editor:setBorder(1) end function RichTextEditor:onMouseEnter(x, y) theView:setCursor("text") end function RichTextEditor:onMouseLeave(x, y) theView:setCursor("default") end function RichTextEditor:repos(x, y) if not self.editor then self = nil; return; end self.editor:setBorderColor((showEditorsBorders and 0) or 0xffffff) self.editor:move(x+1, y+1) Widget.repos(self, x, y) end function RichTextEditor:resize(w, h) if not self.editor then self = nil; return; end self.editor:resize(w-1, h-1) Widget.resize(self, w, h) end function RichTextEditor:setFocus() self.editor:setFocus(true) end function RichTextEditor:releaseFocus() self.editor:setFocus(false) end function RichTextEditor:addString(str) local currentText = self.editor:getText() or "" self.editor:setText(currentText .. str) return true end function RichTextEditor:paint(gc, focused) -- self.editor:paint(gc) end --------------------------------------------------------------------- editable Math widget MathEditor = class(RichTextEditor) -- pretty printed square root characters from MathBoxes take two 'special' characters in returned expression string -- cursor position in expression returned by getExpression() then does not match cursor position in string after the square root function string.ulen(s) if not s then return 0 else return select(2, s:gsub("[^\128-\193]", "")) end end ulen = string.ulen function MathEditor:init(view, x, y, w, h, text) RichTextEditor.init(self, view, x, y, w, h, text) self.editor:setBorder(1) self.acceptsEnter = true self.acceptsBackSpace = true self.result = false -- add editor focus listener which does: setFocus(getME(editor)) self.editor:registerFilter({ arrowLeft = function() local _, curpos = self.editor:getExpressionSelection() if curpos < 7 then on.arrowLeft() return true end return false end, arrowRight = function() local currentText, curpos = self.editor:getExpressionSelection() if curpos > ulen(currentText) - 2 then on.arrowRight() return true end return false end, tabKey = function() theView:tabForward() return true end, mouseDown = function(x, y) theView:onMouseDown(x, y) return false end, backspaceKey = function() if (self == fctEditor) then self:fixCursor() local _, curpos = self.editor:getExpressionSelection() if curpos <= 6 then return true end return false else self:backSpaceHandler() return true end end, deleteKey = function() if (self == fctEditor) then self:fixCursor() local currentText, curpos = self.editor:getExpressionSelection() if curpos >= ulen(currentText) - 1 then return true end return false else self:backSpaceHandler() return true end end, enterKey = function() self:enterHandler() return true end, returnKey = function() theView:enterHandler() return true end, escapeKey = function() on.escapeKey() return true end, clearKey = function() if self == fctEditor then self.editor:setExpression("") self:fixContent() else self:backSpaceHandler() end return true end, charIn = function(c) if (self == fctEditor) then if self.editor:getExpression() then self:fixCursor() end return false else return self.readOnly end end }) end function MathEditor:fixContent() local currentText = self.editor:getExpressionSelection() if not currentText or currentText == "" then self.editor:createMathBox() end end function MathEditor:fixCursor() local currentText, curpos, selstart = self.editor:getExpressionSelection() local l = ulen(currentText) if curpos < 6 or selstart < 6 or curpos > l - 1 or selstart > l - 1 then if curpos < 6 then curpos = 6 end if selstart < 6 then selstart = 6 end if curpos > l - 1 then curpos = l - 1 end if selstart > l - 1 then selstart = l - 1 end self.editor:setExpression(currentText, curpos, selstart) end end function MathEditor:getExpression() if not self.editor then self = nil; return ""; end local rawexpr = self.editor:getExpression() local expr = "" local n = rawexpr:len() local b = 0 local bs = 0 local bi = 0 local status = 0 local i = 1 local c while i <= n do c = rawexpr:sub(i, i) if c == "{" then b = b + 1 elseif c == "}" then b = b - 1 end if status == 0 then if rawexpr:sub(i, i + 5) == "\\0el {" then bs = i + 6 i = i + 5 status = 1 bi = b b = b + 1 end else if b == bi then status = 0 expr = expr .. rawexpr:sub(bs, i - 1) end end i = i + 1 end return expr end function MathEditor:setFocus() if not self.editor then self = nil; return; end self.editor:setFocus(true) end function MathEditor:releaseFocus() if not self.editor then self = nil; return; end self.editor:setFocus(false) end function MathEditor:addString(str) if not self.editor then self = nil; return; end self:fixCursor() local currentText, curpos, selstart = self.editor:getExpressionSelection() currentText = currentText:usub(1, mathmin(curpos, selstart)) .. str .. currentText:usub(mathmax(curpos, selstart) + 1, ulen(currentText)) self.editor:setExpression(currentText, mathmin(curpos, selstart) + ulen(str)) return true end function MathEditor:backSpaceHandler() backSpaceHandler(self) end function MathEditor:enterHandler() enterHandler(self) end function MathEditor:paint(gc) if not self.editor then self = nil; return; end if showHLines and not self.result then gc:setColorRGB(100, 100, 100) local ycoord = self.y - (showEditorsBorders and 0 or 2) gc:drawLine(1, ycoord, platform.window:width() - sbv.w - 2, ycoord) gc:setColorRGB(0) end end --------------------------------------------------------------------- events handling function on.arrowUp() if theView:getFocus() == fctEditor then on.tabKey() else on.tabKey() if theView:getFocus() ~= fctEditor then on.tabKey() end end reposView() end function on.arrowDown() if theView:getFocus() == fctEditor then return end on.backtabKey() if theView:getFocus() ~= fctEditor then on.backtabKey() end reposView() end function on.arrowLeft() if theView:getFocus() == fctEditor then return end on.tabKey() reposView() end function on.arrowRight() if theView:getFocus() == fctEditor then return end on.backtabKey() reposView() end function on.charIn(ch) theView:sendStringToFocus(ch) end function on.tabKey() theView:tabForward() reposView() end function on.backtabKey() theView:tabBackward() reposView() end function on.escapeKey() -- nothing to do ? end function on.enterKey() theView:enterHandler() end on.returnKey = on.enterKey function on.mouseMove(x, y) theView:onMouseMove(x, y) end function on.mouseDown(x, y) theView:onMouseDown(x, y) -- theView:invalidate() end function on.mouseUp(x, y) theView:onMouseUp(x, y) end function initFontGC(gc) gc:setFont(font, style, fsize) end function getStringHeightGC(text, gc) initFontGC(gc) return gc:getStringHeight(text) end function getStringHeight(text) return platform.withGC(getStringHeightGC, text) end function getStringWidthGC(text, gc) initFontGC(gc) return gc:getStringWidth(text) end function getStringWidth(text) return platform.withGC(getStringWidthGC, text) end function initGUI() showEditorsBorders = false showHLines = true scrWidth = platform.window:width() scrHeight = platform.window:height() if (scrWidth > 0 or scrHeight > 0) then theView = View(platform.window) sbv = VScrollBar(theView, 0, -1, 5, scrHeight + 1) sbv:setHConstraints("right", 0) theView:add(sbv) fctEditor = MathEditor(theView, 2, border, 50, 30, "") -- fctEditor = MathEditor(theView, border, border, 50, 30, "") fctEditor:setHConstraints("justify", 1) -- fctEditor:setHConstraints("justify", border + scrWidth - sbv.x) fctEditor:setVConstraints("bottom", 1) fctEditor.editor:setSizeChangeListener(function(editor, w, h) return resizeME(editor, w, h) end) theView:add(fctEditor) fctEditor.editor:setText("") fctEditor.editor:setBorder(0) fctEditor:fixContent() sbv:setVConstraints("justify", scrHeight - fctEditor.y + border) theView:setFocus(fctEditor) inited = true end toolpalette.enableCopy(true) toolpalette.enablePaste(true) end function resizeGC(gc) scrWidth = platform.window:width() scrHeight = platform.window:height() if not inited then initGUI() end if inited then initFontGC(gc) strFullHeight = gc:getStringHeight("H") strHeight = strFullHeight - 3 theView:resize() reposME() theView:invalidate() end end function on.resize() platform.withGC(resizeGC) end forcefocus = true function on.activate() forcefocus = true end dispinfos = true function on.paint(gc) if not inited then initGUI() initFontGC(gc) strFullHeight = gc:getStringHeight("H") strHeight = strFullHeight - 3 end if inited then local obj obj = theView:getFocus() initFontGC(gc) if not obj then theView:setFocus(fctEditor) end if (forcefocus) then if obj == fctEditor then fctEditor.editor:setFocus(true) if fctEditor.editor:hasFocus() then forcefocus = false end else forcefocus = false end end if dispinfos then gc:setColorRGB(0) gc:setFont("sansserif", "r", 10) gc:drawString("Giac CAS engine :", 2, 0, "top") if hasGiac then gc:setColorRGB(0, 127, 0) gc:drawString("OK.", gc:getStringWidth("Giac CAS engine :") + 6, 0, "top") gc:setColorRGB(0) gc:drawString("Giac (c) B. Parisse/R. De Graeve, license GPL3", 2, 1 * strHeight, "top") gc:drawString("Not allowed during exams if", 2, 2 * strHeight, "top") gc:drawString("CAS calculators are forbidden!", 2, 3 * strHeight, "top") gc:drawString("Type ?commandname for short help", 2, scrHeight/2-20, "top") gc:drawString("Beta version, report bugs/issues you encounter !", 2, scrHeight/2, "top") else gc:setColorRGB(255, 0, 0) gc:drawString("NO.", gc:getStringWidth("Giac CAS engine :") + 6, 0, "top") gc:setColorRGB(0) gc:drawString("Make sure to have the .luax file and Ndless installed!", 2, strHeight, "top") gc:drawString("Hint: run ndless_installer again", 2, 2 * strHeight, "top") gc:setFont("sansserif", "i", 10) gc:drawString("Fallback on the Nspire's math engine.", 2, 3 * strHeight + 8, "top") end gc:setFont("sansserif", "r", fsize) end theView:paint(gc) -- gc:drawString(ts, 2, 0, "top") gc:drawRect(0, fctEditor.y - 2, scrWidth, fctEditor.y - 2) end end --------------------------------------------------------------------- global variables font = "sansserif" style = "r" fsize = 12 scrWidth = 0 scrHeight = 0 inited = false delim = " ≟ " border = 3 strHeight = 0 strFullHeight = 0 --------------------------------------------------------------------- global functions evalstr = false histME1 = {} histME2 = {} function addME(expr, res) local mee = MathEditor(theView, border, border, 50, 30, "") mee.readOnly = true table.insert(histME1, mee) mee:setHConstraints("left", border) mee.editor:setSizeChangeListener(function(editor, w, h) return resizeME(editor, w + 3, h) end) mee.editor:setExpression("\\0el {" .. expr .. "}", 0) mee:fixCursor() mee.editor:setReadOnly(true) theView:add(mee) local mer = MathEditor(theView, border, border, 50, 30, "") mer.result = true mer.readOnly = true table.insert(histME2, mer) mer:setHConstraints("right", scrWidth - sbv.x + border) mer.editor:setSizeChangeListener(function(editor, w, h) return resizeMEpar(editor, w + border, h) end) mer.editor:setExpression("\\0el {" .. res .. "}", 0) mer:fixCursor() mer.editor:setReadOnly(true) theView:add(mer) reposME() end limpsuff = "(+)" limmsuff = "(-)" function cleanAns1(expr) local a, b = expr:find(delim, 1, true) local c = 0 local i = 1 local l = string.len(expr) if a then c = expr:find(".", 1, true) if not c then c = 0 end if c == 0 or c > b then expr = expr:sub(1, a - 1) else expr = expr:sub(b + 1, l) end return cleanAns1(expr) end l = string.len(expr) a, b = expr:find(limpsuff, 1, true) if (not (a)) then a, b = expr:find(limmsuff, 1, true) else a = a - 2 local texpr = "" if (a > 1) then texpr = expr:sub(1, a - 1) end if (b < l) then texpr = texpr .. expr:sub(b + 1, l) end expr = texpr end return expr end function escapeStr(expr) local expr2 = "" local l = string.len(expr) local c for i = 1, l do c = expr:sub(i, i) if c == "\"" then c = "\\\"" end expr2 = expr2 .. c end return expr2 end function backSpaceHandler(widget) local i = 1 local f = 0 local n = mathmax(#histME1, #histME2) if (widget ~= fctEditor) then while (f == 0 and i <= n) do if histME1[i] == widget or histME2[i] == widget then f = i end i = i + 1 end end if f > 0 then destroyD2Editor(histME1[f].editor) destroyD2Editor(histME2[f].editor) theView:remove(histME1[f]) theView:remove(histME2[f]) table.remove(histME1, f) table.remove(histME2, f) reposME() end end function enterHandler(widget) local expr, exprkeep local svar local incerr = "incompatible data type" if (widget ~= fctEditor) then expr = cleanAns1(widget:getExpression()) theView:setFocus(fctEditor) fctEditor:addString(expr) else if (fctEditor.editor:getExpression()) then expr = fctEditor.editor:getExpression() expr = fctEditor:getExpression() if (expr and expr ~= "") then dispinfos = false t1 = timer.getMilliSecCounter() res = luagiac.caseval(expr) or "Error" t2 = timer.getMilliSecCounter() ts = string.format("Time : %f" , ( t2 - t1 ) / 1000. ) fctEditor.editor:setText("") fctEditor:fixContent() ioffset = 0 res = " " .. res expr = " " .. expr expr = expr:gsub("^%s+", " ") addME(expr, res) end end end end function getParME(editor) for i = 1, #histME2 do if histME2[i].editor == editor then return histME1[i] end end return nil end function getME(editor) if (fctEditor.editor == editor) then return fctEditor else for i = 1, #histME1 do if histME1[i].editor == editor then return histME1[i] end end for i = 1, #histME2 do if histME2[i].editor == editor then return histME2[i] end end end return nil end function getMEindex(me) local ti if (fctEditor.editor == me) then return 0 else ti = 0 for i = #histME1, 1, -1 do if histME1[i] == me then return ti end ti = ti + 1 end ti = 0 for i = #histME2, 1, -1 do if histME2[i] == me then return ti end ti = ti + 1 end end return 0 end function resizeMEpar(editor, w, h) local pare = getParME(editor) if pare then resizeMElim(editor, w, h, pare.w + pare.dx1 * 2) else resizeME(editor, w, h) end end function resizeME(editor, w, h) if not editor then return end resizeMElim(editor, w, h, scrWidth / 2) end function resizeMElim(editor, w, h, lim) if not editor then return end local met = getME(editor) if met then met.needw = w met.needh = h w = mathmax(w, 0) w = mathmin(w, scrWidth - met.dx1 * 2) h = mathmax(h, strFullHeight + 8) if (me ~= fctEditor) then w = mathmin(w, (scrWidth - lim) - 2 * met.dx1 + 1) end met:resize(w, h) needcenter = true reposME() theView:invalidate() end return editor end ioffset = 0 function reposView() local focusedME = theView:getFocus() if focusedME and focusedME ~= fctEditor then local y = focusedME.y local h = focusedME.h local y0 = fctEditor.y local index = getMEindex(focusedME) if y < 0 and ioffset < index then ioffset = ioffset + 1 reposME() reposView() end if y + h > y0 and ioffset > index then ioffset = ioffset - 1 reposME() reposView() end end end function reposME() local h, y, ry, res, i0, beforeh, totalh, visih, h1, h2 totalh = 0 beforeh = 0 visih = 0 fctEditor.y = scrHeight - fctEditor.h theView:repos(fctEditor) sbv:setVConstraints("justify", scrHeight - fctEditor.y + border) theView:repos(sbv) y = fctEditor.y i0 = mathmax(#histME1, #histME2) for i = i0, 1, -1 do h = 0 h1 = 0 h2 = 0 if i <= #histME1 then h1 = mathmax(h1, histME1[i].h) end if i <= #histME2 then h2 = mathmax(h2, histME2[i].h) end h = mathmax(h1, h2) if i0 - i >= ioffset then if y >= 0 then if y >= h + border then visih = visih + h + border else visih = visih + y end end y = y - h - border ry = y totalh = totalh + h + border else ry = scrHeight beforeh = beforeh + h + border totalh = totalh + h + border end if i <= #histME1 then histME1[i].y = ry theView:repos(histME1[i]) if histME1[i].focus then res = histME1[i] end end if i <= #histME2 then histME2[i].y = ry + mathmax(0, h1 - h2) theView:repos(histME2[i]) if histME2[i].focus then res = histME2[i] end end end if totalh == 0 then sbv.pos = 0 sbv.siz = 100 else sbv.pos = beforeh * 100 / totalh sbv.siz = visih * 100 / totalh end theView:invalidate() end function destroyD2Editor(editor) if not editor then return end editor:setVisible(false) editor:move(-10000, -10000) editor:resize(1, 1) editor = nil end function reset() for _, v in pairs(theView.widgetList) do theView:remove(v) end for _, e in pairs(histME1) do destroyD2Editor(e.editor) end for _, e in pairs(histME2) do destroyD2Editor(e.editor) end histME1 = {} histME2 = {} fsize = 10 applyFontSizeChange() platform.window:invalidate() myErrorHandler() end function myErrorHandler(line, errMsg, callStack, locals) if errMsg then print(errMsg) end defaultFocus = nil theView = nil collectgarbage() initGUI() if errMsg then addME("Script error", errMsg) end collectgarbage() return true -- let the script continue end platform.registerErrorHandler(myErrorHandler) --------------------------------------------------------------------- toolpalette stuff function toggleBorders() showEditorsBorders = not showEditorsBorders on.resize() end function set1d() fctEditor.editor:setDisable2DinRT(true) on.resize() end function set2d() fctEditor.editor:setDisable2DinRT(false) on.resize() end function toggleHLines() showHLines = not showHLines on.resize() end function applyFontSizeChange() fctEditor.editor:setFontSize(fsize) for _, e in pairs(histME1) do e.editor:setFontSize(fsize) end for _, e in pairs(histME2) do e.editor:setFontSize(fsize) end end function fontDown() fsize = fsize > 6 and (fsize - 1) or fsize applyFontSizeChange() end function fontUp() fsize = fsize < 30 and (fsize + 1) or fsize applyFontSizeChange() end function menustring( ch ) -- theView:sendStringToFocus( ch ) if fctEditor then fctEditor:addString( ch ) end end menu = { { "Algebra", { "factor(expr)", function() menustring( "factor(" ) end }, { "normal(expr)", function() menustring( "normal(" ) end }, { "simplify(expr)", function() menustring( "simplify(" ) end }, { "subst(expr,var,value)", function() menustring( "subst(" ) end }, { "convert(expr,...)", function() menustring( "convert(" ) end }, { "solve(expr,var)", function() menustring( "solve(" ) end }, { "fsolve(expr,var,guess)", function() menustring( "fsolve(" ) end }, { "rsolve(eq,un)", function() menustring( "rsolve(" ) end }, { "partfrac(expr)", function() menustring( "partfrac(" ) end }, { "tcollect(expr)", function() menustring( "tcollect(" ) end }, { "texpand(expr)", function() menustring( "texpand(" ) end }, { "cfactor(expr)", function() menustring( "cfactor(" ) end }, { "cpartfrac(expr)", function() menustring( "cpartfrac(" ) end }, { "csolve(expr,var)", function() menustring( "csolve(" ) end }, }, { "Calculus", { "diff(expr,var)", function() menustring( "diff(" ) end }, { "int(expr,var)", function() menustring( "int(" ) end }, { "limit(expr,var,value,[1|-1])", function() menustring( "limit(" ) end }, { "series(expr,var=value,order,[1|-1])", function() menustring( "series(" ) end }, { "sum(expr,var,min,max)", function() menustring( "sum(" ) end }, { "desolve(eq,x,y)", function() menustring( "desolve(" ) end }, }, { "Complex and Reals", { "abs(x)", function() menustring( "abs(" ) end }, { "floor(x)", function() menustring( "floor(" ) end }, { "sign(x)", function() menustring( "sign(" ) end }, { "ceil(x)", function() menustring( "ceil(" ) end }, { "max(x)", function() menustring( "max(" ) end }, { "min(x)", function() menustring( "min(" ) end }, { "round(x[,n])", function() menustring( "round(" ) end }, { "evalf(x[,prec])", function() menustring( "evalf(" ) end }, { "[inf..sup]", function() menustring( "[..]") end }, { "re(z)", function() menustring( "re(" ) end }, { "im(z)", function() menustring( "im(" ) end }, { "conj(z)", function() menustring( "conj(" ) end }, { "arg(z)", function() menustring( "arg(" ) end }, { "convert(expr,interval[,prec])", function() menustring( "convert(,interval)" ) end }, }, { "Integers", { "iquo(a,b): euclidean quotient", function() menustring( "iquo(" ) end }, { "irem(a,b): euclidean remainder", function() menustring( "irem(" ) end }, { "is_prime(p)", function() menustring( "is_prime" ) end }, { "nextprime(n)", function() menustring( "nextprime(" ) end }, { "ifactor(n): factor integer", function() menustring( "ifactor(" ) end }, { "idivis(n): divisor list", function() menustring( "idivis(" ) end }, { "iegcd(a,b): a*u+b*v=gcd(a,b)", function() menustring( "iegcd(" ) end }, { "iabcuv(a,b,c): a*u+b*v=c", function() menustring( "iabcuv(" ) end }, { "euler(n): Euler indicatrix", function() menustring( "euler(" ) end }, { "ichrem([a,n],[b,m]): Chinese remainder", function() menustring( "ichrem(" ) end }, { "powmod(a,m,n): a^m mod n", function() menustring( "powmod(" ) end }, }, { "Linalg", { "linsolve(", function() menustring( "linsolve(" ) end }, { "rref(M)", function() menustring( "rref(" ) end }, { "ref(M)", function() menustring( "ref(" ) end }, { "ker(M)", function() menustring( "ker(" ) end }, { "image(M)", function() menustring( "image(" ) end }, { "det(M)", function() menustring( "det(" ) end }, { "inv(M)", function() menustring( "inv(" ) end }, { "bug(M)", function() menustring( "bug(" ) end }, { "eigenvalues(M)", function() menustring( "eigenvalues(" ) end }, { "eigenvectors(M)", function() menustring( "eigenvects(" ) end }, { "jordan(M)", function() menustring( "jordan(" ) end }, { "matpow(M,n): M^n", function() menustring( "matpow(" ) end }, }, { "Matrix, Vector", { "dot(v1,v2)", function() menustring( "dot(" ) end }, { "cross(v1,v2)", function() menustring( "cross(" ) end }, { "identity(n)", function() menustring( "identity(" ) end }, { "matrix(n,m,function)", function() menustring( "matrix(" ) end }, { "randmatrix(n,m,law,[params])", function() menustring( "randmatrix(" ) end }, { "hilbert(n)", function() menustring( "hilbert(" ) end }, { "vandermonde(list)", function() menustring( "vandermonde(" ) end }, { "l1norm(M)", function() menustring( "l1norm(" ) end }, { "l2norm(M)", function() menustring( "l2norm(" ) end }, { "linfnorm(M)", function() menustring( "linfnorm(" ) end }, { "cond(M,1|2|inf)", function() menustring( "cond(" ) end }, { "lu(M)", function() menustring( "lu(" ) end }, { "qr(M)", function() menustring( "qr(" ) end }, { "schur(M)", function() menustring( "schur(" ) end }, { "svd(M): sing. value dec.", function() menustring( "svd(" ) end }, { "svl(M): singular values", function() menustring( "svl(" ) end }, }, { "Polynomials", { "horner(P,x)", function() menustring( "horner(" ) end }, { "factor(P)", function() menustring( "factor(" ) end }, { "cfactor(P): complex factorization", function() menustring( "cfactor(" ) end }, { "proot(P)", function() menustring( "proot(" ) end }, { "pcoeff(list)", function() menustring( "pcoeff(" ) end }, { "degree(P,var)", function() menustring( "degree(" ) end }, { "canonical_form(P,var)", function() menustring( "canonical_form(" ) end }, { "lagrange(X,Y): interpolation", function() menustring( "lagrange(" ) end }, { "quorem(A,B): eucl. div.", function() menustring( "quorem(" ) end }, { "gcd(A,B)", function() menustring( "gcd(" ) end }, { "egcd(A,B,var): A*U+B*V=gcd(A,B)", function() menustring( "egcd(" ) end }, { "abcuv(A,B,C,var): A*U+B*V=C", function() menustring( "abcuv(" ) end }, { "coeff(P,var,n)", function() menustring( "coeff(" ) end }, { "symb2poly(P,var)", function() menustring( "symb2poly(" ) end }, { "poly2symb(list,var)", function() menustring( "poly2symb(" ) end }, { "resultant(A,B,var)", function() menustring( "resultant(" ) end }, { "gbasis(listpoly,listvar)", function() menustring( "gbasis(" ) end }, { "cyclotomic(n)", function() menustring( "cyclotomic(" ) end }, { "hermite(n)", function() menustring( "hermite(" ) end }, { "tchebyshev1(n)", function() menustring( "tchebyshev1(" ) end }, { "tchebyshev2(n)", function() menustring( "tchebyshev2(" ) end }, { "randpoly(n)", function() menustring( "randpoly(" ) end }, }, { "Proba", { "comb(n,p)", function() menustring( "comb(" ) end }, { "perm(n,p)", function() menustring( "perm(" ) end }, { "factorial(n)", function() menustring( "factorial(" ) end }, { "rand()", function() menustring( "rand(" ) end }, { "binomial(n,p,k)", function() menustring( "binomial(" ) end }, { "binomial_cdf(n,p,x)", function() menustring( "binomial_cdf(" ) end }, { "binomial_icdf(n,p,y)", function() menustring( "binomial_icdf(" ) end }, { "normald(m,sigma,x)", function() menustring( "normald(" ) end }, { "normald_cdf(m,sigma,x)", function() menustring( "normald_cdf(" ) end }, { "normald_icdf(m.sigma,y)", function() menustring( "normald_icdf(" ) end }, { "poisson(mu,k)", function() menustring( "poisson(" ) end }, { "exponentiald(mu,x)", function() menustring( "exponentiald(" ) end }, { "geometric(p,k)", function() menustring( "geometric(" ) end }, { "chisquared(n,x)", function() menustring( "chisquared(" ) end }, { "uniformd(a,b,x)", function() menustring( "uniformd(" ) end }, { "chisquaret(l1,l2): chi2 test", function() menustring( "chisquaret(" ) end }, { "normalt: Z-test", function() menustring( "normalt(" ) end }, { "studentt: Student-test", function() menustring( "studentt(" ) end }, }, { "Program", { ";", function() menustring( ";\n\r" ) end }, { "function", function() menustring( "f(x):={ local y; y:=x*x; return y; }" ) end }, { "local", function() menustring( "local ;\n" ) end }, { "return", function() menustring( "return ;" ) end }, { "print", function() menustring( "print(" ) end }, { "if/then", function() menustring( "if then ; fi;" ) end }, { "if/then/else", function() menustring( "if then ; else ; fi;" ) end }, { "for", function() menustring( "for j from 1 to 5 do ; od;" ) end }, { "while", function() menustring( "while do ; od;" ) end }, { "repeat", function() menustring( "repeat ; until ;" ) end }, { "break", function() menustring( "break;" ) end }, { "continue", function() menustring( "continue;" ) end }, }, { "Lists", { "seq(expr,var,inf,sup)", function() menustring( "seq(" ) end }, { "size(l)", function() menustring( "size(" ) end }, { "op(l): list to sequence", function() menustring( "op(" ) end }, { "apply(f,l) apply function", function() menustring( "apply(" ) end }, { "append(l,expr)", function() menustring( "append(" ) end }, { "concat(l1,l2)", function() menustring( "concat(" ) end }, { "head(l): first element", function() menustring( "head(" ) end }, { "tail(l)", function() menustring( "tail(" ) end }, { "sort(l[,function])", function() menustring( "sort(" ) end }, { "revlist(l)", function() menustring( "revlist(" ) end }, { "contains(l,expr)", function() menustring( "contains(" ) end }, { "suppress(l,n)", function() menustring( "suppress(" ) end }, { "remove(function,l)", function() menustring( "remove(" ) end }, }, { "Options", { "save variables", function() menustring( "write(\"a.tns\",0" ) end }, { "read variables", function() menustring( "eval(read(\"a.tns\"))" ) end }, "-", -- { "1d", set1d }, -- { "2d", set2d }, { "Show/hide editors borders", toggleBorders }, { "Show/hide horizontal lines", toggleHLines }, "-", { "Increase font size", fontUp }, { "Decrease font size", fontDown }, "-", { "Restart CAS", function() menustring( "restart;" ) end }, } } toolpalette.register(menu)