diff --git a/exec/dorkit/screen.js b/exec/dorkit/screen.js new file mode 100644 index 0000000000000000000000000000000000000000..5d65da7a9f911f18484653a8d2397e2609cb5cd6 --- /dev/null +++ b/exec/dorkit/screen.js @@ -0,0 +1,369 @@ +/* + * Screen object... + * This object is the estimate of what a screen looks like modeled + * as a Graphic object (Screen.graphic). + * + * The Graphic object should not be written directly, but instead, + * all data should be provided using the print() method. + */ + +if (js.global.Graphic === undefined) + load("graphic.js"); +if (js.global.Attribute === undefined) + load("attribute.js"); + +function Screen(w, h, attr, fill) +{ + this.graphic = new Graphic(w, h, attr, fill); + this.escbuf = ''; + this.pos = {x:0, y:0}; + this.stored_pos = {x:0, y:0}; + this.attr = new Attribute(7); +} + +Screen.prototype.print=function(str) { + var m; + var ext; + var pb; + var ib; + var fb; + var p; + var chars; + var remain; + var i; + var tg; + var seq; + + function writech(scr, ch) { + var i; + var gr; + + function check_scrollup(scr) { + while (scr.pos.y >= scr.graphic.height) { + // Scroll up... + gr = scr.graphic.get(0,1,scr.graphic.width-1, scr.graphic.height-2); + scr.graphic.put(gr,0,0); + for (i=0; i<scr.graphic.width; i++) { + scr.graphic.data[i][scr.graphic.height-1].ch = scr.graphic.ch; + scr.graphic.data[i][scr.graphic.height-1].attr = scr.attr.value; + } + scr.pos.y--; + } + } + + // Handle special chars. + switch(ch) { + case '\x00': // NUL is not displayed. + break; + case '\x07': // Beep is not displayed. + break; + case '\x08': // Backspace. + scr.pos.x--; + if (scr.pos.x < 0) + scr.pos.x = 0; + break; + case '\x09': // Tab + do { + scr.pos.x++; + } while(scr.pos.x % 8); + if (scr.pos.x >= scr.graphic.width) + scr.pos.x = scr.graphic.width-1; + break; + case '\x0a': // Linefeed + scr.pos.y++; + check_scrollup(scr); + break; + case '\x0c': // For feed (clear screen and home) + scr.graphic.clear(); + scr.pos.x=0; + scr.pos.y=0; + break; + case '\x0d': // Carriage return + scr.pos.x = 0; + break; + default: + scr.graphic.data[scr.pos.x][scr.pos.y].ch = ch; + scr.graphic.data[scr.pos.x][scr.pos.y].attr = new Attribute(scr.attr); + scr.pos.x++; + if (scr.pos.x >= scr.graphic.width) { + scr.pos.x = 0; + scr.pos.y++; + check_scrollup(scr); + } + break; + } + } + + function param_defaults(params, defaults) { + var i; + + for (i=0; i<defaults.length; i++) { + if (params[i] == undefined || params[i].length == 0) + params[i]=defaults[i]; + } + for (i=0; i<params.length; i++) { + if (params[i]===undefined || params[i]==='') + params[i] = 0; + else + params[i] = parseInt(params[i], 10); + } + } + + // Prepend the ESC buffer to avoid failing on split ANSI + str = this.escbuf + str; + this.escbuf = ''; + + while((m=str.match(/^(.*?)\x1b\[([<-\?]{0,1})([0-;]*)([ -\/]*)([@-~])([\x00-\xff]*)$/)) !== null) { + chars = m[1]; + ext = m[2]; + pb = m[3]; + ib = m[4]; + fb = m[5]; + remain = m[6]; + seq = ext + ib + fb; + var x; + var y; + + str = remain; + + // Send regular chars before the sequence... + for (i=0; i<chars.length; i++) + writech(this, chars[i]); + + // We don't support any : paramters... strip and ignore. + p = pb.replace(/:[^;:]*/g, ''); + p = p.split(';'); + + switch(fb) { + case '@': // Insert character + param_defaults(p, [1]); + if (p[1] > this.graphic.width - this.pos.x) + p[1] = this.graphic.width - this.pos.x; + if (this.pos.x < this.graphic.width-1) { + tg = this.graphic.get(this.pos.x, this.pos.y, this.graphic.width-1 - p[1], this.pos.y); + tg = this.graphic.put(this.pos.x + p[1], this.pos.y, this.graphic.width - 1, this.pos.y); + } + for (x = 0; x<p[1]; x++) { + this.graphic.data[this.pos.x + x][this.pos.y].ch = this.graphic.ch; + this.graphic.data[this.pos.x + x][this.pos.y].attr = this.attr.value; + } + break; + case 'A': // Cursor Up + param_defaults(p, [1]); + this.pos.y -= p[0]; + if (this.pos.y < 0) + this.pos.y = 0; + break; + case 'B': // Cursor Down + param_defaults(p, [1]); + this.pos.y += p[0]; + if (this.pos.y >= this.graphic.height) + this.pos.y = this.graphic.height-1; + break; + case 'C': // Cursor Right + param_defaults(p, [1]); + this.pos.x += p[0]; + if (this.pos.x >= this.graphic.width) + this.pos.x = this.graphic.width-1; + break; + case 'D': // Cursor Left + param_defaults(p, [1]); + this.pos.x -= p[0]; + if (this.pos.x < 0) + this.pos.x = 0; + break; + case 'H': // Cursor position + case 'f': + param_defaults(p, [1,1]); + if (p[0] >= 0 && p[0] < this.graphic.height && p[1] >= 0 && p[1] <= this.graphic.width) { + this.pos.x = p[1]; + this.pos.y = p[0]; + } + break; + case 'J': // Erase in screen + param_defaults(p, [0]); + switch(p[0]) { + case 0: // Erase to end of screen... + for (x = this.pos.x; x<this.pos.width; x++) { + this.graphic.data[x][this.pos.y].ch = this.graphic.ch; + this.graphic.data[x][this.pos.y].attr = this.attr.value; + } + for (y = this.pos.y+1; y<this.pos.height; y++) { + for (x = 0; x<this.graphic.width; x++) { + this.graphic.data[x][y].ch = this.graphic.ch; + this.graphic.data[x][y].attr = this.attr.value; + } + } + break; + case 1: // Erase to beginning of screen... + for (y = 0; y < this.pos.y; y++) { + for (x = 0; x<this.graphic.width; x++) { + this.graphic.data[x][y].ch = this.graphic.ch; + this.graphic.data[x][y].attr = this.attr.value; + } + } + for (x = 0; x<=this.pos.x; x++) { + this.graphic.data[x][this.pos.y].ch = this.graphic.ch; + this.graphic.data[x][this.pos.y].attr = this.attr.value; + } + break; + case 2: // Erase entire screen (Most BBS terminals also move to 1/1) + this.graphic.clear(); + break; + } + break; + case 'K': // Erase in line + param_defaults(p, [0]); + switch(p[0]) { + case 0: // Erase to eol + for (x = this.pos.x; x<this.pos.width; x++) { + this.graphic.data[x][this.pos.y].ch = this.graphic.ch; + this.graphic.data[x][this.pos.y].attr = this.attr.value; + } + break; + case 1: // Erase to start of line + for (x = 0; x<=this.pos.x; x++) { + this.graphic.data[x][this.pos.y].ch = this.graphic.ch; + this.graphic.data[x][this.pos.y].attr = this.attr.value; + } + break; + case 2: // Erase entire line + for (x = 0; x<this.graphic.width; x++) { + this.graphic.data[x][this.pos.y].ch = this.graphic.ch; + this.graphic.data[x][this.pos.y].attr = this.attr.value; + } + break; + default: + break; + } + break; + case 'P': // Delete character + param_defaults(p, [1]); + if (p[1] > this.graphic.width - this.pos.x) + p[1] = this.graphic.width - this.pos.x; + if (this.pos.x < this.graphic.width-1) { + tg = this.graphic.get(this.pos.x + p[1], this.pos.y, this.graphic.width - 1, this.pos.y); + tg = this.graphic.put(this.pos.x, this.pos.y, (this.graphic.width - 1) - p[1], this.pos.y); + } + for (x = 0; x<p[1]; x++) { + this.graphic.data[(this.width - 1) - x][this.pos.y].ch = this.graphic.ch; + this.graphic.data[(this.width - 1) - x][this.pos.y].attr = this.attr.value; + } + break; + case 'X': // Erase character + param_defaults(p, [1]); + if (p[1] > this.graphic.width - this.pos.x) + p[1] = this.graphic.width - this.pos.x; + for (x = 0; x<p[1]; x++) { + this.graphic.data[this.pos.x + x][this.pos.y].ch = this.graphic.ch; + this.graphic.data[this.pos.x + x][this.pos.y].attr = this.attr.value; + } + break; + case 'm': + param_defaults(p, [0]); + for (i=0; i<p.length; i++) { + switch(p[i]) { + case 0: + this.attr.value = this.graphic.attribute.value; + break; + case 1: + this.attr.bright = true; + break; + case 2: + case 22: + this.attr.bright = false; + break; + case 5: + case 6: + this.attr.blink = true; + break; + case 7: + tg = this.attr.bg; + this.attr.bg = this.attr.fg; + this.attr.fg = tg; + break; + case 8: + this.attr.fg = this.attr.bg; + break; + case 25: + this.attr.blink = false; + break; + case 30: + this.attr.fg = Attribute.BLACK; + break; + case 31: + this.attr.fg = Attribute.RED; + break; + case 32: + this.attr.fg = Attribute.GREEN; + break; + case 33: + this.attr.fg = Attribute.YELLOW; + break; + case 34: + this.attr.fg = Attribute.BLUE; + break; + case 35: + this.attr.fg = Attribute.MAGENTA; + break; + case 36: + this.attr.fg = Attribute.CYAN; + break; + case 37: + this.attr.fg = Attribute.WHITE; + break; + case 40: + this.attr.bg = Attribute.BLACK; + break; + case 41: + this.attr.bg = Attribute.RED; + break; + case 42: + this.attr.bg = Attribute.GREEN; + break; + case 43: + this.attr.bg = Attribute.YELLOW; + break; + case 44: + this.attr.bg = Attribute.BLUE; + break; + case 45: + this.attr.bg = Attribute.MAGENTA; + break; + case 46: + this.attr.bg = Attribute.CYAN; + break; + case 47: + this.attr.bg = Attribute.WHITE; + break; + } + } + break; + case 's': + this.saved_pos.x = this.pos.x; + this.saved_pos.y = this.pos.y; + break; + case 'u': + this.pos.x = this.saved_pos.x; + this.pos.y = this.saved_pos.y; + break; + // Still TODO... + case 'n': // Device status report... no action from this object. + case 'Z': // Back tabulate + case 'S': // Scroll up + case 'T': // Scroll down + case 'L': // Insert line + case 'M': // Delete line (also ANSI music!) + default: + log("Sent unsupported ANSI sequence '"+ext+pb+ib+fb+"' please let shurd@sasktel.net net know about this so it can be fixed."); + } + } + if ((m=str.match(/^(.*?)(\x1b(\[([<-\?]{0,1})([0-;]*)([ -\/]*)([@-~])?)?)$/)) !== null) { + str = m[1]; + this.escbuf = m[2]; + } + // Send regular chars before the sequence... + for (i=0; i<str.length; i++) + writech(this, str[i]); + +}; diff --git a/exec/load/dorkit.js b/exec/load/dorkit.js index 9779c0cf48a1fba10a8bc6b50e6ae46ba7e9c9b1..10ecdd4d937bc5c6e2a011f4009d147ca361bcd9 100644 --- a/exec/load/dorkit.js +++ b/exec/load/dorkit.js @@ -1,8 +1,7 @@ js.load_path_list.unshift(js.exec_dir+"/dorkit/"); if (js.global.system !== undefined) js.load_path_list.unshift(system.exec_dir+"/dorkit/"); -load("attribute.js"); -load("graphic.js"); +load("screen.js"); var dk = { console:{ @@ -73,14 +72,25 @@ var dk = { x:1, // Current column (1-based) y:1, // Current row (1-based) - attr:new Attribute(7), // Current attribute + _attr:new Attribute(7), + get attr() { + return this._attr; + }, + set attr(val) { + var n = new Attribute(val); + this.print(n.ansi(this._attr)); + this._attr = n; + }, ansi:true, // ANSI support is enabled charset:'cp437', // Supported character set local:true, // True if writes should go to the local screen remote:true, // True if writes should go to the remote terminal - rows:undefined, // Rows in users terminal - cols:undefined, // Columns in users terminal + rows:24, // Rows in users terminal + cols:80, // Columns in users terminal + keybuf:'', + local_screen:new Screen(80, 24, 7, ' '), + remote_screen:new Screen(80, 24, 7, ' '), /* * Returns a string with ^A codes converted to ANSI or stripped @@ -198,6 +208,7 @@ var dk = { * sets the current attribute to 7 */ clear:function() { + this.attr=7; if (this.local) this.local_io.clear(); if (this.remote) @@ -232,19 +243,29 @@ var dk = { * or undefined on error (ie: invalid block specified). */ getblock:function(sx,sy,ex,ey) { + return this.remote_screen.graphic.get(sx,sy,ex,ey); }, /* * Writes a string unmodified. - * TODO: This needs to parse ANSI and set attr... */ print:function(string) { var m; - if (this.local) + if (this.local) { + if (this.local_screen !== undefined) { + this.local_screen.print(string); + this._attr.value = this.local_screen.attr.value; + } this.local_io.print(string); - if (this.remote) + } + if (this.remote) { + if (this.remote_screen !== undefined) { + this.remote_screen.print(string); + this.attr.value = this.remote_screen.attr.value; + } this.remote_io.print(string); + } }, /* @@ -258,14 +279,14 @@ var dk = { * Writes a string after parsing ^A codes. */ aprint:function(string) { - this.println(this.parse_ctrla(line)); + this.println(this.parse_ctrla(line, this.attr)); }, /* * Writes a string after parsing ^A codes and appends a "\r\n". */ aprintln:function(line) { - this.println(this.parse_ctrla(line)); + this.println(this.parse_ctrla(line, this.attr)); }, /* @@ -455,6 +476,10 @@ var dk = { if(this.console.waitkey(500)) { if (this.console.getkey() == this.console.key.POSITION_REPORT) { // TODO: Should we trust the drop file on number of rows? + if (this.console.cols != this.console.last_pos.x || this.console.rows != this.console.last_pos.y) { + this.console.remote_screen = new Screen(this.console.last_pos.x, this.console.last_pos.y, 7, ' '); + this.console.local_screen = new Screen(this.console.last_pos.x, this.console.last_pos.y, 7, ' '); + } this.console.cols = this.console.last_pos.x; this.console.rows = this.console.last_pos.y; this.console.ansi = true; @@ -468,6 +493,7 @@ var dk = { parse_dropfile:function(path) { var f = new File(path); var df; + var rows; if (!f.open("r")) return false; @@ -511,7 +537,12 @@ var dk = { this.codepage = '7-bit'; break; } - this.rows = parseInt(df[20], 10); + rows = parseInt(df[20], 10); + if (rows != this.console.rows) { + this.console.remote_screen = new Screen(this.console.cols, rows, 7, ' '); + this.console.local_screen = new Screen(this.console.cols, rows, 7, ' '); + } + this.rows = rows; this.user.expert_mode = (df[21].toUpperCase === 'Y') ? true : false; this.user.conference = df[22].split(/\s*,\s*/); this.user.curr_conference = parseInt(df[23]);