diff --git a/share/web_surfaces/builtin/mixer-demo/css/main.css b/share/web_surfaces/builtin/mixer-demo/css/main.css deleted file mode 100644 index f0543be875..0000000000 --- a/share/web_surfaces/builtin/mixer-demo/css/main.css +++ /dev/null @@ -1,134 +0,0 @@ -html { - height: 100%; -} - -body { - background: rgb(62,61,61); - color: rgb(248,248,242); - font-family: Helvetica, Arial, sans-serif; - height: 100%; - width: 100%; - position: fixed; - margin: 0; -} - -div { - box-sizing: border-box; -} - -#main { - display: flex; - flex-direction: column; - height: 100%; -} - -#surface { - flex: 1; - display: flex; - flex-direction: column; - overflow: hidden; - box-shadow: 0px 0px 10px #000; -} - -#top { - display: flex; - align-items: center; - padding: 0.25em 0.5em; - box-shadow: 0px 0px 10px #000; -} - -#top > img { - height: 24px; -} - -#top > span { - margin-left: 12px; -} - -#log { - height: 7em; - overflow: scroll; - overflow-x: hidden; - background: rgba(0,0,0,0.4); -} - -#strips { - flex: 1; - display: flex; - flex-direction: column; - align-items: center; - overflow: scroll; - overflow-x: hidden; - background: rgba(0,0,0,0.2); -} - -#log pre { - margin: 0; - font-family: Menlo, monospace; - font-size: 1em; -} - -.message-in { - color: rgb(248,248,242); -} - -.message-out { - color: rgb(172,128,255); -} - -.info { - color: rgb(99,208,230); -} - -.error { - color: rgb(249,36,114); -} - -.comp-name { - font-size: 1.5em; - font-weight: bold; -} - -.strip { - margin: 20px; - padding: 20px 40px; - background: rgba(0,0,0,0.1); - border-radius: 5px; - width: 100%; - max-width: 768px; -} - -.slider-meter { - float: right; -} - -.strip-slider { - margin-top: 20px; -} - -.plugin { - margin: 40px 0; - padding: 20px; - background: rgba(0,0,0,0.05); - border-radius: 5px; -} - -.plugin-enable { - float: right; -} - -.plugin-param { - margin: 20px 0; -} - -.plugin-param.boolean { - text-align: center; -} - -.plugin-param > .widget-switch { - margin: 10px 40px; -} - -.plugin-param.boolean { - display: inline-block; -} diff --git a/share/web_surfaces/builtin/mixer-demo/css/widget.css b/share/web_surfaces/builtin/mixer-demo/css/widget.css deleted file mode 100644 index 9681f5d651..0000000000 --- a/share/web_surfaces/builtin/mixer-demo/css/widget.css +++ /dev/null @@ -1,52 +0,0 @@ -.widget-switch { - display: block; - -webkit-appearance:none; - width: 37px; - height: 37px; - border: 3.5px solid rgb(248,248,242); - border-radius: 50%; -} - -.widget-switch:checked { - background: rgb(72,89,118); -} - -.widget-slider { - display: block; - -webkit-appearance: none; - height: 37px; - width: 100%; - background: transparent; -} - -.widget-slider::-webkit-slider-runnable-track { - height: 4px; - background: rgb(248,248,242); -} - -.widget-slider::-webkit-slider-thumb { - -webkit-appearance: none; - height: 36px; - width: 36px; - margin-top: -16px; - border: 3.5px solid rgb(248,248,242); - border-radius: 50%; - background: rgb(72,89,118); -} - -/* repeat slider style for firefox */ - -.widget-slider::-moz-range-track { - height: 4px; - background: rgb(248,248,242); -} - -.widget-slider::-moz-range-thumb { - -webkit-appearance: none; - height: 36px; - width: 36px; - margin-top: -16px; - border: 3.5px solid rgb(248,248,242); - border-radius: 50%; - background: rgb(72,89,118); -} diff --git a/share/web_surfaces/builtin/mixer-demo/index.html b/share/web_surfaces/builtin/mixer-demo/index.html deleted file mode 100644 index a2262f2b3f..0000000000 --- a/share/web_surfaces/builtin/mixer-demo/index.html +++ /dev/null @@ -1,23 +0,0 @@ - - -
- -center
+ * needs to subtract half of its width from y and half of its height
+ * from x to appear at the correct position.
+ *
+ * @mixin TK.Anchor
+ */
+TK.Anchor = TK.class({
+ /**
+ * Returns real x and y values from a relative positioning.
+ *
+ * @method TK.Anchor#translate_anchor
+ *
+ * @param {string} [anchor="top-left"] - Position of the anchor.
+ * @param {number} x - X position to translate.
+ * @param {number} y - Y position to translate.
+ * @param {number} width - Width of the element.
+ * @param {number} height - Height of the element.
+ *
+ * @returns {object} Object with members x and y as numbers
+ */
+ translate_anchor: function (anchor, x, y, width, height) {
+ switch (anchor) {
+ case "top-left":
+ break;
+ case "top":
+ x += width / 2;
+ break;
+ case "top-right":
+ x += width;
+ break;
+ case "left":
+ y += height / 2;
+ break;
+ case "center":
+ x += width / 2;
+ y += height / 2;
+ break;
+ case "right":
+ x += width;
+ y += height / 2;
+ break;
+ case "bottom-left":
+ y += height;
+ break;
+ case "bottom":
+ x += width / 2;
+ y += height;
+ break;
+ case "bottom-right":
+ x += width;
+ y += height;
+ break;
+ default:
+ throw new Error("Unsupported anchor position");
+ }
+ return {x: Math.round(x), y: Math.round(y)};
+ }
+});
+})(this, this.TK);
diff --git a/share/web_surfaces/builtin/mixer/toolkit/implements/audiomath.js b/share/web_surfaces/builtin/mixer/toolkit/implements/audiomath.js
new file mode 100644
index 0000000000..a1a153c297
--- /dev/null
+++ b/share/web_surfaces/builtin/mixer/toolkit/implements/audiomath.js
@@ -0,0 +1,324 @@
+/*
+ * This file is part of Toolkit.
+ *
+ * Toolkit is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Toolkit is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU General
+ * Public License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA
+ */
+"use strict";
+/**
+ * TK.AudioMath provides a couple of functions for turning
+ * linear values into logarithmic ones and vice versa. If you need
+ * an easy convertion between dB or Hz and a linear scale mixin
+ * this class.
+ *
+ * @mixin TK.AudioMath
+ */
+TK.AudioMath = (function() {
+ var exp = Math.exp;
+ var log = Math.log;
+ var pow = Math.pow;
+ var MAX = Math.max;
+ var LN2 = Math.LN2;
+ var LN10 = Math.LN10;
+
+ function log2(value) {
+ value = +value;
+ return +log(value) / LN2;
+ }
+
+ function log10(value) {
+ value = +value;
+ return +log(value) / LN10;
+ }
+
+ function db2gain(value, factor) {
+ /**
+ * Calculates 10^(value / factor).
+ * Transforms a dBFS value to the corresponding gain.
+ *
+ * @function TK.AudioMath#db2gain
+ *
+ * @param {number} value - A decibel value in dBFS.
+ * @param {number} [factor=20] - The factor.
+ */
+ value = +value;
+ factor = +factor;
+
+ if (!(factor >= 0.0)) factor = 20.0;
+
+ value = +(value / factor);
+ value = +pow(10.0, value);
+
+ return value;
+ }
+
+ function gain2db(value, factor) {
+ /**
+ * Calculates factor * log10(value).
+ * Transforms a gain value to the corresponding dBFS value.
+ *
+ * @function TK.AudioMath#gain2db
+ *
+ * @param {number} value - A gain factor.
+ * @param {number} [factor=20] - The factor.
+ */
+ value = +value;
+ factor = +factor;
+
+ if (!(factor >= 0.0)) factor = 20.0;
+
+ value = factor * +log10(value);
+
+ return value;
+ }
+
+ function db2coef(value, min, max, reverse, factor) {
+ /**
+ * Calculates a linear value between 0.0 and 1.0
+ * from a value and its lower and upper boundaries in decibels.
+ *
+ * @function TK.AudioMath#db2coef
+ *
+ * @param {number} value - The value in decibels.
+ * @param {number} min - The minimum value in decibels.
+ * @param {number} max - The maximum value in decibels.
+ * @param {boolean} reverse - If the scale is reversed.
+ * @param {number} factor - Changes the deflection of the logarithm if other than 1.0.
+ *
+ * @returns {number} A value between 0.0 (min) and 1.0 (max).
+ */
+ value = +value;
+ min = +min;
+ max = +max;
+ reverse = reverse|0;
+ factor = +factor;
+ var logfac = 1.0;
+ if (factor == 0.0) factor = 1.0;
+ else logfac = +MAX(1.0, +pow(2.0, factor) - 1.0);
+ if (reverse) value = max - (value - min);
+ value = +log2(1.0 + (value - min) / (max - min) * logfac) / factor;
+ if (reverse) value = -value + 1.0;
+ return value;
+ }
+
+ function coef2db(coef, min, max, reverse, factor) {
+ /**
+ * Calculates a value in decibels from a value
+ * between 0.0 and 1.0 and some lower and upper boundaries in decibels.
+ *
+ * @function TK.AudioMath#coef2db
+ *
+ * @param {number} coef - A value between 0.0 and 1.0.
+ * @param {number} min - The minimum value in decibels.
+ * @param {number} max - The maximum value in decibels.
+ * @param {boolean} reverse - If the scale is reversed.
+ * @param {number} factor - Changes the deflection of the logarithm if other than 1.0.
+ *
+ * @returns {number} The result in decibels.
+ */
+ coef = +coef;
+ min = +min;
+ max = +max;
+ reverse = reverse|0;
+ factor = +factor;
+ var logfac = 1.0;
+ if (factor == 0.0) factor = 1.0;
+ else logfac = +MAX(1.0, +pow(2.0, factor) - 1.0);
+ if (reverse) coef = -coef + 1.0;
+ coef = (+pow(2.0, coef * factor) - 1.0) / logfac * (max - min) + min;
+ if (reverse) coef = max - coef + min;
+ return coef;
+ }
+ function db2scale(value, min, max, scale, reverse, factor) {
+ /**
+ * Calculates a linear value between 0.0 and scale.
+ * from a value and its lower and upper boundaries in decibels.
+ *
+ * @function TK.AudioMath#db2scale
+ *
+ * @param {number} value - The value in decibels.
+ * @param {number} min - The minimum value in decibels.
+ * @param {number} max - The maximum value in decibels.
+ * @param {boolean} reverse - If the scale is reversed.
+ * @param {number} factor - Changes the deflection of the logarithm if other than 1.0.
+ *
+ * @returns {number} A value between 0.0 and scale.
+ */
+ value = +value;
+ min = +min;
+ max = +max;
+ scale = +scale;
+ reverse = reverse|0;
+ factor = +factor;
+ var logfac = 1.0;
+ if (factor == 0.0) factor = 1.0;
+ else logfac = +MAX(1.0, +pow(2.0, factor) - 1.0);
+ if (reverse) value = max - (value - min);
+ value = +log2(1.0 + (value - min) / (max - min) * logfac) / factor;
+ if (reverse) value = -value + 1.0;
+ return value * scale;
+ }
+ function scale2db(value, min, max, scale, reverse, factor) {
+ /**
+ * Calculates a value in decibels from a value
+ * between 0.0 and scale and some lower and upper boundaries in decibels.
+ *
+ * @function TK.AudioMath#scale2db
+ *
+ * @param {number} value - A value between 0.0 and scale.
+ * @param {number} min - The minimum value in decibels.
+ * @param {number} max - The maximum value in decibels.
+ * @param {boolean} reverse - If the scale is reversed.
+ * @param {number} factor - Changes the deflection of the logarithm if other than 1.0.
+ *
+ * @returns {number} The result in decibels.
+ */
+ value = +value;
+ min = +min;
+ max = +max;
+ scale = +scale;
+ reverse = reverse|0;
+ factor = +factor;
+ var logfac = 1.0;
+ if (factor == 0.0) factor = 1.0;
+ else logfac = +MAX(1.0, +pow(2.0, factor) - 1.0);
+ value = value / scale;
+ if (reverse) value = -value + 1.0;
+ value = (+pow(2.0, value * factor) - 1.0) / logfac * (max - min) + min;
+ if (reverse) value = max - value + min;
+ return value;
+ }
+ function freq2coef(value, min, max, reverse/*, prescaled, factor*/) {
+ /**
+ * Calculates a linear value between 0.0 and 1.0
+ * from a value and its lower and upper boundaries in hertz.
+ *
+ * @function TK.AudioMath#freq2coef
+ *
+ * @param {number} value - The value in hertz.
+ * @param {number} min - The minimum value in hertz.
+ * @param {number} max - The maximum value in hertz.
+ * @param {boolean} reverse - If the scale is reversed.
+ *
+ * @returns {number} A value between 0.0 (min) and 1.0 (max).
+ */
+ value = +value;
+ min = +min;
+ max = +max;
+ reverse = reverse|0;
+ // FIXME: unused
+ if (reverse) value = max - (value - min);
+ min = +log10(min);
+ max = +log10(max);
+ value = ((+log10(value) - min) / (max - min));
+ if (reverse) value = -value + 1.0;
+ return value;
+ }
+ function coef2freq(coef, min, max, reverse) {
+ /**
+ * Calculates a value in hertz from a value
+ * between 0.0 and 1.0 and some lower and upper boundaries in hertz.
+ *
+ * @function TK.AudioMath#coef2freq
+ *
+ * @param {number} coef - A value between 0.0 and 1.0.
+ * @param {number} min - The minimum value in hertz.
+ * @param {number} max - The maximum value in hertz.
+ * @param {boolean} reverse - If the scale is reversed.
+ * @param {number} factor - Changes the deflection of the logarithm if other than 1.0.
+ *
+ * @returns {number} The result in hertz.
+ */
+ coef = +coef;
+ min = +min;
+ max = +max;
+ reverse = reverse|0;
+ if (reverse) coef = -coef + 1.0;
+ min = +log10(min);
+ max = +log10(max);
+ coef = +pow(10.0, (coef * (max - min) + min));
+ if (reverse) coef = max - coef + min;
+ return coef;
+ }
+ function freq2scale(value, min, max, scale, reverse) {
+ /**
+ * Calculates a linear value between 0.0 and scale
+ * from a value and its lower and upper boundaries in hertz.
+ *
+ * @function TK.AudioMath#freq2scale
+ *
+ * @param {number} value - The value in hertz.
+ * @param {number} min - The minimum value in hertz.
+ * @param {number} max - The maximum value in hertz.
+ * @param {boolean} reverse - If the scale is reversed.
+ *
+ * @returns {number} A value between 0.0 and scale.
+ */
+ value = +value;
+ min = +min;
+ max = +max;
+ scale = +scale;
+ reverse = reverse|0;
+ if (reverse) value = max - (value - min);
+ min = +log10(min);
+ max = +log10(max);
+ value = ((+log10(value) - min) / (max - min));
+ if (reverse) value = -value + 1.0;
+ return value * scale;
+ }
+ function scale2freq(value, min, max, scale, reverse) {
+ /**
+ * Calculates a value in hertz from a value
+ * between 0.0 and scale and some lower and upper boundaries in hertz.
+ *
+ * @function TK.AudioMath#scale2freq
+ *
+ * @param {number} value - A value between 0.0 and scale.
+ * @param {number} min - The minimum value in hertz.
+ * @param {number} max - The maximum value in hertz.
+ * @param {boolean} reverse - If the scale is reversed.
+ * @param {number} factor - Changes the deflection of the logarithm if other than 1.0.
+ *
+ * @returns {number} The result in hertz.
+ */
+ value = +value;
+ min = +min;
+ max = +max;
+ scale = +scale;
+ reverse = reverse|0;
+ value = value / scale;
+ if (reverse) value = -value + 1.0;
+ min = +log10(min);
+ max = +log10(max);
+ value = pow(10.0, (value * (max - min) + min));
+ if (reverse) value = max - value + min;
+ return value;
+ }
+
+ return {
+ // DECIBEL CALCULATIONS
+ db2coef: db2coef,
+ coef2db: coef2db,
+ db2scale: db2scale,
+ scale2db: scale2db,
+ gain2db: gain2db,
+ db2gain: db2gain,
+ // FREQUENCY CALCULATIONS
+ freq2coef: freq2coef,
+ coef2freq: coef2freq,
+ freq2scale: freq2scale,
+ scale2freq: scale2freq
+ }
+})();
diff --git a/share/web_surfaces/builtin/mixer/toolkit/implements/base.js b/share/web_surfaces/builtin/mixer/toolkit/implements/base.js
new file mode 100644
index 0000000000..83f7aa4ab2
--- /dev/null
+++ b/share/web_surfaces/builtin/mixer/toolkit/implements/base.js
@@ -0,0 +1,921 @@
+/*
+ * This file is part of Toolkit.
+ *
+ * Toolkit is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Toolkit is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU General
+ * Public License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA
+ */
+"use strict";
+(function(w, TK) {
+var merge = function(dst) {
+ var key, i, src;
+ for (i = 1; i < arguments.length; i++) {
+ src = arguments[i];
+ for (key in src) {
+ dst[key] = src[key];
+ }
+ }
+ return dst;
+};
+var mixin = function(dst, src) {
+ var fun, key;
+ for (key in src) if (!dst[key]) {
+ if (key === "constructor" ||
+ key === "_class" ||
+ key === "Extends" ||
+ key === "Implements" ||
+ key === "options") continue;
+ if (!src.hasOwnProperty(key)) continue;
+
+ fun = src[key];
+
+ dst[key] = fun;
+ }
+
+ return dst;
+};
+function call_handler(self, fun, args) {
+ try {
+ return fun.apply(self, args);
+ } catch (e) {
+ TK.warn("event handler", fun, "threw", e);
+ }
+}
+function dispatch_events(self, handlers, args) {
+ var v;
+ if (Array.isArray(handlers)) {
+ for (var i = 0; i < handlers.length; i++) {
+ v = call_handler(self, handlers[i], args);
+ if (v !== void(0)) return v;
+ }
+ } else return call_handler(self, handlers, args);
+}
+function add_event(to, event, fun) {
+ var tmp = to[event];
+
+ if (!tmp) {
+ to[event] = fun;
+ } else if (Array.isArray(tmp)) {
+ tmp.push(fun);
+ } else {
+ to[event] = [ tmp, fun ];
+ }
+}
+function remove_event(from, event, fun) {
+ var tmp = from[event];
+ if (!tmp) return;
+ if (Array.isArray(tmp)) {
+ for (var i = 0; i < tmp.length; i++) {
+ if (tmp[i] === fun) {
+ tmp[i] = tmp[tmp.length-1];
+ tmp.length --;
+ }
+ }
+ if (tmp.length === 1) from[event] = tmp[0];
+ else if (tmp.length === 0) delete from[event];
+ } else if (tmp === fun) {
+ delete from[event];
+ }
+}
+function add_static_event(w, event, fun) {
+ var p = w.prototype, e;
+ if (!p.hasOwnProperty('static_events')) {
+ if (p.static_events) {
+ p.static_events = e = Object.assign({}, p.static_events);
+ } else {
+ p.static_events = e = {};
+ }
+ } else e = p.static_events;
+ add_event(e, event, fun);
+}
+function arrayify(x) {
+ if (!Array.isArray(x)) x = [ x ];
+ return x;
+}
+function merge_static_events(a, b) {
+ var event;
+ if (!a) return b;
+ if (!b) return Object.assign({}, a);
+ for (event in a) {
+ var tmp = a[event];
+ if (b.hasOwnProperty(event)) {
+ b[event] = arrayify(tmp).concat(arrayify(b[event]));
+ } else {
+ b[event] = Array.isArray(tmp) ? tmp.slice(0) : tmp;
+ }
+ }
+ return Object.assign({}, a, b);
+}
+TK.class = function(o) {
+ var constructor;
+ var methods;
+ var tmp, i, c, key;
+
+ if (tmp = o.Extends) {
+ if (typeof(tmp) === "function") {
+ tmp = tmp.prototype;
+ }
+ if (typeof(o.options) === "object" &&
+ typeof(tmp.options) === "object") {
+ o.options = Object.assign(Object.create(tmp.options), o.options);
+ }
+ if (o.static_events) o.static_events = merge_static_events(tmp.static_events, o.static_events);
+ methods = Object.assign(Object.create(tmp), o);
+ } else {
+ methods = o;
+ }
+
+ tmp = o.Implements;
+ // mixins
+ if (tmp !== void(0)) {
+ tmp = arrayify(tmp);
+ for (i = 0; i < tmp.length; i++) {
+ if (typeof(tmp[i]) === "function") {
+ c = tmp[i].prototype;
+ } else c = tmp[i];
+
+ if (typeof(c.options) === "object") {
+ if (!methods.hasOwnProperty("options")) {
+ methods.options = Object.create(methods.options || null);
+ }
+ methods.options = merge({}, c.options, methods.options);
+ }
+ if (c.static_events) {
+ if (methods.static_events) {
+ methods.static_events = merge_static_events(c.static_events,
+ Object.assign({}, methods.static_events));
+ } else {
+ methods.static_events = c.static_events;
+ }
+ }
+
+ methods = mixin(methods, c, true);
+ }
+ }
+
+ var init = methods.initialize;
+ var post_init = methods.initialized;
+
+ if (post_init) {
+ constructor = function() {
+ init.apply(this, arguments);
+ post_init.call(this);
+ };
+ } else constructor = init || (function() {});
+
+ constructor.prototype = methods;
+ methods.constructor = constructor;
+ return constructor;
+};
+var __native_events = {
+ // mouse
+ mouseenter : true,
+ mouseleave : true,
+ mousedown : true,
+ mouseup : true,
+ mousemove : true,
+ mouseover : true,
+
+ click : true,
+ dblclick : true,
+
+ startdrag : true,
+ stopdrag : true,
+ drag : true,
+ dragenter : true,
+ dragleave : true,
+ dragover : true,
+ drop : true,
+ dragend : true,
+
+ // touch
+ touchstart : true,
+ touchend : true,
+ touchmove : true,
+ touchenter : true,
+ touchleave : true,
+ touchcancel: true,
+
+ keydown : true,
+ keypress : true,
+ keyup : true,
+ scroll : true,
+ focus : true,
+ blur : true,
+
+ // mousewheel
+ mousewheel : true,
+ DOMMouseScroll : true,
+ wheel : true,
+
+ submit : true,
+ contextmenu: true,
+};
+function is_native_event(type) {
+ return __native_events[type];
+}
+
+function remove_native_events(element) {
+ var type;
+ var s = this.static_events;
+ var d = this.__events;
+ var handler = this.__native_handler;
+
+ for (type in s) if (is_native_event(type))
+ TK.remove_event_listener(element, type, handler);
+
+ for (type in d) if (is_native_event(type) && (!s || !s.hasOwnProperty(type)))
+ TK.remove_event_listener(element, type, handler);
+}
+function add_native_events(element) {
+ var type;
+ var s = this.static_events;
+ var d = this.__events;
+ var handler = this.__native_handler;
+
+ for (type in s) if (is_native_event(type))
+ TK.add_event_listener(element, type, handler);
+
+ for (type in d) if (is_native_event(type) && (!s || !s.hasOwnProperty(type)))
+ TK.add_event_listener(element, type, handler);
+}
+function native_handler(ev) {
+ /* FIXME:
+ * * mouseover and error are cancelled with true
+ * * beforeunload is cancelled with null
+ */
+ if (this.fire_event(ev.type, ev) === false) return false;
+}
+function has_event_listeners(event) {
+ var ev = this.__events;
+
+ if (ev.hasOwnProperty(event)) return true;
+
+ ev = this.static_events;
+
+ return ev && ev.hasOwnProperty(event);
+}
+/**
+ * This is the base class for all widgets in toolkit.
+ * It provides an API for event handling and other basic implementations.
+ *
+ * @class TK.Base
+ */
+TK.Base = TK.class({
+ initialize : function(options) {
+ this.__events = {};
+ this.__event_target = null;
+ this.__native_handler = native_handler.bind(this);
+ this.set_options(options);
+ this.fire_event("initialize");
+ },
+ initialized : function() {
+ /**
+ * Is fired when an instance is initialized.
+ *
+ * @event TK.Base#initialized
+ */
+ this.fire_event("initialized");
+ },
+ /**
+ * Destroys all event handlers and the options object.
+ *
+ * @method TK.Base#destroy
+ */
+ destroy : function() {
+ if (this.__event_target) {
+ remove_native_events.call(this, this.__event_target, this.__events);
+ }
+
+ this.__events = null;
+ this.__event_target = null;
+ this.__native_handler = null;
+ this.options = null;
+ },
+ /**
+ * Merges a new options object into the existing one,
+ * including deep copies of objects. If an option key begins with
+ * the string "on" it is considered as event handler. In this case
+ * the value should be the handler function for the event with
+ * the corresponding name without the first "on" characters.
+ *
+ * @method TK.Base#set_options(options)
+ *
+ * @param {Object} [options={ }] - An object containing initial options.
+ */
+ set_options : function(o) {
+ var opt = this.options;
+ var key, a, b;
+ if (typeof(o) !== "object") {
+ delete this.options;
+ o = {};
+ } else if (typeof(opt) === "object") for (key in o) if (o.hasOwnProperty(key)) {
+ a = o[key];
+ b = opt[key];
+ if (typeof a === "object" && a &&
+ Object.getPrototypeOf(Object.getPrototypeOf(a)) === null &&
+ typeof b === "object" && b &&
+ Object.getPrototypeOf(Object.getPrototypeOf(b)) === null
+ ) {
+ o[key] = merge({}, b, a);
+ }
+ }
+ if (this.hasOwnProperty("options")) {
+ this.options = merge(opt, o);
+ } else if (opt) {
+ this.options = Object.assign(Object.create(opt), o);
+ } else {
+ this.options = Object.assign({}, o);
+ }
+ for (key in this.options) if (key.startsWith("on")) {
+ this.add_event(key.substr(2).toLowerCase(), this.options[key]);
+ delete this.options[key];
+ }
+ },
+ /**
+ * Get the value of an option.
+ *
+ * @method TK.Base#get
+ *
+ * @param {string} key - The option name.
+ */
+ get: function (key) {
+ return this.options[key];
+ },
+ /**
+ * Sets an option. Fires both the events set with arguments key
+ * and value; and the event 'set_'+key with arguments value
+ * and key.
+ *
+ * @method TK.Base#set
+ *
+ * @param {string} key - The name of the option.
+ * @param {mixed} value - The value of the option.
+ *
+ * @emits TK.Base#set
+ * @emits TK.Base#set_[option]
+ */
+ set: function (key, value) {
+ var e;
+
+ this.options[key] = value;
+ /**
+ * Is fired when an option is set.
+ *
+ * @event TK.Base#set
+ *
+ * @param {string} name - The name of the option.
+ * @param {mixed} value - The value of the option.
+ */
+ if (this.has_event_listeners("set"))
+ this.fire_event("set", key, value);
+ /**
+ * Is fired when an option is set.
+ *
+ * @event TK.Base#set_[option]
+ *
+ * @param {mixed} value - The value of the option.
+ */
+ e = "set_"+key;
+ if (this.has_event_listeners(e))
+ this.fire_event(e, value, key);
+
+ return value;
+ },
+ /**
+ * Sets an option by user interaction. Emits the userset
+ * event. The userset event can be cancelled (if an event handler
+ * returns false), in which case the option is not set.
+ * Returns true if the option was set, false
+ * otherwise. If the option was set, it will emit a useraction event.
+ *
+ * @method TK.Base#userset
+ *
+ * @param {string} key - The name of the option.
+ * @param {mixed} value - The value of the option.
+ *
+ * @emits TK.Base#userset
+ * @emits TK.Base#useraction
+ */
+ userset: function(key, value) {
+ if (false === this.fire_event("userset", key, value)) return false;
+ value = this.set(key, value);
+ this.fire_event("useraction", key, value);
+ return true;
+ },
+ /**
+ * Delegates all occuring DOM events of a specific DOM node to the widget.
+ * This way the widget fires e.g. a click event if someone clicks on the
+ * given DOM node.
+ *
+ * @method TK.Base#delegate_events
+ *
+ * @param {HTMLElement} element - The element all native events of the widget should be bound to.
+ *
+ * @returns {HTMLElement} The element
+ *
+ * @emits TK.Base#delegated
+ */
+ delegate_events: function (element) {
+ var old_target = this.__event_target;
+ /**
+ * Is fired when an element is delegated.
+ *
+ * @event TK.Base#delegated
+ *
+ * @param {HTMLElement|Array} element - The element which receives all
+ * native DOM events.
+ * @param {HTMLElement|Array} old_element - The element which previously
+ * received all native DOM events.
+ */
+ this.fire_event("delegated", element, old_target);
+
+ if (old_target) remove_native_events.call(this, old_target);
+ if (element) add_native_events.call(this, element);
+
+ this.__event_target = element;
+
+ return element;
+ },
+ /**
+ * Register an event handler.
+ *
+ * @method TK.Base#add_event
+ *
+ * @param {string} event - The event descriptor.
+ * @param {Function} func - The function to call when the event happens.
+ * @param {boolean} prevent - Set to true if the event should prevent the default behavior.
+ * @param {boolean} stop - Set to true if the event should stop bubbling up the tree.
+ */
+ add_event: function (event, func) {
+ var ev, tmp;
+
+ if (typeof event !== "string")
+ throw new TypeError("Expected string.");
+
+ if (typeof func !== "function")
+ throw new TypeError("Expected function.");
+
+ if (arguments.length !== 2)
+ throw new Error("Bad number of arguments.");
+
+ if (is_native_event(event) && (ev = this.__event_target) && !this.has_event_listeners(event))
+ TK.add_event_listener(ev, event, this.__native_handler);
+ ev = this.__events;
+ add_event(ev, event, func);
+ },
+ /**
+ * Removes the given function from the event queue.
+ * If it is a native DOM event, it removes the DOM event listener
+ * as well.
+ *
+ * @method TK.Base#remove_event
+ *
+ * @param {string} event - The event descriptor.
+ * @param {Function} fun - The function to remove.
+ */
+ remove_event: function (event, fun) {
+ remove_event(this.__events, event, fun);
+
+ // remove native DOM event listener from __event_target
+ if (is_native_event(event) && !this.has_event_listeners(event)) {
+ var ev = this.__event_target;
+ if (ev) TK.remove_event_listener(ev, event, this.__native_handler);
+ }
+ },
+ /**
+ * Fires an event.
+ *
+ * @method TK.Base#fire_event
+ *
+ * @param {string} event - The event descriptor.
+ * @param {...*} args - Event arguments.
+ */
+ fire_event: function (event) {
+ var ev;
+ var args;
+ var v;
+
+ ev = this.__events;
+
+ if (ev !== void(0) && (event in ev)) {
+ ev = ev[event];
+
+ args = Array.prototype.slice.call(arguments, 1);
+
+ v = dispatch_events(this, ev, args);
+ if (v !== void(0)) return v;
+ }
+
+ ev = this.static_events;
+
+ if (ev !== void(0) && (event in ev)) {
+ ev = ev[event];
+
+ if (args === void(0)) args = Array.prototype.slice.call(arguments, 1);
+
+ v = dispatch_events(this, ev, args);
+ if (v !== void(0)) return v;
+ }
+ },
+ /**
+ * Test if the event descriptor has some handler functions in the queue.
+ *
+ * @method TK.Base#has_event_listeners
+ *
+ * @param {string} event - The event desriptor.
+ *
+ * @returns {boolean} True if the event has some handler functions in the queue, false if not.
+ */
+ has_event_listeners: has_event_listeners,
+ /**
+ * Add multiple event handlers at once, either as dedicated event handlers or a list of event
+ * descriptors with a single handler function.
+ *
+ * @method TK.Base#add_events
+ *
+ * @param {Object | Array} events - Object with event descriptors as keys and functions as
+ * values or Array of event descriptors. The latter requires a handler function as the
+ * second argument.
+ * @param {Function} func - A function to add as event handler if the first argument is an
+ * array of event desriptors.
+ */
+ add_events: function (events, func) {
+ var i;
+ if (Array.isArray(events)) {
+ for (i = 0; i < events.length; i++)
+ this.add_event(events[i], func);
+ } else {
+ for (i in events)
+ if (events.hasOwnProperty(i))
+ this.add_event(i, events[i]);
+ }
+ },
+ /**
+ * Remove multiple event handlers at once, either as dedicated event handlers or a list of
+ * event descriptors with a single handler function.
+ *
+ * @method TK.Base#remove_events
+ *
+ * @param {Object | Array} events - Object with event descriptors as keys and functions as
+ * values or Array of event descriptors. The latter requires the handler function as the
+ * second argument.
+ * @param {Function} func - A function to remove from event handler queue if the first
+ * argument is an array of event desriptors.
+ */
+ remove_events: function (events, func) {
+ var i;
+ if (Array.isArray(events)) {
+ for (i = 0; i < events.length; i++)
+ this.remove_event(events[i], func);
+ } else {
+ for (i in events)
+ if (events.hasOwnProperty(i))
+ this.remove_event(i, events[i]);
+ }
+ },
+ /**
+ * Fires several events.
+ *
+ * @method TK.Base#fire_events
+ *
+ * @param {Array.TK.Widget.options[config.name + "." + option]
+ * on the parent widget.
+ *
+ * @param {TK.Widget} widget - The {@link TK.Widget} to add the TK.ChildWidget to.
+ * @param {string} name - The identifier of the element inside the parent Element, TK.Widget[config.name].
+ * @param {object} config - The configuration of the child element.
+ *
+ * @property {TK.Widget} config.create - A TK.Widget class derivate to be used as child widget.
+ * @property {boolean} [config.fixed] - A fixed child widget cannot be removed after initialization.
+ * @property {boolean} [config.show=false] - Show/hide a non-fixed child widget on initialization.
+ * @property {string} [config.option="show_"+config.name] - A custom option of the parent widget
+ * to determine the visibility of the child element. If this is
+ * null, TK.Widget.options["show_"+ config.name]
+ * is used to toggle its visibility. The child element is visible, if
+ * this options is !== false.
+ * @property {function} [config.append] - A function overriding the generic
+ * append mechanism. If not null, this function is
+ * supposed to take care of adding the child widget to the parent
+ * widgets DOM. Otherwise the element of the child widget is added
+ * to the element of the parent widget.
+ * @property {boolean} [config.inherit_options=false] - Defines if both widgets share the
+ * same set of options. If true, Setting an option on the
+ * parent widget also sets the same option on the child widget. If false,
+ * the options of the child widget can be accessed via options[config.name + "." + option]
+ * in the parent widget.
+ * @property {array} [config.map_options=[