<!DOCTYPE html>
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />
    <script type="text/javascript" src="simcir.js"></script>
    <link rel="stylesheet" type="text/css" href="https://kazuhikoarase.github.io/simcirjs/simcir.css" />
    <script type="text/javascript" src="https://kazuhikoarase.github.io/simcirjs/simcir-basicset.js"></script>
    <link rel="stylesheet" type="text/css" href="https://kazuhikoarase.github.io/simcirjs/simcir-basicset.css" />
    <script type="text/javascript" src="https://kazuhikoarase.github.io/simcirjs/simcir-library.js"></script>
    <title></title>
  </head>
  <body>
    <textarea id="koko"></textarea>
    <textarea id="koko0" rows="15" cols="40"> 
var c0 = new J(8);
c0.in(0, 2);
c0.wire(3, 5);
c0.xor(1, 2, 3);
c0.xor(3, 0, 7);
c0.and(1, 2, 4);
c0.and(3, 0, 5);
c0.or(4, 5, 6);
c0.out(6, 7);
c0.disp('koko');
    </textarea>
     <input type="button" id="make" value="make"/>
    <div class="simcir"> 
    </div>
  </body>
</html>



/* Styles go here */

'use strict';
class J {
	constructor(n) {
	  var str = '{"width": 600,	"height": 600, "showToolbox": false, "toolbox": [], "devices": [], "connectors": []}';
    this.json = JSON.parse(str);
    this.dev = [];
    this.con = [];
    this.id = 0;
    this.y0 = 40;
    this.y1 = 40;
    this.y2 = 40;
	  this.n = n;
		this.s = [];
		return this;
	}
	in(a, b) {
	  b += 1;
	  for(var i = a; i < b; i++)
	  {
	    var d = {
	      "type": "In",
	      "id": "dev" + this.id,
	      "x": 40,
	      "y": this.y0,
	      "label": "In"
	   };
	   this.json.devices.push(d);
	   this.s[i] = this.id;
	   this.y0 += 50;
	   this.id += 1;
	  }
	  return this;
	}
	wire(a, b) {
	  b += 1;
	  for(var i = a; i < b; i++)
	  {
	    this.s[i] = i;
	  }
	  return this;
	}
	out(a, b) {
	  b += 1;
	  for(var i = a; i < b; i++)
	  {
	    var d = {
	      "type": "Out",
	      "id": "dev" + this.id,
	      "x": 400,
	      "y": this.y2,
	      "label": "Out"
	    };
	    this.json.devices.push(d);
	    var c0 = {
	      "from": "dev" + this.id + ".in0",
	      "to": "dev" + this.s[i] + ".out0"
	    };
	    this.json.connectors.push(c0);
	    this.y2 += 50;
	    this.id += 1;
	  }
	  return this;
	}
	xor(a, b, c) {
    var d = {
	    "type": "XOR",
	    "id": "dev" + this.id,
	    "x": 150,
	    "y": this.y1,
	    "label": "XOR"
	  };
	  this.json.devices.push(d);
	  var c0 = {
	    "from": "dev" + this.id + ".in0",
	    "to": "dev" + this.s[a] + ".out0"
	  };
	  var c1 = {
	    "from": "dev" + this.id + ".in1",
	    "to": "dev" + this.s[b] + ".out0"
	  }
	  this.s[c] = this.id;
	  this.json.connectors.push(c0);
	  this.json.connectors.push(c1);
	  this.y1 += 50;
	  this.id += 1;
	  return this;
	}
	and(a, b, c) {
    var d = {
	    "type": "AND",
	    "id": "dev" + this.id,
	    "x": 220,
	    "y": this.y1,
	    "label": "AND"
	  };
	  this.json.devices.push(d);
	  var c0 = {
	    "from": "dev" + this.id + ".in0",
	    "to": "dev" + this.s[a] + ".out0"
	  };
	  var c1 = {
	    "from": "dev" + this.id + ".in1",
	    "to": "dev" + this.s[b] + ".out0"
	  }
	  this.s[c] = this.id;
	  this.json.connectors.push(c0);
	  this.json.connectors.push(c1);
	  this.y1 += 50;
	  this.id += 1;
	  return this;
	}
	or(a, b, c) {
    var d = {
	    "type": "OR",
	    "id": "dev" + this.id,
	    "x": 290,
	    "y": this.y1,
	    "label": "OR"
	  };
	  this.json.devices.push(d);
	  var c0 = {
	    "from": "dev" + this.id + ".in0",
	    "to": "dev" + this.s[a] + ".out0"
	  };
	  var c1 = {
	    "from": "dev" + this.id + ".in1",
	    "to": "dev" + this.s[b] + ".out0"
	  }
	  this.s[c] = this.id;
	  this.json.connectors.push(c0);
	  this.json.connectors.push(c1);
	  this.y1 += 50;
	  this.id += 1;
	  return this;
	}
	disp(s) {
	  var koko = document.getElementById(s);
	  var str = JSON.stringify(this.json);  
    koko.value = str;
	}
}
var simcir = {};
simcir.$ = function() {
	var debug = location.hash == '#debug';
	var cacheIdKey = '.lessqCacheId';
	var cacheIdSeq = 0;
	var cache = {};
	var getCache = function(elm) {
		var cacheId = elm[cacheIdKey];
		if (typeof cacheId == 'undefined')
		{
			elm[cacheIdKey] = cacheId = cacheIdSeq++;
			cache[cacheId] = debug? { e: elm }: {};
		}
		return cache[cacheId];
	};
	var hasCache = function(elm) {
		return typeof elm[cacheIdKey] != 'undefined';
	};
	if (debug)
	{
		var lastKeys = {};
		var showCacheCount = function() {
			var cnt = 0;
			var keys = {};
			for (var k in cache)
			{
				cnt += 1;
				if (!lastKeys[k])
				{
					console.log(cache[k]);
				}
				keys[k] = true;
			}
			lastKeys = keys;
			console.log('cacheCount:' + cnt);
			window.setTimeout(showCacheCount, 5000);
		};
		showCacheCount();
	}
	var removeCache = function(elm) {
		if (typeof elm[cacheIdKey] != 'undefined')
		{
			var cacheId = elm[cacheIdKey];
			var listenerMap = cache[cacheId].listenerMap;
			for (var type in listenerMap)
			{
				var listeners = listenerMap[type];
				for (var i = 0; i < listeners.length; i += 1)
				{
					elm.removeEventListener(type, listeners[i]);
				}
			}
			delete elm[cacheIdKey];
			delete cache[cacheId];
		}
		while (elm.firstChild)
		{
			removeCache(elm.firstChild);
			elm.removeChild(elm.firstChild);
		}
	};
	var getData = function(elm) {
		if (!getCache(elm).data)
		{
			getCache(elm).data = {};
		}
		return getCache(elm).data;
	};
	var getListeners = function(elm, type) {
		if (!getCache(elm).listenerMap)
		{
			getCache(elm).listenerMap = {};
		}
		if (!getCache(elm).listenerMap[type])
		{
			getCache(elm).listenerMap[type] = [];
		}
		return getCache(elm).listenerMap[type];
	};
	var addEventListener = function(elm, type, listener, add) {
		var listeners = getListeners(elm, type);
		var newListeners = [];
		for (var i = 0; i < listeners.length; i += 1)
		{
			if (listeners[i] != listener)
			{
				newListeners.push(listeners[i]);
			}
		}
		if (add)
		{
			newListeners.push(listener);
		}
		getCache(elm).listenerMap[type] = newListeners;
		return true;
	};
	var CustomEvent = {
		preventDefault: function() {
			this._pD = true;
		},
		stopPropagation: function() {
			this._sP = true;
		},
		stopImmediatePropagation: function() {
			this._sIp = true;
		}
	};
	var trigger = function(elm, type, data) {
		var event = {
			type: type,
			target: elm,
			currentTarget: null,
			_pD: false,
			_sP: false,
			_sIp: false,
			__proto__: CustomEvent
		};
		for (var e = elm; e != null; e = e.parentNode)
		{
			if (!hasCache(e))
			{
				continue;
			}
			if (!getCache(e).listenerMap)
			{
				continue;
			}
			if (!getCache(e).listenerMap[type])
			{
				continue;
			}
			event.currentTarget = e;
			var listeners = getCache(e).listenerMap[type];
			for (var i = 0; i < listeners.length; i += 1)
			{
				listeners[i].call(e, event, data);
				if (event._sIp)
				{
					return;
				}
			}
			if (event._sP)
			{
				return;
			}
		}
	};
	var data = function(elm, kv) {
		if (arguments.length == 2)
		{
			if (typeof kv == 'string') return getData(elm)[kv];
			for (var k in kv)
			{
				getData(elm)[k] = kv[k];
			}
		}
		else if (arguments.length == 3)
		{
			getData(elm)[kv] = arguments[2];
		}
		return elm;
	};
	var extend = function(o1, o2) {
		for (var k in o2)
		{
			o1[k] = o2[k];
		}
		return o1;
	};
	var each = function(it, callback) {
		if (typeof it.splice == 'function')
		{
			for (var i = 0; i < it.length; i += 1)
			{
				callback(i, it[i]);
			}
		}
		else
		{
			for (var k in it)
			{
				callback(k, it[k]);
			}
		}
	};
	var grep = function(list, accept) {
		var newList = [];
		for (var i = 0; i < list.length; i += 1)
		{
			var item = list[i];
			if (accept(item))
			{
				newList.push(item);
			}
		}
		return newList;
	};
	var addClass = function(elm, className, add) {
		var classes = (elm.getAttribute('class') || '').split(/\s+/g);
		var newClasses = '';
		for (var i = 0; i < classes.length; i+= 1)
		{
			if (classes[i] == className)
			{
				continue;
			}
			newClasses += ' ' + classes[i];
		}
		if (add)
		{
			newClasses += ' ' + className;
		}
		elm.setAttribute('class', newClasses);
	};
	var hasClass = function(elm, className) {
		var classes = (elm.getAttribute('class') || '').split(/\s+/g);
		for (var i = 0; i < classes.length; i+= 1)
		{
			if (classes[i] == className)
			{
				return true;
			}
		}
		return false;
	};
	var matches = function(elm, selector) {
		if (elm.nodeType != 1)
		{
			return false;
		}
		else if (!selector)
		{
			return true;
		}
		var sels = selector.split(/[,\s]+/g);
		for (var i = 0; i < sels.length; i += 1)
		{
			var sel = sels[i];
			if (sel.substring(0, 1) == '#')
			{
				throw 'not supported:' + sel;
			}
			else if (sel.substring(0, 1) == '.')
			{
				if (hasClass(elm, sel.substring(1)))
				{
					return true;
				}
			}
			else
			{
				if (elm.tagName.toUpperCase() == sel.toUpperCase())
				{
					return true;
				}
			}
		}
		return false;
	};
	var parser = new window.DOMParser();
	var html = function(html) {
		var doc = parser.parseFromString('<div xmlns="http://www.w3.org/1999/xhtml">' + html + '</div>', 'text/xml').firstChild;
		var elms = [];
		while (doc.firstChild)
		{
			elms.push(doc.firstChild);
			doc.removeChild(doc.firstChild);
		}
		elms.__proto__ = fn;
		return elms;
	};
	var pxToNum = function(px) {
		if (typeof px != 'string' || px.length <= 2 || px.charAt(px.length - 2) != 'p' || px.charAt(px.length - 1) != 'x')
		{
			throw 'illegal px:' + px;
		}
		return +px.substring(0, px.length - 2);
	};
	var buildQuery = function(data) {
		var query = '';
		for (var k in data)
		{
			if (query.length > 0)
			{
				query += '&';
			}
			query += window.encodeURIComponent(k);
			query += '=';
			query += window.encodeURIComponent(data[k]);
		}
		return query;
	};
	var parseResponse = function() {
		var contentType = this.getResponseHeader('content-type');
		if (contentType != null)
		{
			contentType = contentType.replace(/\s*;.+$/, '').toLowerCase();
		}
		if (contentType == 'text/xml' || contentType == 'application/xml')
		{
			return parser.parseFromString(this.responseText, 'text/xml');
		}
		else if (contentType == 'text/json' || contentType == 'application/json')
		{
			return JSON.parse(this.responseText);
		}
		else
		{
			return this.response;
		}
	};
	var ajax = function(params) {
		params = extend({
			url: '',
			method: 'GET',
			contentType: 'application/x-www-form-urlencoded;charset=UTF-8',
			cache: true,
			processData: true,
			async: true
		}, params);
		if (!params.async)
		{
			throw 'not supported.';
		}
		var method = params.method.toUpperCase();
		var data = null;
		var contentType = params.contentType;
		if (method == 'POST' || method == 'PUT')
		{
			data = (typeof params.data == 'object' && params.processData) ? buildQuery(params.data) : params.data;
		}
		else
		{
			contentType = false;
		}
		var xhr = params.xhr? params.xhr(): new window.XMLHttpRequest();
		xhr.open(method, params.url, params.async);
		if (contentType !== false)
		{
			xhr.setRequestHeader('Content-Type', contentType);
		}
		xhr.onreadystatechange = function() {
			if(xhr.readyState == window.XMLHttpRequest.DONE)
			{
				try
				{
					if (xhr.status == 200)
					{
						done.call(xhr, parseResponse.call(this));
					}
					else
					{
						fail.call(xhr);
					}
				}
				finally
				{
					always.call(xhr);
				}
			}
		};
		window.setTimeout(function() {
			xhr.send(data);
		}, 0);
		var done = function(data) {};
		var fail = function() {};
		var always = function() {};
		var $ = {
			done: function(callback) {
				done = callback;
				return $;
			},
			fail: function(callback) {
				fail = callback;
				return $;
			},
			always: function(callback) {
				always = callback;
				return $;
			},
			abort: function() {
				xhr.abort();
				return $;
			}
		};
		return $;
	};
	var fn = {
		attr: function(kv) {
			if (arguments.length == 1)
			{
				if (typeof kv == 'string') return this.getAttribute(kv);
				for (var k in kv)
				{
					this.setAttribute(k, kv[k]);
				}
			}
			else if (arguments.length == 2)
			{
				this.setAttribute(kv, arguments[1]);
			}
			return this;
		},
		prop: function(kv) {
			if (arguments.length == 1)
			{
				if (typeof kv == 'string') return this[kv];
				for (var k in kv)
				{
					this[k] = kv[k];
				}
			}
			else if (arguments.length == 2)
			{
				this[kv] = arguments[1];
			}
			return this;
		},
		css: function(kv) {
			if (arguments.length == 1)
			{
				if (typeof kv == 'string') return this.style[kv];
				for (var k in kv)
				{
					this.style[k] = kv[k];
				}
			}
			else if (arguments.length == 2)
			{
				this.style[kv] = arguments[1];
			}
			return this;
		},
		data: function(kv) {
			var args = [ this ];
			for (var i = 0; i < arguments.length; i += 1)
			{
				args.push(arguments[i]);
			};
			return data.apply(null, args);
		},
		val: function() {
			if (arguments.length == 0)
			{
				return this.value || '';
			}
			else if (arguments.length == 1)
			{
				this.value = arguments[0];
			}
			return this;
		},
		on: function(type, listener) {
			var types = type.split(/\s+/g);
			for (var i = 0; i < types.length; i += 1)
			{
				this.addEventListener(types[i], listener);
				addEventListener(this, types[i], listener, true);
			}
			return this;
		},
		off: function(type, listener) {
			var types = type.split(/\s+/g);
			for (var i = 0; i < types.length; i += 1)
			{
				this.removeEventListener(types[i], listener);
				addEventListener(this, types[i], listener, false);
			}
			return this;
		},
		trigger: function(type, data) {
			trigger(this, type, data);
			return this;
		},
		offset: function() {
			var off = {
				left: 0,
				top: 0
			};
			var base = null;
			for (var e = this; e.parentNode != null; e = e.parentNode)
			{
				if (e.offsetParent != null)
				{
					base = e;
					break;
				}
			}
			if (base != null)
			{
				for (var e = base; e.offsetParent != null; e = e.offsetParent)
				{
					off.left += e.offsetLeft;
					off.top += e.offsetTop;
				}
			}
			for (var e = this; e.parentNode != null && e != document.body; e = e.parentNode)
			{
				off.left -= e.scrollLeft;
				off.top -= e.scrollTop;
			}
			return off;
		},
		append: function(elms) {
			if (typeof elms == 'string')
			{
				elms = html(elms);
			}
			for (var i = 0; i < elms.length; i += 1)
			{
				this.appendChild(elms[i]);
			}
			return this;
		},
		prepend: function(elms) {
			if (typeof elms == 'string')
			{
				elms = html(elms);
			}
			for (var i = 0; i < elms.length; i += 1)
			{
				if (this.firstChild)
				{
					this.insertBefore(elms[i], this.firstChild);
				}
				else
				{
					this.appendChild(elms[i]);
				}
			}
			return this;
		},
		insertBefore: function(elms) {
			var elm = elms[0];
			elm.parentNode.insertBefore(this, elm);
			return this;
		},
		insertAfter: function(elms) {
			var elm = elms[0];
			if (elm.nextSibling)
			{
				elm.parentNode.insertBefore(this, elm.nextSibling);
			}
			else
			{
				elm.parentNode.appendChild(this);
			}
			return this;
		},
		remove: function() {
			if (this.parentNode)
			{
				this.parentNode.removeChild(this);
			}
			removeCache(this);
			return this;
		},
		detach: function() {
			if (this.parentNode)
			{
				this.parentNode.removeChild(this);
			}
			return this;
		},
		parent: function() {
			return $(this.parentNode);
		},
		closest: function(selector) {
			for (var e = this; e != null; e = e.parentNode)
			{
				if (matches(e, selector))
				{
					return $(e);
				}
			}
			return $();
		},
		find: function(selector) {
			var elms = [];
			var childNodes = this.querySelectorAll(selector);
			for (var i = 0; i < childNodes.length; i += 1)
			{
				elms.push(childNodes.item(i));
			}
			elms.__proto__ = fn;
			return elms;
		},
		children: function(selector) {
			var elms = [];
			var childNodes = this.childNodes;
			for (var i = 0; i < childNodes.length; i += 1)
			{
				if (matches(childNodes.item(i), selector))
				{
					elms.push(childNodes.item(i));
				}
			}
			elms.__proto__ = fn;
			return elms;
		},
		index: function(selector) {
			return Array.prototype.indexOf.call($(this).parent().children(selector), this);
		},
		clone: function() {
			return $(this.cloneNode(true));
		},
		focus: function() {
			this.focus(); return this;
		},
		select: function() {
			this.select(); return this;
		},
		submit: function() {
			this.submit(); return this;
		},
		scrollLeft: function() {
			if (arguments.length == 0) return this.scrollLeft;
			this.scrollLeft = arguments[0];
			return this;
		},
		scrollTop: function() {
			if (arguments.length == 0) return this.scrollTop;
			this.scrollTop = arguments[0];
			return this;
		},
		html: function() {
			if (arguments.length == 0) return this.innerHTML;
			this.innerHTML = arguments[0];
			return this;
		},
		text: function() {
			if (typeof this.textContent != 'undefined')
			{
				if (arguments.length == 0) return this.textContent;
				this.textContent = arguments[0];
				return this;
			}
			else
			{
				if (arguments.length == 0) return this.innerText;
				this.innerText = arguments[0];
				return this;
			}
		},
		outerWidth: function(margin) {
			var w = this.offsetWidth;
			if (margin)
			{
				var cs = window.getComputedStyle(this, null);
				return w + pxToNum(cs.marginLeft) + pxToNum(cs.marginRight);
			}
			return w;
		},
		innerWidth: function() {
			var cs = window.getComputedStyle(this, null);
			return this.offsetWidth - pxToNum(cs.borderLeftWidth) - pxToNum(cs.borderRightWidth);
		},
		width: function() {
			if (this == window) return this.innerWidth;
			var cs = window.getComputedStyle(this, null);
			return this.offsetWidth - pxToNum(cs.borderLeftWidth) - pxToNum(cs.borderRightWidth) - pxToNum(cs.paddingLeft) - pxToNum(cs.paddingRight);
		},
		outerHeight: function(margin) {
			var h = this.offsetHeight;
			if (margin)
			{
				var cs = window.getComputedStyle(this, null);
				return h + pxToNum(cs.marginTop) + pxToNum(cs.marginBottom);
			}
			return h;
		},
		innerHeight: function() {
			var cs = window.getComputedStyle(this, null);
			return this.offsetHeight - pxToNum(cs.borderTopWidth) - pxToNum(cs.borderBottomWidth);
		},
		height: function() {
			if (this == window) return this.innerHeight;
			var cs = window.getComputedStyle(this, null);
			return this.offsetHeight - pxToNum(cs.borderTopWidth) - pxToNum(cs.borderBottomWidth) - pxToNum(cs.paddingTop) - pxToNum(cs.paddingBottom);
		},
		addClass: function(className) {
			addClass(this, className, true);
			return this;
		},
		removeClass: function(className) {
			addClass(this, className, false);
			return this;
		},
		hasClass: function(className) {
			return hasClass(this, className);
		}
	};
	each(fn, function(name, func) {
		fn[name] = function() {
			var newRet = null;
			for (var i = 0; i < this.length; i += 1)
			{
				var elm = this[i];
				var ret = func.apply(elm, arguments);
				if (elm !== ret)
				{
					if (ret != null && ret.__proto__ == fn)
					{
						if (newRet == null)
						{
							newRet = [];
						}
						newRet = newRet.concat(ret);
					}
					else
					{
						return ret;
					}
				}
			}
			if (newRet != null)
			{
				newRet.__proto__ = fn;
				return newRet;
			}
			return this;
		};
	});
	fn = extend(fn, {
		each: function(callback) {
			for (var i = 0; i < this.length; i += 1)
			{
				callback.call(this[i], i);
			}
			return this;
		},
		first: function() {
			return $(this.length > 0 ? this[0] : null);
		},
		last: function() {
			return $(this.length > 0 ? this[this.length - 1] : null);
		}
	});
	var $ = function(target) {
		if (typeof target == 'function')
		{
			return $(document).on('DOMContentLoaded', target);
		}
		else if (typeof target == 'string')
		{
			if (target.charAt(0) == '<')
			{
				return html(target);
			}
			else
			{
				var childNodes = document.querySelectorAll(target);
				var elms = [];
				for (var i = 0; i < childNodes.length; i += 1)
				{
					elms.push(childNodes.item(i));
				}
				elms.__proto__ = fn;
				return elms;
			}
		}
		else if (typeof target == 'object' && target != null)
		{
			if (target.__proto__ == fn)
			{
				return target;
			}
			else
			{
				var elms = [];
				elms.push(target);
				elms.__proto__ = fn;
				return elms;
			}
		}
		else
		{
			var elms = [];
			elms.__proto__ = fn;
			return elms;
		}
	};
	return extend($, {
		fn: fn,
		extend: extend,
		each: each,
		grep: grep,
		data: data,
		ajax: ajax
	});
}();
!function($s) {
	var $ = $s.$;
	var createSVGElement = function(tagName) {
		return $(document.createElementNS('http://www.w3.org/2000/svg', tagName));
	};
	var createSVG = function(w, h) {
		return createSVGElement('svg').attr({
			version: '1.1',
			width: w,
			height: h,
			viewBox: '0 0 ' + w + ' ' + h
		});
	};
	var graphics = function($target) {
		var attr = {};
		var buf = '';
		var moveTo = function(x, y) {
			buf += ' M ' + x + ' ' + y;
		};
		var lineTo = function(x, y) {
			buf += ' L ' + x + ' ' + y;
		};
		var curveTo = function(x1, y1, x, y) {
			buf += ' Q ' + x1 + ' ' + y1 + ' ' + x + ' ' + y;
		};
		var closePath = function(close) {
			if (close)
			{
				buf += ' Z';
			}
			$target.append(createSVGElement('path').attr('d', buf).attr(attr));
			buf = '';
		};
		var drawRect = function(x, y, width, height) {
			$target.append(createSVGElement('rect').attr({
				x: x,
				y: y,
				width: width,
				height: height
			}).attr(attr));
		};
		var drawCircle = function(x, y, r) {
			$target.append(createSVGElement('circle').attr({
				cx: x,
				cy: y,
				r: r
			}).attr(attr));
		};
		return {
			attr: attr,
			moveTo: moveTo,
			lineTo: lineTo,
			curveTo: curveTo,
			closePath: closePath,
			drawRect: drawRect,
			drawCircle: drawCircle
		};
	};
	var transform = function() {
		var attrX = 'simcir-transform-x';
		var attrY = 'simcir-transform-y';
		var attrRotate = 'simcir-transform-rotate';
		var num = function($o, k) {
			var v = $o.attr(k);
			return v ? +v : 0;
		};
		return function($o, x, y, rotate) {
			if (arguments.length >= 3)
			{
				var transform = 'translate(' + x + ' ' + y + ')';
				if (rotate)
				{
					transform += ' rotate(' + rotate + ')';
				}
				$o.attr('transform', transform);
				$o.attr(attrX, x);
				$o.attr(attrY, y);
				$o.attr(attrRotate, rotate);
			}
			else if (arguments.length == 1)
			{
				return {
					x: num($o, attrX),
					y: num($o, attrY),
					rotate: num($o, attrRotate)
				};
			}
		};
	}();
	var offset = function($o) {
		var x = 0;
		var y = 0;
		while ($o[0].nodeName != 'svg')
		{
			var pos = transform($o);
			x += pos.x;
			y += pos.y;
			$o = $o.parent();
		}
		return {
			x: x,
			y: y
		};
	};
	var enableEvents = function($o, enable) {
		$o.css('pointer-events', enable ? 'visiblePainted' : 'none');
	};
	var disableSelection = function($o) {
		$o.each(function() {
			this.onselectstart = function() {
				return false;
			};
		}).css('-webkit-user-select', 'none');
	};
	var controller = function() {
		var id = 'controller';
		return function($ui, controller) {
			if (arguments.length == 1)
			{
				return $.data($ui[0], id);
			}
			else if (arguments.length == 2)
			{
				$.data($ui[0], id, controller);
			}
		};
	}();
	var eventQueue = function() {
		var delay = 50;
		var limit = 40;
		var _queue = null;
		var postEvent = function(event) {
			if (_queue == null)
			{
				_queue = [];
			}
			_queue.push(event);
		};
		var dispatchEvent = function() {
			var queue = _queue;
			_queue = null;
			while (queue.length > 0)
			{
				var e = queue.shift();
				e.target.trigger(e.type);
			}
		};
		var getTime = function() {
			return new Date().getTime();
		};
		var timerHandler = function() {
			var start = getTime();
			while (_queue != null && getTime() - start < limit)
			{
				dispatchEvent();
			}
			window.setTimeout(timerHandler, Math.max(delay - limit, delay - (getTime() - start)));
		};
		timerHandler();
		return {
			postEvent: postEvent
		};
	}();
	var unit = 16;
	var fontSize = 12;
	var createLabel = function(text) {
		return createSVGElement('text').text(text).css('font-size', fontSize + 'px');
	};
	var createNode = function(type, label, description, headless) {
		var $node = createSVGElement('g').attr('simcir-node-type', type);
		if (!headless)
		{
			$node.attr('class', 'simcir-node');
		}
		var node = createNodeController({
			$ui: $node,
			type: type,
			label: label,
			description: description,
			headless: headless
		});
		if (type == 'in')
		{
			controller($node, createInputNodeController(node));
		}
		else if (type == 'out')
		{
			controller($node, createOutputNodeController(node));
		}
		else
		{
			throw 'unknown type:' + type;
		}
		return $node;
	};
	var isActiveNode = function($o) {
		return $o.closest('.simcir-node').length == 1 && $o.closest('.simcir-toolbox').length == 0;
	};
	var createNodeController = function(node) {
		var _value = null;
		var setValue = function(value, force) {
			if (_value === value && !force)
			{
				return;
			}
			_value = value;
			eventQueue.postEvent({target: node.$ui, type: 'nodeValueChange'});
		};
		var getValue = function() {
			return _value;
		};
		if (!node.headless)
		{
			node.$ui.attr('class', 'simcir-node simcir-node-type-' + node.type);
			var $circle = createSVGElement('circle').attr({
				cx: 0,
				cy: 0,
				r: 4
			});
			node.$ui.on('mouseover', function(event) {
				if (isActiveNode(node.$ui))
				{
					node.$ui.addClass('simcir-node-hover');
				}
			});
			node.$ui.on('mouseout', function(event) {
				if (isActiveNode(node.$ui))
				{
					node.$ui.removeClass('simcir-node-hover');
				}
			});
			node.$ui.append($circle);
			var appendLabel = function(text, align) {
				var $label = createLabel(text).attr('class', 'simcir-node-label');
				enableEvents($label, false);
				if (align == 'right')
				{
					$label.attr('text-anchor', 'start').attr('x', 6).attr('y', fontSize / 2);
				} else if (align == 'left')
				{
					$label.attr('text-anchor', 'end').attr('x', -6).attr('y', fontSize / 2);
				}
				node.$ui.append($label);
			};
			if (node.label)
			{
				if (node.type == 'in')
				{
					appendLabel(node.label, 'right');
				}
				else if (node.type == 'out')
				{
					appendLabel(node.label, 'left');
				}
			}
			if (node.description)
			{
				if (node.type == 'in')
				{
					appendLabel(node.description, 'left');
				}
				else if (node.type == 'out')
				{
					appendLabel(node.description, 'right');
				}
			}
			node.$ui.on('nodeValueChange', function(event) {
				if (_value != null)
				{
					node.$ui.addClass('simcir-node-hot');
				}
				else
				{
					node.$ui.removeClass('simcir-node-hot');
				}
			});
		}
		return $.extend(node, {
			setValue: setValue,
			getValue: getValue
		});
	};
	var createInputNodeController = function(node) {
		var output = null;
		var setOutput = function(outNode) {
			output = outNode;
		};
		var getOutput = function() {
			return output;
		};
		return $.extend(node, {
			setOutput: setOutput,
			getOutput: getOutput
		});
	};
	var createOutputNodeController = function(node) {
		var inputs = [];
		var super_setValue = node.setValue;
		var setValue = function(value) {
			super_setValue(value);
			for (var i = 0; i < inputs.length; i += 1)
			{
				inputs[i].setValue(value);
			}
		};
		var connectTo = function(inNode) {
			if (inNode.getOutput() != null)
			{
				inNode.getOutput().disconnectFrom(inNode);
			}
			inNode.setOutput(node);
			inputs.push(inNode);
			inNode.setValue(node.getValue(), true);
		};
		var disconnectFrom = function(inNode) {
			if (inNode.getOutput() != node)
			{
				throw 'not connected.';
			}
			inNode.setOutput(null);
			inNode.setValue(null, true);
			inputs = $.grep(inputs, function(v) {
				return v != inNode;
			});
		};
		var getInputs = function() {
			return inputs;
		};
		return $.extend(node, {
			setValue: setValue,
			getInputs: getInputs,
			connectTo: connectTo,
			disconnectFrom: disconnectFrom
		});
	};
	var createDevice = function(deviceDef, headless, scope) {
		headless = headless || false;
		scope = scope || null;
		var $dev = createSVGElement('g');
		if (!headless)
		{
			$dev.attr('class', 'simcir-device');
		}
		controller($dev, createDeviceController({
			$ui: $dev,
			deviceDef: deviceDef,
			headless: headless,
			scope: scope,
			doc: null
		}));
		var factory = factories[deviceDef.type];
		if (factory)
		{
			factory(controller($dev));
		}
		if (!headless)
		{
			controller($dev).createUI();
		}
		return $dev;
	};
	var createDeviceController = function(device) {
		var inputs = [];
		var outputs = [];
		var addInput = function(label, description) {
			var $node = createNode('in', label, description, device.headless);
			$node.on('nodeValueChange', function(event) {
				device.$ui.trigger('inputValueChange');
			});
			if (!device.headless)
			{
				device.$ui.append($node);
			}
			var node = controller($node);
			inputs.push(node);
			return node;
		};
		var addOutput = function(label, description) {
			var $node = createNode('out', label, description, device.headless);
			if (!device.headless)
			{
				device.$ui.append($node);
			}
			var node = controller($node);
			outputs.push(node);
			return node;
		};
		var getInputs = function() {
			return inputs;
		};
		var getOutputs = function() {
			return outputs;
		};
		var disconnectAll = function() {
			$.each(getInputs(), function(i, inNode) {
				var outNode = inNode.getOutput();
				if (outNode != null)
				{
					outNode.disconnectFrom(inNode);
				}
			});
			$.each(getOutputs(), function(i, outNode) {
				$.each(outNode.getInputs(), function(i, inNode) {
					outNode.disconnectFrom(inNode);
				});
			});
		};
		device.$ui.on('dispose', function() {
			$.each(getInputs(), function(i, inNode) {
				inNode.$ui.remove();
			});
			$.each(getOutputs(), function(i, outNode) {
				outNode.$ui.remove();
			});
			device.$ui.remove();
		});
		var selected = false;
		var setSelected = function(value) {
			selected = value;
			device.$ui.trigger('deviceSelect');
		};
		var isSelected = function() {
			return selected;
		};
		var label = device.deviceDef.label;
		var defaultLabel = device.deviceDef.type;
		if (typeof label == 'undefined')
		{
			label = defaultLabel;
		}
		var setLabel = function(value) {
			value = value.replace(/^\s+|\s+$/g, '');
			label = value || defaultLabel;
			device.$ui.trigger('deviceLabelChange');
		};
		var getLabel = function() {
			return label;
		};
		var getSize = function() {
			var nodes = Math.max(device.getInputs().length, device.getOutputs().length);
			return {
				width: unit * 2,
				height: unit * Math.max(2, device.halfPitch ? (nodes + 1) / 2 : nodes)
			};
		};
		var layoutUI = function() {
			var size = device.getSize();
			var w = size.width;
			var h = size.height;
			device.$ui.children('.simcir-device-body').attr({x: 0, y: 0, width: w, height: h});
			var pitch = device.halfPitch? unit / 2: unit;
			var layoutNodes = function(nodes, x) {
				var offset = (h - pitch * (nodes.length - 1)) / 2;
				$.each(nodes, function(i, node) {
					transform(node.$ui, x, pitch * i + offset);
				});
			};
			layoutNodes(getInputs(), 0);
			layoutNodes(getOutputs(), w);
			device.$ui.children('.simcir-device-label').attr({
				x: w / 2,
				y: h + fontSize
			});
		};
		var createUI = function() {
			device.$ui.attr('class', 'simcir-device');
			device.$ui.on('deviceSelect', function() {
				if (selected)
				{
					$(this).addClass('simcir-device-selected');
				}
				else
				{
					$(this).removeClass('simcir-device-selected');
				}
			});
			var $body = createSVGElement('rect').
				attr('class', 'simcir-device-body').
				attr('rx', 2).attr('ry', 2);
			device.$ui.prepend($body);
			var $label = createLabel(label).
				attr('class', 'simcir-device-label').
				attr('text-anchor', 'middle');
			device.$ui.on('deviceLabelChange', function() {
				$label.text(getLabel());
			});
			var label_dblClickHandler = function(event) {
				event.preventDefault();
				event.stopPropagation();
				var $workspace = $(event.target).closest('.simcir-workspace');
				if (!controller($workspace).data().editable) 
				{
					return;
				}
				var title = 'Enter device name ';
				var $labelEditor = $('<input type="text"/>').
					addClass('simcir-label-editor').
					val($label.text()).
					on('keydown', function(event) {
						if (event.keyCode == 13) 
						{
							// ENTER
							setLabel($(this).val());
							$dlg.remove();
						} 
						else if (event.keyCode == 27) 
						{
							// ESC
							$dlg.remove();
						}
					});
				var $placeHolder = $('<div></div>').
					append($labelEditor);
				var $dlg = showDialog(title, $placeHolder);
				$labelEditor.focus();
			};
			device.$ui.on('deviceAdd', function() {
				$label.on('dblclick', label_dblClickHandler);
			});
			device.$ui.on('deviceRemove', function() {
				$label.off('dblclick', label_dblClickHandler);
			});
			device.$ui.append($label);
			layoutUI();
		};
		var getState = function() {
			return null;
		};
		return $.extend(device, {
			addInput: addInput,
			addOutput: addOutput,
			getInputs: getInputs,
			getOutputs: getOutputs,
			disconnectAll: disconnectAll,
			setSelected: setSelected,
			isSelected: isSelected,
			getLabel: getLabel,
			halfPitch: false,
			getSize: getSize,
			createUI: createUI,
			layoutUI: layoutUI,
			getState: getState
		});
	};
	var createConnector = function(x1, y1, x2, y2) {
		return createSVGElement('path').attr('d', 'M ' + x1 + ' ' + y1 + ' L ' + x2 + ' ' + y2).attr('class', 'simcir-connector');
	};
	var connect = function($node1, $node2) {
		var type1 = $node1.attr('simcir-node-type');
		var type2 = $node2.attr('simcir-node-type');
		if (type1 == 'in' && type2 == 'out')
		{
			controller($node2).connectTo(controller($node1));
		}
		else if (type1 == 'out' && type2 == 'in')
		{
			controller($node1).connectTo(controller($node2));
		}
	};
	var buildCircuit = function(data, headless, scope) {
		var $devices = [];
		var $devMap = {};
		var getNode = function(path) {
			if (!path.match(/^(\w+)\.(in|out)([0-9]+)$/g))
			{
				throw 'unknown path:' + path;
			}
			var devId = RegExp.$1;
			var type = RegExp.$2;
			var index = +RegExp.$3;
			return (type == 'in') ? controller($devMap[devId]).getInputs()[index] : controller($devMap[devId]).getOutputs()[index];
		};
		$.each(data.devices, function(i, deviceDef) {
			var $dev = createDevice(deviceDef, headless, scope);
			transform($dev, deviceDef.x, deviceDef.y);
			$devices.push($dev);
			$devMap[deviceDef.id] = $dev;
		});
		$.each(data.connectors, function(i, conn) {
			var nodeFrom = getNode(conn.from);
			var nodeTo = getNode(conn.to);
			if (nodeFrom && nodeTo)
			{
				connect(nodeFrom.$ui, nodeTo.$ui);
			}
		});
		return $devices;
	};
	var dialogManager = function() {
		var dialogs = [];
		var updateDialogs = function($dlg, remove) {
			var newDialogs = [];
			$.each(dialogs, function(i) {
				if (dialogs[i] != $dlg)
				{
					newDialogs.push(dialogs[i]);
				}
			});
			if (!remove)
			{
				newDialogs.push($dlg);
			}
			$.each(newDialogs, function(i) {
				newDialogs[i].css('z-index', '' + (i + 1));
			});
			dialogs = newDialogs;
		};
		return {
			add: function($dlg) {
				updateDialogs($dlg);
			},
			remove: function($dlg) {
				updateDialogs($dlg, true);
			},
			toFront: function($dlg) {
				updateDialogs($dlg);
			}
		};
	}();
	var showDialog = function(title, $content) {
		var $closeButton = function() {
			var r = 16;
			var pad = 4;
			var $btn = createSVG(r, r).attr('class', 'simcir-dialog-close-button');
			var g = graphics($btn);
			g.drawRect(0, 0, r, r);
			g.attr['class'] = 'simcir-dialog-close-button-symbol';
			g.moveTo(pad, pad);
			g.lineTo(r - pad, r - pad);
			g.closePath();
			g.moveTo(r - pad, pad);
			g.lineTo(pad, r - pad);
			g.closePath();
			return $btn;
		}();
		var $title = $('<div></div>').addClass('simcir-dialog-title').text(title).css('cursor', 'default').on('mousedown', function(event) {
				event.preventDefault();
			});
		var $dlg = $('<div></div>').addClass('simcir-dialog').css({
			position:'absolute'
		}).append($title.css('float', 'left')).append($closeButton.css('float', 'right')).append($('<br/>').css('clear', 'both')).append($content);
		$('BODY').append($dlg);
		dialogManager.add($dlg);
		var dragPoint = null;
		var dlg_mouseDownHandler = function(event) {
			if (!$(event.target).hasClass('simcir-dialog') && !$(event.target).hasClass('simcir-dialog-title'))
			{
				return;
			}
			event.preventDefault();
			dialogManager.toFront($dlg);
			var off = $dlg.offset();
			dragPoint = {
				x: event.pageX - off.left,
				y: event.pageY - off.top
			};
			$(document).on('mousemove', dlg_mouseMoveHandler);
			$(document).on('mouseup', dlg_mouseUpHandler);
		};
		var dlg_mouseMoveHandler = function(event) {
			moveTo(event.pageX - dragPoint.x, event.pageY - dragPoint.y);
		};
		var dlg_mouseUpHandler = function(event) {
			$(document).off('mousemove', dlg_mouseMoveHandler);
			$(document).off('mouseup', dlg_mouseUpHandler);
		};
		$dlg.on('mousedown', dlg_mouseDownHandler);
		$closeButton.on('mousedown', function() {
			$dlg.trigger('close');
			$dlg.remove();
			dialogManager.remove($dlg);
		});
		var w = $dlg.width();
		var h = $dlg.height();
		var cw = $(window).width();
		var ch = $(window).height();
		var getProp = function(id) {
			return $('HTML')[id]() || $('BODY')[id]();
		};
		var x = (cw - w) / 2 + getProp('scrollLeft');
		var y = (ch - h) / 2 + getProp('scrollTop');
		var moveTo = function(x, y) {
			$dlg.css({
				left: x + 'px',
				top: y + 'px'
			});
		};
		moveTo(x, y);
		return $dlg;
	};
	var createDeviceRefFactory = function(data) {
		return function(device) {
			var $devs = buildCircuit(data, true, {});
			var $ports = [];
			$.each($devs, function(i, $dev) {
				var deviceDef = controller($dev).deviceDef;
				if (deviceDef.type == 'In' || deviceDef.type == 'Out')
				{
					$ports.push($dev);
				}
			});
			$ports.sort(function($p1, $p2) {
				var x1 = controller($p1).deviceDef.x;
				var y1 = controller($p1).deviceDef.y;
				var x2 = controller($p2).deviceDef.x;
				var y2 = controller($p2).deviceDef.y;
				if (x1 == x2)
				{
					return (y1 < y2) ? -1 : 1;
				}
				return (x1 < x2) ? -1 : 1;
			});
			var getDesc = function(port) {
				return port? port.description: '';
			};
			$.each($ports, function(i, $port) {
				var port = controller($port);
				var portDef = port.deviceDef;
				var inPort;
				var outPort;
				if (portDef.type == 'In')
				{
					outPort = port.getOutputs()[0];
					inPort = device.addInput(portDef.label, getDesc(outPort.getInputs()[0]));
					var inNode = port.getInputs()[0];
					if (inNode.getOutput() != null)
					{
						inNode.getOutput().disconnectFrom(inNode);
					}
				}
				else if (portDef.type == 'Out')
				{
					inPort = port.getInputs()[0];
					outPort = device.addOutput(portDef.label, getDesc(inPort.getOutput()));
					var outNode = port.getOutputs()[0];
					$.each(outNode.getInputs(), function(i, inNode) {
						if (inNode.getOutput() != null)
						{
							inNode.getOutput().disconnectFrom(inNode);
						}
					});
				}
				inPort.$ui.on('nodeValueChange', function() {
					outPort.setValue(inPort.getValue());
				});
			});
			var super_getSize = device.getSize;
			device.getSize = function() {
				var size = super_getSize();
				return {
					width: unit * 4,
					height: size.height
				};
			};
			device.$ui.on('dispose', function() {
				$.each($devs, function(i, $dev) {
					$dev.trigger('dispose');
				});
			});
			device.$ui.on('dblclick', function(event) {
				event.preventDefault();
				event.stopPropagation();
				showDialog(device.deviceDef.label || device.deviceDef.type, setupSimcir($('<div></div>'), data)).on('close', function() {
					$(this).find('.simcir-workspace').trigger('dispose');
				});
			});
		};
	};
	var createCustomLayoutDeviceRefFactory = function(data) {
		return function(device) {
			var $devs = buildCircuit(data, true, {});
			var $ports = [];
			var intfs = [];
			$.each($devs, function(i, $dev) {
				var deviceDef = controller($dev).deviceDef;
				if (deviceDef.type == 'In' || deviceDef.type == 'Out')
				{
					$ports.push($dev);
				}
			});
			var getDesc = function(port) {
				return port? port.description: '';
			};
			$.each($ports, function(i, $port) {
				var port = controller($port);
				var portDef = port.deviceDef;
				var inPort;
				var outPort;
				if (portDef.type == 'In')
				{
					outPort = port.getOutputs()[0];
					inPort = device.addInput();
					intfs.push({
						node: inPort,
						label: portDef.label,
						desc: getDesc(outPort.getInputs()[0])
					});
					var inNode = port.getInputs()[0];
					if (inNode.getOutput() != null)
					{
						inNode.getOutput().disconnectFrom(inNode);
					}
				}
				else if (portDef.type == 'Out')
				{
					inPort = port.getInputs()[0];
					outPort = device.addOutput();
					intfs.push({
						node: outPort,
						label: portDef.label,
						desc: getDesc(inPort.getOutput())
					});
					var outNode = port.getOutputs()[0];
					$.each(outNode.getInputs(), function(i, inNode) {
						if (inNode.getOutput() != null)
						{
							inNode.getOutput().disconnectFrom(inNode);
						}
					});
				}
				inPort.$ui.on('nodeValueChange', function() {
					outPort.setValue(inPort.getValue());
				});
			});
			var layout = data.layout;
			var cols = layout.cols;
			var rows = layout.rows;
			rows = ~~((Math.max(1, rows) + 1) / 2) * 2;
			cols = ~~((Math.max(1, cols) + 1) / 2) * 2;
			var updateIntf = function(intf, x, y, align) {
				transform(intf.node.$ui, x, y);
				if (!intf.$label)
				{
					intf.$label = createLabel(intf.label).attr('class', 'simcir-node-label');
					enableEvents(intf.$label, false);
					intf.node.$ui.append(intf.$label);
				}
				if (align == 'right')
				{
					intf.$label.attr('text-anchor', 'start').attr('x', 6).attr('y', fontSize / 2);
				}
				else if (align == 'left')
				{
					intf.$label.attr('text-anchor', 'end').attr('x', -6).attr('y', fontSize / 2);
				}
				else if (align == 'top')
				{
					intf.$label.attr('text-anchor', 'middle').attr('x', 0).attr('y', -6);
				}
				else if (align == 'bottom')
				{
					intf.$label.attr('text-anchor', 'middle').attr('x', 0).attr('y', fontSize + 6);
				}
			};
			var doLayout = function() {
				var x = 0;
				var y = 0;
				var w = unit * cols / 2;
				var h = unit * rows / 2;
				device.$ui.children('.simcir-device-label').attr({y: y + h + fontSize});
				device.$ui.children('.simcir-device-body').attr({x: x, y: y, width: w, height: h});
				$.each(intfs, function(i, intf) {
					if (layout.nodes[intf.label] && layout.nodes[intf.label].match(/^([TBLR])([0-9]+)$/))
					{
						var off = +RegExp.$2 * unit / 2;
						switch(RegExp.$1)
						{
						case 'T':
							updateIntf(intf, x + off, y, 'bottom');
						break;
						case 'B':
							updateIntf(intf, x + off, y + h, 'top');
						break;
						case 'L':
							updateIntf(intf, x, y + off, 'right');
						break;
						case 'R':
							updateIntf(intf, x + w, y + off, 'left');
						break;
						}
					}
					else
					{
						transform(intf.node.$ui, 0, 0);
					}
				});
			};
			device.getSize = function() {
				return {width: unit * cols / 2, height: unit * rows / 2};
			};
			device.$ui.on('dispose', function() {
				$.each($devs, function(i, $dev) {
					$dev.trigger('dispose');
				});
			});
			if (data.layout.hideLabelOnWorkspace) {
				device.$ui.on('deviceAdd', function() {
					device.$ui.children('.simcir-device-label').css('display', 'none');
				}).on('deviceRemove', function() {
					device.$ui.children('.simcir-device-label').css('display', '');
				});
			}
			device.$ui.on('dblclick', function(event) {
				event.preventDefault();
				event.stopPropagation();
				showDialog(device.deviceDef.label || device.deviceDef.type, setupSimcir($('<div></div>'), data)).on('close', function() {
					$(this).find('.simcir-workspace').trigger('dispose');
				});
			});
			var super_createUI = device.createUI;
			device.createUI = function() {
				super_createUI();
				doLayout();
			};
		};
	};
	var factories = {};
	var defaultToolbox = [];
	var registerDevice = function(type, factory, deprecated) {
		if (typeof factory == 'object')
		{
			if (typeof factory.layout == 'object')
			{
				factory = createCustomLayoutDeviceRefFactory(factory);
			}
			else
			{
				factory = createDeviceRefFactory(factory);
			}
		}
		factories[type] = factory;
		if (!deprecated)
		{
			defaultToolbox.push({type: type});
		}
	};
	var createScrollbar = function() {
		var _value = 0;
		var _min = 0;
		var _max = 0;
		var _barSize = 0;
		var _width = 0;
		var _height = 0;
		var $body = createSVGElement('rect');
		var $bar = createSVGElement('g').append(createSVGElement('rect')).attr('class', 'simcir-scrollbar-bar');
		var $scrollbar = createSVGElement('g').attr('class', 'simcir-scrollbar').append($body).append($bar).on('unitup', function(event) {
			setValue(_value - unit * 2);
		}).on('unitdown', function(event) {
			setValue(_value + unit * 2);
		}).on('rollup', function(event) {
			setValue(_value - _barSize);
		}).on('rolldown', function(event) {
			setValue(_value + _barSize);
		});
		var dragPoint = null;
		var bar_mouseDownHandler = function(event) {
			event.preventDefault();
			event.stopPropagation();
			var pos = transform($bar);
			dragPoint = {
				x: event.pageX - pos.x,
				y: event.pageY - pos.y
			};
			$(document).on('mousemove', bar_mouseMoveHandler);
			$(document).on('mouseup', bar_mouseUpHandler);
		};
		var bar_mouseMoveHandler = function(event) {
			calc(function(unitSize) {
				setValue((event.pageY - dragPoint.y) / unitSize);
			});
		};
		var bar_mouseUpHandler = function(event) {
			$(document).off('mousemove', bar_mouseMoveHandler);
			$(document).off('mouseup', bar_mouseUpHandler);
		};
		$bar.on('mousedown', bar_mouseDownHandler);
		var body_mouseDownHandler = function(event) {
			event.preventDefault();
			event.stopPropagation();
			var off = $scrollbar.parent('svg').offset();
			var pos = transform($scrollbar);
			var y = event.pageY - off.top - pos.y;
			var barPos = transform($bar);
			if (y < barPos.y)
			{
				$scrollbar.trigger('rollup');
			}
			else
			{
				$scrollbar.trigger('rolldown');
			}
		};
		$body.on('mousedown', body_mouseDownHandler);
		var setSize = function(width, height) {
			_width = width;
			_height = height;
			layout();
		};
		var layout = function() {
			$body.attr({
				x: 0,
				y: 0,
				width: _width,
				height: _height
			});
			var visible = _max - _min > _barSize;
			$bar.css('display', visible? 'inline': 'none');
			if (!visible)
			{
				return;
			}
			calc(function(unitSize) {
				$bar.children('rect').attr({
					x: 0,
					y: 0,
					width: _width,
					height: _barSize * unitSize
				});
				transform($bar, 0, _value * unitSize);
			});
		};
		var calc = function(f) {
			f(_height / (_max - _min));
		};
		var setValue = function(value) {
			setValues(value, _min, _max, _barSize);
		};
		var setValues = function(value, min, max, barSize) {
			value = Math.max(min, Math.min(value, max - barSize));
			var changed = (value != _value);
			_value = value;
			_min = min;
			_max = max;
			_barSize = barSize;
			layout();
			if (changed)
			{
				$scrollbar.trigger('scrollValueChange');
			}
		};
		var getValue = function() {
			return _value;
		};
		controller($scrollbar, {
			setSize: setSize,
			setValues: setValues,
			getValue: getValue
		});
		return $scrollbar;
	};
	var getUniqueId = function() {
		var uniqueIdCount = 0;
		return function() {
			return 'simcir-id' + uniqueIdCount++;
		};
	}();
	var createWorkspace = function(data) {
		data = $.extend({
			width: 400,
			height: 200,
			showToolbox: true,
			editable: true,
			toolbox: defaultToolbox,
			devices: [],
			connectors: [],
		}, data);
		var scope = {};
		var workspaceWidth = data.width;
		var workspaceHeight = data.height;
		var barWidth = unit;
		var toolboxWidth = data.showToolbox? unit * 6 + barWidth: 0;
		var connectorsValid = true;
		var connectorsValidator = function() {
			if (!connectorsValid)
			{
				updateConnectors();
				connectorsValid = true;
			}
		};
		var $workspace = createSVG(workspaceWidth, workspaceHeight).attr('class', 'simcir-workspace').on('nodeValueChange', function(event) {
			connectorsValid = false;
			window.setTimeout(connectorsValidator, 0);
		}).on('dispose', function() {
			$(this).find('.simcir-device').trigger('dispose');
			$toolboxPane.remove();
			$workspace.remove();
		});
		disableSelection($workspace);
		var $defs = createSVGElement('defs');
		$workspace.append($defs);
		!function() {
			var patId = getUniqueId();
			var pitch = unit / 2;
			var w = workspaceWidth - toolboxWidth;
			var h = workspaceHeight;
			$defs.append(createSVGElement('pattern').attr({
				id: patId,
				x: 0,
				y: 0,
				width: pitch / w,
				height: pitch / h
			}).append(createSVGElement('rect').attr('class', 'simcir-pin-hole').attr({
				x: 0,
				y: 0,
				width: 1,
				height: 1
			})));
			$workspace.append(createSVGElement('rect').attr({
				x: toolboxWidth,
				y: 0,
				width: w,
				height: h
			}).css({
				fill: 'url(#' + patId + ')'
			}));
		}();
		var $toolboxDevicePane = createSVGElement('g');
		var $scrollbar = createScrollbar();
		$scrollbar.on('scrollValueChange', function(event) {
			transform($toolboxDevicePane, 0, -controller($scrollbar).getValue());
		});
		controller($scrollbar).setSize(barWidth, workspaceHeight);
		transform($scrollbar, toolboxWidth - barWidth, 0);
		var $toolboxPane = createSVGElement('g').attr('class', 'simcir-toolbox').append(createSVGElement('rect').attr({
			x: 0,
			y: 0,
			width: toolboxWidth,
			height: workspaceHeight
		})).append($toolboxDevicePane).append($scrollbar).on('wheel', function(event) {
			event.preventDefault();
			var oe = event.originalEvent || event;
			if (oe.deltaY < 0)
			{
				$scrollbar.trigger('unitup');
			}
			else if (oe.deltaY > 0)
			{
				$scrollbar.trigger('unitdown');
			}
		});
		var $devicePane = createSVGElement('g');
		transform($devicePane, toolboxWidth, 0);
		var $connectorPane = createSVGElement('g');
		var $temporaryPane = createSVGElement('g');
		enableEvents($connectorPane, false);
		enableEvents($temporaryPane, false);
		if (data.showToolbox)
		{
			$workspace.append($toolboxPane);
		}
		$workspace.append($devicePane);
		$workspace.append($connectorPane);
		$workspace.append($temporaryPane);
		var addDevice = function($dev) {
			$devicePane.append($dev);
			$dev.trigger('deviceAdd');
		};
		var removeDevice = function($dev) {
			$dev.trigger('deviceRemove');
			controller($dev).disconnectAll();
			$dev.trigger('dispose');
			updateConnectors();
		};
		var disconnect = function($inNode) {
			var inNode = controller($inNode);
			if (inNode.getOutput() != null)
			{
				inNode.getOutput().disconnectFrom(inNode);
			}
			updateConnectors();
		};
		var updateConnectors = function() {
			$connectorPane.children().remove();
			$devicePane.children('.simcir-device').each(function() {
				var device = controller($(this));
				$.each(device.getInputs(), function(i, inNode) {
					if (inNode.getOutput() != null)
					{
						var p1 = offset(inNode.$ui);
						var p2 = offset(inNode.getOutput().$ui);
						var $conn = createConnector(p1.x, p1.y, p2.x, p2.y);
						if (inNode.getOutput().getValue() != null)
						{
							$conn.addClass('simcir-connector-hot');
						}
						$connectorPane.append($conn);
					}
				});
			});
		};
		var loadToolbox = function(data) {
			var vgap = 8;
			var y = vgap;
			$.each(data.toolbox, function(i, deviceDef) {
				var $dev = createDevice(deviceDef);
				$toolboxDevicePane.append($dev);
				var size = controller($dev).getSize();
				transform($dev, (toolboxWidth - barWidth - size.width) / 2, y);
				y += (size.height + fontSize + vgap);
			});
			controller($scrollbar).setValues(0, 0, y, workspaceHeight);
		};
		var getData = function() {
			var devIdCount = 0;
			$devicePane.children('.simcir-device').each(function() {
				var $dev = $(this);
				var device = controller($dev);
				var devId = 'dev' + devIdCount++;
				device.id = devId;
				$.each(device.getInputs(), function(i, node) {
					node.id = devId + '.in' + i;
				});
				$.each(device.getOutputs(), function(i, node) {
					node.id = devId + '.out' + i;
				});
			});
			var toolbox = [];
			var devices = [];
			var connectors = [];
			var clone = function(obj) {
				return JSON.parse(JSON.stringify(obj));
			};
			$toolboxDevicePane.children('.simcir-device').each(function() {
				var $dev = $(this);
				var device = controller($dev);
				toolbox.push(device.deviceDef);
			});
			$devicePane.children('.simcir-device').each(function() {
				var $dev = $(this);
				var device = controller($dev);
				$.each(device.getInputs(), function(i, inNode) {
					if (inNode.getOutput() != null)
					{
						connectors.push({from:inNode.id, to:inNode.getOutput().id});
					}
				});
				var pos = transform($dev);
				var deviceDef = clone(device.deviceDef);
				deviceDef.id = device.id;
				deviceDef.x = pos.x;
				deviceDef.y = pos.y;
				deviceDef.label = device.getLabel();
				var state = device.getState();
				if (state != null)
				{
					deviceDef.state = state;
				}
				devices.push(deviceDef);
			});
			return {
				width: data.width,
				height: data.height,
				showToolbox: data.showToolbox,
				editable: data.editable,
				toolbox: toolbox,
				devices: devices,
				connectors: connectors
			};
		};
		var getText = function() {
			var data = getData();
			var buf = '';
			var print = function(s) {
				buf += s;
			};
			var println = function(s) {
				print(s);
				buf += '\r\n';
			};
			var printArray = function(array) {
				$.each(array, function(i, item) {
					println('		' + JSON.stringify(item).replace(/</g, '\\u003c').replace(/>/g, '\\u003e') + (i + 1 < array.length? ',': ''));
				});
			};
			println('{');
			println('	"width":' + data.width + ',');
			println('	"height":' + data.height + ',');
			println('	"showToolbox":' + data.showToolbox + ',');
			println('	"toolbox":[');
			printArray(data.toolbox);
			println('	],');
			println('	"devices":[');
			printArray(data.devices);
			println('	],');
			println('	"connectors":[');
			printArray(data.connectors);
			println('	]');
			print('}');
			return buf;
		};
		var dragMoveHandler = null;
		var dragCompleteHandler = null;
		var adjustDevice = function($dev) {
			var pitch = unit / 2;
			var adjust = function(v) {
				return Math.round(v / pitch) * pitch;
			};
			var pos = transform($dev);
			var size = controller($dev).getSize();
			var x = Math.max(0, Math.min(pos.x, workspaceWidth - toolboxWidth - size.width));
			var y = Math.max(0, Math.min(pos.y, workspaceHeight - size.height));
			transform($dev, adjust(x), adjust(y));
		};
		var beginConnect = function(event, $target) {
			var $srcNode = $target.closest('.simcir-node');
			var off = $workspace.offset();
			var pos = offset($srcNode);
			if ($srcNode.attr('simcir-node-type') == 'in')
			{
				disconnect($srcNode);
			}
			dragMoveHandler = function(event) {
				var x = event.pageX - off.left;
				var y = event.pageY - off.top;
				$temporaryPane.children().remove();
				$temporaryPane.append(createConnector(pos.x, pos.y, x, y));
			};
			dragCompleteHandler = function(event) {
				$temporaryPane.children().remove();
				var $dst = $(event.target);
				if (isActiveNode($dst))
				{
					var $dstNode = $dst.closest('.simcir-node');
					connect($srcNode, $dstNode);
					updateConnectors();
				}
			};
		};
		var beginNewDevice = function(event, $target) {
			var $dev = $target.closest('.simcir-device');
			var pos = offset($dev);
			$dev = createDevice(controller($dev).deviceDef, false, scope);
			transform($dev, pos.x, pos.y);
			$temporaryPane.append($dev);
			var dragPoint = {
				x: event.pageX - pos.x,
				y: event.pageY - pos.y
			};
			dragMoveHandler = function(event) {
				transform($dev, event.pageX - dragPoint.x, event.pageY - dragPoint.y);
			};
			dragCompleteHandler = function(event) {
				var $target = $(event.target);
				if ($target.closest('.simcir-toolbox').length == 0)
				{
					$dev.detach();
					var pos = transform($dev);
					transform($dev, pos.x - toolboxWidth, pos.y);
					adjustDevice($dev);
					addDevice($dev);
				}
				else
				{
					$dev.trigger('dispose');
				}
			};
		};
		var $selectedDevices = [];
		var addSelected = function($dev) {
			controller($dev).setSelected(true);
			$selectedDevices.push($dev);
		};
		var deselectAll = function() {
			$devicePane.children('.simcir-device').each(function() {
				controller($(this)).setSelected(false);
			});
			$selectedDevices = [];
		};
		var beginMoveDevice = function(event, $target) {
			var $dev = $target.closest('.simcir-device');
			var pos = transform($dev);
			if (!controller($dev).isSelected())
			{
				deselectAll();
				addSelected($dev);
				$dev.parent().append($dev.detach());
			}
			var dragPoint = {
				x: event.pageX - pos.x,
				y: event.pageY - pos.y
			};
			dragMoveHandler = function(event) {
				enableEvents($dev, false);
				var curPos = transform($dev);
				var deltaPos = {
					x: event.pageX - dragPoint.x - curPos.x,
					y: event.pageY - dragPoint.y - curPos.y
				};
				$.each($selectedDevices, function(i, $dev) {
					var curPos = transform($dev);
					transform($dev, curPos.x + deltaPos.x, curPos.y + deltaPos.y);
				});
				updateConnectors();
			};
			dragCompleteHandler = function(event) {
				var $target = $(event.target);
				enableEvents($dev, true);
				$.each($selectedDevices, function(i, $dev) {
					if ($target.closest('.simcir-toolbox').length == 0)
					{
						adjustDevice($dev);
						updateConnectors();
					}
					else
					{
						removeDevice($dev);
					}
				});
			};
		};
		var beginSelectDevice = function(event, $target) {
			var intersect = function(rect1, rect2) {
				return !(rect1.x > rect2.x + rect2.width || rect1.y > rect2.y + rect2.height || rect1.x + rect1.width < rect2.x || rect1.y + rect1.height < rect2.y);
			};
			var pointToRect = function(p1, p2) {
				return {
					x: Math.min(p1.x, p2.x),
					y: Math.min(p1.y, p2.y),
					width: Math.abs(p1.x - p2.x),
					height: Math.abs(p1.y - p2.y)
				};
			};
			deselectAll();
			var off = $workspace.offset();
			var pos = offset($devicePane);
			var p1 = {
				x: event.pageX - off.left,
				y: event.pageY - off.top
			};
			dragMoveHandler = function(event) {
				deselectAll();
				var p2 = {
					x: event.pageX - off.left,
					y: event.pageY - off.top
				};
				var selRect = pointToRect(p1, p2);
				$devicePane.children('.simcir-device').each(function() {
					var $dev = $(this);
					var devPos = transform($dev);
					var devSize = controller($dev).getSize();
					var devRect = {
						x: devPos.x + pos.x,
						y: devPos.y + pos.y,
						width: devSize.width,
						height: devSize.height
					};
					if (intersect(selRect, devRect))
					{
						addSelected($dev);
					}
				});
				$temporaryPane.children().remove();
				$temporaryPane.append(createSVGElement('rect').attr(selRect).attr('class', 'simcir-selection-rect'));
			};
		};
		var mouseDownHandler = function(event) {
			event.preventDefault();
			event.stopPropagation();
			var $target = $(event.target);
			if (!data.editable)
			{
				return;
			}
			if (isActiveNode($target))
			{
				beginConnect(event, $target);
			}
			else if ($target.closest('.simcir-device').length == 1)
			{
				if ($target.closest('.simcir-toolbox').length == 1)
				{
					beginNewDevice(event, $target);
				}
				else
				{
					beginMoveDevice(event, $target);
				}
			}
			else
			{
				beginSelectDevice(event, $target);
			}
			$(document).on('mousemove', mouseMoveHandler);
			$(document).on('mouseup', mouseUpHandler);
		};
		var mouseMoveHandler = function(event) {
			if (dragMoveHandler != null)
			{
				dragMoveHandler(event);
			}
		};
		var mouseUpHandler = function(event) {
			if (dragCompleteHandler != null)
			{
				dragCompleteHandler(event);
			}
			dragMoveHandler = null;
			dragCompleteHandler = null;
			$devicePane.children('.simcir-device').each(function() {
				enableEvents($(this), true);
			});
			$temporaryPane.children().remove();
			$(document).off('mousemove', mouseMoveHandler);
			$(document).off('mouseup', mouseUpHandler);
		};
		$workspace.on('mousedown', mouseDownHandler);
		loadToolbox(data);
		$.each(buildCircuit(data, false, scope), function(i, $dev) {
			addDevice($dev);
		});
		updateConnectors();
		controller($workspace, {
			data: getData,
			text: getText
		});
		return $workspace;
	};
	var clearSimcir = function($placeHolder) {
		$placeHolder = $($placeHolder[0]);
		$placeHolder.find('.simcir-workspace').trigger('dispose');
		$placeHolder.children().remove();
		return $placeHolder;
	};
	var setupSimcir = function($placeHolder, data) {
		$placeHolder = clearSimcir($placeHolder);
		var $workspace = simcir.createWorkspace(data);
		var $dataArea = $('<textarea></textarea>').addClass('simcir-json-data-area').attr('readonly', 'readonly').css('width', $workspace.attr('width') + 'px').css('height', $workspace.attr('height') + 'px');
		var showData = false;
		var toggle = function() {
			$workspace.css('display', !showData? 'inline': 'none');
			$dataArea.css('display', showData? 'inline': 'none');
			if (showData)
			{
				$dataArea.val(controller($workspace).text()).focus();
			}
			showData = !showData;
		};
		$placeHolder.text('');
		$placeHolder.append($('<div></div>').addClass('simcir-body').append($workspace).append($dataArea).on('click', function(event) {
			if (event.ctrlKey || event.metaKey)
			{
				toggle();
			}
		}));
		toggle();
		return $placeHolder;
	};
	var setupSimcirDoc = function($placeHolder) {
		var $table = $('<table><tbody></tbody></table>').addClass('simcir-doc-table');
		$.each(defaultToolbox, function(i, deviceDef) {
			var $dev = createDevice(deviceDef);
			var device = controller($dev);
			if (!device.doc)
			{
				return;
			}
			var doc = $.extend({description: '', params: []},device.doc);
			var size = device.getSize();
			var $tr = $('<tr></tr>');
			var hgap = 32;
			var vgap = 8;
			var $view = createSVG(size.width + hgap * 2, size.height + vgap * 2 + fontSize);
			var $dev = createDevice(deviceDef);
			transform($dev, hgap, vgap);
			$view.append($dev);
			$tr.append($('<td></td>').css('text-align', 'center').append($view));
			var $desc = $('<td></td>');
			$tr.append($desc);
			if (doc.description)
			{
				$desc.append($('<span></span>').text(doc.description));
			}
			$desc.append($('<div>Params</div>').addClass('simcir-doc-title'));
			var $paramsTable = $('<table><tbody></tbody></table>').addClass('simcir-doc-params-table');
			$paramsTable.children('tbody').append($('<tr></tr>').append($('<th>Name</th>')).append($('<th>Type</th>')).append($('<th>Default</th>')).append($('<th>Description</th>')));
			$paramsTable.children('tbody').append($('<tr></tr>').append($('<td>type</td>')).append($('<td>string</td>')).append($('<td>-</td>').css('text-align', 'center')).append($('<td>"' + deviceDef.type + '"</td>')));
			if (!doc.labelless)
			{
				$paramsTable.children('tbody').append($('<tr></tr>').append($('<td>label</td>')).append($('<td>string</td>')).append($('<td>same with type</td>').css('text-align', 'center')).append($('<td>label for a device.</td>')));
			}
			if (doc.params)
			{
				$.each(doc.params, function(i, param) {
					$paramsTable.children('tbody').append($('<tr></tr>').append($('<td></td>').text(param.name)).append($('<td></td>').text(param.type)).append($('<td></td>').css('text-align', 'center').text(param.defaultValue)).append($('<td></td>').text(param.description)));
				});
			}
			$desc.append($paramsTable);
			if (doc.code)
			{
				$desc.append($('<div>Code</div>').addClass('simcir-doc-title'));
				$desc.append($('<div></div>').addClass('simcir-doc-code').text(doc.code));
			}
			$table.children('tbody').append($tr);
		});
		$placeHolder.append($table);
	};
	$(function() {
	  document.getElementById('make').onclick = function() {
   		$('.simcir').each(function() {
	  		var koko0 = document.getElementById('koko0');
	  		var koko = document.getElementById('koko');
	  		eval(koko0.value);
		  	var text = koko.value.replace(/^\s+|\s+$/g, '');
		  	//alert(text);
	  		var $placeHolder = $(this);
  			//var text = $placeHolder.text().replace(/^\s+|\s+$/g, '');
  			setupSimcir($placeHolder, JSON.parse(text || '{}'));
  		});
	  };
	});
	$(function() {
		$('.simcir-doc').each(function() {
			setupSimcirDoc($(this));
		});
	});
	$.extend($s, {
		registerDevice: registerDevice,
		clearSimcir: clearSimcir,
		setupSimcir: setupSimcir,
		createWorkspace: createWorkspace,
		createSVGElement: createSVGElement,
		offset: offset,
		transform: transform,
		enableEvents: enableEvents,
		graphics: graphics,
		controller: controller,
		unit: unit
	});
}(simcir);
!function($s) {
	'use strict';
	var $ = $s.$;
	var unit = $s.unit;
	var connectNode = function(in1, out1) {
		var in1_super_setValue = in1.setValue;
		in1.setValue = function(value, force) {
			var changed = in1.getValue() !== value;
			in1_super_setValue(value, force);
			if (changed || force)
			{
				out1.setValue(in1.getValue());
			}
		};
	};
	var createPortFactory = function(type) {
		return function(device) {
			var in1 = device.addInput();
			var out1 = device.addOutput();
			connectNode(in1, out1);
			var super_createUI = device.createUI;
			device.createUI = function() {
				super_createUI();
				var size = device.getSize();
				var cx = size.width / 2;
				var cy = size.height / 2;
				device.$ui.append($s.createSVGElement('circle').attr({
					cx: cx,
					cy: cy,
					r: unit / 2
				}).attr('class', 'simcir-port simcir-node-type-' + type));
				device.$ui.append($s.createSVGElement('circle').attr({
					cx: cx,
					cy: cy,
					r: unit / 4
				}).attr('class', 'simcir-port-hole'));
			};
		};
	};
	var createJointFactory = function() {
		var maxFadeCount = 16;
		var fadeTimeout = 100;
		var Direction = {
			WE: 0,
			NS: 1,
			EW: 2,
			SN: 3
		};
		return function(device) {
			var in1 = device.addInput();
			var out1 = device.addOutput();
			connectNode(in1, out1);
			var state = device.deviceDef.state || {
				direction: Direction.WE
			};
			device.getState = function() {
				return state;
			};
			device.getSize = function() {
				return {
					width: unit,
					height: unit
				};
			};
			var super_createUI = device.createUI;
			device.createUI = function() {
				super_createUI();
				var $label = device.$ui.children('.simcir-device-label');
				$label.attr('y', $label.attr('y') - unit / 4);
				var $point = $s.createSVGElement('circle').css('pointer-events', 'none').css('opacity', 0).attr('r', 2).addClass('simcir-connector').addClass('simcir-joint-point');
				device.$ui.append($point);
				var $path = $s.createSVGElement('path').css('pointer-events', 'none').css('opacity', 0).addClass('simcir-connector');
				device.$ui.append($path);
				var $title = $s.createSVGElement('title').text('Double-Click to change a direction.');
				var updatePoint = function() {
					$point.css('display', out1.getInputs().length > 1 ? '' : 'none');
				};
				updatePoint();
				var super_connectTo = out1.connectTo;
				out1.connectTo = function(inNode) {
					super_connectTo(inNode);
					updatePoint();
				};
				var super_disconnectFrom = out1.disconnectFrom;
				out1.disconnectFrom = function(inNode) {
					super_disconnectFrom(inNode);
					updatePoint();
				};
				var updateUI = function() {
					var x0,
						y0,
						x1,
						y1;
					x0 = y0 = x1 = y1 = unit / 2;
					var d = unit / 2;
					var direction = state.direction;
					if (direction == Direction.WE)
					{
						x0 -= d;
						x1 += d;
					}
					else if (direction == Direction.NS)
					{
						y0 -= d;
						y1 += d;
					}
					else if (direction == Direction.EW)
					{
						x0 += d;
						x1 -= d;
					}
					else if (direction == Direction.SN)
					{
						y0 += d;
						y1 -= d;
					}
					$path.attr('d', 'M' + x0 + ' ' + y0 + 'L' + x1 + ' ' + y1);
					$s.transform(in1.$ui, x0, y0);
					$s.transform(out1.$ui, x1, y1);
					$point.attr({
						cx: x1,
						cy: y1
					});
					if (direction == Direction.EW || direction == Direction.WE)
					{
						device.$ui.children('.simcir-device-body').attr({
							x: 0,
							y: unit / 4,
							width: unit,
							height: unit / 2
						});
					}
					else
					{
						device.$ui.children('.simcir-device-body').attr({
							x: unit / 4,
							y: 0,
							width: unit / 2,
							height: unit
						});
					}
				};
				updateUI();
				var fadeCount = 0;
				var setOpacity = function(opacity) {
					device.$ui.children('.simcir-device-body,.simcir-node').css('opacity', opacity);
					$path.css('opacity', 1 - opacity);
					$point.css('opacity', 1 - opacity);
				};
				var fadeout = function() {
					window.setTimeout(function() {
						if (fadeCount > 0)
						{
							fadeCount -= 1;
							setOpacity(fadeCount / maxFadeCount);
							fadeout();
						}
					}, fadeTimeout);
				};
				var isEditable = function($dev) {
					var $workspace = $dev.closest('.simcir-workspace');
					return !!$s.controller($workspace).data().editable;
				};
				var device_mouseoutHandler = function(event) {
					if (!isEditable($(event.target)))
					{
						return;
					}
					if (!device.isSelected())
					{
						fadeCount = maxFadeCount;
						fadeout();
					}
				};
				var device_dblclickHandler = function(event) {
					if (!isEditable($(event.target)))
					{
						return;
					}
					state.direction = (state.direction + 1) % 4;
					updateUI();
					$(this).trigger('mousedown').trigger('mouseup');
				};
				device.$ui.on('mouseover', function(event) {
						if (!isEditable($(event.target)))
						{
							$title.text('');
							return;
						}
						setOpacity(1);
						fadeCount = 0;
					}).on('deviceAdd', function() {
						if ($(this).closest('BODY').length == 0)
						{
							setOpacity(0);
						}
						$(this).append($title).on('mouseout', device_mouseoutHandler).on('dblclick', device_dblclickHandler);
						$label.css('display', 'none');
					}).on('deviceRemove', function() {
						$(this).off('mouseout', device_mouseoutHandler).off('dblclick', device_dblclickHandler);
						$title.remove();
						$label.css('display', '');
					}).on('deviceSelect', function() {
						if (device.isSelected())
						{
							setOpacity(1);
							fadeCount = 0;
						}
						else
						{
							if (fadeCount == 0)
							{
								setOpacity(0);
							}
						}
					});
			};
		};
	};
	$s.registerDevice('In', createPortFactory('in'));
	$s.registerDevice('Out', createPortFactory('out'));
	$s.registerDevice('Joint', createJointFactory());
}(simcir);