neo-blessed/lib/widgets/listbar.js

412 lines
8.6 KiB
JavaScript

/**
* listbar.js - listbar element for blessed
* Copyright (c) 2013-2015, Christopher Jeffrey and contributors (MIT License).
* https://github.com/chjj/blessed
*/
/**
* Modules
*/
var helpers = require('../helpers');
var Node = require('./node');
var Box = require('./box');
/**
* Listbar / HorizontalList
*/
function Listbar(options) {
var self = this;
if (!(this instanceof Node)) {
return new Listbar(options);
}
options = options || {};
this.items = [];
this.ritems = [];
this.commands = [];
this.leftBase = 0;
this.leftOffset = 0;
this.mouse = options.mouse || false;
Box.call(this, options);
if (!this.style.selected) {
this.style.selected = {};
}
if (!this.style.item) {
this.style.item = {};
}
if (options.commands || options.items) {
this.setItems(options.commands || options.items);
}
if (options.keys) {
this.on('keypress', function(ch, key) {
if (key.name === 'left'
|| (options.vi && key.name === 'h')
|| (key.shift && key.name === 'tab')) {
self.moveLeft();
self.screen.render();
// Stop propagation if we're in a form.
if (key.name === 'tab') return false;
return;
}
if (key.name === 'right'
|| (options.vi && key.name === 'l')
|| key.name === 'tab') {
self.moveRight();
self.screen.render();
// Stop propagation if we're in a form.
if (key.name === 'tab') return false;
return;
}
if (key.name === 'enter'
|| (options.vi && key.name === 'k' && !key.shift)) {
self.emit('action', self.items[self.selected], self.selected);
self.emit('select', self.items[self.selected], self.selected);
var item = self.items[self.selected];
if (item._.cmd.callback) {
item._.cmd.callback();
}
self.screen.render();
return;
}
if (key.name === 'escape' || (options.vi && key.name === 'q')) {
self.emit('action');
self.emit('cancel');
return;
}
});
}
if (options.autoCommandKeys) {
this.onScreenEvent('keypress', function(ch, key) {
if (/^[0-9]$/.test(ch)) {
var i = +ch - 1;
if (!~i) i = 9;
return self.selectTab(i);
}
});
}
this.on('focus', function() {
self.select(self.selected);
});
}
Listbar.prototype.__proto__ = Box.prototype;
Listbar.prototype.type = 'listbar';
Listbar.prototype.__defineGetter__('selected', function() {
return this.leftBase + this.leftOffset;
});
Listbar.prototype.setItems = function(commands) {
var self = this;
if (!Array.isArray(commands)) {
commands = Object.keys(commands).reduce(function(obj, key, i) {
var cmd = commands[key]
, cb;
if (typeof cmd === 'function') {
cb = cmd;
cmd = { callback: cb };
}
if (cmd.text == null) cmd.text = key;
if (cmd.prefix == null) cmd.prefix = ++i + '';
if (cmd.text == null && cmd.callback) {
cmd.text = cmd.callback.name;
}
obj.push(cmd);
return obj;
}, []);
}
this.items.forEach(function(el) {
el.detach();
});
this.items = [];
this.ritems = [];
this.commands = [];
commands.forEach(function(cmd) {
self.add(cmd);
});
this.emit('set items');
};
Listbar.prototype.add =
Listbar.prototype.addItem =
Listbar.prototype.appendItem = function(item, callback) {
var self = this
, prev = this.items[this.items.length - 1]
, drawn
, cmd
, title
, len;
if (!this.parent) {
drawn = 0;
} else {
drawn = prev ? prev.aleft + prev.width : 0
if (!this.screen.autoPadding) {
drawn += this.ileft;
}
}
if (typeof item === 'object') {
cmd = item;
if (cmd.prefix == null) cmd.prefix = (this.items.length + 1) + '';
}
if (typeof item === 'string') {
cmd = {
prefix: (this.items.length + 1) + '',
text: item,
callback: callback
};
}
if (typeof item === 'function') {
cmd = {
prefix: (this.items.length + 1) + '',
text: item.name,
callback: item
};
}
if (cmd.keys && cmd.keys[0]) {
cmd.prefix = cmd.keys[0];
}
var t = helpers.generateTags(this.style.prefix || { fg: 'lightblack' });
title = (cmd.prefix != null ? t.open + cmd.prefix + t.close + ':' : '') + cmd.text;
len = ((cmd.prefix != null ? cmd.prefix + ':' : '') + cmd.text).length;
var options = {
screen: this.screen,
top: 0,
left: drawn + 1,
height: 1,
content: title,
width: len + 2,
align: 'center',
autoFocus: false,
tags: true,
mouse: true,
style: helpers.merge({}, this.style.item),
noOverflow: true
};
if (!this.screen.autoPadding) {
options.top += this.itop;
options.left += this.ileft;
}
['bg', 'fg', 'bold', 'underline',
'blink', 'inverse', 'invisible'].forEach(function(name) {
options.style[name] = function() {
var attr = self.items[self.selected] === el
? self.style.selected[name]
: self.style.item[name];
if (typeof attr === 'function') attr = attr(el);
return attr;
};
});
var el = new Box(options);
this._[cmd.text] = el;
cmd.element = el;
el._.cmd = cmd;
this.ritems.push(cmd.text);
this.items.push(el);
this.commands.push(cmd);
this.append(el);
if (cmd.callback) {
if (cmd.keys) {
this.screen.key(cmd.keys, function(ch, key) {
self.emit('action', el, self.selected);
self.emit('select', el, self.selected);
if (el._.cmd.callback) {
el._.cmd.callback();
}
self.select(el);
self.screen.render();
});
}
}
if (this.items.length === 1) {
this.select(0);
}
// XXX May be affected by new element.options.mouse option.
if (this.mouse) {
el.on('click', function(data) {
self.emit('action', el, self.selected);
self.emit('select', el, self.selected);
if (el._.cmd.callback) {
el._.cmd.callback();
}
self.select(el);
self.screen.render();
});
}
this.emit('add item');
};
Listbar.prototype.render = function() {
var self = this
, drawn = 0;
if (!this.screen.autoPadding) {
drawn += this.ileft;
}
this.items.forEach(function(el, i) {
if (i < self.leftBase) {
el.hide();
} else {
el.rleft = drawn + 1;
drawn += el.width + 2;
el.show();
}
});
return this._render();
};
Listbar.prototype.select = function(offset) {
if (typeof offset !== 'number') {
offset = this.items.indexOf(offset);
}
if (offset < 0) {
offset = 0;
} else if (offset >= this.items.length) {
offset = this.items.length - 1;
}
if (!this.parent) {
this.emit('select item', el, offset);
return;
}
var lpos = this._getCoords();
if (!lpos) return;
var self = this
, width = (lpos.xl - lpos.xi) - this.iwidth
, drawn = 0
, visible = 0
, el;
el = this.items[offset];
if (!el) return;
this.items.forEach(function(el, i) {
if (i < self.leftBase) return;
var lpos = el._getCoords();
if (!lpos) return;
if (lpos.xl - lpos.xi <= 0) return;
drawn += (lpos.xl - lpos.xi) + 2;
if (drawn <= width) visible++;
});
var diff = offset - (this.leftBase + this.leftOffset);
if (offset > this.leftBase + this.leftOffset) {
if (offset > this.leftBase + visible - 1) {
this.leftOffset = 0;
this.leftBase = offset;
} else {
this.leftOffset += diff;
}
} else if (offset < this.leftBase + this.leftOffset) {
diff = -diff;
if (offset < this.leftBase) {
this.leftOffset = 0;
this.leftBase = offset;
} else {
this.leftOffset -= diff;
}
}
// XXX Move `action` and `select` events here.
this.emit('select item', el, offset);
};
Listbar.prototype.removeItem = function(child) {
var i = typeof child !== 'number'
? this.items.indexOf(child)
: child;
if (~i && this.items[i]) {
child = this.items.splice(i, 1)[0];
this.ritems.splice(i, 1);
this.commands.splice(i, 1);
this.remove(child);
if (i === this.selected) {
this.select(i - 1);
}
}
this.emit('remove item');
};
Listbar.prototype.move = function(offset) {
this.select(this.selected + offset);
};
Listbar.prototype.moveLeft = function(offset) {
this.move(-(offset || 1));
};
Listbar.prototype.moveRight = function(offset) {
this.move(offset || 1);
};
Listbar.prototype.selectTab = function(index) {
var item = this.items[index];
if (item) {
if (item._.cmd.callback) {
item._.cmd.callback();
}
this.select(index);
this.screen.render();
}
this.emit('select tab', item, index);
};
/**
* Expose
*/
module.exports = Listbar;