API Docs for: 2.0.0

public/src/text.js

// SAGE2 is available for use under the SAGE2 Software License
//
// University of Illinois at Chicago's Electronic Visualization Laboratory (EVL)
// and University of Hawai'i at Manoa's Laboratory for Advanced Visualization and
// Applications (LAVA)
//
// See full text, terms and conditions in the LICENSE.txt included file
//
// Copyright (c) 2014

"use strict";

/**
 * Text input widget for application UI
 *
 * @module client
 * @submodule text
 */

/**
 * Blinking cursor
 *
 * @class Blinker
 * @constructor
 */
function Blinker(id, ctx, date, color) {
	this.id       = id;
	this.visible  = true;
	this.color    = color;
	this.blinkerX = null;
	this.blinkerY = null;
	this.textIdx  = null;
	this.blinkerL = null;
	this.blinkerC = null;

	/**
	 *@method draw
	 */
	this.draw = function(atext, fH) {
		// This function assumes that offSet function is always called prior to this function
		this.blinkerY += this.blinkerL * fH;
		if (this.blinkerC > 0 && this.blinkerL in atext) {
			this.blinkerX += ctx.measureText(atext[this.blinkerL].substring(0, this.blinkerC)).width;
		}

		var col    = ctx.strokeStyle;
		var offset = fH * 0.25;
		var offY   = this.blinkerY + offset;
		ctx.strokeStyle = "rgba(" + this.color[0] + "," + this.color[1] + "," + this.color[2] + ",1.0)";
		ctx.beginPath();
		ctx.moveTo(this.blinkerX, offY);
		ctx.lineTo(this.blinkerX, offY - fH);
		ctx.moveTo(this.blinkerX, offY);
		ctx.closePath();
		ctx.stroke();
		ctx.strokeStyle = col;
	};

	/**
	 *@method offSet
	 */
	this.moveLC = function(l, c) {
		this.blinkerL = l;
		this.blinkerC = c;
	};

	/**
	 *@method offSet
	 */
	this.offSet = function(x, y) {
		this.blinkerX = x;
		this.blinkerY = y;
	};

}

