diff --git a/exec/load/frame.js b/exec/load/frame.js index bb865f239c289278e83145cc8f848b3e554d4a5a..c834fe1f4f464565bc6562bb59bab52373ed81a5 100644 --- a/exec/load/frame.js +++ b/exec/load/frame.js @@ -1,1762 +1,1760 @@ -/* $Id: frame.js,v 1.91 2020/08/01 19:32:23 rswindell Exp $ */ - -/** - Javascript Frame Library - for Synchronet v3.15a+ - by Matt Johnson (mcmlxxix) - -DESCRIPTION: - - this library is meant to be used in conjunction with other libraries that - store display data in a Frame() object or objects - this allows for "windows" that can be hidden, moved, closed, etc... - without destroying the data behind them. - - the object itself takes the following parameters: - - x: the coordinate representing the top left corner of the frame (horiz) - y: the coordinate representing the top left corner of the frame (vert) - width: the horizontal width of the frame - height: the vertical height of the frame - attr: the default color attributes of the frame - parent: a frame object representing the parent of the new frame - -METHODS: - - frame.open() //populate frame contents in character canvas - frame.close() //remove frame contents from character canvas - frame.delete() //delete this frame (remove from screen buffer, destroy internal references) - frame.invalidate() //clear screen buffer to redraw contents on cycle() or draw() - frame.draw() //open frame (if not open) move to top (if not on top) and cycle() - frame.cycle() //check the display matrix for updated characters and displays them - frame.refresh() //flag all frame sectors for potential update - frame.load(filename) //load a binary graphic (.BIN) or ANSI graphic (.ANS) file - frame.bottom() //push frame to bottom of display stack - frame.top() //pull frame to top of display stack - frame.scroll(x,y) //scroll frame n spaces in any direction - frame.scrollTo(x,y) //scroll frame to absolute offset - frame.move(x,y) //move frame n spaces in any direction - frame.moveTo(x,y) //move frame to absolute position - frame.end() //opposite of frame.home() - frame.screenShot(file,append) - //capture the contents of a frame to file - frame.getData(x,y,use_offset) - //return the character and attribute located at x,y in frame.data (optional scroll offset) - frame.setData(x,y,ch,attr,use_offset) - //modify the character and attribute located at x,y in frame.data (optional scroll offset) - frame.clearData(x,y,use_offset) - //delete the character and attribute located at x,y in frame.data (optional scroll offset) - frame.clearline(attr) //see http://synchro.net/docs/jsobjs.html#console - frame.cleartoeol(attr) - frame.putmsg(str,attr) - frame.clear(attr) - frame.home() - frame.center(str,attr) - frame.crlf() - frame.getxy() - frame.gotoxy(x,y) - frame.pushxy() - frame.popxy() - -PROPERTIES: - - frame.x //x screen position - frame.y //y screen position - frame.width //frame width - frame.height //frame height - frame.data //frame data matrix - frame.data_height //true height of frame contents (READ ONLY) - frame.data_width //true width of frame contents (READ ONLY) - frame.attr //default attributes for frame - frame.checkbounds //toggle true/false to restrict/allow frame movement outside display - frame.lf_strict //toggle true/false to force newline after a crlf-terminated string - frame.v_scroll //toggle true/false to enable/disable vertical scrolling - frame.h_scroll //toggle true/false to enable/disable horizontal scrolling - frame.scrollbars //toggle true/false to show/hide scrollbars - frame.transparent //toggle true/false to enable transparency mode - //(do not display frame sectors where char == undefined) - frame.offset //current offset object {x,y} - frame.cursor //current cursor object {x,y} - frame.parent //the parent frame of a frame - frame.id //a unique identifier (e.g. "0.1.1.2.3") - frame.is_open //return true is frame is opened in screen buffer - -USAGE: - - //create a new frame object at screen position 1,1. 80 characters wide by 24 tall - load("frame.js"); - var frame = new Frame(1,1,80,24,BG_BLUE); - - //add frame to the display canvas - frame.open(); - - //add a new frame within the frame object that will display on top at position 10,10 - var subframe = new Frame(10,10,10,10,BG_GREEN,frame); - - //add subframe to the display canvas - subframe.open(); - - //place cursor at position x:5 y:5 relative to subframe's coordinates - subframe.gotoxy(5,5); - - //beware this sample infinite loop - while(!js.terminated) { - //print a message into subframe - subframe.putmsg("1"); - - //on first call this will draw the entire initial frame, - //as triggered by the open() method call. - //on subsequent calls this will draw only areas that have changed - frame.cycle(); - //NOTE: if frames are linked, only one frame needs to be cycled - // for all frames to update - } - - //close out the entire frame tree - frame.close(); - - */ - -load("sbbsdefs.js"); -function Frame(x,y,width,height,attr,parent) { - - /* private properties */ - this.__properties__ = { - x:undefined, - y:undefined, - width:undefined, - height:undefined, - attr:undefined, - display:undefined, - data:[], - open:false, - ctrl_a:false, - curr_attr:undefined, - attr_stack:[], - id:0 - }; - this.__settings__ = { - v_scroll:true, - h_scroll:false, - scrollbars:false, - lf_strict:true, - checkbounds:true, - transparent:false, - word_wrap:false - }; - this.__relations__ = { - parent:undefined, - child:[] - }; - this.__position__ = { - cursor:undefined, - offset:undefined, - stored:undefined - }; - - function init(x,y,width,height,attr,parent) { - if(parent instanceof Frame) { - this.__properties__.id = parent.display.nextID; - this.__properties__.display = parent.display; - this.__settings__.checkbounds = parent.checkbounds; - this.__relations__.parent = parent; - parent.child = this; - } - else { - this.__properties__.display = new Display(x,y,width,height); - } - - this.x = x; - this.y = y; - this.width = width; - this.height = height; - this.attr = attr; - - this.__position__.cursor = new Cursor(0,0,this); - this.__position__.offset = new Offset(0,0,this); - this.__position__.stored = new Cursor(0,0,this); - - //log(LOG_DEBUG,format("new frame initialized: %sx%s at %s,%s",this.width,this.height,this.x,this.y)); - } - init.apply(this,arguments); -} - -/* protected properties */ -Frame.prototype.__defineGetter__("child", function() { - return this.__relations__.child; -}); -Frame.prototype.__defineSetter__("child", function(frame) { - if(frame instanceof Frame) - this.__relations__.child.push(frame); - else - throw("child not an instance of Frame()"); -}); -Frame.prototype.__defineGetter__("attr", function() { - return this.__properties__.attr; -}); -Frame.prototype.__defineSetter__("attr", function(attr) { - if(attr !== undefined && isNaN(attr)) - throw("invalid attribute: " + attr); - this.__properties__.attr = attr; -}); -Frame.prototype.__defineGetter__("x", function() { - if(this.__properties__.x == undefined) - return this.__properties__.display.x; - return this.__properties__.x; -}); -Frame.prototype.__defineSetter__("x", function(x) { - if(x == undefined) - return; - if(!this.__checkX__(x)) - throw("invalid x coordinate: " + x); - this.__properties__.x = Number(x); -}); -Frame.prototype.__defineGetter__("y", function() { - if(this.__properties__.y == undefined) - return this.__properties__.display.y; - return this.__properties__.y; -}); -Frame.prototype.__defineSetter__("y", function(y) { - if(y == undefined) - return; - if(!this.__checkY__(y)) - throw("invalid y coordinate: " + y); - this.__properties__.y = Number(y); -}); -Frame.prototype.__defineGetter__("width", function() { - if(this.__properties__.width == undefined) - return this.__properties__.display.width; - return this.__properties__.width; -}); -Frame.prototype.__defineSetter__("width", function(width) { - if(width == undefined) - return; - if(!this.__checkWidth__(this.x,Number(width))) - throw("invalid width: " + width); - this.__properties__.width = Number(width); -}); -Frame.prototype.__defineGetter__("height", function() { - if(this.__properties__.height == undefined) - return this.__properties__.display.height; - return this.__properties__.height; -}); -Frame.prototype.__defineSetter__("height", function(height) { - if(height == undefined) - return; - if(!this.__checkHeight__(this.y,Number(height))) - throw("invalid height: " + height); - this.__properties__.height = Number(height); -}); - -/* read-only properties */ -Frame.prototype.__defineGetter__("cursor",function() { - return this.__position__.cursor; -}); -Frame.prototype.__defineGetter__("offset",function() { - return this.__position__.offset; -}); -Frame.prototype.__defineGetter__("id", function() { - return this.__properties__.id; -}); -Frame.prototype.__defineGetter__("parent", function() { - return this.__relations__.parent; -}); -Frame.prototype.__defineGetter__("display", function() { - return this.__properties__.display; -}); -Frame.prototype.__defineGetter__("data_height", function() { - return this.__properties__.data.length; -}); -Frame.prototype.__defineGetter__("data_width", function() { - if(typeof this.__properties__.data[0] == "undefined") - return 0; - var longest = 0; - for(var d in this.__properties__.data) { - if(this.__properties__.data[d].length > longest) - longest = this.__properties__.data[d].length; - } - return longest + 1; -}); -Frame.prototype.__defineGetter__("data", function() { - return this.__properties__.data; -}); - -/* protected settings */ -Frame.prototype.__defineGetter__("checkbounds", function() { - return this.__settings__.checkbounds; -}); -Frame.prototype.__defineSetter__("checkbounds", function(bool) { - if(typeof bool == "boolean") - this.__settings__.checkbounds=bool; - else - throw("non-boolean checkbounds: " + bool); -}); -Frame.prototype.__defineGetter__("transparent", function() { - return this.__settings__.transparent; -}); -Frame.prototype.__defineSetter__("transparent", function(bool) { - if(typeof bool == "boolean") - this.__settings__.transparent=bool; - else - throw("non-boolean transparent: " + bool); -}); -Frame.prototype.__defineGetter__("lf_strict", function() { - return this.__settings__.lf_strict; -}); -Frame.prototype.__defineSetter__("lf_strict", function(bool) { - if(typeof bool == "boolean") - this.__settings__.lf_strict=bool; - else - throw("non-boolean lf_strict: " + bool); -}); -Frame.prototype.__defineGetter__("scrollbars", function() { - return this.__settings__.scrollbars; -}); -Frame.prototype.__defineSetter__("scrollbars", function(bool) { - if(typeof bool == "boolean") - this.__settings__.scrollbars=bool; - else - throw("non-boolean scrollbars: " + bool); -}); -Frame.prototype.__defineGetter__("v_scroll", function() { - return this.__settings__.v_scroll; -}); -Frame.prototype.__defineSetter__("v_scroll", function(bool) { - if(typeof bool == "boolean") - this.__settings__.v_scroll=bool; - else - throw("non-boolean v_scroll: " + bool); -}); -Frame.prototype.__defineGetter__("word_wrap", function() { - return this.__settings__.word_wrap; -}); -Frame.prototype.__defineSetter__("word_wrap", function(bool) { - if(typeof bool == "boolean") - this.__settings__.word_wrap=bool; - else - throw("non-boolean word_wrap: " + bool); -}); -Frame.prototype.__defineGetter__("h_scroll", function() { - return this.__settings__.h_scroll; -}); -Frame.prototype.__defineSetter__("h_scroll", function(bool) { - if(typeof bool == "boolean") - this.__settings__.h_scroll=bool; - else - throw("non-boolean h_scroll: " + bool); -}); -Frame.prototype.__defineGetter__("is_open",function() { - return this.__properties__.open; -}); - -/* public methods */ -Frame.prototype.getData = function(x,y,use_offset) { - var px = x; - var py = y; - if(use_offset) { - px += this.__position__.offset.x; - py += this.__position__.offset.y; - } - // if(!this.__properties__.data[py] || !this.__properties__.data[py][px]) - // throw("Frame.getData() - invalid coordinates: " + px + "," + py); - if(!this.__properties__.data[py] || !this.__properties__.data[py][px]) - return new Char(); - return this.__properties__.data[py][px]; -} -Frame.prototype.setData = function(x,y,ch,attr,use_offset) { - var px = x; - var py = y; - if(use_offset) { - px += this.__position__.offset.x; - py += this.__position__.offset.y; - } - //I don't remember why I did this, but it was probably important at the time - //if(!this.__properties__.data[py] || !this.__properties__.data[py][px]) - // throw("Frame.setData() - invalid coordinates: " + px + "," + py); - if(!this.__properties__.data[py]) - this.__properties__.data[py] = []; - if(!this.__properties__.data[py][px]) - this.__properties__.data[py][px] = new Char(); - if(this.__properties__.data[py][px].ch == ch && this.__properties__.data[py][px].attr == attr) - return; - if(ch) - this.__properties__.data[py][px].ch = ch; - if(attr) - this.__properties__.data[py][px].attr = attr; - if(this.__properties__.open) - this.__properties__.display.updateChar(this,x,y); -} -Frame.prototype.getWord = function(x,y) { - var word = [] - var nx = x-this.x; - var ny = y-this.y; - var cell = this.getData(nx,ny,false); - while(nx >= 0 && cell != undefined && cell.ch != undefined && cell.ch.match(/[0-9a-zA-Z]/)) { - word.unshift(cell.ch); - nx--; - cell = this.getData(nx,ny,false); - } - nx = x-this.x+1; - cell = this.getData(nx,ny,false); - while(nx < this.width && cell != undefined && cell.ch != undefined && cell.ch.match(/[0-9a-zA-Z]/)) { - word.push(cell.ch); - nx++; - cell = this.getData(nx,ny,false); - } - return word.join(""); -} -Frame.prototype.clearData = function(x,y,use_offset) { - var px = x; - var py = y; - if(use_offset) { - px += this.__position__.offset.x; - py += this.__position__.offset.y; - } - if(!this.__properties__.data[py] || !this.__properties__.data[py][px]) - return; - else if(this.__properties__.data[py][px].ch == undefined && this.__properties__.data[py][px].attr == undefined) - return; - this.__properties__.data[py][px].ch = undefined; - this.__properties__.data[py][px].attr = undefined; - if(this.__properties__.open) - this.__properties__.display.updateChar(this,x,y); -} -Frame.prototype.bottom = function() { - if(this.__properties__.open) { - for each(var c in this.__relations__.child) - c.bottom(); - this.__properties__.display.bottom(this); - } -} -Frame.prototype.top = function() { - if(this.__properties__.open) { - this.__properties__.display.top(this); - for each(var c in this.__relations__.child) - c.top(); - } -} -Frame.prototype.open = function() { - if(!this.__properties__.open) { - this.__properties__.display.open(this); - this.__properties__.open = true; - } - for each(var c in this.__relations__.child) { - c.open(); - } -} -Frame.prototype.refresh = function() { - if(this.__properties__.open) { - this.__properties__.display.updateFrame(this); - for each(var c in this.__relations__.child) - c.refresh(); - } -} -Frame.prototype.close = function() { - for each(var c in this.__relations__.child) - c.close(); - if(this.__properties__.open) { - this.__properties__.display.close(this); - this.__properties__.open = false; - } -} -Frame.prototype.delete = function(id) { - if(id == undefined) { - this.close(); - if(this.__relations__.parent) { - this.__relations__.parent.delete(this.id); - } - } - else { - for(var c=0;c<this.__relations__.child.length;c++) { - if(this.__relations__.child[c].id == id) { - this.__relations__.child.splice(c--,1); - break; - } - } - } -} -Frame.prototype.move = function(x,y) { - var nx = undefined; - var ny = undefined; - if(this.__checkX__(this.x+x) && this.__checkWidth__(this.x+x,this.width)) - nx = this.x+x; - if(this.__checkY__(this.y+y) && this.__checkHeight__(this.y+y,this.height)) - ny = this.y+y; - if(nx == undefined && ny == undefined) - return; - this.__properties__.display.updateFrame(this); - if(nx !== undefined) - this.x=nx; - if(ny !== undefined) - this.y=ny; - this.__properties__.display.updateFrame(this); - for each(var c in this.__relations__.child) - c.move(x,y); -} -Frame.prototype.moveTo = function(x,y) { - for each(var c in this.__relations__.child) { - var cx = (x + (c.x-this.x)); - var cy = (y + (c.y-this.y)); - c.moveTo(cx,cy); - } - var nx = undefined; - var ny = undefined; - if(this.__checkX__(x)) - nx = x; - if(this.__checkY__(y)) - ny = y; - if(nx == undefined && ny == undefined) - return; - this.__properties__.display.updateFrame(this); - if(nx !== undefined) - this.x=nx; - if(ny !== undefined) - this.y=ny; - this.__properties__.display.updateFrame(this); -} -Frame.prototype.draw = function() { - if(this.__properties__.open) - this.refresh(); - else - this.open(); - this.cycle(); -} -Frame.prototype.cycle = function() { - return this.__properties__.display.cycle(); -} -Frame.prototype.load = function(filename,width,height) { - var f=new File(filename); - if (!f.open("rb", true)) - return false; - var contents=f.read(); - f.close(); - var valid_sauce = false; - var ext = file_getext(filename).substr(1).toUpperCase(); - - if (contents.substr(-128, 7) == "SAUCE00") { - var sauceless_size = ascii(contents.substr(-35,1)); - sauceless_size <<= 8; - sauceless_size |= ascii(contents.substr(-36,1)); - sauceless_size <<= 8; - sauceless_size |= ascii(contents.substr(-37,1)); - sauceless_size <<= 8; - sauceless_size |= ascii(contents.substr(-38,1)); - - var data_type = ascii(contents.substr(-34,1)); - var file_type = ascii(contents.substr(-33,1)); - var tinfo1 = ascii(contents.substr(-31,1)); - tinfo1 <<= 8; - tinfo1 |= ascii(contents.substr(-32,1)); - var tinfo2 = ascii(contents.substr(-29,1)); - tinfo2 <<= 8; - tinfo2 |= ascii(contents.substr(-30,1)); - switch(data_type) { - case 1: - switch(file_type) { - case 0: // PLain ASCII - ext = "TXT"; - if (tinfo1) - width = tinfo1; - if (tinfo2) - height = tinfo2; - break; - case 1: // ANSi - ext = "ANS"; - if (tinfo1) - width = tinfo1; - if (tinfo2) - height = tinfo2; - break; - case 7: // Source - ext = "TXT"; - break; - } - valid_sauce = true; - break; - case 5: - ext = 'BIN'; - width = file_type * 2; - height = (sauceless_size / 2) / width; - valid_sauce = true; - break; - } - if (valid_sauce) - contents = contents.substr(0, sauceless_size); - } - - switch(ext) { - case "ANS": - /* - * TODO: This doesn't do exactly what reading a text file does - * one Windows (nor on Linux), but it should be close to what - * was meant, and should work given that it resets x every line. - */ - var lines=contents.split(/\r\n/); - - var attr = this.attr; - var bg = BG_BLACK; - var fg = LIGHTGRAY; - - var i = 0; - var y = 0; - var saved = {}; - - while(lines.length > 0) { - var x = 0; - var line = lines.shift(); - /* parse 'ATCODES'?? - line = line.replace(/@(.*)@/g, - function (str, code, offset, s) { - return bbs.atcode(code); - } - ); - */ - while(line.length > 0) { - /* parse an attribute sequence*/ - var m = line.match(/^\x1b\[(\d*);?(\d*);?(\d*)m/); - if(m !== null) { - line = line.substr(m.shift().length); - if(m[0] == 0) { - bg = BG_BLACK; - fg = LIGHTGRAY; - i = 0; - m.shift(); - } - if(m[0] == 1) { - i = HIGH; - m.shift(); - } - if(m[0] >= 40) { - switch(Number(m.shift())) { - case 40: - bg = BG_BLACK; - break; - case 41: - bg = BG_RED; - break; - case 42: - bg = BG_GREEN; - break; - case 43: - bg = BG_BROWN; - break; - case 44: - bg = BG_BLUE; - break; - case 45: - bg = BG_MAGENTA; - break; - case 46: - bg = BG_CYAN; - break; - case 47: - bg = BG_LIGHTGRAY; - break; - } - } - if(m[0] >= 30) { - switch(Number(m.shift())) { - case 30: - fg = BLACK; - break; - case 31: - fg = RED; - break; - case 32: - fg = GREEN; - break; - case 33: - fg = BROWN; - break; - case 34: - fg = BLUE; - break; - case 35: - fg = MAGENTA; - break; - case 36: - fg = CYAN; - break; - case 37: - fg = LIGHTGRAY; - break; - } - } - attr = bg + fg + i; - continue; - } - - /* parse absolute character position */ - var m = line.match(/^\x1b\[(\d*);?(\d*)[Hf]/); - if(m !== null) { - line = line.substr(m.shift().length); - - if(m.length==0) { - x=0; - y=0; - } - else { - if(m[0]) - y = Number(m.shift())-1; - if(m[0]) - x = Number(m.shift())-1; - } - continue; - } - - /* ignore a bullshit sequence */ - var n = line.match(/^\x1b\[\?7h/); - if(n !== null) { - line = line.substr(n.shift().length); - continue; - } - - /* parse an up positional sequence */ - var n = line.match(/^\x1b\[(\d*)A/); - if(n !== null) { - line = line.substr(n.shift().length); - var chars = n.shift(); - if(chars < 1) - y-=1; - else - y-=Number(chars); - continue; - } - - /* parse a down positional sequence */ - var n = line.match(/^\x1b\[(\d*)B/); - if(n !== null) { - line = line.substr(n.shift().length); - var chars = n.shift(); - if(chars < 1) - y+=1; - else - y+=Number(chars); - continue; - } - - /* parse a forward positional sequence */ - var n = line.match(/^\x1b\[(\d*)C/); - if(n !== null) { - line = line.substr(n.shift().length); - var chars = n.shift(); - if(chars < 1) - x+=1; - else - x+=Number(chars); - continue; - } - - /* parse a backward positional sequence */ - var n = line.match(/^\x1b\[(\d*)D/); - if(n !== null) { - line = line.substr(n.shift().length); - var chars = n.shift() - if(chars < 1) - x-=1; - else - x-=Number(chars); - continue; - } - - /* parse a clear screen sequence */ - var n = line.match(/^\x1b\[2J/); - if(n !== null) { - line = line.substr(n.shift().length); - continue; - } - - /* parse save cursor sequence */ - var n = line.match(/^\x1b\[s/); - if(n !== null) { - line = line.substr(n.shift().length); - saved.x = x; - saved.y = y; - continue; - } - - /* parse restore cursor sequence */ - var n = line.match(/^\x1b\[u/); - if(n !== null) { - line = line.substr(n.shift().length); - x = saved.x; - y = saved.y; - continue; - } - - /* set character and attribute */ - var ch = line[0]; - line = line.substr(1); - - /* validate position */ - if(y<0) - y=0; - if(x<0) - x=0; - if(x>=this.width) { - x=0; - y++; - } - /* set character and attribute */ - if(!this.__properties__.data[y]) - this.__properties__.data[y]=[]; - this.__properties__.data[y][x]=new Char(ch,attr); - x++; - } - y++; - } - break; - case "BIN": - if (!this.load_bin(contents, width, height, 0)) return false; - break; - case "TXT": - var lines=contents.split(/\r\n/); - while(lines.length > 0) - this.putmsg(lines.shift() + "\r\n"); - break; - default: - throw("unsupported filetype"); - break; - } -} -Frame.prototype.load_bin = function(contents, width, height, offset) { - if(width == undefined || height == undefined) - throw("unknown graphic dimensions"); - if(offset == undefined) offset = 0; - for(var y=0; y<height; y++) { - for(var x=0; x<width; x++) { - var c = new Char(); - if(offset >= contents.length) - return(false); - c.ch = contents.substr(offset++, 1); - if(offset == contents.length) - return(false); - c.attr = ascii(contents.substr(offset++, 1)); - c.id = this.id; - if(!this.__properties__.data[y]) - this.__properties__.data[y]=[]; - this.__properties__.data[y][x] = c; - } - } - return true; -} -Frame.prototype.scroll = function(x,y) { - var update = false; - /* default: add a new line to the data matrix */ - if(x == undefined && y == undefined) { - if(this.__settings__.v_scroll) { - var newrow = []; - for(var x = 0;x<this.width;x++) { - for(var y = 0;y<this.height;y++) - this.__properties__.display.updateChar(this,x,y); - newrow.push(new Char()); - } - this.__properties__.data.push(newrow); - this.__position__.offset.y++; - update = true; - } - } - /* otherwise, adjust the x/y offset */ - else { - if(typeof x == "number" && x !== 0 && this.__settings__.h_scroll) { - this.__position__.offset.x += x; - update = true; - } - if(typeof y == "number" && y !== 0 && this.__settings__.v_scroll) { - this.__position__.offset.y += y; - update = true; - } - if(update) - this.refresh(); - } - return update; -} -Frame.prototype.scrollTo = function(x,y) { - var update = false; - if(typeof x == "number") { - if(this.__settings__.h_scroll) { - this.__position__.offset.x = x; - update = true; - } - } - if(typeof y == "number") { - if(this.__settings__.v_scroll) { - this.__position__.offset.y = y; - update = true; - } - } - if(update) - this.refresh(); -} -Frame.prototype.insertLine = function(y) { - if(this.__properties__.data[y]) - this.__properties__.data.splice(y,0,[]); - else - this.__properties__.data[y] = []; - this.refresh(); - return this.__properties__.data[y]; -} -Frame.prototype.deleteLine = function(y) { - var l = undefined; - if(this.__properties__.data[y]) { - l = this.__properties__.data.splice(y,1); - this.refresh(); - } - return l; -} -Frame.prototype.screenShot = function(file,append) { - return this.__properties__.display.screenShot(file,append); -} -Frame.prototype.invalidate = function() { - this.__properties__.display.invalidate(); - this.refresh(); -} -Frame.prototype.dump = function() { - return this.__properties__.display.dump(); -} - -/* console method emulation */ -Frame.prototype.home = function() { - this.__position__.cursor.x = 0; - this.__position__.cursor.y = 0; - return true; -} -Frame.prototype.end = function() { - this.__position__.cursor.x = this.width-1; - this.__position__.cursor.y = this.height-1; - return true; -} -Frame.prototype.pagedown = function() { - this.__position__.offset.y += this.height-1; - if(this.__position__.offset.y >= this.data_height) - this.__position__.offset.y = this.data_height - this.height; - this.refresh(); -} -Frame.prototype.pageup = function() { - this.__position__.offset.y -= this.height-1; - if(this.__position__.offset.y < 0) - this.__position__.offset.y = 0; - this.refresh(); -} -Frame.prototype.clear = function (attr) { - if (attr) this.attr = attr; - this.__properties__.data = []; - this.__position__.offset.x = 0; - this.__position__.offset.y = 0; - this.home(); - this.invalidate(); -} -Frame.prototype.erase = function(ch, attr) { - if(attr == undefined) - attr = this.attr; - var px = this.__position__.offset.x; - var py = this.__position__.offset.y; - for(var y = 0; y< this.height; y++) { - if(!this.__properties__.data[py + y]) { - continue; - } - for(var x = 0; x<this.width; x++) { - if(!this.__properties__.data[py + y][px + x]) { - continue; - } - if((this.__properties__.data[py + y][px + x].ch === undefined || - this.__properties__.data[py + y][px + x].ch === ch) && - this.__properties__.data[py + y][px + x].attr == attr) { - continue; - } - this.__properties__.data[py + y][px + x].ch = undefined; - this.__properties__.data[py + y][px + x].attr = attr; - this.__properties__.display.updateChar(this, x, y); - } - } - this.home(); -} -Frame.prototype.clearline = function(attr) { - if(attr == undefined) - attr = this.attr; - var py = this.__position__.offset.y + this.__position__.cursor.y; - if(!this.__properties__.data[py]) - return false; - for(var x=0;x<this.__properties__.data[py].length;x++) { - if(this.__properties__.data[py][x]) { - this.__properties__.data[py][x].ch = undefined; - this.__properties__.data[py][x].attr = attr; - } - } - for(var x=0;x<this.width;x++) { - this.__properties__.display.updateChar(this,x,this.__position__.cursor.y); - } -} -Frame.prototype.cleartoeol = function(attr) { - if(attr == undefined) - attr = this.attr; - var px = this.__position__.offset.x + this.__position__.cursor.x; - var py = this.__position__.offset.y + this.__position__.cursor.y; - if(!this.__properties__.data[this.__position__.cursor.y]) - return false; - for(var x=px;x<this.__properties__.data[py].length;x++) { - if(this.__properties__.data[py][x]) { - if(this.__properties__.data[py][x].ch !== undefined) { - this.__properties__.data[py][x].ch = undefined; - this.__properties__.display.updateChar(this,x - this.__position__.offset.x, this.__position__.cursor.y); - } - this.__properties__.data[py][x].attr = attr; - } - } -} -Frame.prototype.crlf = function() { - this.__position__.cursor.x = 0; - if(this.__position__.cursor.y < this.height-1) - this.__position__.cursor.y += 1; - else {} -} -Frame.prototype.write = function(str,attr) { - if(str == undefined) - return; - if(this.__settings__.word_wrap) - str = word_wrap(str, this.width,str.length, false); - str = str.toString().split(''); - - if(attr) - this.__properties__.curr_attr = attr; - else - this.__properties__.curr_attr = this.attr; - var pos = this.__position__.cursor; - while(str.length > 0) { - var ch = str.shift(); - this.__putChar__.call(this,ch,this.__properties__.curr_attr); - pos.x++; - } -} -Frame.prototype.putmsg = function(str,attr) { - if(str == undefined) - return; - if(this.__settings__.word_wrap) { - var remainingWidth = this.width - this.__position__.cursor.x; - if(str.length > remainingWidth) { - str = word_wrap(str,remainingWidth,str.length,false).split('\n'); - var trailing_newline = str[str.length - 1].length == 0; - str = str.shift() + '\r\n' + word_wrap(str.join('\r\n'),this.width,remainingWidth,false); - if(!trailing_newline) str = str.trimRight(); - } - } - str = str.toString().split(''); - - if(attr) - this.__properties__.curr_attr = attr; - else - this.__properties__.curr_attr = this.attr; - var pos = this.__position__.cursor; - - while(str.length > 0) { - var ch = str.shift(); - if(this.__properties__.ctrl_a) { - var k = ch; - if(k) - k = k.toUpperCase(); - switch(k) { - case '\1': /* A "real" ^A code */ - this.__putChar__.call(this,ch,this.__properties__.curr_attr); - pos.x++; - break; - case 'K': /* Black */ - this.__properties__.curr_attr=(this.__properties__.curr_attr)&0xf8; - break; - case 'R': /* Red */ - this.__properties__.curr_attr=((this.__properties__.curr_attr)&0xf8)|RED; - break; - case 'G': /* Green */ - this.__properties__.curr_attr=((this.__properties__.curr_attr)&0xf8)|GREEN; - break; - case 'Y': /* Yellow */ - this.__properties__.curr_attr=((this.__properties__.curr_attr)&0xf8)|BROWN; - break; - case 'B': /* Blue */ - this.__properties__.curr_attr=((this.__properties__.curr_attr)&0xf8)|BLUE; - break; - case 'M': /* Magenta */ - this.__properties__.curr_attr=((this.__properties__.curr_attr)&0xf8)|MAGENTA; - break; - case 'C': /* Cyan */ - this.__properties__.curr_attr=((this.__properties__.curr_attr)&0xf8)|CYAN; - break; - case 'W': /* White */ - this.__properties__.curr_attr=((this.__properties__.curr_attr)&0xf8)|LIGHTGRAY; - break; - case '0': /* Black */ - this.__properties__.curr_attr=(this.__properties__.curr_attr)&0x8f; - break; - case '1': /* Red */ - this.__properties__.curr_attr=((this.__properties__.curr_attr)&0x8f)|(RED<<4); - break; - case '2': /* Green */ - this.__properties__.curr_attr=((this.__properties__.curr_attr)&0x8f)|(GREEN<<4); - break; - case '3': /* Yellow */ - this.__properties__.curr_attr=((this.__properties__.curr_attr)&0x8f)|(BROWN<<4); - break; - case '4': /* Blue */ - this.__properties__.curr_attr=((this.__properties__.curr_attr)&0x8f)|(BLUE<<4); - break; - case '5': /* Magenta */ - this.__properties__.curr_attr=((this.__properties__.curr_attr)&0x8f)|(MAGENTA<<4); - break; - case '6': /* Cyan */ - this.__properties__.curr_attr=((this.__properties__.curr_attr)&0x8f)|(CYAN<<4); - break; - case '7': /* White */ - this.__properties__.curr_attr=((this.__properties__.curr_attr)&0x8f)|(LIGHTGRAY<<4); - break; - case 'H': /* High Intensity */ - this.__properties__.curr_attr|=HIGH; - break; - case 'I': /* Blink */ - this.__properties__.curr_attr|=BLINK; - break; - case 'N': /* Normal */ - this.__properties__.curr_attr&=~HIGH; - this.__properties__.curr_attr&=~BLINK; - this.__properties__.curr_attr=((this.__properties__.curr_attr)&0x8f)|LIGHTGRAY; - break; - case '+': - this.__properties__.attr_stack.push(this.__properties__.curr_attr); - break; - case '-': /* Normal if High, Blink, or BG */ - if (this.__properties__.attr_stack.length) { - this.__properties__.curr_attr = this.__properties__.attr_stack.pop(); - } else if(this.__properties__.curr_attr & 0xf8) { - this.__properties__.curr_attr=this.attr; - } - break; - case '_': /* Normal if blink/background */ - if(this.__properties__.curr_attr & 0xf0) - this.__properties__.curr_attr=this.attr; - break; - case '[': /* CR */ - pos.x=0; - break; - case ']': /* LF */ - pos.y++; - if(this.__settings__.lf_strict && pos.y >= this.height) { - this.scroll(); - pos.y--; - } - break; - default: /* Other stuff... specifically, check for right movement */ - if(ch.charCodeAt(0)>127) - pos.x+=ch.charCodeAt(0)-127; - break; - } - this.__properties__.ctrl_a = false; - } - else { - switch(ch) { - case '\1': /* CTRL-A code */ - case ctrl('A'): - this.__properties__.ctrl_a = true; - break; - case '\7': /* Beep */ - break; - case '\x7f': /* DELETE */ - break; - case '\b': - if(pos.x > 0) { - pos.x--; - this.__putChar__.call(this," ",this.__properties__.curr_attr); - } - break; - case '\r': - pos.x=0; - break; - case '\t': - pos.x += 4; - break; - case '\n': - pos.y++; - if(this.__settings__.lf_strict && pos.y >= this.height) { - this.scroll(); - pos.y--; - } - break; - default: - this.__putChar__.call(this,ch,this.__properties__.curr_attr); - pos.x++; - break; +/* $Id: frame.js,v 1.91 2020/08/01 19:32:23 rswindell Exp $ */ + +/** + Javascript Frame Library + for Synchronet v3.15a+ + by Matt Johnson (mcmlxxix) + +DESCRIPTION: + + this library is meant to be used in conjunction with other libraries that + store display data in a Frame() object or objects + this allows for "windows" that can be hidden, moved, closed, etc... + without destroying the data behind them. + + the object itself takes the following parameters: + + x: the coordinate representing the top left corner of the frame (horiz) + y: the coordinate representing the top left corner of the frame (vert) + width: the horizontal width of the frame + height: the vertical height of the frame + attr: the default color attributes of the frame + parent: a frame object representing the parent of the new frame + +METHODS: + + frame.open() //populate frame contents in character canvas + frame.close() //remove frame contents from character canvas + frame.delete() //delete this frame (remove from screen buffer, destroy internal references) + frame.invalidate() //clear screen buffer to redraw contents on cycle() or draw() + frame.draw() //open frame (if not open) move to top (if not on top) and cycle() + frame.cycle() //check the display matrix for updated characters and displays them + frame.refresh() //flag all frame sectors for potential update + frame.load(filename) //load a binary graphic (.BIN) or ANSI graphic (.ANS) file + frame.bottom() //push frame to bottom of display stack + frame.top() //pull frame to top of display stack + frame.scroll(x,y) //scroll frame n spaces in any direction + frame.scrollTo(x,y) //scroll frame to absolute offset + frame.move(x,y) //move frame n spaces in any direction + frame.moveTo(x,y) //move frame to absolute position + frame.end() //opposite of frame.home() + frame.screenShot(file,append) + //capture the contents of a frame to file + frame.getData(x,y,use_offset) + //return the character and attribute located at x,y in frame.data (optional scroll offset) + frame.setData(x,y,ch,attr,use_offset) + //modify the character and attribute located at x,y in frame.data (optional scroll offset) + frame.clearData(x,y,use_offset) + //delete the character and attribute located at x,y in frame.data (optional scroll offset) + frame.clearline(attr) //see http://synchro.net/docs/jsobjs.html#console + frame.cleartoeol(attr) + frame.putmsg(str,attr) + frame.clear(attr) + frame.home() + frame.center(str,attr) + frame.crlf() + frame.getxy() + frame.gotoxy(x,y) + frame.pushxy() + frame.popxy() + +PROPERTIES: + + frame.x //x screen position + frame.y //y screen position + frame.width //frame width + frame.height //frame height + frame.data //frame data matrix + frame.data_height //true height of frame contents (READ ONLY) + frame.data_width //true width of frame contents (READ ONLY) + frame.attr //default attributes for frame + frame.checkbounds //toggle true/false to restrict/allow frame movement outside display + frame.lf_strict //toggle true/false to force newline after a crlf-terminated string + frame.v_scroll //toggle true/false to enable/disable vertical scrolling + frame.h_scroll //toggle true/false to enable/disable horizontal scrolling + frame.scrollbars //toggle true/false to show/hide scrollbars + frame.transparent //toggle true/false to enable transparency mode + //(do not display frame sectors where char == undefined) + frame.offset //current offset object {x,y} + frame.cursor //current cursor object {x,y} + frame.parent //the parent frame of a frame + frame.id //a unique identifier (e.g. "0.1.1.2.3") + frame.is_open //return true is frame is opened in screen buffer + +USAGE: + + //create a new frame object at screen position 1,1. 80 characters wide by 24 tall + load("frame.js"); + var frame = new Frame(1,1,80,24,BG_BLUE); + + //add frame to the display canvas + frame.open(); + + //add a new frame within the frame object that will display on top at position 10,10 + var subframe = new Frame(10,10,10,10,BG_GREEN,frame); + + //add subframe to the display canvas + subframe.open(); + + //place cursor at position x:5 y:5 relative to subframe's coordinates + subframe.gotoxy(5,5); + + //beware this sample infinite loop + while(!js.terminated) { + //print a message into subframe + subframe.putmsg("1"); + + //on first call this will draw the entire initial frame, + //as triggered by the open() method call. + //on subsequent calls this will draw only areas that have changed + frame.cycle(); + //NOTE: if frames are linked, only one frame needs to be cycled + // for all frames to update + } + + //close out the entire frame tree + frame.close(); + + */ + +load("sbbsdefs.js"); +function Frame(x,y,width,height,attr,parent) { + + /* private properties */ + this.__properties__ = { + x:undefined, + y:undefined, + width:undefined, + height:undefined, + attr:undefined, + display:undefined, + data:[], + open:false, + ctrl_a:false, + curr_attr:undefined, + attr_stack:[], + id:0 + }; + this.__settings__ = { + v_scroll:true, + h_scroll:false, + scrollbars:false, + lf_strict:true, + checkbounds:true, + transparent:false, + word_wrap:false + }; + this.__relations__ = { + parent:undefined, + child:[] + }; + this.__position__ = { + cursor:undefined, + offset:undefined, + stored:undefined + }; + + function init(x,y,width,height,attr,parent) { + if(parent instanceof Frame) { + this.__properties__.id = parent.display.nextID; + this.__properties__.display = parent.display; + this.__settings__.checkbounds = parent.checkbounds; + this.__relations__.parent = parent; + parent.child = this; + } + else { + this.__properties__.display = new Display(x,y,width,height); + } + + this.x = x; + this.y = y; + this.width = width; + this.height = height; + this.attr = attr; + + this.__position__.cursor = new Cursor(0,0,this); + this.__position__.offset = new Offset(0,0,this); + this.__position__.stored = new Cursor(0,0,this); + + //log(LOG_DEBUG,format("new frame initialized: %sx%s at %s,%s",this.width,this.height,this.x,this.y)); + } + init.apply(this,arguments); +} + +/* protected properties */ +Frame.prototype.__defineGetter__("child", function() { + return this.__relations__.child; +}); +Frame.prototype.__defineSetter__("child", function(frame) { + if(frame instanceof Frame) + this.__relations__.child.push(frame); + else + throw("child not an instance of Frame()"); +}); +Frame.prototype.__defineGetter__("attr", function() { + return this.__properties__.attr; +}); +Frame.prototype.__defineSetter__("attr", function(attr) { + if(attr !== undefined && isNaN(attr)) + throw("invalid attribute: " + attr); + this.__properties__.attr = attr; +}); +Frame.prototype.__defineGetter__("x", function() { + if(this.__properties__.x == undefined) + return this.__properties__.display.x; + return this.__properties__.x; +}); +Frame.prototype.__defineSetter__("x", function(x) { + if(x == undefined) + return; + if(!this.__checkX__(x)) + throw("invalid x coordinate: " + x); + this.__properties__.x = Number(x); +}); +Frame.prototype.__defineGetter__("y", function() { + if(this.__properties__.y == undefined) + return this.__properties__.display.y; + return this.__properties__.y; +}); +Frame.prototype.__defineSetter__("y", function(y) { + if(y == undefined) + return; + if(!this.__checkY__(y)) + throw("invalid y coordinate: " + y); + this.__properties__.y = Number(y); +}); +Frame.prototype.__defineGetter__("width", function() { + if(this.__properties__.width == undefined) + return this.__properties__.display.width; + return this.__properties__.width; +}); +Frame.prototype.__defineSetter__("width", function(width) { + if(width == undefined) + return; + if(!this.__checkWidth__(this.x,Number(width))) + throw("invalid width: " + width); + this.__properties__.width = Number(width); +}); +Frame.prototype.__defineGetter__("height", function() { + if(this.__properties__.height == undefined) + return this.__properties__.display.height; + return this.__properties__.height; +}); +Frame.prototype.__defineSetter__("height", function(height) { + if(height == undefined) + return; + if(!this.__checkHeight__(this.y,Number(height))) + throw("invalid height: " + height); + this.__properties__.height = Number(height); +}); + +/* read-only properties */ +Frame.prototype.__defineGetter__("cursor",function() { + return this.__position__.cursor; +}); +Frame.prototype.__defineGetter__("offset",function() { + return this.__position__.offset; +}); +Frame.prototype.__defineGetter__("id", function() { + return this.__properties__.id; +}); +Frame.prototype.__defineGetter__("parent", function() { + return this.__relations__.parent; +}); +Frame.prototype.__defineGetter__("display", function() { + return this.__properties__.display; +}); +Frame.prototype.__defineGetter__("data_height", function() { + return this.__properties__.data.length; +}); +Frame.prototype.__defineGetter__("data_width", function() { + if(typeof this.__properties__.data[0] == "undefined") + return 0; + var longest = 0; + for(var d in this.__properties__.data) { + if(this.__properties__.data[d].length > longest) + longest = this.__properties__.data[d].length; + } + return longest + 1; +}); +Frame.prototype.__defineGetter__("data", function() { + return this.__properties__.data; +}); + +/* protected settings */ +Frame.prototype.__defineGetter__("checkbounds", function() { + return this.__settings__.checkbounds; +}); +Frame.prototype.__defineSetter__("checkbounds", function(bool) { + if(typeof bool == "boolean") + this.__settings__.checkbounds=bool; + else + throw("non-boolean checkbounds: " + bool); +}); +Frame.prototype.__defineGetter__("transparent", function() { + return this.__settings__.transparent; +}); +Frame.prototype.__defineSetter__("transparent", function(bool) { + if(typeof bool == "boolean") + this.__settings__.transparent=bool; + else + throw("non-boolean transparent: " + bool); +}); +Frame.prototype.__defineGetter__("lf_strict", function() { + return this.__settings__.lf_strict; +}); +Frame.prototype.__defineSetter__("lf_strict", function(bool) { + if(typeof bool == "boolean") + this.__settings__.lf_strict=bool; + else + throw("non-boolean lf_strict: " + bool); +}); +Frame.prototype.__defineGetter__("scrollbars", function() { + return this.__settings__.scrollbars; +}); +Frame.prototype.__defineSetter__("scrollbars", function(bool) { + if(typeof bool == "boolean") + this.__settings__.scrollbars=bool; + else + throw("non-boolean scrollbars: " + bool); +}); +Frame.prototype.__defineGetter__("v_scroll", function() { + return this.__settings__.v_scroll; +}); +Frame.prototype.__defineSetter__("v_scroll", function(bool) { + if(typeof bool == "boolean") + this.__settings__.v_scroll=bool; + else + throw("non-boolean v_scroll: " + bool); +}); +Frame.prototype.__defineGetter__("word_wrap", function() { + return this.__settings__.word_wrap; +}); +Frame.prototype.__defineSetter__("word_wrap", function(bool) { + if(typeof bool == "boolean") + this.__settings__.word_wrap=bool; + else + throw("non-boolean word_wrap: " + bool); +}); +Frame.prototype.__defineGetter__("h_scroll", function() { + return this.__settings__.h_scroll; +}); +Frame.prototype.__defineSetter__("h_scroll", function(bool) { + if(typeof bool == "boolean") + this.__settings__.h_scroll=bool; + else + throw("non-boolean h_scroll: " + bool); +}); +Frame.prototype.__defineGetter__("is_open",function() { + return this.__properties__.open; +}); + +/* public methods */ +Frame.prototype.getData = function(x,y,use_offset) { + var px = x; + var py = y; + if(use_offset) { + px += this.__position__.offset.x; + py += this.__position__.offset.y; + } + // if(!this.__properties__.data[py] || !this.__properties__.data[py][px]) + // throw("Frame.getData() - invalid coordinates: " + px + "," + py); + if(!this.__properties__.data[py] || !this.__properties__.data[py][px]) + return new Char(); + return this.__properties__.data[py][px]; +} +Frame.prototype.setData = function(x,y,ch,attr,use_offset) { + var px = x; + var py = y; + if(use_offset) { + px += this.__position__.offset.x; + py += this.__position__.offset.y; + } + //I don't remember why I did this, but it was probably important at the time + //if(!this.__properties__.data[py] || !this.__properties__.data[py][px]) + // throw("Frame.setData() - invalid coordinates: " + px + "," + py); + if(!this.__properties__.data[py]) + this.__properties__.data[py] = []; + if(!this.__properties__.data[py][px]) + this.__properties__.data[py][px] = new Char(); + if(this.__properties__.data[py][px].ch == ch && this.__properties__.data[py][px].attr == attr) + return; + if(ch) + this.__properties__.data[py][px].ch = ch; + if(attr) + this.__properties__.data[py][px].attr = attr; + if(this.__properties__.open) + this.__properties__.display.updateChar(this,x,y); +} +Frame.prototype.getWord = function(x,y) { + var word = [] + var nx = x-this.x; + var ny = y-this.y; + var cell = this.getData(nx,ny,false); + while(nx >= 0 && cell != undefined && cell.ch != undefined && cell.ch.match(/[0-9a-zA-Z]/)) { + word.unshift(cell.ch); + nx--; + cell = this.getData(nx,ny,false); + } + nx = x-this.x+1; + cell = this.getData(nx,ny,false); + while(nx < this.width && cell != undefined && cell.ch != undefined && cell.ch.match(/[0-9a-zA-Z]/)) { + word.push(cell.ch); + nx++; + cell = this.getData(nx,ny,false); + } + return word.join(""); +} +Frame.prototype.clearData = function(x,y,use_offset) { + var px = x; + var py = y; + if(use_offset) { + px += this.__position__.offset.x; + py += this.__position__.offset.y; + } + if(!this.__properties__.data[py] || !this.__properties__.data[py][px]) + return; + else if(this.__properties__.data[py][px].ch == undefined && this.__properties__.data[py][px].attr == undefined) + return; + this.__properties__.data[py][px].ch = undefined; + this.__properties__.data[py][px].attr = undefined; + if(this.__properties__.open) + this.__properties__.display.updateChar(this,x,y); +} +Frame.prototype.bottom = function() { + if(this.__properties__.open) { + for each(var c in this.__relations__.child) + c.bottom(); + this.__properties__.display.bottom(this); + } +} +Frame.prototype.top = function() { + if(this.__properties__.open) { + this.__properties__.display.top(this); + for each(var c in this.__relations__.child) + c.top(); + } +} +Frame.prototype.open = function() { + if(!this.__properties__.open) { + this.__properties__.display.open(this); + this.__properties__.open = true; + } + for each(var c in this.__relations__.child) { + c.open(); + } +} +Frame.prototype.refresh = function() { + if(this.__properties__.open) { + this.__properties__.display.updateFrame(this); + for each(var c in this.__relations__.child) + c.refresh(); + } +} +Frame.prototype.close = function() { + for each(var c in this.__relations__.child) + c.close(); + if(this.__properties__.open) { + this.__properties__.display.close(this); + this.__properties__.open = false; + } +} +Frame.prototype.delete = function(id) { + if(id == undefined) { + this.close(); + if(this.__relations__.parent) { + this.__relations__.parent.delete(this.id); + } + } + else { + for(var c=0;c<this.__relations__.child.length;c++) { + if(this.__relations__.child[c].id == id) { + this.__relations__.child.splice(c--,1); + break; } - } - } -} -Frame.prototype.center = function(str,attr) { - this.__position__.cursor.x = Math.ceil(this.width/2) - Math.ceil(console.strlen(strip_ctrl(str))/2); - if(this.__position__.cursor.x < 0) - this.__position__.cursor.x = 0; - this.putmsg(str,attr); -} -Frame.prototype.gotoxy = function(x,y) { - if(typeof x == "object" && x.x && x.y) { - if(this.__validateCursor__.call(this,x.x-1,x.y-1)) { - this.__position__.cursor.x = x.x-1; - this.__position__.cursor.y = x.y-1; - return true; - } - return false; - } - if(this.__validateCursor__.call(this,x-1,y-1)) { - this.__position__.cursor.x = x-1; - this.__position__.cursor.y = y-1; - return true; - } - return false; -} -Frame.prototype.getxy = function() { - return { - x:this.__position__.cursor.x+1, - y:this.__position__.cursor.y+1 - }; -} -Frame.prototype.pushxy = function() { - this.__position__.stored.x = this.__position__.cursor.x; - this.__position__.stored.y = this.__position__.cursor.y; -} -Frame.prototype.popxy = function() { - this.__position__.cursor.x = this.__position__.stored.x; - this.__position__.cursor.y = this.__position__.stored.y; -} -Frame.prototype.up = function(n) { - if(this.__position__.cursor.y == 0 && this.__position__.offset.y == 0) - return false; - if(isNaN(n)) - n = 1; - while(n > 0) { - if(this.__position__.cursor.y > 0) { - this.__position__.cursor.y--; - n--; - } - else break; - } - if(n > 0) - this.scroll(0,-(n)); - return true; -} -Frame.prototype.down = function(n) { - if(this.__position__.cursor.y == this.height-1 && this.__position__.offset.y == this.data_height - this.height) - return false; - if(isNaN(n)) - n = 1; - while(n > 0) { - if(this.__position__.cursor.y < this.height - 1) { - this.__position__.cursor.y++; - n--; - } - else break; - } - if(n > 0) - this.scroll(0,n); - return true; -} -Frame.prototype.left = function(n) { - if(this.__position__.cursor.x == 0 && this.__position__.offset.x == 0) - return false; - if(isNaN(n)) - n = 1; - while(n > 0) { - if(this.__position__.cursor.x > 0) { - this.__position__.cursor.x--; - n--; - } - else break; - } - if(n > 0) - this.scroll(-(n),0); - return true; -} -Frame.prototype.right = function(n) { - if(this.__position__.cursor.x == this.width-1 && this.__position__.offset.x == this.data_width - this.width) - return false; - if(isNaN(n)) - n = 1; - while(n > 0) { - if(this.__position__.cursor.x < this.width - 1) { - this.__position__.cursor.x++; - n--; - } - else break; - } - if(n > 0) - this.scroll(n,0); - return true; -} - -/* internal frame methods */ -Frame.prototype.__checkX__ = function(x) { - if( isNaN(x) || (this.__settings__.checkbounds && - (x > this.__properties__.display.x + this.__properties__.display.width || - x < this.__properties__.display.x))) - return false; - return true; -} -Frame.prototype.__checkY__ = function(y) { - if( isNaN(y) || (this.__settings__.checkbounds && - (y > this.__properties__.display.y + this.__properties__.display.height || - y < this.__properties__.display.y))) - return false; - return true; -} -Frame.prototype.__checkWidth__ = function(x,width) { - if( width < 1 || isNaN(width) || (this.__settings__.checkbounds && - x + width > this.__properties__.display.x + this.__properties__.display.width)) - return false; - return true; -} -Frame.prototype.__checkHeight__ = function(y,height) { - if( height < 1 || isNaN(height) || (this.__settings__.checkbounds && - y + height > this.__properties__.display.y + this.__properties__.display.height)) - return false; - return true; -} -Frame.prototype.__validateCursor__ = function(x,y) { - if(x >= this.width) - return false; - if(y >= this.height) - return false; - return true; -} -Frame.prototype.__putChar__ = function(ch,attr) { - if(this.__position__.cursor.x >= this.width) { - this.__position__.cursor.x=0; - this.__position__.cursor.y++; - } - if(this.__position__.cursor.y >= this.height) { - this.scroll(); - this.__position__.cursor.y--; - } - this.setData(this.__position__.cursor.x,this.__position__.cursor.y,ch,attr,true); -} - -Frame.prototype.__flip__ = function (swaps, flipx) { - for (var y = 0; y < this.data_height; y++) { - if (!Array.isArray(this.data[y])) continue; - if (flipx) this.data[y] = this.data[y].reverse(); - for (var x = 0; x < this.data[y].length; x++) { - for (var s = 0; s < swaps.length; s++) { - var i = swaps[s].indexOf(ascii(this.data[y][x].ch)); - if (i >= 0) { - this.data[y][x].ch = ascii(swaps[s][(i + 1) % 2]); - break; - } - } - } - } - if (!flipx) this.data.reverse(); -} - -Frame.prototype.flipX = function () { - const swaps = [ - [ 40, 41 ], - [ 47, 92 ], - [ 60, 62 ], - [ 91, 93 ], - [ 123, 125 ], - [ 169, 170 ], - [ 174, 175 ], - [ 180, 195 ], - [ 181, 198 ], - [ 182, 199 ], - [ 183, 214 ], - [ 184, 213 ], - [ 185, 204 ], - [ 187, 201 ], - [ 188, 200 ], - [ 189, 211 ], - [ 190, 212 ], - [ 191, 218 ], - [ 192, 217 ], - [ 221, 222 ] - ]; - this.__flip__(swaps, true); -} - -Frame.prototype.flipY = function () { - const swaps = [ - [ 47, 92 ], - [ 183, 189 ], - [ 184, 190 ], - [ 187, 188 ], - [ 191, 217 ], - [ 192, 218 ], - [ 193, 194 ], - [ 200, 201 ], - [ 202, 203 ], - [ 207, 209 ], - [ 208, 210 ], - [ 211, 214 ], - [ 212, 213 ], - [ 220, 223 ] - ]; - this.__flip__(swaps, false); -} - -/* frame reference object */ -function Canvas(frame,display) { - this.frame = frame; - this.display = display; - this.__defineGetter__("xoff",function() { - return this.frame.x - this.display.x; - }); - this.__defineGetter__("yoff",function() { - return this.frame.y - this.display.y; - }); -} - -Canvas.prototype.hasData = function(x,y) { - var xpos = x-this.xoff; - var ypos = y-this.yoff; - - if(xpos < 0 || ypos < 0) - return undefined; - if(xpos >= this.frame.width || ypos >= this.frame.height) - return undefined; - if(this.frame.transparent && this.frame.getData(xpos,ypos,true).ch == undefined) - return undefined; - return true; -} - -/* object representing screen positional and dimensional limits and canvas stack */ -function Display(x,y,width,height) { - /* private properties */ - this.__properties__ = { - x:undefined, - y:undefined, - width:undefined, - height:undefined, - canvas:{}, - update:{}, - buffer:{}, - count:0 - } - - /* initialize display properties */ - this.x = x; - this.y = y; - this.width = width; - this.height = height; - log(LOG_DEBUG,format("new display initialized: %sx%s at %s,%s",this.width,this.height,this.x,this.y)); -} - -/* protected display properties */ -Display.prototype.__defineGetter__("x", function() { - return this.__properties__.x; -}); -Display.prototype.__defineSetter__("x", function(x) { - if(x == undefined) - this.__properties__.x = 1; - else if(isNaN(x)) - throw("invalid x coordinate: " + x); - else - this.__properties__.x = Number(x); -}); -Display.prototype.__defineGetter__("y", function() { - return this.__properties__.y; -}); -Display.prototype.__defineSetter__("y", function(y) { - if(y == undefined) - this.__properties__.y = 1; - else if(isNaN(y) || y < 1 || y > console.screen_rows) - throw("invalid y coordinate: " + y); - else - this.__properties__.y = Number(y); -}); -Display.prototype.__defineGetter__("width", function() { - return this.__properties__.width; -}); -Display.prototype.__defineSetter__("width", function(width) { - if(width == undefined) - this.__properties__.width = console.screen_columns; - else if(isNaN(width) || (this.x + Number(width) - 1) > (console.screen_columns)) - throw("invalid width: " + width); - else - this.__properties__.width = Number(width); -}); -Display.prototype.__defineGetter__("height", function() { - return this.__properties__.height; -}); -Display.prototype.__defineSetter__("height", function(height) { - if(height == undefined) - this.__properties__.height = console.screen_rows; - else if(isNaN(height) || (this.y + Number(height) - 1) > (console.screen_rows)) - throw("invalid height: " + height); - else - this.__properties__.height = Number(height); -}); -Display.prototype.__defineGetter__("nextID",function() { - return ++this.__properties__.count; -}); - -/* public display methods */ -Display.prototype.cycle = function() { - var updates = this.__getUpdateList__(); - if(updates.length > 0) { - var lasty = undefined; - var lastx = undefined; - var lastf = undefined; - for each(var u in updates) { - if(lasty !== u.y || lastx == undefined || (u.x - lastx) !== 1) - console.gotoxy(u.px,u.py); - if(lastf !== u.id) - console.attributes = undefined; - this.__drawChar__(u.ch,u.attr,u.px,u.py); - lastx = u.x; - lasty = u.y; - lastf = u.id; - } - //console.attributes=undefined; - return true; - } - return false; -} -Display.prototype.draw = function() { - for(var y = 0;y<this.height;y++) { - for(var x = 0;x<this.width;x++) { - this.__updateChar__(x,y); - } - } - this.cycle(); -} -Display.prototype.open = function(frame) { - var canvas = new Canvas(frame,this); - this.__properties__.canvas[frame.id] = canvas; - this.updateFrame(frame); -} -Display.prototype.close = function(frame) { - this.updateFrame(frame); - delete this.__properties__.canvas[frame.id]; -} -Display.prototype.top = function(frame) { - var canvas = this.__properties__.canvas[frame.id]; - delete this.__properties__.canvas[frame.id]; - this.__properties__.canvas[frame.id] = canvas; - this.updateFrame(frame); -} -Display.prototype.bottom = function(frame) { - for(var c in this.__properties__.canvas) { - if(c == frame.id) - continue; - var canvas = this.__properties__.canvas[c]; - delete this.__properties__.canvas[c]; - this.__properties__.canvas[c] = canvas; - } - this.updateFrame(frame); -} -Display.prototype.updateFrame = function(frame) { - for(var y = 0;y<frame.height;y++) { - for(var x = 0;x<frame.width;x++) { - this.updateChar(frame,x,y/*,data*/); - } - } -} -Display.prototype.updateChar = function(frame,x,y) { - var xoff = frame.x - this.x; - var yoff = frame.y - this.y; - this.__updateChar__(xoff + x,yoff + y); -} -Display.prototype.screenShot = function(file,append) { - var f = new File(file); - if(append) - f.open('ab',true,4096); - else - f.open('wb',true,4096) ; - if(!f.is_open) - return false; - - for(var y = 0;y<this.height;y++) { - for(var x = 0;x<this.width;x++) { - var c = this.__getTopCanvas__(x,y); - var d = this.__getData__(c,x,y); - if(d.ch) - f.write(d.ch); - else - f.write(" "); - if(d.attr) - f.writeBin(d.attr,1); - else - f.writeBin(0,1); - } - } - - f.close(); - return true; -} -Display.prototype.invalidate = function() { - this.__properties__.buffer = {}; -} -Display.prototype.dump = function() { - var arr = []; - for(var y = 0; y < this.height; y++) { - arr[y] = []; - for(var x = 0; x < this.width; x++) { - var c = this.__getTopCanvas__(x, y); - if(typeof c == "undefined") - continue; - var d = this.__getData__(c, x, y); - if(typeof d.ch != "undefined") - arr[y][x] = d; - } - } - return arr; -} - -/* private functions */ -Display.prototype.__updateChar__ = function(x,y/*,data*/) { - //ToDo: maybe store char update so I dont have to retrieve it later? - if(!this.__properties__.update[y]) - this.__properties__.update[y] = {}; - this.__properties__.update[y][x] = 1; /*data; */ -} - -Display.prototype.__getUpdateList__ = function() { - var list = []; - for(var y in this.__properties__.update) { - for(var x in this.__properties__.update[y]) { - var c = this.__getTopCanvas__(x,y); - var d = this.__getData__(c,x,y); - - if(d.px < 1 || d.py < 1 || d.px > console.screen_columns || d.py > console.screen_rows) - continue; - if(!this.__properties__.buffer[x]) { - this.__properties__.buffer[x] = {}; - this.__properties__.buffer[x][y] = d; - list.push(d); - } - else if(this.__properties__.buffer[x][y] == undefined || - this.__properties__.buffer[x][y].ch != d.ch || - this.__properties__.buffer[x][y].attr != d.attr) { - this.__properties__.buffer[x][y] = d; - list.push(d); - } - } - } - this.__properties__.update={}; - return list.sort(function(a,b) { - if(a.y == b.y) - return a.x-b.x; - return a.y-b.y; - }); -} -Display.prototype.__getData__ = function(c,x,y) { - var cd = { - x:Number(x), - y:Number(y), - px:Number(x) + this.__properties__.x, - py:Number(y) + this.__properties__.y - }; - if(c) { - var d = c.frame.getData(x-c.xoff,y-c.yoff,true); - cd.id = c.frame.id; - cd.ch = d.ch; - if(d.attr) - cd.attr = d.attr; - else - cd.attr = c.frame.attr; - } - return cd; -} -Display.prototype.__drawChar__ = function(ch,attr,xpos,ypos) { - if(attr) - console.attributes = attr; - if(xpos == console.screen_columns && ypos == console.screen_rows) - console.cleartoeol(); - else if(ch == undefined) { - console.write(" "); - } - else { - console.write(ch); - } -} -Display.prototype.__getTopCanvas__ = function(x,y) { - var top = undefined; - for each(var c in this.__properties__.canvas) { - if(c.frame.parent == undefined || c.hasData(x,y)) - top = c; - } - return top; -} - -/* character/attribute pair representing a screen position and its contents */ -function Char(ch,attr) { - this.ch = ch; - this.attr = attr; -} - -/* self-validating cursor position object */ -function Cursor(x,y,frame) { - this.__properties__ = { - x:undefined, - y:undefined, - frame:undefined - } - - if(frame instanceof Frame) - this.__properties__.frame = frame; - else - throw("the frame is not a frame"); - - this.x = x; - this.y = y; -} - -Cursor.prototype.__defineGetter__("x", function() { - return this.__properties__.x; -}); -Cursor.prototype.__defineSetter__("x", function(x) { - if(isNaN(x)) - throw("invalid x coordinate: " + x); - this.__properties__.x = x; -}); -Cursor.prototype.__defineGetter__("y", function() { - return this.__properties__.y; -}); -Cursor.prototype.__defineSetter__("y", function(y) { - if(isNaN(y)) - throw("invalid y coordinate: " + y); - this.__properties__.y = y; -}); - - - -/* self-validating scroll offset object */ -function Offset(x,y,frame) { - this.__properties__ = { - x:undefined, - y:undefined, - frame:undefined - } - - if(frame instanceof Frame) - this.__properties__.frame = frame; - else - throw("the frame is not a frame"); - - this.x = x; - this.y = y; -} - -Offset.prototype.__defineGetter__("x", function() { - return this.__properties__.x; -}); -Offset.prototype.__defineSetter__("x", function(x) { - if(x == undefined) - throw("invalid x offset: " + x); - else if(x < 0) - x = 0; - this.__properties__.x = x; -}); -Offset.prototype.__defineGetter__("y", function() { - return this.__properties__.y; -}); -Offset.prototype.__defineSetter__("y", function(y) { - if(y == undefined) - throw("invalid y offset: " + y); - else if(y < 0) - y = 0; - this.__properties__.y = y; -}); + } + } +} +Frame.prototype.move = function(x,y) { + var nx = undefined; + var ny = undefined; + if(this.__checkX__(this.x+x) && this.__checkWidth__(this.x+x,this.width)) + nx = this.x+x; + if(this.__checkY__(this.y+y) && this.__checkHeight__(this.y+y,this.height)) + ny = this.y+y; + if(nx == undefined && ny == undefined) + return; + this.__properties__.display.updateFrame(this); + if(nx !== undefined) + this.x=nx; + if(ny !== undefined) + this.y=ny; + this.__properties__.display.updateFrame(this); + for each(var c in this.__relations__.child) + c.move(x,y); +} +Frame.prototype.moveTo = function(x,y) { + for each(var c in this.__relations__.child) { + var cx = (x + (c.x-this.x)); + var cy = (y + (c.y-this.y)); + c.moveTo(cx,cy); + } + var nx = undefined; + var ny = undefined; + if(this.__checkX__(x)) + nx = x; + if(this.__checkY__(y)) + ny = y; + if(nx == undefined && ny == undefined) + return; + this.__properties__.display.updateFrame(this); + if(nx !== undefined) + this.x=nx; + if(ny !== undefined) + this.y=ny; + this.__properties__.display.updateFrame(this); +} +Frame.prototype.draw = function() { + if(this.__properties__.open) + this.refresh(); + else + this.open(); + this.cycle(); +} +Frame.prototype.cycle = function() { + return this.__properties__.display.cycle(); +} +Frame.prototype.load = function(filename,width,height) { + var f=new File(filename); + if (!f.open("rb", true)) + return false; + var contents=f.read(); + f.close(); + var valid_sauce = false; + var ext = file_getext(filename).substr(1).toUpperCase(); + + if (contents.substr(-128, 7) == "SAUCE00") { + var sauceless_size = ascii(contents.substr(-35,1)); + sauceless_size <<= 8; + sauceless_size |= ascii(contents.substr(-36,1)); + sauceless_size <<= 8; + sauceless_size |= ascii(contents.substr(-37,1)); + sauceless_size <<= 8; + sauceless_size |= ascii(contents.substr(-38,1)); + + var data_type = ascii(contents.substr(-34,1)); + var file_type = ascii(contents.substr(-33,1)); + var tinfo1 = ascii(contents.substr(-31,1)); + tinfo1 <<= 8; + tinfo1 |= ascii(contents.substr(-32,1)); + var tinfo2 = ascii(contents.substr(-29,1)); + tinfo2 <<= 8; + tinfo2 |= ascii(contents.substr(-30,1)); + switch(data_type) { + case 1: + switch(file_type) { + case 0: // PLain ASCII + ext = "TXT"; + if (tinfo1) + width = tinfo1; + if (tinfo2) + height = tinfo2; + break; + case 1: // ANSi + ext = "ANS"; + if (tinfo1) + width = tinfo1; + if (tinfo2) + height = tinfo2; + break; + case 7: // Source + ext = "TXT"; + break; + } + valid_sauce = true; + break; + case 5: + ext = 'BIN'; + width = file_type * 2; + height = (sauceless_size / 2) / width; + valid_sauce = true; + break; + } + if (valid_sauce) + contents = contents.substr(0, sauceless_size); + } + + switch(ext) { + case "ANS": + /* + * TODO: This doesn't do exactly what reading a text file does + * one Windows (nor on Linux), but it should be close to what + * was meant, and should work given that it resets x every line. + */ + var lines=contents.split(/\r\n/); + + var attr = this.attr; + var bg = BG_BLACK; + var fg = LIGHTGRAY; + + var i = 0; + var y = 0; + var saved = {}; + + while(lines.length > 0) { + var x = 0; + var line = lines.shift(); + /* parse 'ATCODES'?? + line = line.replace(/@(.*)@/g, + function (str, code, offset, s) { + return bbs.atcode(code); + } + ); + */ + while(line.length > 0) { + /* parse an attribute sequence*/ + var m = line.match(/^\x1b\[(\d*);?(\d*);?(\d*)m/); + if(m !== null) { + line = line.substr(m.shift().length); + if(m[0] == 0) { + bg = BG_BLACK; + fg = LIGHTGRAY; + i = 0; + m.shift(); + } + if(m[0] == 1) { + i = HIGH; + m.shift(); + } + if(m[0] >= 40) { + switch(Number(m.shift())) { + case 40: + bg = BG_BLACK; + break; + case 41: + bg = BG_RED; + break; + case 42: + bg = BG_GREEN; + break; + case 43: + bg = BG_BROWN; + break; + case 44: + bg = BG_BLUE; + break; + case 45: + bg = BG_MAGENTA; + break; + case 46: + bg = BG_CYAN; + break; + case 47: + bg = BG_LIGHTGRAY; + break; + } + } + if(m[0] >= 30) { + switch(Number(m.shift())) { + case 30: + fg = BLACK; + break; + case 31: + fg = RED; + break; + case 32: + fg = GREEN; + break; + case 33: + fg = BROWN; + break; + case 34: + fg = BLUE; + break; + case 35: + fg = MAGENTA; + break; + case 36: + fg = CYAN; + break; + case 37: + fg = LIGHTGRAY; + break; + } + } + attr = bg + fg + i; + continue; + } + + /* parse absolute character position */ + var m = line.match(/^\x1b\[(\d*);?(\d*)[Hf]/); + if(m !== null) { + line = line.substr(m.shift().length); + + if(m.length==0) { + x=0; + y=0; + } + else { + if(m[0]) + y = Number(m.shift())-1; + if(m[0]) + x = Number(m.shift())-1; + } + continue; + } + + /* ignore a bullshit sequence */ + var n = line.match(/^\x1b\[\?7h/); + if(n !== null) { + line = line.substr(n.shift().length); + continue; + } + + /* parse an up positional sequence */ + var n = line.match(/^\x1b\[(\d*)A/); + if(n !== null) { + line = line.substr(n.shift().length); + var chars = n.shift(); + if(chars < 1) + y-=1; + else + y-=Number(chars); + continue; + } + + /* parse a down positional sequence */ + var n = line.match(/^\x1b\[(\d*)B/); + if(n !== null) { + line = line.substr(n.shift().length); + var chars = n.shift(); + if(chars < 1) + y+=1; + else + y+=Number(chars); + continue; + } + + /* parse a forward positional sequence */ + var n = line.match(/^\x1b\[(\d*)C/); + if(n !== null) { + line = line.substr(n.shift().length); + var chars = n.shift(); + if(chars < 1) + x+=1; + else + x+=Number(chars); + continue; + } + + /* parse a backward positional sequence */ + var n = line.match(/^\x1b\[(\d*)D/); + if(n !== null) { + line = line.substr(n.shift().length); + var chars = n.shift() + if(chars < 1) + x-=1; + else + x-=Number(chars); + continue; + } + + /* parse a clear screen sequence */ + var n = line.match(/^\x1b\[2J/); + if(n !== null) { + line = line.substr(n.shift().length); + continue; + } + + /* parse save cursor sequence */ + var n = line.match(/^\x1b\[s/); + if(n !== null) { + line = line.substr(n.shift().length); + saved.x = x; + saved.y = y; + continue; + } + + /* parse restore cursor sequence */ + var n = line.match(/^\x1b\[u/); + if(n !== null) { + line = line.substr(n.shift().length); + x = saved.x; + y = saved.y; + continue; + } + + /* set character and attribute */ + var ch = line[0]; + line = line.substr(1); + + /* validate position */ + if(y<0) + y=0; + if(x<0) + x=0; + if(x>=this.width) { + x=0; + y++; + } + /* set character and attribute */ + if(!this.__properties__.data[y]) + this.__properties__.data[y]=[]; + this.__properties__.data[y][x]=new Char(ch,attr); + x++; + } + y++; + } + break; + case "BIN": + if (!this.load_bin(contents, width, height, 0)) return false; + break; + case "TXT": + var lines=contents.split(/\r\n/); + while(lines.length > 0) + this.putmsg(lines.shift() + "\r\n"); + break; + default: + throw("unsupported filetype"); + break; + } +} +Frame.prototype.load_bin = function(contents, width, height, offset) { + if(width == undefined || height == undefined) + throw("unknown graphic dimensions"); + if(offset == undefined) offset = 0; + for(var y=0; y<height; y++) { + for(var x=0; x<width; x++) { + var c = new Char(); + if(offset >= contents.length) + return(false); + c.ch = contents.substr(offset++, 1); + if(offset == contents.length) + return(false); + c.attr = ascii(contents.substr(offset++, 1)); + c.id = this.id; + if(!this.__properties__.data[y]) + this.__properties__.data[y]=[]; + this.__properties__.data[y][x] = c; + } + } + return true; +} +Frame.prototype.scroll = function(x,y) { + var update = false; + /* default: add a new line to the data matrix */ + if(x == undefined && y == undefined) { + if(this.__settings__.v_scroll) { + var newrow = []; + for(var x = 0;x<this.width;x++) { + for(var y = 0;y<this.height;y++) + this.__properties__.display.updateChar(this,x,y); + newrow.push(new Char()); + } + this.__properties__.data.push(newrow); + this.__position__.offset.y++; + update = true; + } + } + /* otherwise, adjust the x/y offset */ + else { + if(typeof x == "number" && x !== 0 && this.__settings__.h_scroll) { + this.__position__.offset.x += x; + update = true; + } + if(typeof y == "number" && y !== 0 && this.__settings__.v_scroll) { + this.__position__.offset.y += y; + update = true; + } + if(update) + this.refresh(); + } + return update; +} +Frame.prototype.scrollTo = function(x,y) { + var update = false; + if(typeof x == "number") { + if(this.__settings__.h_scroll) { + this.__position__.offset.x = x; + update = true; + } + } + if(typeof y == "number") { + if(this.__settings__.v_scroll) { + this.__position__.offset.y = y; + update = true; + } + } + if(update) + this.refresh(); +} +Frame.prototype.insertLine = function(y) { + if(this.__properties__.data[y]) + this.__properties__.data.splice(y,0,[]); + else + this.__properties__.data[y] = []; + this.refresh(); + return this.__properties__.data[y]; +} +Frame.prototype.deleteLine = function(y) { + var l = undefined; + if(this.__properties__.data[y]) { + l = this.__properties__.data.splice(y,1); + this.refresh(); + } + return l; +} +Frame.prototype.screenShot = function(file,append) { + return this.__properties__.display.screenShot(file,append); +} +Frame.prototype.invalidate = function() { + this.__properties__.display.invalidate(); + this.refresh(); +} +Frame.prototype.dump = function() { + return this.__properties__.display.dump(); +} + +/* console method emulation */ +Frame.prototype.home = function() { + this.__position__.cursor.x = 0; + this.__position__.cursor.y = 0; + return true; +} +Frame.prototype.end = function() { + this.__position__.cursor.x = this.width-1; + this.__position__.cursor.y = this.height-1; + return true; +} +Frame.prototype.pagedown = function() { + this.__position__.offset.y += this.height-1; + if(this.__position__.offset.y >= this.data_height) + this.__position__.offset.y = this.data_height - this.height; + this.refresh(); +} +Frame.prototype.pageup = function() { + this.__position__.offset.y -= this.height-1; + if(this.__position__.offset.y < 0) + this.__position__.offset.y = 0; + this.refresh(); +} +Frame.prototype.clear = function (attr) { + if (attr) this.attr = attr; + this.__properties__.data = []; + this.__position__.offset.x = 0; + this.__position__.offset.y = 0; + this.home(); + this.invalidate(); +} +Frame.prototype.erase = function(ch, attr) { + if(attr == undefined) + attr = this.attr; + var px = this.__position__.offset.x; + var py = this.__position__.offset.y; + for(var y = 0; y< this.height; y++) { + if(!this.__properties__.data[py + y]) { + continue; + } + for(var x = 0; x<this.width; x++) { + if(!this.__properties__.data[py + y][px + x]) { + continue; + } + if((this.__properties__.data[py + y][px + x].ch === undefined || + this.__properties__.data[py + y][px + x].ch === ch) && + this.__properties__.data[py + y][px + x].attr == attr) { + continue; + } + this.__properties__.data[py + y][px + x].ch = undefined; + this.__properties__.data[py + y][px + x].attr = attr; + this.__properties__.display.updateChar(this, x, y); + } + } + this.home(); +} +Frame.prototype.clearline = function(attr) { + if(attr == undefined) + attr = this.attr; + var py = this.__position__.offset.y + this.__position__.cursor.y; + if(!this.__properties__.data[py]) + return false; + for(var x=0;x<this.__properties__.data[py].length;x++) { + if(this.__properties__.data[py][x]) { + this.__properties__.data[py][x].ch = undefined; + this.__properties__.data[py][x].attr = attr; + } + } + for(var x=0;x<this.width;x++) { + this.__properties__.display.updateChar(this,x,this.__position__.cursor.y); + } +} +Frame.prototype.cleartoeol = function(attr) { + if(attr == undefined) + attr = this.attr; + var px = this.__position__.offset.x + this.__position__.cursor.x; + var py = this.__position__.offset.y + this.__position__.cursor.y; + if(!this.__properties__.data[this.__position__.cursor.y]) + return false; + for(var x=px;x<this.__properties__.data[py].length;x++) { + if(this.__properties__.data[py][x]) { + if(this.__properties__.data[py][x].ch !== undefined) { + this.__properties__.data[py][x].ch = undefined; + this.__properties__.display.updateChar(this,x - this.__position__.offset.x, this.__position__.cursor.y); + } + this.__properties__.data[py][x].attr = attr; + } + } +} +Frame.prototype.crlf = function() { + this.__position__.cursor.x = 0; + if(this.__position__.cursor.y < this.height-1) + this.__position__.cursor.y += 1; + else {} +} +Frame.prototype.write = function(str,attr) { + if(str == undefined) + return; + if(this.__settings__.word_wrap) + str = word_wrap(str, this.width,str.length, false); + str = str.toString().split(''); + + if(attr) + this.__properties__.curr_attr = attr; + else + this.__properties__.curr_attr = this.attr; + var pos = this.__position__.cursor; + while(str.length > 0) { + var ch = str.shift(); + this.__putChar__.call(this,ch,this.__properties__.curr_attr); + pos.x++; + } +} +Frame.prototype.putmsg = function(str,attr) { + if(str == undefined) + return; + if(this.__settings__.word_wrap) { + var remainingWidth = this.width - this.__position__.cursor.x; + if(str.length > remainingWidth) { + str = word_wrap(str,remainingWidth,str.length,false).split('\n'); + var trailing_newline = str[str.length - 1].length == 0; + str = str.shift() + '\r\n' + word_wrap(str.join('\r\n'),this.width,remainingWidth,false); + if(!trailing_newline) str = str.trimRight(); + } + } + str = str.toString().split(''); + + if(attr) + this.__properties__.curr_attr = attr; + else + this.__properties__.curr_attr = this.attr; + var pos = this.__position__.cursor; + + while(str.length > 0) { + var ch = str.shift(); + if(this.__properties__.ctrl_a) { + var k = ch; + if(k) + k = k.toUpperCase(); + switch(k) { + case '\1': /* A "real" ^A code */ + this.__putChar__.call(this,ch,this.__properties__.curr_attr); + pos.x++; + break; + case 'K': /* Black */ + this.__properties__.curr_attr=(this.__properties__.curr_attr)&0xf8; + break; + case 'R': /* Red */ + this.__properties__.curr_attr=((this.__properties__.curr_attr)&0xf8)|RED; + break; + case 'G': /* Green */ + this.__properties__.curr_attr=((this.__properties__.curr_attr)&0xf8)|GREEN; + break; + case 'Y': /* Yellow */ + this.__properties__.curr_attr=((this.__properties__.curr_attr)&0xf8)|BROWN; + break; + case 'B': /* Blue */ + this.__properties__.curr_attr=((this.__properties__.curr_attr)&0xf8)|BLUE; + break; + case 'M': /* Magenta */ + this.__properties__.curr_attr=((this.__properties__.curr_attr)&0xf8)|MAGENTA; + break; + case 'C': /* Cyan */ + this.__properties__.curr_attr=((this.__properties__.curr_attr)&0xf8)|CYAN; + break; + case 'W': /* White */ + this.__properties__.curr_attr=((this.__properties__.curr_attr)&0xf8)|LIGHTGRAY; + break; + case '0': /* Black */ + this.__properties__.curr_attr=(this.__properties__.curr_attr)&0x8f; + break; + case '1': /* Red */ + this.__properties__.curr_attr=((this.__properties__.curr_attr)&0x8f)|(RED<<4); + break; + case '2': /* Green */ + this.__properties__.curr_attr=((this.__properties__.curr_attr)&0x8f)|(GREEN<<4); + break; + case '3': /* Yellow */ + this.__properties__.curr_attr=((this.__properties__.curr_attr)&0x8f)|(BROWN<<4); + break; + case '4': /* Blue */ + this.__properties__.curr_attr=((this.__properties__.curr_attr)&0x8f)|(BLUE<<4); + break; + case '5': /* Magenta */ + this.__properties__.curr_attr=((this.__properties__.curr_attr)&0x8f)|(MAGENTA<<4); + break; + case '6': /* Cyan */ + this.__properties__.curr_attr=((this.__properties__.curr_attr)&0x8f)|(CYAN<<4); + break; + case '7': /* White */ + this.__properties__.curr_attr=((this.__properties__.curr_attr)&0x8f)|(LIGHTGRAY<<4); + break; + case 'H': /* High Intensity */ + this.__properties__.curr_attr|=HIGH; + break; + case 'I': /* Blink */ + this.__properties__.curr_attr|=BLINK; + break; + case 'N': /* Normal */ + this.__properties__.curr_attr=(this.attr); + break; + case '+': + this.__properties__.attr_stack.push(this.__properties__.curr_attr); + break; + case '-': /* Normal if High, Blink, or BG */ + if (this.__properties__.attr_stack.length) { + this.__properties__.curr_attr = this.__properties__.attr_stack.pop(); + } else if(this.__properties__.curr_attr & 0xf8) { + this.__properties__.curr_attr=this.attr; + } + break; + case '_': /* Normal if blink/background */ + if(this.__properties__.curr_attr & 0xf0) + this.__properties__.curr_attr=this.attr; + break; + case '[': /* CR */ + pos.x=0; + break; + case ']': /* LF */ + pos.y++; + if(this.__settings__.lf_strict && pos.y >= this.height) { + this.scroll(); + pos.y--; + } + break; + default: /* Other stuff... specifically, check for right movement */ + if(ch.charCodeAt(0)>127) + pos.x+=ch.charCodeAt(0)-127; + break; + } + this.__properties__.ctrl_a = false; + } + else { + switch(ch) { + case '\1': /* CTRL-A code */ + case ctrl('A'): + this.__properties__.ctrl_a = true; + break; + case '\7': /* Beep */ + break; + case '\x7f': /* DELETE */ + break; + case '\b': + if(pos.x > 0) { + pos.x--; + this.__putChar__.call(this," ",this.__properties__.curr_attr); + } + break; + case '\r': + pos.x=0; + break; + case '\t': + pos.x += 4; + break; + case '\n': + pos.y++; + if(this.__settings__.lf_strict && pos.y >= this.height) { + this.scroll(); + pos.y--; + } + break; + default: + this.__putChar__.call(this,ch,this.__properties__.curr_attr); + pos.x++; + break; + } + } + } +} +Frame.prototype.center = function(str,attr) { + this.__position__.cursor.x = Math.ceil(this.width/2) - Math.ceil(console.strlen(strip_ctrl(str))/2); + if(this.__position__.cursor.x < 0) + this.__position__.cursor.x = 0; + this.putmsg(str,attr); +} +Frame.prototype.gotoxy = function(x,y) { + if(typeof x == "object" && x.x && x.y) { + if(this.__validateCursor__.call(this,x.x-1,x.y-1)) { + this.__position__.cursor.x = x.x-1; + this.__position__.cursor.y = x.y-1; + return true; + } + return false; + } + if(this.__validateCursor__.call(this,x-1,y-1)) { + this.__position__.cursor.x = x-1; + this.__position__.cursor.y = y-1; + return true; + } + return false; +} +Frame.prototype.getxy = function() { + return { + x:this.__position__.cursor.x+1, + y:this.__position__.cursor.y+1 + }; +} +Frame.prototype.pushxy = function() { + this.__position__.stored.x = this.__position__.cursor.x; + this.__position__.stored.y = this.__position__.cursor.y; +} +Frame.prototype.popxy = function() { + this.__position__.cursor.x = this.__position__.stored.x; + this.__position__.cursor.y = this.__position__.stored.y; +} +Frame.prototype.up = function(n) { + if(this.__position__.cursor.y == 0 && this.__position__.offset.y == 0) + return false; + if(isNaN(n)) + n = 1; + while(n > 0) { + if(this.__position__.cursor.y > 0) { + this.__position__.cursor.y--; + n--; + } + else break; + } + if(n > 0) + this.scroll(0,-(n)); + return true; +} +Frame.prototype.down = function(n) { + if(this.__position__.cursor.y == this.height-1 && this.__position__.offset.y == this.data_height - this.height) + return false; + if(isNaN(n)) + n = 1; + while(n > 0) { + if(this.__position__.cursor.y < this.height - 1) { + this.__position__.cursor.y++; + n--; + } + else break; + } + if(n > 0) + this.scroll(0,n); + return true; +} +Frame.prototype.left = function(n) { + if(this.__position__.cursor.x == 0 && this.__position__.offset.x == 0) + return false; + if(isNaN(n)) + n = 1; + while(n > 0) { + if(this.__position__.cursor.x > 0) { + this.__position__.cursor.x--; + n--; + } + else break; + } + if(n > 0) + this.scroll(-(n),0); + return true; +} +Frame.prototype.right = function(n) { + if(this.__position__.cursor.x == this.width-1 && this.__position__.offset.x == this.data_width - this.width) + return false; + if(isNaN(n)) + n = 1; + while(n > 0) { + if(this.__position__.cursor.x < this.width - 1) { + this.__position__.cursor.x++; + n--; + } + else break; + } + if(n > 0) + this.scroll(n,0); + return true; +} + +/* internal frame methods */ +Frame.prototype.__checkX__ = function(x) { + if( isNaN(x) || (this.__settings__.checkbounds && + (x > this.__properties__.display.x + this.__properties__.display.width || + x < this.__properties__.display.x))) + return false; + return true; +} +Frame.prototype.__checkY__ = function(y) { + if( isNaN(y) || (this.__settings__.checkbounds && + (y > this.__properties__.display.y + this.__properties__.display.height || + y < this.__properties__.display.y))) + return false; + return true; +} +Frame.prototype.__checkWidth__ = function(x,width) { + if( width < 1 || isNaN(width) || (this.__settings__.checkbounds && + x + width > this.__properties__.display.x + this.__properties__.display.width)) + return false; + return true; +} +Frame.prototype.__checkHeight__ = function(y,height) { + if( height < 1 || isNaN(height) || (this.__settings__.checkbounds && + y + height > this.__properties__.display.y + this.__properties__.display.height)) + return false; + return true; +} +Frame.prototype.__validateCursor__ = function(x,y) { + if(x >= this.width) + return false; + if(y >= this.height) + return false; + return true; +} +Frame.prototype.__putChar__ = function(ch,attr) { + if(this.__position__.cursor.x >= this.width) { + this.__position__.cursor.x=0; + this.__position__.cursor.y++; + } + if(this.__position__.cursor.y >= this.height) { + this.scroll(); + this.__position__.cursor.y--; + } + this.setData(this.__position__.cursor.x,this.__position__.cursor.y,ch,attr,true); +} + +Frame.prototype.__flip__ = function (swaps, flipx) { + for (var y = 0; y < this.data_height; y++) { + if (!Array.isArray(this.data[y])) continue; + if (flipx) this.data[y] = this.data[y].reverse(); + for (var x = 0; x < this.data[y].length; x++) { + for (var s = 0; s < swaps.length; s++) { + var i = swaps[s].indexOf(ascii(this.data[y][x].ch)); + if (i >= 0) { + this.data[y][x].ch = ascii(swaps[s][(i + 1) % 2]); + break; + } + } + } + } + if (!flipx) this.data.reverse(); +} + +Frame.prototype.flipX = function () { + const swaps = [ + [ 40, 41 ], + [ 47, 92 ], + [ 60, 62 ], + [ 91, 93 ], + [ 123, 125 ], + [ 169, 170 ], + [ 174, 175 ], + [ 180, 195 ], + [ 181, 198 ], + [ 182, 199 ], + [ 183, 214 ], + [ 184, 213 ], + [ 185, 204 ], + [ 187, 201 ], + [ 188, 200 ], + [ 189, 211 ], + [ 190, 212 ], + [ 191, 218 ], + [ 192, 217 ], + [ 221, 222 ] + ]; + this.__flip__(swaps, true); +} + +Frame.prototype.flipY = function () { + const swaps = [ + [ 47, 92 ], + [ 183, 189 ], + [ 184, 190 ], + [ 187, 188 ], + [ 191, 217 ], + [ 192, 218 ], + [ 193, 194 ], + [ 200, 201 ], + [ 202, 203 ], + [ 207, 209 ], + [ 208, 210 ], + [ 211, 214 ], + [ 212, 213 ], + [ 220, 223 ] + ]; + this.__flip__(swaps, false); +} + +/* frame reference object */ +function Canvas(frame,display) { + this.frame = frame; + this.display = display; + this.__defineGetter__("xoff",function() { + return this.frame.x - this.display.x; + }); + this.__defineGetter__("yoff",function() { + return this.frame.y - this.display.y; + }); +} + +Canvas.prototype.hasData = function(x,y) { + var xpos = x-this.xoff; + var ypos = y-this.yoff; + + if(xpos < 0 || ypos < 0) + return undefined; + if(xpos >= this.frame.width || ypos >= this.frame.height) + return undefined; + if(this.frame.transparent && this.frame.getData(xpos,ypos,true).ch == undefined) + return undefined; + return true; +} + +/* object representing screen positional and dimensional limits and canvas stack */ +function Display(x,y,width,height) { + /* private properties */ + this.__properties__ = { + x:undefined, + y:undefined, + width:undefined, + height:undefined, + canvas:{}, + update:{}, + buffer:{}, + count:0 + } + + /* initialize display properties */ + this.x = x; + this.y = y; + this.width = width; + this.height = height; + log(LOG_DEBUG,format("new display initialized: %sx%s at %s,%s",this.width,this.height,this.x,this.y)); +} + +/* protected display properties */ +Display.prototype.__defineGetter__("x", function() { + return this.__properties__.x; +}); +Display.prototype.__defineSetter__("x", function(x) { + if(x == undefined) + this.__properties__.x = 1; + else if(isNaN(x)) + throw("invalid x coordinate: " + x); + else + this.__properties__.x = Number(x); +}); +Display.prototype.__defineGetter__("y", function() { + return this.__properties__.y; +}); +Display.prototype.__defineSetter__("y", function(y) { + if(y == undefined) + this.__properties__.y = 1; + else if(isNaN(y) || y < 1 || y > console.screen_rows) + throw("invalid y coordinate: " + y); + else + this.__properties__.y = Number(y); +}); +Display.prototype.__defineGetter__("width", function() { + return this.__properties__.width; +}); +Display.prototype.__defineSetter__("width", function(width) { + if(width == undefined) + this.__properties__.width = console.screen_columns; + else if(isNaN(width) || (this.x + Number(width) - 1) > (console.screen_columns)) + throw("invalid width: " + width); + else + this.__properties__.width = Number(width); +}); +Display.prototype.__defineGetter__("height", function() { + return this.__properties__.height; +}); +Display.prototype.__defineSetter__("height", function(height) { + if(height == undefined) + this.__properties__.height = console.screen_rows; + else if(isNaN(height) || (this.y + Number(height) - 1) > (console.screen_rows)) + throw("invalid height: " + height); + else + this.__properties__.height = Number(height); +}); +Display.prototype.__defineGetter__("nextID",function() { + return ++this.__properties__.count; +}); + +/* public display methods */ +Display.prototype.cycle = function() { + var updates = this.__getUpdateList__(); + if(updates.length > 0) { + var lasty = undefined; + var lastx = undefined; + var lastf = undefined; + for each(var u in updates) { + if(lasty !== u.y || lastx == undefined || (u.x - lastx) !== 1) + console.gotoxy(u.px,u.py); + if(lastf !== u.id) + console.attributes = undefined; + this.__drawChar__(u.ch,u.attr,u.px,u.py); + lastx = u.x; + lasty = u.y; + lastf = u.id; + } + //console.attributes=undefined; + return true; + } + return false; +} +Display.prototype.draw = function() { + for(var y = 0;y<this.height;y++) { + for(var x = 0;x<this.width;x++) { + this.__updateChar__(x,y); + } + } + this.cycle(); +} +Display.prototype.open = function(frame) { + var canvas = new Canvas(frame,this); + this.__properties__.canvas[frame.id] = canvas; + this.updateFrame(frame); +} +Display.prototype.close = function(frame) { + this.updateFrame(frame); + delete this.__properties__.canvas[frame.id]; +} +Display.prototype.top = function(frame) { + var canvas = this.__properties__.canvas[frame.id]; + delete this.__properties__.canvas[frame.id]; + this.__properties__.canvas[frame.id] = canvas; + this.updateFrame(frame); +} +Display.prototype.bottom = function(frame) { + for(var c in this.__properties__.canvas) { + if(c == frame.id) + continue; + var canvas = this.__properties__.canvas[c]; + delete this.__properties__.canvas[c]; + this.__properties__.canvas[c] = canvas; + } + this.updateFrame(frame); +} +Display.prototype.updateFrame = function(frame) { + for(var y = 0;y<frame.height;y++) { + for(var x = 0;x<frame.width;x++) { + this.updateChar(frame,x,y/*,data*/); + } + } +} +Display.prototype.updateChar = function(frame,x,y) { + var xoff = frame.x - this.x; + var yoff = frame.y - this.y; + this.__updateChar__(xoff + x,yoff + y); +} +Display.prototype.screenShot = function(file,append) { + var f = new File(file); + if(append) + f.open('ab',true,4096); + else + f.open('wb',true,4096) ; + if(!f.is_open) + return false; + + for(var y = 0;y<this.height;y++) { + for(var x = 0;x<this.width;x++) { + var c = this.__getTopCanvas__(x,y); + var d = this.__getData__(c,x,y); + if(d.ch) + f.write(d.ch); + else + f.write(" "); + if(d.attr) + f.writeBin(d.attr,1); + else + f.writeBin(0,1); + } + } + + f.close(); + return true; +} +Display.prototype.invalidate = function() { + this.__properties__.buffer = {}; +} +Display.prototype.dump = function() { + var arr = []; + for(var y = 0; y < this.height; y++) { + arr[y] = []; + for(var x = 0; x < this.width; x++) { + var c = this.__getTopCanvas__(x, y); + if(typeof c == "undefined") + continue; + var d = this.__getData__(c, x, y); + if(typeof d.ch != "undefined") + arr[y][x] = d; + } + } + return arr; +} + +/* private functions */ +Display.prototype.__updateChar__ = function(x,y/*,data*/) { + //ToDo: maybe store char update so I dont have to retrieve it later? + if(!this.__properties__.update[y]) + this.__properties__.update[y] = {}; + this.__properties__.update[y][x] = 1; /*data; */ +} + +Display.prototype.__getUpdateList__ = function() { + var list = []; + for(var y in this.__properties__.update) { + for(var x in this.__properties__.update[y]) { + var c = this.__getTopCanvas__(x,y); + var d = this.__getData__(c,x,y); + + if(d.px < 1 || d.py < 1 || d.px > console.screen_columns || d.py > console.screen_rows) + continue; + if(!this.__properties__.buffer[x]) { + this.__properties__.buffer[x] = {}; + this.__properties__.buffer[x][y] = d; + list.push(d); + } + else if(this.__properties__.buffer[x][y] == undefined || + this.__properties__.buffer[x][y].ch != d.ch || + this.__properties__.buffer[x][y].attr != d.attr) { + this.__properties__.buffer[x][y] = d; + list.push(d); + } + } + } + this.__properties__.update={}; + return list.sort(function(a,b) { + if(a.y == b.y) + return a.x-b.x; + return a.y-b.y; + }); +} +Display.prototype.__getData__ = function(c,x,y) { + var cd = { + x:Number(x), + y:Number(y), + px:Number(x) + this.__properties__.x, + py:Number(y) + this.__properties__.y + }; + if(c) { + var d = c.frame.getData(x-c.xoff,y-c.yoff,true); + cd.id = c.frame.id; + cd.ch = d.ch; + if(d.attr) + cd.attr = d.attr; + else + cd.attr = c.frame.attr; + } + return cd; +} +Display.prototype.__drawChar__ = function(ch,attr,xpos,ypos) { + if(attr) + console.attributes = attr; + if(xpos == console.screen_columns && ypos == console.screen_rows) + console.cleartoeol(); + else if(ch == undefined) { + console.write(" "); + } + else { + console.write(ch); + } +} +Display.prototype.__getTopCanvas__ = function(x,y) { + var top = undefined; + for each(var c in this.__properties__.canvas) { + if(c.frame.parent == undefined || c.hasData(x,y)) + top = c; + } + return top; +} + +/* character/attribute pair representing a screen position and its contents */ +function Char(ch,attr) { + this.ch = ch; + this.attr = attr; +} + +/* self-validating cursor position object */ +function Cursor(x,y,frame) { + this.__properties__ = { + x:undefined, + y:undefined, + frame:undefined + } + + if(frame instanceof Frame) + this.__properties__.frame = frame; + else + throw("the frame is not a frame"); + + this.x = x; + this.y = y; +} + +Cursor.prototype.__defineGetter__("x", function() { + return this.__properties__.x; +}); +Cursor.prototype.__defineSetter__("x", function(x) { + if(isNaN(x)) + throw("invalid x coordinate: " + x); + this.__properties__.x = x; +}); +Cursor.prototype.__defineGetter__("y", function() { + return this.__properties__.y; +}); +Cursor.prototype.__defineSetter__("y", function(y) { + if(isNaN(y)) + throw("invalid y coordinate: " + y); + this.__properties__.y = y; +}); + + + +/* self-validating scroll offset object */ +function Offset(x,y,frame) { + this.__properties__ = { + x:undefined, + y:undefined, + frame:undefined + } + + if(frame instanceof Frame) + this.__properties__.frame = frame; + else + throw("the frame is not a frame"); + + this.x = x; + this.y = y; +} + +Offset.prototype.__defineGetter__("x", function() { + return this.__properties__.x; +}); +Offset.prototype.__defineSetter__("x", function(x) { + if(x == undefined) + throw("invalid x offset: " + x); + else if(x < 0) + x = 0; + this.__properties__.x = x; +}); +Offset.prototype.__defineGetter__("y", function() { + return this.__properties__.y; +}); +Offset.prototype.__defineSetter__("y", function(y) { + if(y == undefined) + throw("invalid y offset: " + y); + else if(y < 0) + y = 0; + this.__properties__.y = y; +});