(function ()
{
var _sigCount = {};
var _byName = {};
var _byId = {};
var _getByCallback = function(callback)
{
var id;
for (id in _byId)
{
if (callback == _byId[id].callback)
{
return _byId[id];
}
}
return null;
};
var _getBySelfOrCallback = function(selfOrCallback)
{
if (selfOrCallback instanceof Signal)
return selfOrCallback;
return _getByCallback(selfOrCallback);
};
/**
*
* @name Signal
* @class
* A Signal can be used to connect to certain browser events. For a list
* of available signals see {@link signals}. Signals are directly
* emitted on {@link signals}, this class is just a convience
* class to handle signals.
* @description
* Constructs a new Signal
* @example
* function navigationCallback(wv, frame, request, response)
* {
* ...
* }
*
* // Create a new signal
* var a = new Signal("navigation", navigationCallback);
* var b = new Signal("navigation");
*
* // The following patterns are equivalent
* a.connect();
*
* b.connect(navigationCallback);
*
* // using the static method
* var c = Signal.connect("navigation", navigationCallback);
*
* // set the signal callback directly on {@link signals}
* // you won't get the constructed signal then
* signals.onNavigation = navigationCallback;
*
* @param {String} name
* The event to connect to
* @param {Function} [callback]
* Callback that will be called when the signal is emitted. If omitted
* a callback must be passed to {@link connect}.
*
* @returns {Signal}
* A new Signal
* */
Object.defineProperty(this, "Signal", {
writable : true,
value : (function() {
var id = 0;
return function(name, callback)
{
if (!name)
throw new Error("new Signal() : missing signal name");
id++;
return Object.create(Signal.prototype,
{
/**
* The id of the signal
* @name id
* @memberOf Signal.prototype
* @readonly
*
* */
"id" : { value : id },
/**
* The callback that will be called when the signal
* is emitted, the context of the signal will be the
* signal itself (i.e. this refers to the
* connected Signal).
*
* @name callback
* @memberOf Signal.prototype
*
* @example
* function a() {
* io.print("Calling from a");
* this.callback = b;
* }
* function b() {
* io.print("Calling from b");
* this.callback = a;
* }
* var s = new Signal("createTab", a).connect();
* */
"callback" : { value : callback, writable : true },
/**
* The name of the event
* @name name
* @memberOf Signal.prototype
* @readonly
* */
"name" : { value : name },
/**
* Disconnect this signal from the event, if disconnected the
* callback will no longer be called, to reconnect
* call signal.connect()
*
* @name disconnect
* @function
* @memberOf Signal.prototype
*
* @returns {Signal}
* self
*
* @example
* var i = 0;
* var signal = new Signal("navigation", function(wv, frame, request) {
* i++;
* if (i == 3)
* this.disconnect();
* });
* */
"disconnect" :
{
value : function()
{
if (!this.connected)
return this;
var name = this.name, id = this.id;
if (_sigCount[name] > 0)
_sigCount[name]--;
_byName[name][id] = null;
delete _byName[name][id];
_byId[id] = null;
delete _byId[id];
if (_sigCount[name] == 0)
signals[name] = null;
return this;
}
},
/**
* Connect this signal to the event
*
* @name connect
* @function
* @memberOf Signal.prototype
*
* @param {Function} [callback]
* The callback function to call, if no
* callback was passed to the constructor
* callback is mandatory.
*
* @returns {Signal}
* self
* @example
* function a() {
* ...
* }
* function b() {
* ...
* }
* var signal = new Signal("navigation", a);
* // connect to a
* signal.connect();
* // connect to b
* signal.connect(b);
* */
"connect" :
{
value : function(callback)
{
if (callback)
this.callback = callback;
if (this.connected)
return this;
if (!this.callback)
throw new Error("Signal.connect() : missing callback");
var name = this.name, id = this.id;
if (!_sigCount[name])
_sigCount[name] = 0;
if (!_byName[name])
_byName[name] = {};
if (_sigCount[name] == 0)
signals[name] = function() { return Signal.emit(name, arguments); };
_sigCount[name]++;
_byName[name][id] = this;
_byId[id] = this;
return this;
}
},
/**
* Whether the signal is connected
*
* @name connected
* @memberOf Signal.prototype
* @type Boolean
* @readonly
*
* */
"connected" :
{
get : function()
{
return Boolean(_byId[this.id]);
}
},
/**
* Toggles a signal, if it is connected it will be
* disconnected and vice versa
*
* @name toggle
* @memberOf Signal.prototype
* @function
*
* @returns {Boolean}
* true if the signal was connected, false
* otherwise
* */
"toggle" :
{
value : function()
{
var connected = this.connected;
if (connected)
this.disconnect();
else
this.connect();
return !connected;
}
}
}
);
};
})()
});
Object.defineProperties(Signal, {
/**
* Connects to an event
*
* @name connect
* @memberOf Signal
* @function
*
* @param {String} name
* The signal to connect to
* @param {Function} callback
* Callback that will be called when the signal is emitted.
*
* @returns {Signal}
* A new Signal
*
* @example
* function onCloseTab()
* {
* ...
* }
* var s = Signal.connect("closeTab", onCloseTab);
* // equivalent to
* var s = new Signal("closeTab", onCloseTab);
* s.connect();
* */
"connect" :
{
value : function(name, callback)
{
return new Signal(name, callback).connect();
}
},
/**
* Disconnects from an event.
* @name disconnect
* @memberOf Signal
* @function
*
* @param {Signal|Callback} object
* Either a Signal or the callback of a signal
* If a callback is passed to this function and the same
* callback is connected multiple times only the first matching
* callback will be disconnected, to disconnect all matching
* callbacks call use {@link Signal.disconnectAll}
*
* @returns {Signal}
* The disconnected Signal
*
* @example
* function callback(wv)
* {
* ...
* }
* var s = new Signal("loadStatus").connect(callback);
*
* // Disconnect from the first matching callback
* Signal.disconnect(callback);
*
* Signal.disconnect(s);
* // or equivalently
* s.disconnect();
* */
"disconnect" :
{
value : function(selfOrCallback)
{
var signal = _getBySelfOrCallback(selfOrCallback);
if (signal)
signal.disconnect();
return signal;
}
},
/**
* Connects all webviews to a GObject signal.
*
* @name connectWebView
* @memberOf Signal
* @function
*
* @param {String} signal The signal name
* @param {GObject~connectCallback} callback
* A callback function the will be called when the signal is
* emitted, the arguments of the callback correspond to the GObject
* callback
* @example
* Signal.connectWebView("hovering-over-link", function(title, uri) {
* io.write("/tmp/hovered_sites", "a", uri + " " + title);
* });
*
* */
"connectWebView" :
{
value : function(name, callback)
{
var wv;
for (var i=0; itrue the overall return value
* will be true
* */
"emit" :
{
value : function(signal, args)
{
var id, current;
var ret = false;
var connected = _byName[signal];
for (id in connected)
{
current = connected[id];
ret = current.callback.apply(current, args) || ret;
}
return ret;
}
},
/**
* Disconnect from all signals with matching callback function
*
* @name disconnectAll
* @memberOf Signal
* @function
*
* @param {Function} callback
* A callback function
*
* @returns {Array}
* Array of signals that were disconnected
*
* @example
* function onNavigation(wv, frame, request)
* {
* ...
* }
* var a = new Signal("navigation", onNavigation).connect();
* var b = new Signal("navigation", onNavigation).connect();
*
* Signals.disconnectAll(onNavigation);
* */
"disconnectAll" :
{
value : function(callback)
{
var signals = [];
var signal;
while((signal = _getBySelfOrCallback(callback)))
{
if (signal.connected)
{
signals.push(signal);
signal.disconnect();
}
}
return signals;
}
},
/**
* Connect to more than one signal at once
*
* @name connectAll
* @memberOf Signal
* @function
*
* @param {Array} signals
* Array of signals
* @param {Function} [callback]
* Callbackfunction to connect to
*
*
* @example
* function onNavigation(wv, frame, request)
* {
* ...
* }
* function onNavigation2(wv, frame, request)
* {
* ...
* }
* var a = new Signal("navigation", onNavigation).connect();
* var b = new Signal("navigation", onNavigation).connect();
*
* // disconnect from all signals
* var signals = Signal.disconnectAll(onNavigation);
*
* // reconnect to all signals
* Signal.connectAll(signals);
*
* // Reconnect to all signals with a new callback
* Signal.connectAll([a, b], onNavigation2);
* */
"connectAll" :
{
value : function(signalOrArray, callback)
{
var i, l;
if (signalOrArray instanceof Signal)
signalOrArray.connect(callback);
else
{
for (i=signalOrArray.length-1; i>=0; i--)
signalOrArray[i].connect(callback);
}
}
}
});
Object.defineProperties(signals, {
/**
* @name emit
* @memberOf signals
* @function
* @deprecated use {@link Signal.emit} instead
* */
"emit" :
{
value : function(sig, func, pre)
{
return _deprecated("signals.emit", "Signal.emit", arguments);
}
},
/**
* @name connect
* @memberOf signals
* @function
* @deprecated use {@link Signal.connect} instead
* */
"connect" :
{
value : function(sig, func, pre)
{
return _deprecated("signals.connect", "Signal.connect", arguments);
}
},
/**
* Connects all webviews to a GObject signal.
*
* @name connectWebView
* @memberOf signals
* @function
* @deprecated use {@link Signal.connectWebView} instead
*
* */
"connectWebView" :
{
value : function()
{
return _deprecated("signals.connectWebView", "Signal.connectWebView", arguments);
}
},
/**
* @name disconnect
* @memberOf signals
* @function
* @deprecated use {@link Signal.disconnect} instead
* */
"disconnect" :
{
value : function()
{
return _deprecated("signals.disconnect", "Signal.disconnect", arguments);
}
},
/**
* @name disconnectAll
* @memberOf signals
* @function
* @deprecated use {@link Signal.disconnectAll}
* */
"disconnectAll" :
{
value : function(callback)
{
return _deprecated("signals.disconnectAll", "Signal.disconnectAll", arguments);
}
},
/**
* @name connectAll
* @memberOf signals
* @function
* @deprecated use {@link Signal.connectAll} instead
* */
"connectAll" :
{
value : function()
{
return _deprecated("signals.connectAll", "Signal.connectAll", arguments);
}
}
});
})();