diff --git a/lib/events.js b/lib/events.js new file mode 100644 index 0000000..3acb07a --- /dev/null +++ b/lib/events.js @@ -0,0 +1,171 @@ +/** + * events.js - event emitter for blessed + * Copyright (c) 2013, Christopher Jeffrey (MIT License) + * https://github.com/chjj/blessed + */ + +var slice = Array.prototype.slice; + +/** + * EventEmitter + */ + +function EventEmitter() { + if (!this._events) this._events = {}; +} + +EventEmitter.prototype.addListener = function(type, listener) { + if (!this._events[type]) { + this._events[type] = listener; + } else if (typeof this._events[type] === 'function') { + this._events[type] = [this._events[type], listener]; + } else { + this._events[type].push(listener); + } + this._emit('newListener', [type, listener]); +}; + +EventEmitter.prototype.on = EventEmitter.prototype.addListener; + +EventEmitter.prototype.removeListener = function(type, listener) { + var handler = this._events[type]; + if (!handler) return; + + if (typeof handler === 'function' || handler.length === 1) { + delete this._events[type]; + this._emit('removeListener', [type, listener]); + return; + } + + for (var i = 0; i < handler.length; i++) { + if (handler[i] === listener || handler[i].listener === listener) { + handler.splice(i, 1); + this._emit('removeListener', [type, listener]); + return; + } + } +}; + +EventEmitter.prototype.off = EventEmitter.prototype.removeListener; + +EventEmitter.prototype.removeAllListeners = function(type) { + if (type) { + delete this._events[type]; + } else { + this._events = {}; + } +}; + +EventEmitter.prototype.once = function(type, listener) { + function on() { + this.removeListener(type, on); + return listener.apply(this, arguments); + } + on.listener = listener; + return this.on(type, on); +}; + +EventEmitter.prototype.listeners = function(type) { + return typeof this._events[type] === 'function' + ? [this._events[type]] + : this._events[type] || []; +}; + +EventEmitter.prototype._emit = function(type, args) { + var handler = this._events[type] + , ret; + + if (!handler) { + if (type === 'error') { + throw new Error('Unhandled error event: ' + + JSON.stringify(slice.call(arguments))); + } + return; + } + + if (typeof handler === 'function') { + return handler.apply(this, args); + } + + for (var i = 0; i < handler.length; i++) { + if (handler[i].apply(this, args) === false) { + ret = false; + } + } + + return ret !== false; +}; + +EventEmitter.prototype.emit = function(type) { + var args = slice.call(arguments, 1) + , el = this; + + if (this.type === 'screen') { + return this._emit(type, args); + } + + this._emit(type, args); + + type = 'element ' + type; + args.unshift(this); + + do { + if (!el._events[type]) continue; + if (el._emit(type, args) === false) { + return false; + } + } while (el = el.parent); + + return true; +}; + +// For hooking into the main EventEmitter if we want to. +// Might be better to do things this way being that it +// will always be compatible with node, not to mention +// it gives us domain support as well. +// Node.prototype._emit = Node.prototype.emit; +// Node.prototype.emit = function(type) { +// var args, el; +// +// if (this.type === 'screen') { +// return this._emit.apply(this, arguments); +// } +// +// this._emit.apply(this, arguments); +// +// args = slice.call(arguments, 1); +// el = this; +// +// args.unshift('element ' + type, this); +// this._bubbleStopped = false; +// //args.push(stopBubble); +// +// do { +// if (!el._events || !el._events[type]) continue; +// el._emit.apply(el, args); +// if (this._bubbleStopped) return false; +// } while (el = el.parent); +// +// return true; +// }; +// +// Node.prototype._addListener = Node.prototype.addListener; +// Node.prototype.on = +// Node.prototype.addListener = function(type, listener) { +// function on() { +// if (listener.apply(this, arguments) === false) { +// this._bubbleStopped = true; +// } +// } +// on.listener = listener; +// return this._addListener(type, on); +// }; + +/** + * Expose + */ + +exports = EventEmitter; +exports.EventEmitter = EventEmitter; + +module.exports = exports; diff --git a/lib/widget.js b/lib/widget.js index 7135c0b..3f58a6f 100644 --- a/lib/widget.js +++ b/lib/widget.js @@ -9,7 +9,7 @@ * Modules */ -var EventEmitter = require('events').EventEmitter +var EventEmitter = require('./events').EventEmitter , assert = require('assert') , path = require('path') , fs = require('fs') @@ -325,7 +325,6 @@ function Screen(options) { el.emit('resize'); el.children.forEach(emit); })(self); - // self.emitDescendants('resize'); } this.program.on('resize', function() { @@ -427,6 +426,10 @@ Screen.prototype._listenMouse = function(el) { if (el && !~this.clickable.indexOf(el)) { el.clickable = true; + if (el.options.autoFocus && !el._autoFocused) { + el._autoFocused = true; + el.on('element click', el.focus.bind(el)); + } this.clickable.push(el); } @@ -435,8 +438,6 @@ Screen.prototype._listenMouse = function(el) { this.program.enableMouse(); - // this.on('element click', el.focus.bind(el)); - this.program.on('mouse', function(data) { if (self.lockKeys) return; @@ -472,10 +473,8 @@ Screen.prototype._listenMouse = function(el) { if (data.x >= left && data.x < left + width && data.y >= top && data.y < top + height) { el.emit('mouse', data); - self.emit('element mouse', el, data); if (data.action === 'mouseup') { el.emit('click', data); - self.emit('element click', el, data); } else if (data.action === 'mousemove') { if (self.hover && el.index > self.hover.index) { set = false; @@ -483,28 +482,14 @@ Screen.prototype._listenMouse = function(el) { if (self.hover !== el && !set) { if (self.hover) { self.hover.emit('mouseout', data); - self.emit('element mouseout', self.hover, data); } el.emit('mouseover', data); - self.emit('element mouseover', el, data); self.hover = el; } set = true; } el.emit(data.action, data); - self.emit('element ' + data.action, el, data); - // XXX Temporary workaround for List wheel events. - // Make sure they get propogated to the list instead of the list item. - if ((data.action === 'wheeldown' || data.action === 'wheelup') - && el.listeners(data.action).length === 0 - && el.parent.listeners(data.action).length > 0) { - el.parent.emit(data.action, data); - self.emit('element ' + data.action, el.parent, data); - } - - // Seems to work better without this if statement: - // if (data.action !== 'mousemove') break; } } @@ -516,7 +501,6 @@ Screen.prototype._listenMouse = function(el) { && self.hover && !set) { self.hover.emit('mouseout', data); - self.emit('element mouseout', self.hover, data); self.hover = null; } @@ -532,10 +516,12 @@ Screen.prototype._listenKeys = function(el) { el.keyable = true; // Listen for click, but do not enable // mouse if it's not enabled yet. - if (el.options.autoFocus !== false) { + if (el.options.autoFocus !== false && !el._autoFocused) { + el._autoFocused = true; var lm = this._listenedMouse; this._listenedMouse = true; - el.on('click', el.focus.bind(el)); + this._listenMouse(el); + el.on('element click', el.focus.bind(el)); this._listenedMouse = lm; } this.keyable.push(el); @@ -570,8 +556,6 @@ Screen.prototype._listenKeys = function(el) { if (~self.keyable.indexOf(focused)) { focused.emit('keypress', ch, key); focused.emit('key ' + key.full, ch, key); - self.emit('element keypress', focused, ch, key); - self.emit('element key ' + key.full, focused, ch, key); } }); }; @@ -1438,7 +1422,6 @@ Screen.prototype.rewindFocus = function() { if (old) { old.emit('blur'); - this.screen.emit('element blur', old); } }; @@ -1470,11 +1453,9 @@ Screen.prototype._focus = function(self, old) { if (old) { old.emit('blur', self); - self.screen.emit('element blur', old, self); } self.emit('focus', old); - self.screen.emit('element focus', old, self); }; Screen.prototype.__defineGetter__('focused', function() { @@ -1838,6 +1819,7 @@ function Element(options) { // TODO: Possibly move this to Node for screen.on('mouse', ...). this.on('newListener', function fn(type) { + //type = type.split(' ').slice(1).join(' '); if (type === 'mouse' || type === 'click' || type === 'mouseover' @@ -3503,11 +3485,11 @@ function ScrollableBox(options) { } if (options.mouse) { - this.on('wheeldown', function(data) { + this.on('wheeldown', function(el, data) { self.scroll(self.height / 2 | 0 || 1); self.screen.render(); }); - this.on('wheelup', function(data) { + this.on('wheelup', function(el, data) { self.scroll(-(self.height / 2 | 0) || -1); self.screen.render(); }); @@ -3808,12 +3790,16 @@ function List(options) { } if (options.mouse) { - this.on('wheeldown', function(data) { + this.screen._listenMouse(this); + //this.screen.on('element wheeldown', function(el, data) { + this.on('element wheeldown', function(el, data) { + // if (el !== self && !el.hasAncestor(self)) return; self.select(self.selected + 2); self.screen.render(); }); - - this.on('wheelup', function(data) { + //this.screen.on('element wheelup', function(el, data) { + this.on('element wheelup', function(el, data) { + // if (el !== self && !el.hasAncestor(self)) return; self.select(self.selected - 2); self.screen.render(); }); @@ -4063,16 +4049,15 @@ function Form(options) { if (options.keys) { this.screen._listenKeys(this); - this.screen.on('element keypress', function(el, ch, key) { + //this.screen.on('element keypress', function(el, ch, key) { + this.on('element keypress', function(el, ch, key) { // Make sure we're not entering input into a textbox. // if (self.screen.grabKeys || self.screen.lockKeys) { // return; // } // Make sure we're a form or input element. - if (el !== self && !el.hasAncestor(self)) { - return; - } + // if (el !== self && !el.hasAncestor(self)) return; if ((key.name === 'tab' && !key.shift) || key.name === 'down' diff --git a/test/widget.js b/test/widget.js index 6a03d08..f32f11e 100644 --- a/test/widget.js +++ b/test/widget.js @@ -183,7 +183,7 @@ stext.on('keypress', function(ch, key) { } }); -screen.on('element focus', function(old, cur) { +screen.on('element focus', function(cur, old) { if (old.border) old.style.border.fg = 'default'; if (cur.border) cur.style.border.fg = 'green'; screen.render();