/scripts/lib/signals.js

https://bitbucket.org/thenonameguy/dwb · JavaScript · 569 lines · 227 code · 15 blank · 327 comment · 27 complexity · 4583080f127e9442197c8bd5ff656cd5 MD5 · raw file

  1. (function ()
  2. {
  3. var _sigCount = {};
  4. var _byName = {};
  5. var _byId = {};
  6. var _getByCallback = function(callback)
  7. {
  8. var id;
  9. for (id in _byId)
  10. {
  11. if (callback == _byId[id].callback)
  12. {
  13. return _byId[id];
  14. }
  15. }
  16. return null;
  17. };
  18. var _getBySelfOrCallback = function(selfOrCallback)
  19. {
  20. if (selfOrCallback instanceof Signal)
  21. return selfOrCallback;
  22. return _getByCallback(selfOrCallback);
  23. };
  24. /**
  25. *
  26. * @name Signal
  27. * @class
  28. * A Signal can be used to connect to certain browser events. For a list
  29. * of available signals see {@link signals}. Signals are directly
  30. * emitted on {@link signals}, this class is just a convience
  31. * class to handle signals.
  32. * @description
  33. * Constructs a new Signal
  34. * @example
  35. * function navigationCallback(wv, frame, request, response)
  36. * {
  37. * ...
  38. * }
  39. *
  40. * // Create a new signal
  41. * var a = new Signal("navigation", navigationCallback);
  42. * var b = new Signal("navigation");
  43. *
  44. * // The following patterns are equivalent
  45. * a.connect();
  46. *
  47. * b.connect(navigationCallback);
  48. *
  49. * // using the static method
  50. * var c = Signal.connect("navigation", navigationCallback);
  51. *
  52. * // set the signal callback directly on {@link signals}
  53. * // you won't get the constructed signal then
  54. * signals.onNavigation = navigationCallback;
  55. *
  56. * @param {String} name
  57. * The event to connect to
  58. * @param {Function} [callback]
  59. * Callback that will be called when the signal is emitted. If omitted
  60. * a callback must be passed to {@link connect}.
  61. *
  62. * @returns {Signal}
  63. * A new Signal
  64. * */
  65. Object.defineProperty(this, "Signal", {
  66. writable : true,
  67. value : (function() {
  68. var id = 0;
  69. return function(name, callback)
  70. {
  71. if (!name)
  72. throw new Error("new Signal() : missing signal name");
  73. id++;
  74. return Object.create(Signal.prototype,
  75. {
  76. /**
  77. * The id of the signal
  78. * @name id
  79. * @memberOf Signal.prototype
  80. * @readonly
  81. *
  82. * */
  83. "id" : { value : id },
  84. /**
  85. * The callback that will be called when the signal
  86. * is emitted, the context of the signal will be the
  87. * signal itself (i.e. <i>this</i> refers to the
  88. * connected Signal).
  89. *
  90. * @name callback
  91. * @memberOf Signal.prototype
  92. *
  93. * @example
  94. * function a() {
  95. * io.print("Calling from a");
  96. * this.callback = b;
  97. * }
  98. * function b() {
  99. * io.print("Calling from b");
  100. * this.callback = a;
  101. * }
  102. * var s = new Signal("createTab", a).connect();
  103. * */
  104. "callback" : { value : callback, writable : true },
  105. /**
  106. * The name of the event
  107. * @name name
  108. * @memberOf Signal.prototype
  109. * @readonly
  110. * */
  111. "name" : { value : name },
  112. /**
  113. * Disconnect this signal from the event, if disconnected the
  114. * callback will no longer be called, to reconnect
  115. * call signal.connect()
  116. *
  117. * @name disconnect
  118. * @function
  119. * @memberOf Signal.prototype
  120. *
  121. * @returns {Signal}
  122. * self
  123. *
  124. * @example
  125. * var i = 0;
  126. * var signal = new Signal("navigation", function(wv, frame, request) {
  127. * i++;
  128. * if (i == 3)
  129. * this.disconnect();
  130. * });
  131. * */
  132. "disconnect" :
  133. {
  134. value : function()
  135. {
  136. if (!this.connected)
  137. return this;
  138. var name = this.name, id = this.id;
  139. if (_sigCount[name] > 0)
  140. _sigCount[name]--;
  141. _byName[name][id] = null;
  142. delete _byName[name][id];
  143. _byId[id] = null;
  144. delete _byId[id];
  145. if (_sigCount[name] == 0)
  146. signals[name] = null;
  147. return this;
  148. }
  149. },
  150. /**
  151. * Connect this signal to the event
  152. *
  153. * @name connect
  154. * @function
  155. * @memberOf Signal.prototype
  156. *
  157. * @param {Function} [callback]
  158. * The callback function to call, if no
  159. * callback was passed to the constructor
  160. * callback is mandatory.
  161. *
  162. * @returns {Signal}
  163. * self
  164. * @example
  165. * function a() {
  166. * ...
  167. * }
  168. * function b() {
  169. * ...
  170. * }
  171. * var signal = new Signal("navigation", a);
  172. * // connect to a
  173. * signal.connect();
  174. * // connect to b
  175. * signal.connect(b);
  176. * */
  177. "connect" :
  178. {
  179. value : function(callback)
  180. {
  181. if (callback)
  182. this.callback = callback;
  183. if (this.connected)
  184. return this;
  185. if (!this.callback)
  186. throw new Error("Signal.connect() : missing callback");
  187. var name = this.name, id = this.id;
  188. if (!_sigCount[name])
  189. _sigCount[name] = 0;
  190. if (!_byName[name])
  191. _byName[name] = {};
  192. if (_sigCount[name] == 0)
  193. signals[name] = function() { return Signal.emit(name, arguments); };
  194. _sigCount[name]++;
  195. _byName[name][id] = this;
  196. _byId[id] = this;
  197. return this;
  198. }
  199. },
  200. /**
  201. * Whether the signal is connected
  202. *
  203. * @name connected
  204. * @memberOf Signal.prototype
  205. * @type Boolean
  206. * @readonly
  207. *
  208. * */
  209. "connected" :
  210. {
  211. get : function()
  212. {
  213. return Boolean(_byId[this.id]);
  214. }
  215. },
  216. /**
  217. * Toggles a signal, if it is connected it will be
  218. * disconnected and vice versa
  219. *
  220. * @name toggle
  221. * @memberOf Signal.prototype
  222. * @function
  223. *
  224. * @returns {Boolean}
  225. * <i>true</i> if the signal was connected, <i>false</i>
  226. * otherwise
  227. * */
  228. "toggle" :
  229. {
  230. value : function()
  231. {
  232. var connected = this.connected;
  233. if (connected)
  234. this.disconnect();
  235. else
  236. this.connect();
  237. return !connected;
  238. }
  239. }
  240. }
  241. );
  242. };
  243. })()
  244. });
  245. Object.defineProperties(Signal, {
  246. /**
  247. * Connects to an event
  248. *
  249. * @name connect
  250. * @memberOf Signal
  251. * @function
  252. *
  253. * @param {String} name
  254. * The signal to connect to
  255. * @param {Function} callback
  256. * Callback that will be called when the signal is emitted.
  257. *
  258. * @returns {Signal}
  259. * A new Signal
  260. *
  261. * @example
  262. * function onCloseTab()
  263. * {
  264. * ...
  265. * }
  266. * var s = Signal.connect("closeTab", onCloseTab);
  267. * // equivalent to
  268. * var s = new Signal("closeTab", onCloseTab);
  269. * s.connect();
  270. * */
  271. "connect" :
  272. {
  273. value : function(name, callback)
  274. {
  275. return new Signal(name, callback).connect();
  276. }
  277. },
  278. /**
  279. * Disconnects from an event.
  280. * @name disconnect
  281. * @memberOf Signal
  282. * @function
  283. *
  284. * @param {Signal|Callback} object
  285. * Either a Signal or the callback of a signal
  286. * If a callback is passed to this function and the same
  287. * callback is connected multiple times only the first matching
  288. * callback will be disconnected, to disconnect all matching
  289. * callbacks call use {@link Signal.disconnectAll}
  290. *
  291. * @returns {Signal}
  292. * The disconnected Signal
  293. *
  294. * @example
  295. * function callback(wv)
  296. * {
  297. * ...
  298. * }
  299. * var s = new Signal("loadStatus").connect(callback);
  300. *
  301. * // Disconnect from the first matching callback
  302. * Signal.disconnect(callback);
  303. *
  304. * Signal.disconnect(s);
  305. * // or equivalently
  306. * s.disconnect();
  307. * */
  308. "disconnect" :
  309. {
  310. value : function(selfOrCallback)
  311. {
  312. var signal = _getBySelfOrCallback(selfOrCallback);
  313. if (signal)
  314. signal.disconnect();
  315. return signal;
  316. }
  317. },
  318. /**
  319. * Connects all webviews to a GObject signal.
  320. *
  321. * @name connectWebView
  322. * @memberOf Signal
  323. * @function
  324. *
  325. * @param {String} signal The signal name
  326. * @param {GObject~connectCallback} callback
  327. * A callback function the will be called when the signal is
  328. * emitted, the arguments of the callback correspond to the GObject
  329. * callback
  330. * @example
  331. * Signal.connectWebView("hovering-over-link", function(title, uri) {
  332. * io.write("/tmp/hovered_sites", "a", uri + " " + title);
  333. * });
  334. *
  335. * */
  336. "connectWebView" :
  337. {
  338. value : function(name, callback)
  339. {
  340. var wv;
  341. for (var i=0; i<tabs.length; i++)
  342. {
  343. if (tabs.nth(i))
  344. tabs.nth(i).connect(name, function() { callback.apply(wv, arguments); });
  345. }
  346. Signal.connect("createTab", function(wv) {
  347. wv.connect(name, function() { callback.apply(wv, arguments);});
  348. });
  349. }
  350. },
  351. /**
  352. * Emits a signal, can be used to implement custom signals.
  353. *
  354. * @name emit
  355. * @memberOf Signal
  356. * @function
  357. *
  358. * @param {String} signal The signal name
  359. * @param {varargs} args Arguments passed to the callback function of
  360. * {@link Signal.connect}
  361. *
  362. * @returns {Boolean}
  363. * The overall return value of all callback function, if one
  364. * callback function returns <i>true</i> the overall return value
  365. * will be <i>true</i>
  366. * */
  367. "emit" :
  368. {
  369. value : function(signal, args)
  370. {
  371. var id, current;
  372. var ret = false;
  373. var connected = _byName[signal];
  374. for (id in connected)
  375. {
  376. current = connected[id];
  377. ret = current.callback.apply(current, args) || ret;
  378. }
  379. return ret;
  380. }
  381. },
  382. /**
  383. * Disconnect from all signals with matching callback function
  384. *
  385. * @name disconnectAll
  386. * @memberOf Signal
  387. * @function
  388. *
  389. * @param {Function} callback
  390. * A callback function
  391. *
  392. * @returns {Array}
  393. * Array of signals that were disconnected
  394. *
  395. * @example
  396. * function onNavigation(wv, frame, request)
  397. * {
  398. * ...
  399. * }
  400. * var a = new Signal("navigation", onNavigation).connect();
  401. * var b = new Signal("navigation", onNavigation).connect();
  402. *
  403. * Signals.disconnectAll(onNavigation);
  404. * */
  405. "disconnectAll" :
  406. {
  407. value : function(callback)
  408. {
  409. var signals = [];
  410. var signal;
  411. while((signal = _getBySelfOrCallback(callback)))
  412. {
  413. if (signal.connected)
  414. {
  415. signals.push(signal);
  416. signal.disconnect();
  417. }
  418. }
  419. return signals;
  420. }
  421. },
  422. /**
  423. * Connect to more than one signal at once
  424. *
  425. * @name connectAll
  426. * @memberOf Signal
  427. * @function
  428. *
  429. * @param {Array} signals
  430. * Array of signals
  431. * @param {Function} [callback]
  432. * Callbackfunction to connect to
  433. *
  434. *
  435. * @example
  436. * function onNavigation(wv, frame, request)
  437. * {
  438. * ...
  439. * }
  440. * function onNavigation2(wv, frame, request)
  441. * {
  442. * ...
  443. * }
  444. * var a = new Signal("navigation", onNavigation).connect();
  445. * var b = new Signal("navigation", onNavigation).connect();
  446. *
  447. * // disconnect from all signals
  448. * var signals = Signal.disconnectAll(onNavigation);
  449. *
  450. * // reconnect to all signals
  451. * Signal.connectAll(signals);
  452. *
  453. * // Reconnect to all signals with a new callback
  454. * Signal.connectAll([a, b], onNavigation2);
  455. * */
  456. "connectAll" :
  457. {
  458. value : function(signalOrArray, callback)
  459. {
  460. var i, l;
  461. if (signalOrArray instanceof Signal)
  462. signalOrArray.connect(callback);
  463. else
  464. {
  465. for (i=signalOrArray.length-1; i>=0; i--)
  466. signalOrArray[i].connect(callback);
  467. }
  468. }
  469. }
  470. });
  471. Object.defineProperties(signals, {
  472. /**
  473. * @name emit
  474. * @memberOf signals
  475. * @function
  476. * @deprecated use {@link Signal.emit} instead
  477. * */
  478. "emit" :
  479. {
  480. value : function(sig, func, pre)
  481. {
  482. return _deprecated("signals.emit", "Signal.emit", arguments);
  483. }
  484. },
  485. /**
  486. * @name connect
  487. * @memberOf signals
  488. * @function
  489. * @deprecated use {@link Signal.connect} instead
  490. * */
  491. "connect" :
  492. {
  493. value : function(sig, func, pre)
  494. {
  495. return _deprecated("signals.connect", "Signal.connect", arguments);
  496. }
  497. },
  498. /**
  499. * Connects all webviews to a GObject signal.
  500. *
  501. * @name connectWebView
  502. * @memberOf signals
  503. * @function
  504. * @deprecated use {@link Signal.connectWebView} instead
  505. *
  506. * */
  507. "connectWebView" :
  508. {
  509. value : function()
  510. {
  511. return _deprecated("signals.connectWebView", "Signal.connectWebView", arguments);
  512. }
  513. },
  514. /**
  515. * @name disconnect
  516. * @memberOf signals
  517. * @function
  518. * @deprecated use {@link Signal.disconnect} instead
  519. * */
  520. "disconnect" :
  521. {
  522. value : function()
  523. {
  524. return _deprecated("signals.disconnect", "Signal.disconnect", arguments);
  525. }
  526. },
  527. /**
  528. * @name disconnectAll
  529. * @memberOf signals
  530. * @function
  531. * @deprecated use {@link Signal.disconnectAll}
  532. * */
  533. "disconnectAll" :
  534. {
  535. value : function(callback)
  536. {
  537. return _deprecated("signals.disconnectAll", "Signal.disconnectAll", arguments);
  538. }
  539. },
  540. /**
  541. * @name connectAll
  542. * @memberOf signals
  543. * @function
  544. * @deprecated use {@link Signal.connectAll} instead
  545. * */
  546. "connectAll" :
  547. {
  548. value : function()
  549. {
  550. return _deprecated("signals.connectAll", "Signal.connectAll", arguments);
  551. }
  552. }
  553. });
  554. })();