/**
* Constructor
*
* @class text
* @constructor
*/
function text() {
	this.element     = null;
	this.ctx         = null;
	this.resrcPath   = null;
	this.lines       = null;
	this.linesVisble = null;
	this.columns     = null;

	this.fontSize   = 12;
	this.font       = "Arial";
	this.bold       = false;
	this.fontHeight = null;
	this.height     = null;
	this.space      = null;
	this.lMargin    = 40;

	this.minDim     = null;
	this.text       = "";
	this.textArr    = [];
	this.fileName   = "~/uploads/text/buffer.txt";
	this.blinkerArr = [];
	this.range      = [];
	this.specialKeyFlag = false;

	/**
	*
	*
	* @method computeMetrics
	*/
	this.computeMetrics = function() {
		this.height       = this.element.height;
		this.fontHeight   = getHeightOfText(this.bold, this.font, this.fontSize);
		this.linesVisible = Math.floor(this.element.height / this.fontHeight);
		this.space        = this.ctx.measureText(" ").width;
		this.columns      = Math.floor(this.element.width / this.space);
	};

	/**
	*
	*
	* @method findRange
	*/
	this.findRange = function() {
		this.range[0] = [1, this.linesVisible];
	};

	/**
	*
	*
	* @method getHeightOfText
	*/
	function getHeightOfText(bold, font, size) {
		var div = document.createElement('div');
		div.innerHTML      = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
		div.style.position = 'absolute';
		div.style.top      = '-100px';
		div.style.left     = '-100px';
		div.style.fontFamily = font;
		div.style.fontWeight = bold ? 'bold' : 'normal';
		div.style.fontSize   = size + 'pt';
		document.body.appendChild(div);
		var asize = div.offsetHeight;
		document.body.removeChild(div);
		return asize;
	}

	/**
	*
	*
	* @method init
	*/
	this.init = function(id, date, resrc) {
		this.element = document.getElementById(id);
		this.ctx     = this.element.getContext("2d");
		this.resrcPath = resrc;
		this.minDim    = Math.min(this.element.width, this.element.height);
		this.computeMetrics();
	};

	/**
	*
	*
	* @method displayText
	*/
	this.displayText = function() {
		var count = 1;
		for (var parts = 0; parts < this.range.length; parts++) {
			var start = this.range[parts][0];
			var end   = this.range[parts][1];
			for (var i = start; i <= end; i++) {

				this.ctx.font = "16px " + this.font;
				this.ctx.fillText(('000' + i).slice(-3), 5, count * this.fontHeight);
				this.ctx.font = this.fontSize + "px " + this.font;
				if (i in this.textArr) {
					var wrSpc = this.element.width - (2 * this.space + this.lMargin);
					if (this.ctx.measureText(this.textArr[i]).width > wrSpc) {
						var cut = Math.floor(wrSpc / this.ctx.measureText(this.textArr[i]).width * this.textArr[i].length);
						var re  = new RegExp(".{1," + cut + "}", "g");
						var mLines = this.textArr[i].match(re);
						for (var ml = 0; ml < mLines.length; ml++) {
							this.ctx.fillText(mLines[ml], this.space + this.lMargin, count * this.fontHeight);
							count++;
						}
					} else {
						this.ctx.fillText(this.textArr[i], this.space + this.lMargin, count * this.fontHeight);
					}
				}
				count++;
			}
		}
	};

	/**
	*
	*
	* @method draw
	*/
	this.draw = function(date) {
		// clear canvas
		this.ctx.clearRect(0, 0, this.element.width, this.element.height);

		this.ctx.fillStyle = "rgba(255, 255, 255, 1.0)";
		this.ctx.fillRect(0, 0, this.element.width, this.element.height);
		this.ctx.fillStyle = "rgba(150, 150, 150, 1.0)";
		this.ctx.fillRect(0, 0, this.lMargin, this.element.height);
		this.ctx.fillStyle = "rgba(200, 200, 200, 1.0)";
		this.ctx.fillRect(this.element.width - 30, 0, 30, this.element.height);

		this.ctx.strokeStyle = "rgba(0, 0, 0, 1.0)";

		this.ctx.fillStyle = "rgba(0, 0, 0, 1.0)";
		this.space = this.ctx.measureText(" ").width;
		this.findRange();
		this.displayText();
		if (date.getMilliseconds() < 500) {
			for (var bkr in this.blinkerArr) {
				this.blinkerArr[bkr].offSet(this.space + this.lMargin, 0);
				this.blinkerArr[bkr].draw(this.textArr, this.fontHeight);
			}
		}
	};

	/**
	*
	*
	* @method resize
	*/
	this.resize = function(date) {
		this.fontSize = Math.max(Math.floor(0.02 * this.element.height), 14);
		this.computeMetrics();
		this.draw(date);
	};

	/**
	*
	*
	* @method enterKey
	*/
	this.enterKey = function(curL, curC, userId) {
		if (curL in this.textArr) {
			var nl = this.textArr[curL].substring(curC, this.textArr[curL].length);
			this.textArr[curL] = this.textArr[curL].substring(0, curC);
			if (curL + 1 in this.textArr) {
				this.textArr.splice(curL + 1, 0, nl);
			} else {
				this.textArr[curL + 1] = nl;
			}
		} else {
			this.textArr[curL] = "";
		}
		this.blinkerArr[userId].moveLC(curL + 1, 0);
	};

	/**
	*
	*
	* @method event
	*/
	this.event = function(type, userId, x, y, data, date, user_color) {
		var curL;
		var curC;
		var pre;
		var post;
		var t;

		user_color = user_color || [255, 0, 0];

		if (type === "pointerPress") {
			if (data.button === "left") {
				if (!(userId in this.blinkerArr)) {
					console.log(user_color);
					var bkr = new Blinker(userId, this.ctx, date, user_color);
					this.blinkerArr[userId] = bkr;
				} else {
					this.blinkerArr[userId].color = user_color;
				}
				var lno    = Math.ceil(y / this.fontHeight);
				var tArIdx = lno;
				if (tArIdx in this.textArr) {
					var len = this.ctx.measureText(this.textArr[tArIdx]).width;
					if (x >= len) {
						this.blinkerArr[userId].moveLC(lno, this.textArr[tArIdx].length);
					} else {
						var c;
						for (c = 0; c < this.textArr[tArIdx].length; c++) {
							if (this.ctx.measureText(this.textArr[tArIdx].substring(0, c)).width > x) {
								break;
							}
						}
						this.blinkerArr[userId].moveLC(lno, c - 1);
					}
				} else {
					this.blinkerArr[userId].moveLC(lno, 0);
				}
			} else if (data.button === "right") {
				// not implemented yet
				console.log('Text> NYI');
			}
		} else if (type === "pointerRelease") {
			if (data.button === "left") {
				// not implemented yet
				console.log('Text> NYI');
			} else if (data.button === "right") {
				// not implemented yet
				console.log('Text> NYI');
			}
		} else if (type === "pointerMove") { // x and y hold current pointer positions
			// not implemented yet
			console.log('Text> NYI');
		} else if (type === "pointerDoubleClick") {
			// not implemented yet
			console.log('Text> NYI');
		} else if (type === "keyboard") {
			// the key character is stored in ascii in data.code
			// all other keys will come in as typed:  'a', 'A', '1', '!' etc
			// tabs and new lines ought to be coming in too
			var theAsciiCode = data.code;
			// Bad code. need to remove once the event handler has been modified.
			if ((userId in this.blinkerArr) === false) {
				return;
			}
			curL = this.blinkerArr[userId].blinkerL;
			curC = this.blinkerArr[userId].blinkerC;
			if (theAsciiCode === 13) {
				this.enterKey(curL, curC, userId);
			} else {
				if (curL in this.textArr) {
					this.textArr[curL] = this.textArr[curL].substring(0, curC) +  String.fromCharCode(theAsciiCode) +
							this.textArr[curL].substring(curC, this.textArr[curL].length);
				} else {
					this.textArr[curL] = String.fromCharCode(theAsciiCode);
				}
				this.blinkerArr[userId].moveLC(curL, curC + 1);
			}
		} else if (type === "specialKey" && data.state === "down") {
			// anything not printed
			// alt, command, up arrow
			// comes in as javascript key code
			var theJavascriptCode = data.code;
			curL = userId && this.blinkerArr[userId].blinkerL;
			curC = this.blinkerArr[userId].blinkerC;
			if (theJavascriptCode === 8) {
				if (curL in this.textArr) {

					pre = this.textArr[curL].substring(0, curC - 1);
					post = this.textArr[curL].substring(curC, this.textArr[curL].length);
					if (curC > 0) {
						this.textArr[curL] = pre + post;
						this.blinkerArr[userId].moveLC(curL, curC - 1);

					} else if (curL > 1) {
						t = "";
						if (curL - 1 in this.textArr) {
							t =  this.textArr[curL - 1];
						}
						this.textArr[curL - 1] = t + post;
						this.textArr.splice(curL, 1);
						this.blinkerArr[userId].moveLC(curL - 1, t.length);
					}
				} else if (curL - 1 in this.textArr) {

					this.blinkerArr[userId].moveLC(curL - 1, this.textArr[curL - 1].length);
				} else if (curL > 1) {
					this.blinkerArr[userId].moveLC(curL - 1, 0);
				}
			} else if (theJavascriptCode === 46) {
				pre  = this.textArr[curL].substring(0, curC);
				post = this.textArr[curL].substring(curC + 1, this.textArr[curL].length);

				if ((curL in this.textArr) === false) {
					return;
				}

				if (curC < this.textArr[curL].length) {
					this.textArr[curL] = pre + post;
				} else {
					t = "";
					if (curL + 1 in this.textArr) {
						t = this.textArr[curL + 1];
						this.textArr.splice(curL + 1, 1);
					}
					this.textArr[curL] = this.textArr[curL]  + t;
				}
			} else if (theJavascriptCode === 37) {
				if (curC > 0) {
					curC--;
				} else {
					curL = curL - 1 || curL;
					curC = (curL in this.textArr) ? this.textArr[curL].length : 0;
				}
				this.blinkerArr[userId].moveLC(curL, curC);

			} else if (theJavascriptCode === 39) {
				if (curL in this.textArr && curC < this.textArr[curL].length) {
					curC++;
				} else {
					curL++;
					curC = 0;
				}
				this.blinkerArr[userId].moveLC(curL, curC);
			} else if (theJavascriptCode === 38) {
				if (curL - 1 in this.textArr) {
					curC = Math.min(curC, this.textArr[curL - 1].length);
					curL--;
				} else if (curL - 1 > 0) {
					curL--;
					curC = 0;
				}
				this.blinkerArr[userId].moveLC(curL, curC);
			} else if (theJavascriptCode === 40) {
				if (curL + 1 in this.textArr) {
					curC = Math.min(curC, this.textArr[curL + 1].length);
					curL++;
				} else {
					curL++;
					curC = 0;
				}
				this.blinkerArr[userId].moveLC(curL, curC);
			}
		} else if (type === "pointerScroll") {
			// not implemented yet
			console.log('Text> NYI');
		}

		// redraw
		this.draw(date);
	};
}