2014-10-17 04:03:32 +00:00
|
|
|
/* jslint node: true */
|
|
|
|
'use strict';
|
|
|
|
|
2014-10-22 05:12:44 +00:00
|
|
|
var events = require('events');
|
2014-10-17 04:03:32 +00:00
|
|
|
var util = require('util');
|
2014-10-20 03:06:39 +00:00
|
|
|
var assert = require('assert');
|
2014-10-22 05:12:44 +00:00
|
|
|
var ansi = require('./ansi_term.js');
|
2015-07-01 04:45:27 +00:00
|
|
|
var colorCodes = require('./color_codes.js');
|
2015-06-06 06:33:59 +00:00
|
|
|
|
2015-04-09 04:54:13 +00:00
|
|
|
var _ = require('lodash');
|
2014-10-17 04:03:32 +00:00
|
|
|
|
2014-10-24 04:18:38 +00:00
|
|
|
exports.View = View;
|
|
|
|
exports.VIEW_SPECIAL_KEY_MAP_DEFAULT = VIEW_SPECIAL_KEY_MAP_DEFAULT;
|
2014-10-20 03:06:39 +00:00
|
|
|
|
2014-10-22 05:12:44 +00:00
|
|
|
var VIEW_SPECIAL_KEY_MAP_DEFAULT = {
|
2015-06-05 04:29:14 +00:00
|
|
|
accept : [ 'return' ],
|
2014-10-20 03:06:39 +00:00
|
|
|
exit : [ 'esc' ],
|
2015-04-14 06:19:14 +00:00
|
|
|
backspace : [ 'backspace', 'del' ],
|
2014-10-22 05:12:44 +00:00
|
|
|
del : [ 'del' ],
|
2014-10-20 03:06:39 +00:00
|
|
|
next : [ 'tab' ],
|
2014-11-01 15:50:11 +00:00
|
|
|
up : [ 'up arrow' ],
|
|
|
|
down : [ 'down arrow' ],
|
2015-04-28 02:19:17 +00:00
|
|
|
end : [ 'end' ],
|
|
|
|
home : [ 'home' ],
|
2015-04-27 22:04:41 +00:00
|
|
|
left : [ 'left arrow' ],
|
|
|
|
right : [ 'right arrow' ],
|
2015-06-05 22:20:26 +00:00
|
|
|
clearLine : [ 'ctrl + y' ],
|
2014-10-20 03:06:39 +00:00
|
|
|
};
|
|
|
|
|
2015-04-09 04:54:13 +00:00
|
|
|
function View(options) {
|
2014-10-22 05:12:44 +00:00
|
|
|
events.EventEmitter.call(this);
|
2014-10-20 03:06:39 +00:00
|
|
|
|
2015-04-09 04:54:13 +00:00
|
|
|
assert(_.isObject(options));
|
|
|
|
assert(_.isObject(options.client));
|
2014-10-20 03:06:39 +00:00
|
|
|
|
2014-10-22 05:12:44 +00:00
|
|
|
var self = this;
|
2014-10-20 03:06:39 +00:00
|
|
|
|
2015-04-09 04:54:13 +00:00
|
|
|
this.client = options.client;
|
2014-10-20 05:30:44 +00:00
|
|
|
|
2015-04-09 04:54:13 +00:00
|
|
|
this.cursor = options.cursor || 'show';
|
2015-04-28 02:19:17 +00:00
|
|
|
this.cursorStyle = options.cursorStyle || 'default';
|
2015-04-09 04:54:13 +00:00
|
|
|
|
2014-10-23 05:41:00 +00:00
|
|
|
this.acceptsFocus = options.acceptsFocus || false;
|
|
|
|
this.acceptsInput = options.acceptsInput || false;
|
2014-10-20 05:30:44 +00:00
|
|
|
|
2014-10-22 05:12:44 +00:00
|
|
|
this.position = { x : 0, y : 0 };
|
|
|
|
this.dimens = { height : 1, width : 0 };
|
2014-10-20 03:06:39 +00:00
|
|
|
|
2015-04-09 04:54:13 +00:00
|
|
|
this.textStyle = options.textStyle || 'normal';
|
|
|
|
this.focusTextStyle = options.focusTextStyle || this.textStyle;
|
2015-04-08 05:15:34 +00:00
|
|
|
|
2015-04-09 04:54:13 +00:00
|
|
|
if(options.id) {
|
|
|
|
this.setId(options.id);
|
2014-10-23 05:41:00 +00:00
|
|
|
}
|
|
|
|
|
2015-04-09 04:54:13 +00:00
|
|
|
if(options.position) {
|
|
|
|
this.setPosition(options.position);
|
2014-10-20 03:06:39 +00:00
|
|
|
}
|
|
|
|
|
2015-05-07 22:14:16 +00:00
|
|
|
if(_.isObject(options.autoScale)) {
|
|
|
|
this.autoScale = options.autoScale;
|
|
|
|
} else {
|
|
|
|
this.autoScale = { height : true, width : true };
|
|
|
|
}
|
2015-05-01 04:29:24 +00:00
|
|
|
|
2015-05-06 04:19:21 +00:00
|
|
|
if(options.dimens) {
|
|
|
|
this.setDimension(options.dimens);
|
2015-05-07 22:14:16 +00:00
|
|
|
this.autoScale = { height : false, width : false };
|
2015-05-01 04:29:24 +00:00
|
|
|
} else {
|
2015-07-28 04:10:20 +00:00
|
|
|
this.dimens = {
|
|
|
|
width : options.width || 0,
|
|
|
|
height : 0
|
|
|
|
};
|
2014-10-20 03:06:39 +00:00
|
|
|
}
|
|
|
|
|
2015-07-11 22:39:42 +00:00
|
|
|
// :TODO: Just use styleSGRx for these, e.g. styleSGR0, styleSGR1 = norm/focus
|
2015-04-30 20:39:03 +00:00
|
|
|
this.ansiSGR = options.ansiSGR || ansi.getSGRFromGraphicRendition( { fg : 39, bg : 49 }, true);
|
|
|
|
this.ansiFocusSGR = options.ansiFocusSGR || this.ansiSGR;
|
2014-10-20 03:06:39 +00:00
|
|
|
|
2015-04-30 22:41:43 +00:00
|
|
|
this.styleSGR1 = options.styleSGR1 || this.ansiSGR;
|
|
|
|
this.styleSGR2 = options.styleSGR2 || this.ansiFocusSGR;
|
2015-04-29 04:42:22 +00:00
|
|
|
|
2014-10-22 05:12:44 +00:00
|
|
|
if(this.acceptsInput) {
|
2015-04-09 04:54:13 +00:00
|
|
|
this.specialKeyMap = options.specialKeyMap || VIEW_SPECIAL_KEY_MAP_DEFAULT;
|
2014-10-20 03:06:39 +00:00
|
|
|
}
|
2014-10-23 05:41:00 +00:00
|
|
|
|
2015-07-02 02:18:34 +00:00
|
|
|
this.isKeyMapped = function(keySet, keyName) {
|
2015-06-06 06:33:59 +00:00
|
|
|
return _.has(this.specialKeyMap, keySet) && this.specialKeyMap[keySet].indexOf(keyName) > -1;
|
2014-10-23 05:41:00 +00:00
|
|
|
};
|
2014-10-27 04:06:41 +00:00
|
|
|
|
|
|
|
this.getANSIColor = function(color) {
|
|
|
|
var sgr = [ color.flags, color.fg ];
|
|
|
|
if(color.bg !== color.flags) {
|
|
|
|
sgr.push(color.bg);
|
|
|
|
}
|
|
|
|
return ansi.sgr(sgr);
|
|
|
|
};
|
2015-04-28 02:19:17 +00:00
|
|
|
|
|
|
|
this.hideCusor = function() {
|
2015-07-06 01:05:55 +00:00
|
|
|
self.client.term.rawWrite(ansi.hideCursor());
|
2015-04-28 02:19:17 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
this.restoreCursor = function() {
|
|
|
|
//this.client.term.write(ansi.setCursorStyle(this.cursorStyle));
|
2015-07-06 01:05:55 +00:00
|
|
|
this.client.term.rawWrite('show' === this.cursor ? ansi.showCursor() : ansi.hideCursor());
|
2015-12-24 18:54:38 +00:00
|
|
|
};
|
2014-10-20 03:06:39 +00:00
|
|
|
}
|
|
|
|
|
2014-10-22 05:12:44 +00:00
|
|
|
util.inherits(View, events.EventEmitter);
|
2014-10-20 05:30:44 +00:00
|
|
|
|
2014-10-23 05:41:00 +00:00
|
|
|
View.prototype.setId = function(id) {
|
|
|
|
this.id = id;
|
|
|
|
};
|
2014-10-20 05:30:44 +00:00
|
|
|
|
2015-04-14 06:19:14 +00:00
|
|
|
View.prototype.getId = function() {
|
|
|
|
return this.id;
|
|
|
|
};
|
|
|
|
|
2014-10-22 05:12:44 +00:00
|
|
|
View.prototype.setPosition = function(pos) {
|
|
|
|
//
|
2015-05-18 17:31:35 +00:00
|
|
|
// Allow the following forms: [row, col], { row : r, col : c }, or (row, col)
|
2014-10-22 05:12:44 +00:00
|
|
|
//
|
|
|
|
if(util.isArray(pos)) {
|
2015-05-18 17:31:35 +00:00
|
|
|
this.position.row = pos[0];
|
|
|
|
this.position.col = pos[1];
|
|
|
|
} else if(pos.row && pos.col) {
|
|
|
|
this.position.row = pos.row;
|
|
|
|
this.position.col = pos.col;
|
2014-10-22 05:12:44 +00:00
|
|
|
} else if(2 === arguments.length) {
|
2015-05-18 17:31:35 +00:00
|
|
|
this.position.row = parseInt(arguments[0], 10);
|
|
|
|
this.position.col = parseInt(arguments[1], 10);
|
2014-10-20 03:06:39 +00:00
|
|
|
}
|
2014-10-23 05:41:00 +00:00
|
|
|
|
2015-05-18 17:31:35 +00:00
|
|
|
assert(!(isNaN(this.position.row)));
|
|
|
|
assert(!(isNaN(this.position.col)));
|
2014-10-20 03:06:39 +00:00
|
|
|
|
2014-10-30 04:23:44 +00:00
|
|
|
assert(
|
2015-05-18 17:31:35 +00:00
|
|
|
this.position.row > 0 && this.position.row <= this.client.term.termHeight,
|
|
|
|
'X position ' + this.position.row + ' out of terminal range ' + this.client.term.termHeight);
|
2014-10-30 04:23:44 +00:00
|
|
|
|
|
|
|
assert(
|
2015-05-18 17:31:35 +00:00
|
|
|
this.position.col > 0 && this.position.col <= this.client.term.termWidth,
|
|
|
|
'Y position ' + this.position.col + ' out of terminal range ' + this.client.term.termWidth);
|
2014-10-20 03:06:39 +00:00
|
|
|
};
|
|
|
|
|
2015-05-06 04:19:21 +00:00
|
|
|
View.prototype.setDimension = function(dimens) {
|
|
|
|
assert(_.isObject(dimens) && _.isNumber(dimens.height) && _.isNumber(dimens.width));
|
|
|
|
|
|
|
|
this.dimens = dimens;
|
2015-05-07 22:14:16 +00:00
|
|
|
this.autoScale = { height : false, width : false };
|
2015-05-06 04:19:21 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
View.prototype.setHeight = function(height) {
|
2015-06-30 05:14:17 +00:00
|
|
|
height = parseInt(height, 10);
|
2015-06-29 04:45:57 +00:00
|
|
|
assert(_.isNumber(height));
|
|
|
|
// :TODO: assert height is within this.client.term.termHeight
|
|
|
|
|
2015-05-07 22:14:16 +00:00
|
|
|
this.dimens.height = height;
|
|
|
|
this.autoScale.height = false;
|
2015-05-06 04:19:21 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
View.prototype.setWidth = function(width) {
|
2015-06-30 05:14:17 +00:00
|
|
|
width = parseInt(width);
|
2015-06-29 04:45:57 +00:00
|
|
|
assert(_.isNumber(width));
|
|
|
|
// :TODO: assert width is appropriate for this.client.term.termWidth
|
|
|
|
|
2015-05-07 22:14:16 +00:00
|
|
|
this.dimens.width = width;
|
|
|
|
this.autoScale.width = false;
|
2015-05-06 04:19:21 +00:00
|
|
|
};
|
|
|
|
|
2015-04-29 21:38:20 +00:00
|
|
|
View.prototype.getSGR = function() {
|
2015-04-30 20:39:03 +00:00
|
|
|
return this.ansiSGR;
|
2015-05-06 04:19:21 +00:00
|
|
|
};
|
2015-04-29 21:38:20 +00:00
|
|
|
|
2015-05-18 17:31:35 +00:00
|
|
|
View.prototype.getStyleSGR = function(n) {
|
|
|
|
assert(_.isNumber(n));
|
|
|
|
return this['styleSGR' + n];
|
|
|
|
};
|
2015-05-15 05:01:00 +00:00
|
|
|
|
2015-04-29 21:38:20 +00:00
|
|
|
View.prototype.getFocusSGR = function() {
|
2015-04-30 20:39:03 +00:00
|
|
|
return this.ansiFocusSGR;
|
2015-05-06 04:19:21 +00:00
|
|
|
};
|
2014-10-20 03:06:39 +00:00
|
|
|
|
2015-06-30 05:14:17 +00:00
|
|
|
View.prototype.setPropertyValue = function(propName, value) {
|
2015-06-29 04:45:57 +00:00
|
|
|
switch(propName) {
|
|
|
|
case 'height' : this.setHeight(value); break;
|
|
|
|
case 'width' : this.setWidth(value); break;
|
|
|
|
case 'focus' : this.setFocus(value); break;
|
2015-06-30 05:14:17 +00:00
|
|
|
|
|
|
|
case 'text' :
|
2015-07-27 04:51:06 +00:00
|
|
|
if('setText' in this) {
|
2015-06-30 05:14:17 +00:00
|
|
|
this.setText(value);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'textStyle' : this.textStyle = value; break;
|
|
|
|
case 'focusTextStyle' : this.focusTextStyle = value; break;
|
|
|
|
|
|
|
|
case 'justify' : this.justify = value; break;
|
|
|
|
|
|
|
|
case 'fillChar' :
|
|
|
|
if('fillChar' in this) {
|
|
|
|
if(_.isNumber(value)) {
|
|
|
|
this.fillChar = String.fromCharCode(value);
|
|
|
|
} else if(_.isString(value)) {
|
|
|
|
this.fillChar = value.substr(0, 1);
|
|
|
|
}
|
|
|
|
}
|
2015-07-03 05:28:39 +00:00
|
|
|
break;
|
2015-06-30 05:14:17 +00:00
|
|
|
|
|
|
|
case 'submit' :
|
|
|
|
if(_.isBoolean(value)) {
|
|
|
|
this.submit = value;
|
2015-07-10 17:11:08 +00:00
|
|
|
}/* else {
|
2015-06-30 05:14:17 +00:00
|
|
|
this.submit = _.isArray(value) && value.length > 0;
|
|
|
|
}
|
2015-07-10 17:11:08 +00:00
|
|
|
*/
|
2015-06-30 05:14:17 +00:00
|
|
|
break;
|
|
|
|
|
2015-07-28 04:10:20 +00:00
|
|
|
case 'resizable' :
|
|
|
|
if(_.isBoolean(value)) {
|
|
|
|
this.resizable = value;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
2015-07-13 19:41:21 +00:00
|
|
|
case 'argName' : this.submitArgName = value; break;
|
2015-12-10 07:03:58 +00:00
|
|
|
|
|
|
|
case 'validate' :
|
|
|
|
if(_.isFunction(value)) {
|
|
|
|
this.validate = value;
|
|
|
|
}
|
|
|
|
break;
|
2015-06-29 04:45:57 +00:00
|
|
|
}
|
|
|
|
|
2015-06-30 05:14:17 +00:00
|
|
|
if(/styleSGR[0-9]{1,2}/.test(propName)) {
|
|
|
|
if(_.isObject(value)) {
|
|
|
|
this[propName] = ansi.getSGRFromGraphicRendition(value, true);
|
|
|
|
} else if(_.isString(value)) {
|
2015-07-01 04:45:27 +00:00
|
|
|
this[propName] = colorCodes.pipeToAnsi(value);
|
2015-06-30 05:14:17 +00:00
|
|
|
}
|
2015-06-29 04:45:57 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2014-10-22 05:12:44 +00:00
|
|
|
View.prototype.redraw = function() {
|
2015-05-18 17:31:35 +00:00
|
|
|
this.client.term.write(ansi.goto(this.position.row, this.position.col));
|
2014-10-20 03:06:39 +00:00
|
|
|
};
|
|
|
|
|
2014-10-22 05:12:44 +00:00
|
|
|
View.prototype.setFocus = function(focused) {
|
|
|
|
assert(this.acceptsFocus, 'View does not accept focus');
|
2014-10-20 03:06:39 +00:00
|
|
|
|
2014-10-22 05:12:44 +00:00
|
|
|
this.hasFocus = focused;
|
2015-04-28 02:19:17 +00:00
|
|
|
this.restoreCursor();
|
2014-10-24 04:18:38 +00:00
|
|
|
};
|
2015-06-05 22:20:26 +00:00
|
|
|
|
|
|
|
View.prototype.onKeyPress = function(ch, key) {
|
2015-09-21 01:10:09 +00:00
|
|
|
if(false === this.hasFocus) {
|
|
|
|
console.log('doh!');
|
|
|
|
}
|
2015-07-10 17:11:08 +00:00
|
|
|
assert(this.hasFocus, 'View does not have focus');
|
|
|
|
assert(this.acceptsInput, 'View does not accept input');
|
2015-06-05 22:20:26 +00:00
|
|
|
|
|
|
|
if(key) {
|
|
|
|
assert(this.specialKeyMap, 'No special key map defined');
|
|
|
|
|
2015-07-02 02:18:34 +00:00
|
|
|
if(this.isKeyMapped('accept', key.name)) {
|
2015-08-13 22:05:17 +00:00
|
|
|
this.emit('action', 'accept', key);
|
2015-07-02 02:18:34 +00:00
|
|
|
} else if(this.isKeyMapped('next', key.name)) {
|
2015-08-13 22:05:17 +00:00
|
|
|
this.emit('action', 'next', key);
|
2015-06-05 22:20:26 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if(ch) {
|
|
|
|
assert(1 === ch.length);
|
|
|
|
}
|
|
|
|
};
|
2014-11-02 19:07:17 +00:00
|
|
|
|
2015-04-17 04:29:53 +00:00
|
|
|
View.prototype.getData = function() {
|
2015-12-10 07:03:58 +00:00
|
|
|
};
|