<commit_before>/*
* @Copyright (c) 2010 Aurélio Saraiva, Diego Plentz
* @Page http://github.com/plentz/jquery-maskmoney
* try at http://inoveideia.com.br/maskInputMoney/

* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/

/*
* @Version: 0.9
* @Release: 2011-01-10
*/
(function($) {
	$.fn.maskMoney = function(settings) {
		settings = $.extend({
			symbol:'US$',
			decimal:'.',
			precision:2,
			thousands:',',
			allowZero:false,
			allowNegative:false,
			showSymbol:false,
			symbolStay:false,
			defaultZero:true
		}, settings);

		return this.each(function() {
			var input = $(this);

			function keypressEvent(e) {
				e = e||window.event;
				var k = e.charCode||e.keyCode||e.which;
				if (k == undefined) return false; //needed to handle an IE "special" event

				if (k<48||k>57) { // any key except the numbers 0-9
					if (k==45) { // -(minus) key
						input.val(changeSign(input));
						return false;
					}
					if (k==43) { // +(plus) key
						input.val(input.val().replace('-',''));
						return false;
					} else if (k==13) { // enter key
						return true;
					} else { // any other key with keycode less than 48 and greater than 57
						preventDefault(e);
						return true;
					}
				} else if (input.val().length==input.attr('maxlength')) {
					return false;
				} else {
					preventDefault(e);

					var key = String.fromCharCode(k);
					var x = input.get(0);
					var selection = input.getInputSelection(x);
					var startPos = selection.start;
					var endPos = selection.end;
					x.value = x.value.substring(0, startPos) + key + x.value.substring(endPos, x.value.length);
					maskAndPosition(x, startPos + 1);
					return false;
				}
			}

			function keydownEvent(e) {
				e = e||window.event;
				var k = e.charCode||e.keyCode||e.which;
				if (k == undefined) return false; //needed to handle an IE "special" event

				var x = input.get(0);
				var selection = input.getInputSelection(x);
				var startPos = selection.start;
				var endPos = selection.end;

				if (k==8) { // backspace key
				preventDefault(e);

					if(startPos == endPos)
					{
						// Remove single character
						x.value = x.value.substring(0, startPos - 1) + x.value.substring(endPos, x.value.length);
						startPos = startPos - 1;
					}
					else
					{
						// Remove multiple characters
						x.value = x.value.substring(0, startPos) + x.value.substring(endPos, x.value.length);
					}
					maskAndPosition(x, startPos);
					return false;
				} else if (k==9) { // tab key
					return true;
				} else if (k==46||k==63272) { // delete key (with special case for safari)
					preventDefault(e);
					if(x.selectionStart == x.selectionEnd)
					{
						// Remove single character
						x.value = x.value.substring(0, startPos) + x.value.substring(endPos + 1, x.value.length);
					}
					else
					{
						//Remove multiple characters
						x.value = x.value.substring(0, startPos) + x.value.substring(endPos, x.value.length);
					}
					maskAndPosition(x, startPos);
					return false;
				} else { // any other key
					return true;
				}
			}

			function focusEvent(e) {
				if (input.val()==''&&settings.defaultZero) {
					input.val(setSymbol(getDefaultMask()));
				} else {
					input.val(setSymbol(input.val()));
				}
                if (this.createTextRange) {
                    var textRange = this.createTextRange();
                    textRange.collapse(false); // set the cursor at the end of the input
                    textRange.select();
                }
			}

			function blurEvent(e) {
                if ($.browser.msie) {
                    keypressEvent(e);
                }

				if (input.val()==setSymbol(getDefaultMask())) {
					if(!settings.allowZero) input.val('');
				} else {
					if (!settings.symbolStay) input.val(input.val().replace(settings.symbol,''));
					else if (settings.symbolStay&&input.val()==settings.symbol) input.val('');
				}
			}

			function preventDefault(e) {
				if (e.preventDefault) { //standard browsers
					e.preventDefault();
				} else { // internet explorer
					e.returnValue = false
				}
			}

			function maskAndPosition(x, startPos) {
				var originalLen = input.val().length;
				input.val(maskValue(x.value));
				var newLen = input.val().length;
				startPos = startPos - (originalLen - newLen);
				input.setCursorPosition(startPos);
			}

			function maskValue(v) {
				v = v.replace(settings.symbol,'');

				var strCheck = '0123456789';
				var len = v.length;
				var a = '', t = '', neg='';

				if(len!=0 && v.charAt(0)=='-'){
					v = v.replace('-','');
					if(settings.allowNegative){
						neg = '-';
					}
				}

				if (len==0) {
					if (!settings.defaultZero) return t;
					t = '0.00';
				}

				for (var i = 0; i<len; i++) {
					if ((v.charAt(i)!='0') && (v.charAt(i)!=settings.decimal)) break;
                }

				for (; i<len; i++) {
					if (strCheck.indexOf(v.charAt(i))!=-1) a+= v.charAt(i);
				}

				var n = parseFloat(a);
				n = isNaN(n) ? 0 : n/Math.pow(10,settings.precision);
				t = n.toFixed(settings.precision);

                i = settings.precision == 0 ? 0 : 1;
				var p, d = (t=t.split('.'))[i].substr(0,settings.precision);
				for (p = (t=t[0]).length; (p-=3)>=1;) {
					t = t.substr(0,p)+settings.thousands+t.substr(p);
				}

				return (settings.precision>0)
                    ? setSymbol(neg+t+settings.decimal+d+Array((settings.precision+1)-d.length).join(0))
                    : setSymbol(neg+t);
			}

			function getDefaultMask() {
				var n = parseFloat('0')/Math.pow(10,settings.precision);
				return (n.toFixed(settings.precision)).replace(new RegExp('\\.','g'),settings.decimal);
			}

			function setSymbol(v) {
				if (settings.showSymbol) {
					if (v.substr(0, settings.symbol.length) != settings.symbol) return settings.symbol+v;
				}
				return v;
			}

			function changeSign(i){
				if (settings.allowNegative) {
					var vic = i.val();
					if (i.val()!='' && i.val().charAt(0)=='-'){
						return i.val().replace('-','');
					} else{
						return '-'+i.val();
					}
				} else {
					return i.val();
				}
			}

			input.bind('keypress',keypressEvent);
			input.bind('keydown',keydownEvent);
			input.bind('blur',blurEvent);
			input.bind('focus',focusEvent);

			input.one('unmaskMoney',function() {
				input.unbind('focus',focusEvent);
				input.unbind('blur',blurEvent);
				input.unbind('keydown',keydownEvent);
				input.unbind('keypress',keypressEvent);

				if ($.browser.msie) {
                    this.onpaste= null;
				} else if ($.browser.mozilla) {
                    this.removeEventListener('input',blurEvent,false);
                }
			});
		});
	}

	$.fn.unmaskMoney=function() {
		return this.trigger('unmaskMoney');
	};

	$.fn.setCursorPosition = function(pos) {
		this.each(function(index, elem) {
			if (elem.setSelectionRange) {
				elem.focus();
				elem.setSelectionRange(pos, pos);
			} else if (elem.createTextRange) {
				var range = elem.createTextRange();
				range.collapse(true);
				range.moveEnd('character', pos);
				range.moveStart('character', pos);
				range.select();
			}
		});
		return this;
	};

	$.fn.getInputSelection = function(el) {
		var start = 0, end = 0, normalizedValue, range, textInputRange, len, endRange;

		if (typeof el.selectionStart == "number" && typeof el.selectionEnd == "number") {
			start = el.selectionStart;
			end = el.selectionEnd;
		} else {
			range = document.selection.createRange();

			if (range && range.parentElement() == el) {
				len = el.value.length;
				normalizedValue = el.value.replace(/\r\n/g, "\n");

				// Create a working TextRange that lives only in the input
				textInputRange = el.createTextRange();
				textInputRange.moveToBookmark(range.getBookmark());

				// Check if the start and end of the selection are at the very end
				// of the input, since moveStart/moveEnd doesn't return what we want
				// in those cases
				endRange = el.createTextRange();
				endRange.collapse(false);

				if (textInputRange.compareEndPoints("StartToEnd", endRange) > -1) {
					start = end = len;
				} else {
					start = -textInputRange.moveStart("character", -len);
					start += normalizedValue.slice(0, start).split("\n").length - 1;

					if (textInputRange.compareEndPoints("EndToEnd", endRange) > -1) {
						end = len;
					} else {
						end = -textInputRange.moveEnd("character", -len);
						end += normalizedValue.slice(0, end).split("\n").length - 1;
					}
				}
			}
		}

		return {
			start: start,
			end: end
		};
	}

})(jQuery);<commit_msg>removing stray tabs<commit_after>/*
* @Copyright (c) 2010 Aurélio Saraiva, Diego Plentz
* @Page http://github.com/plentz/jquery-maskmoney
* try at http://inoveideia.com.br/maskInputMoney/

* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/

/*
* @Version: 0.9
* @Release: 2011-01-10
*/
(function($) {
	$.fn.maskMoney = function(settings) {
		settings = $.extend({
			symbol:'US$',
			decimal:'.',
			precision:2,
			thousands:',',
			allowZero:false,
			allowNegative:false,
			showSymbol:false,
			symbolStay:false,
			defaultZero:true
		}, settings);

		return this.each(function() {
			var input = $(this);

			function keypressEvent(e) {
				e = e||window.event;
				var k = e.charCode||e.keyCode||e.which;
				if (k == undefined) return false; //needed to handle an IE "special" event

				if (k<48||k>57) { // any key except the numbers 0-9
					if (k==45) { // -(minus) key
						input.val(changeSign(input));
						return false;
					}
					if (k==43) { // +(plus) key
						input.val(input.val().replace('-',''));
						return false;
					} else if (k==13) { // enter key
						return true;
					} else { // any other key with keycode less than 48 and greater than 57
						preventDefault(e);
						return true;
					}
				} else if (input.val().length==input.attr('maxlength')) {
					return false;
				} else {
					preventDefault(e);

					var key = String.fromCharCode(k);
					var x = input.get(0);
					var selection = input.getInputSelection(x);
					var startPos = selection.start;
					var endPos = selection.end;
					x.value = x.value.substring(0, startPos) + key + x.value.substring(endPos, x.value.length);
					maskAndPosition(x, startPos + 1);
					return false;
				}
			}

			function keydownEvent(e) {
				e = e||window.event;
				var k = e.charCode||e.keyCode||e.which;
				if (k == undefined) return false; //needed to handle an IE "special" event

				var x = input.get(0);
				var selection = input.getInputSelection(x);
				var startPos = selection.start;
				var endPos = selection.end;

				if (k==8) { // backspace key
				preventDefault(e);

					if(startPos == endPos)
					{
						// Remove single character
						x.value = x.value.substring(0, startPos - 1) + x.value.substring(endPos, x.value.length);
						startPos = startPos - 1;
					}
					else
					{
						// Remove multiple characters
						x.value = x.value.substring(0, startPos) + x.value.substring(endPos, x.value.length);
					}
					maskAndPosition(x, startPos);
					return false;
				} else if (k==9) { // tab key
					return true;
				} else if (k==46||k==63272) { // delete key (with special case for safari)
					preventDefault(e);
					if(x.selectionStart == x.selectionEnd)
					{
						// Remove single character
						x.value = x.value.substring(0, startPos) + x.value.substring(endPos + 1, x.value.length);
					}
					else
					{
						//Remove multiple characters
						x.value = x.value.substring(0, startPos) + x.value.substring(endPos, x.value.length);
					}
					maskAndPosition(x, startPos);
					return false;
				} else { // any other key
					return true;
				}
			}

			function focusEvent(e) {
				if (input.val()==''&&settings.defaultZero) {
					input.val(setSymbol(getDefaultMask()));
				} else {
					input.val(setSymbol(input.val()));
				}
                if (this.createTextRange) {
                    var textRange = this.createTextRange();
                    textRange.collapse(false); // set the cursor at the end of the input
                    textRange.select();
                }
			}

			function blurEvent(e) {
                if ($.browser.msie) {
                    keypressEvent(e);
                }

				if (input.val()==setSymbol(getDefaultMask())) {
					if(!settings.allowZero) input.val('');
				} else {
					if (!settings.symbolStay) input.val(input.val().replace(settings.symbol,''));
					else if (settings.symbolStay&&input.val()==settings.symbol) input.val('');
				}
			}

			function preventDefault(e) {
				if (e.preventDefault) { //standard browsers
					e.preventDefault();
				} else { // internet explorer
					e.returnValue = false
				}
			}

			function maskAndPosition(x, startPos) {
				var originalLen = input.val().length;
				input.val(maskValue(x.value));
				var newLen = input.val().length;
				startPos = startPos - (originalLen - newLen);
				input.setCursorPosition(startPos);
			}

			function maskValue(v) {
				v = v.replace(settings.symbol,'');

				var strCheck = '0123456789';
				var len = v.length;
				var a = '', t = '', neg='';

				if(len!=0 && v.charAt(0)=='-'){
					v = v.replace('-','');
					if(settings.allowNegative){
						neg = '-';
					}
				}

				if (len==0) {
					if (!settings.defaultZero) return t;
					t = '0.00';
				}

				for (var i = 0; i<len; i++) {
					if ((v.charAt(i)!='0') && (v.charAt(i)!=settings.decimal)) break;
                }

				for (; i<len; i++) {
					if (strCheck.indexOf(v.charAt(i))!=-1) a+= v.charAt(i);
				}

				var n = parseFloat(a);
				n = isNaN(n) ? 0 : n/Math.pow(10,settings.precision);
				t = n.toFixed(settings.precision);

                i = settings.precision == 0 ? 0 : 1;
				var p, d = (t=t.split('.'))[i].substr(0,settings.precision);
				for (p = (t=t[0]).length; (p-=3)>=1;) {
					t = t.substr(0,p)+settings.thousands+t.substr(p);
				}

				return (settings.precision>0)
                    ? setSymbol(neg+t+settings.decimal+d+Array((settings.precision+1)-d.length).join(0))
                    : setSymbol(neg+t);
			}

			function getDefaultMask() {
				var n = parseFloat('0')/Math.pow(10,settings.precision);
				return (n.toFixed(settings.precision)).replace(new RegExp('\\.','g'),settings.decimal);
			}

			function setSymbol(v) {
				if (settings.showSymbol) {
					if (v.substr(0, settings.symbol.length) != settings.symbol) return settings.symbol+v;
				}
				return v;
			}

			function changeSign(i){
				if (settings.allowNegative) {
					var vic = i.val();
					if (i.val()!='' && i.val().charAt(0)=='-'){
						return i.val().replace('-','');
					} else{
						return '-'+i.val();
					}
				} else {
					return i.val();
				}
			}

			input.bind('keypress',keypressEvent);
			input.bind('keydown',keydownEvent);
			input.bind('blur',blurEvent);
			input.bind('focus',focusEvent);

			input.one('unmaskMoney',function() {
				input.unbind('focus',focusEvent);
				input.unbind('blur',blurEvent);
				input.unbind('keydown',keydownEvent);
				input.unbind('keypress',keypressEvent);

				if ($.browser.msie) {
                    this.onpaste= null;
				} else if ($.browser.mozilla) {
                    this.removeEventListener('input',blurEvent,false);
                }
			});
		});
	}

	$.fn.unmaskMoney=function() {
		return this.trigger('unmaskMoney');
	};

	$.fn.setCursorPosition = function(pos) {
		this.each(function(index, elem) {
			if (elem.setSelectionRange) {
				elem.focus();
				elem.setSelectionRange(pos, pos);
			} else if (elem.createTextRange) {
				var range = elem.createTextRange();
				range.collapse(true);
				range.moveEnd('character', pos);
				range.moveStart('character', pos);
				range.select();
			}
		});
		return this;
	};

	$.fn.getInputSelection = function(el) {
		var start = 0, end = 0, normalizedValue, range, textInputRange, len, endRange;

		if (typeof el.selectionStart == "number" && typeof el.selectionEnd == "number") {
			start = el.selectionStart;
			end = el.selectionEnd;
		} else {
			range = document.selection.createRange();

			if (range && range.parentElement() == el) {
				len = el.value.length;
				normalizedValue = el.value.replace(/\r\n/g, "\n");

				// Create a working TextRange that lives only in the input
				textInputRange = el.createTextRange();
				textInputRange.moveToBookmark(range.getBookmark());

				// Check if the start and end of the selection are at the very end
				// of the input, since moveStart/moveEnd doesn't return what we want
				// in those cases
				endRange = el.createTextRange();
				endRange.collapse(false);

				if (textInputRange.compareEndPoints("StartToEnd", endRange) > -1) {
					start = end = len;
				} else {
					start = -textInputRange.moveStart("character", -len);
					start += normalizedValue.slice(0, start).split("\n").length - 1;

					if (textInputRange.compareEndPoints("EndToEnd", endRange) > -1) {
						end = len;
					} else {
						end = -textInputRange.moveEnd("character", -len);
						end += normalizedValue.slice(0, end).split("\n").length - 1;
					}
				}
			}
		}

		return {
			start: start,
			end: end
		};
	}

})(jQuery);<|endoftext|>


<commit_before>/**
 * The Modal jQuery plugin
 *
 * @author Alexander Makarov <sam@rmcreative.ru>
 * @link https://github.com/samdark/the-modal
 * @version 0.0.14
 */
;(function($, window, document, undefined) {
	"use strict";
	/*jshint smarttabs:true*/

	// :focusable expression, needed for tabindexes in modal
	$.extend($.expr[':'],{
		focusable: function(element){
			var map, mapName, img,
				nodeName = element.nodeName.toLowerCase(),
				isTabIndexNotNaN = !isNaN($.attr(element,'tabindex'));
			if ('area' === nodeName) {
				map = element.parentNode;
				mapName = map.name;
				if (!element.href || !mapName || map.nodeName.toLowerCase() !== 'map') {
					return false;
				}
				img = $('img[usemap=#' + mapName + ']')[0];
				return !!img && visible(img);
			}

			var result = isTabIndexNotNaN;
			if (/input|select|textarea|button|object/.test(nodeName)) {
				result = !element.disabled;
			} else if ('a' === nodeName) {
				result = element.href || isTabIndexNotNaN;
			}

			return result && visible(element);

			function visible(element) {
				return $.expr.filters.visible(element) &&
					!$(element).parents().addBack().filter(function() {
						return $.css(this,'visibility') === 'hidden';
					}).length;
			}
		}
	});

	var pluginNamespace = 'the-modal',
		// global defaults
		defaults = {
			lockClass: 'themodal-lock',
			overlayClass: 'themodal-overlay',

			closeOnEsc: true,
			closeOnOverlayClick: true,

			onBeforeClose: null,
			onClose: null,
			onOpen: null,

			cloning: true
		};
	var oMargin = {};
	var ieBodyTopMargin = 0;

	function isIE() {
		return ((navigator.appName == 'Microsoft Internet Explorer') ||
			(navigator.userAgent.match(/MSIE\s+\d+\.\d+/)) ||
			(navigator.userAgent.match(/Trident\/\d+\.\d+/)));
	}

	function lockContainer(options, overlay) {
		var body = $('body');
		var oWidth = body.outerWidth(true);
		body.addClass(options.lockClass);
		var sbWidth = body.outerWidth(true) - oWidth;
		if (isIE()) {
			ieBodyTopMargin = body.css('margin-top');
			body.css('margin-top', 0);
		}

		if (sbWidth != 0) {
			var tags = $('html, body');
			tags.each(function () {
				var $this = $(this);
				oMargin[$this.prop('tagName').toLowerCase()] = parseInt($this.css('margin-right'));
			});
			$('html').css('margin-right', oMargin['html'] + sbWidth);
			overlay.css('left', 0 - sbWidth);
		}
	}

	function unlockContainer(options) {
		if (isIE()) {
			$('body').css('margin-top', ieBodyTopMargin);
		}

		var body = $('body');
		var oWidth = body.outerWidth(true);
		body.removeClass(options.lockClass);
		var sbWidth = body.outerWidth(true) - oWidth;

		if (sbWidth != 0) {
			$('html, body').each(function () {
				var $this = $(this);
				$this.css('margin-right', oMargin[$this.prop('tagName').toLowerCase()])
			});
		}
	}

	function init(els, options) {
		var modalOptions = options;

		if(els.length) {
			els.each(function(){
				$(this).data(pluginNamespace+'.options', modalOptions);
			});
		} else {
			$.extend(defaults, modalOptions);
		}

		// on Ctrl+A click fire `onSelectAll` event
		$(window).bind('keydown',function(e){
			if (!(e.ctrlKey && e.keyCode == 65)) {
				return true;
			}

			if ( $('input:focus, textarea:focus').length > 0 ) {
			    return true;
			}

			var selectAllEvent = new $.Event('onSelectAll');
			selectAllEvent.parentEvent = e;
			$(window).trigger(selectAllEvent);
			return true;
		});

		els.bind('keydown',function(e){
			var modalFocusableElements = $(':focusable',$(this));
			if(modalFocusableElements.filter(':last').is(':focus') && (e.which || e.keyCode) == 9){
				e.preventDefault();
				modalFocusableElements.filter(':first').focus();
			}
		});

		return {
			open: function(options) {
				var el = els.get(0),
					localOptions = $.extend({}, defaults, $(el).data(pluginNamespace+'.options'), options);

				// close modal if opened
				if($('.'+localOptions.overlayClass).length) {
					$.modal().close();
				}

				var overlay = $('<div/>').addClass(localOptions.overlayClass).prependTo('body');
				overlay.data(pluginNamespace+'.options', localOptions);

				lockContainer(localOptions, overlay);

				if(el) {
					var openedModalElement = null;
					if (!localOptions.cloning) {
						overlay.data(pluginNamespace+'.el', el);
						$(el).data(pluginNamespace+'.parent', $(el).parent());
						openedModalElement = $(el).appendTo(overlay).show();
					} else {
						openedModalElement = $(el).clone(true).appendTo(overlay).show();
					}
				}

				if(localOptions.closeOnEsc) {
					$(document).bind('keyup.'+pluginNamespace, function(e){
						if(e.keyCode === 27) {
							$.modal().close(localOptions);
						}
					});
				}

				if(localOptions.closeOnOverlayClick) {
					overlay.children().on('click.' + pluginNamespace, function(e){
						e.stopPropagation();
					});
					$('.' + localOptions.overlayClass).on('click.' + pluginNamespace, function(e){
						$.modal().close(localOptions);
					});
				}

				$(document).bind('touchmove.'+pluginNamespace,function(e){
					if(!$(e).parents('.' + localOptions.overlayClass)) {
						e.preventDefault();
					}
				});

				if(el) {
					$(window).bind('onSelectAll',function(e){
						e.parentEvent.preventDefault();

						var range = null,
							selection = null,
							selectionElement = openedModalElement.get(0);
						if (document.body.createTextRange) { //ms
							range = document.body.createTextRange();
							range.moveToElementText(selectionElement);
							range.select();
						} else if (window.getSelection) { //all others
							selection = window.getSelection();
							range = document.createRange();
							range.selectNodeContents(selectionElement);
							selection.removeAllRanges();
							selection.addRange(range);
						}
					});
				}

				if(localOptions.onOpen) {
					localOptions.onOpen(overlay, localOptions);
				}
			},
			close: function(options) {
				var el = els.get(0),
				  localOptions = $.extend({}, defaults, $(el).data(pluginNamespace+'.options'), options);
				var overlay = $('.' + localOptions.overlayClass);

				if ($.isFunction(localOptions.onBeforeClose)) {
					if (localOptions.onBeforeClose(overlay, localOptions) === false) {
						return;
					}
				}

				if (!localOptions.cloning) {
					if (!el) {
						el = overlay.data(pluginNamespace+'.el');
					}
					$(el).hide().appendTo($(el).data(pluginNamespace+'.parent'));
				}

				overlay.remove();
				unlockContainer(localOptions);

				if(localOptions.closeOnEsc) {
					$(document).unbind('keyup.'+pluginNamespace);
				}

				$(window).unbind('onSelectAll');

				if(localOptions.onClose) {
					localOptions.onClose(overlay, localOptions);
				}
			}
		};
	}

	$.modal = function(options){
		return init($(), options);
	};

	$.fn.modal = function(options) {
		return init(this, options);
	};

})(jQuery, window, document);
<commit_msg>Fix click events when closeOnOverlayClick is true<commit_after>/**
 * The Modal jQuery plugin
 *
 * @author Alexander Makarov <sam@rmcreative.ru>
 * @link https://github.com/samdark/the-modal
 * @version 0.0.14
 */
;(function($, window, document, undefined) {
	"use strict";
	/*jshint smarttabs:true*/

	// :focusable expression, needed for tabindexes in modal
	$.extend($.expr[':'],{
		focusable: function(element){
			var map, mapName, img,
				nodeName = element.nodeName.toLowerCase(),
				isTabIndexNotNaN = !isNaN($.attr(element,'tabindex'));
			if ('area' === nodeName) {
				map = element.parentNode;
				mapName = map.name;
				if (!element.href || !mapName || map.nodeName.toLowerCase() !== 'map') {
					return false;
				}
				img = $('img[usemap=#' + mapName + ']')[0];
				return !!img && visible(img);
			}

			var result = isTabIndexNotNaN;
			if (/input|select|textarea|button|object/.test(nodeName)) {
				result = !element.disabled;
			} else if ('a' === nodeName) {
				result = element.href || isTabIndexNotNaN;
			}

			return result && visible(element);

			function visible(element) {
				return $.expr.filters.visible(element) &&
					!$(element).parents().addBack().filter(function() {
						return $.css(this,'visibility') === 'hidden';
					}).length;
			}
		}
	});

	var pluginNamespace = 'the-modal',
		// global defaults
		defaults = {
			lockClass: 'themodal-lock',
			overlayClass: 'themodal-overlay',

			closeOnEsc: true,
			closeOnOverlayClick: true,

			onBeforeClose: null,
			onClose: null,
			onOpen: null,

			cloning: true
		};
	var oMargin = {};
	var ieBodyTopMargin = 0;

	function isIE() {
		return ((navigator.appName == 'Microsoft Internet Explorer') ||
			(navigator.userAgent.match(/MSIE\s+\d+\.\d+/)) ||
			(navigator.userAgent.match(/Trident\/\d+\.\d+/)));
	}

	function lockContainer(options, overlay) {
		var body = $('body');
		var oWidth = body.outerWidth(true);
		body.addClass(options.lockClass);
		var sbWidth = body.outerWidth(true) - oWidth;
		if (isIE()) {
			ieBodyTopMargin = body.css('margin-top');
			body.css('margin-top', 0);
		}

		if (sbWidth != 0) {
			var tags = $('html, body');
			tags.each(function () {
				var $this = $(this);
				oMargin[$this.prop('tagName').toLowerCase()] = parseInt($this.css('margin-right'));
			});
			$('html').css('margin-right', oMargin['html'] + sbWidth);
			overlay.css('left', 0 - sbWidth);
		}
	}

	function unlockContainer(options) {
		if (isIE()) {
			$('body').css('margin-top', ieBodyTopMargin);
		}

		var body = $('body');
		var oWidth = body.outerWidth(true);
		body.removeClass(options.lockClass);
		var sbWidth = body.outerWidth(true) - oWidth;

		if (sbWidth != 0) {
			$('html, body').each(function () {
				var $this = $(this);
				$this.css('margin-right', oMargin[$this.prop('tagName').toLowerCase()])
			});
		}
	}

	function init(els, options) {
		var modalOptions = options;

		if(els.length) {
			els.each(function(){
				$(this).data(pluginNamespace+'.options', modalOptions);
			});
		} else {
			$.extend(defaults, modalOptions);
		}

		// on Ctrl+A click fire `onSelectAll` event
		$(window).bind('keydown',function(e){
			if (!(e.ctrlKey && e.keyCode == 65)) {
				return true;
			}

			if ( $('input:focus, textarea:focus').length > 0 ) {
			    return true;
			}

			var selectAllEvent = new $.Event('onSelectAll');
			selectAllEvent.parentEvent = e;
			$(window).trigger(selectAllEvent);
			return true;
		});

		els.bind('keydown',function(e){
			var modalFocusableElements = $(':focusable',$(this));
			if(modalFocusableElements.filter(':last').is(':focus') && (e.which || e.keyCode) == 9){
				e.preventDefault();
				modalFocusableElements.filter(':first').focus();
			}
		});

		return {
			open: function(options) {
				var el = els.get(0),
					localOptions = $.extend({}, defaults, $(el).data(pluginNamespace+'.options'), options);

				// close modal if opened
				if($('.'+localOptions.overlayClass).length) {
					$.modal().close();
				}

				var overlay = $('<div/>').addClass(localOptions.overlayClass).prependTo('body');
				overlay.data(pluginNamespace+'.options', localOptions);

				lockContainer(localOptions, overlay);

				if(el) {
					var openedModalElement = null;
					if (!localOptions.cloning) {
						overlay.data(pluginNamespace+'.el', el);
						$(el).data(pluginNamespace+'.parent', $(el).parent());
						openedModalElement = $(el).appendTo(overlay).show();
					} else {
						openedModalElement = $(el).clone(true).appendTo(overlay).show();
					}
				}

				if(localOptions.closeOnEsc) {
					$(document).bind('keyup.'+pluginNamespace, function(e){
						if(e.keyCode === 27) {
							$.modal().close(localOptions);
						}
					});
				}

				if(localOptions.closeOnOverlayClick) {
					$('.' + localOptions.overlayClass).on('click.' + pluginNamespace, function(e){
						if (e.target.className == localOptions.overlayClass){
							$.modal().close();
						}
					});
				}

				$(document).bind('touchmove.'+pluginNamespace,function(e){
					if(!$(e).parents('.' + localOptions.overlayClass)) {
						e.preventDefault();
					}
				});

				if(el) {
					$(window).bind('onSelectAll',function(e){
						e.parentEvent.preventDefault();

						var range = null,
							selection = null,
							selectionElement = openedModalElement.get(0);
						if (document.body.createTextRange) { //ms
							range = document.body.createTextRange();
							range.moveToElementText(selectionElement);
							range.select();
						} else if (window.getSelection) { //all others
							selection = window.getSelection();
							range = document.createRange();
							range.selectNodeContents(selectionElement);
							selection.removeAllRanges();
							selection.addRange(range);
						}
					});
				}

				if(localOptions.onOpen) {
					localOptions.onOpen(overlay, localOptions);
				}
			},
			close: function(options) {
				var el = els.get(0),
				  localOptions = $.extend({}, defaults, $(el).data(pluginNamespace+'.options'), options);
				var overlay = $('.' + localOptions.overlayClass);

				if ($.isFunction(localOptions.onBeforeClose)) {
					if (localOptions.onBeforeClose(overlay, localOptions) === false) {
						return;
					}
				}

				if (!localOptions.cloning) {
					if (!el) {
						el = overlay.data(pluginNamespace+'.el');
					}
					$(el).hide().appendTo($(el).data(pluginNamespace+'.parent'));
				}

				overlay.remove();
				unlockContainer(localOptions);

				if(localOptions.closeOnEsc) {
					$(document).unbind('keyup.'+pluginNamespace);
				}

				$(window).unbind('onSelectAll');

				if(localOptions.onClose) {
					localOptions.onClose(overlay, localOptions);
				}
			}
		};
	}

	$.modal = function(options){
		return init($(), options);
	};

	$.fn.modal = function(options) {
		return init(this, options);
	};

})(jQuery, window, document);
<|endoftext|>


<commit_before>(function( $ ) {
	var requests = {};
	var zipValid = {
		us: /[0-9]{5}(-[0-9]{4})?/
	};

	$.ziptastic = function(country, zip, callback){
		// If only zip and callback are given default to US
		if (arguments.length == 2 && typeof arguments[1] == 'function') {
			callback = arguments[1];
			zip = arguments[0];
			country = 'US';
		}

		country = country.toUpperCase();
		// Only make unique requests
		if(!requests[country]) {
			requests[country] = {};
		}
		if(!requests[country][zip]) {
			requests[country][zip] = $.getJSON('https://zip.getziptastic.com/v2/' + country + '/' + zip);
		}

		// Bind to the finished request
		requests[country][zip].done(function(data) {
			if (typeof callback == 'function') {
				callback(data.country, data.state, data.state_short, data.city, zip);
			}
		});

		// Allow for binding to the deferred object
		return requests[country][zip];
	};

	$.fn.ziptastic = function( options ) {
		return this.each(function() {
			var ele = $(this);

			ele.on('keyup', function() {
				var zip = ele.val();

				// TODO Non-US zip codes?
				if(zipValid.us.test(zip)) {
					$.ziptastic(zip, function(country, state, state_short, city) {
						// Trigger the updated information
						ele.trigger('zipChange', [country, state, state_short, city, zip]);
					});
				}
			});
		});
	};
})( jQuery );
<commit_msg>trigger a zipchange on change events as well to catch browser autofills<commit_after>(function( $ ) {
	var requests = {};
	var zipValid = {
		us: /[0-9]{5}(-[0-9]{4})?/
	};

	$.ziptastic = function(country, zip, callback){
		// If only zip and callback are given default to US
		if (arguments.length == 2 && typeof arguments[1] == 'function') {
			callback = arguments[1];
			zip = arguments[0];
			country = 'US';
		}

		country = country.toUpperCase();
		// Only make unique requests
		if(!requests[country]) {
			requests[country] = {};
		}
		if(!requests[country][zip]) {
			requests[country][zip] = $.getJSON('https://zip.getziptastic.com/v2/' + country + '/' + zip);
		}

		// Bind to the finished request
		requests[country][zip].done(function(data) {
			if (typeof callback == 'function') {
				callback(data.country, data.state, data.state_short, data.city, zip);
			}
		});

		// Allow for binding to the deferred object
		return requests[country][zip];
	};

	$.fn.ziptastic = function( options ) {
		return this.each(function() {
			var ele = $(this);

			ele.on('keyup change', function() {
				var zip = ele.val();

				// TODO Non-US zip codes?
				if(zipValid.us.test(zip)) {
					$.ziptastic(zip, function(country, state, state_short, city) {
						// Trigger the updated information
						ele.trigger('zipChange', [country, state, state_short, city, zip]);
					});
				}
			});
		});
	};
})( jQuery );
<|endoftext|>


<commit_before>// Copyright 2002-2014, University of Colorado Boulder

/**
 * A scenery node that is used to represent a draggable Measuring Tape.
 * It contains a tip and a base that can be dragged separately,
 * with a text indicating the measurement.
 * The motion of the measuring tape can be confined by drag bounds.
 * It assumes that the position of this node is set to (0,0) in the parent Node.
 *
 * @author Vasily Shakhov (Mlearner)
 * @author Siddhartha Chinthapally (ActualConcepts)
 * @author Aaron Davis (PhET)
 * @author Martin Veillette (Berea College)
 */
define( function( require ) {
    'use strict';

    // modules
    var Bounds2 = require( 'DOT/Bounds2' );
    var Circle = require( 'SCENERY/nodes/Circle' );
    var Image = require( 'SCENERY/nodes/Image' );
    var inherit = require( 'PHET_CORE/inherit' );
    var Line = require( 'SCENERY/nodes/Line' );
    var ModelViewTransform2 = require( 'PHETCOMMON/view/ModelViewTransform2' );
    var Node = require( 'SCENERY/nodes/Node' );
    var Path = require( 'SCENERY/nodes/Path' );
    var PhetFont = require( 'SCENERY_PHET/PhetFont' );
    var Property = require( 'AXON/Property' );
    var Shape = require( 'KITE/Shape' );
    var SimpleDragHandler = require( 'SCENERY/input/SimpleDragHandler' );
    var Text = require( 'SCENERY/nodes/Text' );
    var Util = require( 'DOT/Util' );
    var Vector2 = require( 'DOT/Vector2' );

    // images
    var measuringTapeImage = require( 'image!SCENERY_PHET/measuringTape.png' );

    /**
     * Constructor for the measuring Tape
     * @param {Property.<Object>} unitsProperty - it has two fields, (1) name <string> and (2) multiplier <number>, eg. {name: 'cm', multiplier: 100},
     * @param {Property.<boolean>} isVisibleProperty
     * @param {Object} [options]
     * @constructor
     */
    function MeasuringTape( unitsProperty, isVisibleProperty, options ) {
      var measuringTape = this;

      Node.call( this );
      options = _.extend( {
        basePosition: new Vector2( 40, 40 ), // base Position in view coordinates (rightBottom position of the measuring tape image)
        unrolledTapeDistance: 1, // in model coordinates
        angle: 0.0, // angle of the tape in radians, recall that in the view, a positive angle means clockwise rotation.
        textPosition: new Vector2( 0, 30 ), // position of the text relative to center of the base image in view units
        modelViewTransform: ModelViewTransform2.createIdentity(),
        dragBounds: Bounds2.EVERYTHING,// bounds for the measuring tape (in the parent Node Coordinates reference frame), default value is no (effective) bounds
        scaleProperty: new Property( 1 ), // scale the apparent length of the unrolled Tape, without changing the measurement, analogous to a zoom factor
        significantFigures: 1, // number of significant figures in the length measurement
        textColor: 'white', // color of the length measurement and unit
        textFont: new PhetFont( {size: 16, weight: 'bold'} ), // font for the measurement text
        baseScale: 0.8, // control the size of the measuringTape Image (the base)
        lineColor: 'gray', // color of the tapeline itself
        tapeLineWidth: 2, // linewidth of the tape line
        tipCircleColor: 'rgba(0,0,0,0.1)', // color of the circle at the tip
        tipCircleRadius: 10, // radius of the circle on the tip
        crosshairColor: 'rgb(224, 95, 32)', // orange, color of the two crosshairs
        crosshairSize: 5, // size of the crosshairs in scenery coordinates ( measured from center)
        crosshairLineWidth: 2, // linewidth of the crosshairs
        isBaseCrosshairRotating: true, // do crosshairs rotate around their own axis to line up with the tapeline
        isTipCrosshairRotating: true // do crosshairs rotate around their own axis to line up with the tapeline
      }, options );

      assert && assert( Math.abs( options.modelViewTransform.modelToViewDeltaX( 1 ) ) === Math.abs( options.modelViewTransform.modelToViewDeltaY( 1 ) ), 'The y and x scale factor are not identical' );
      this.modelToViewScale = options.modelViewTransform.modelToViewDeltaX( 1 ); // private

      this.significantFigures = options.significantFigures; // @private
      this.unitsProperty = unitsProperty; // @private
      this.isVisibleProperty = isVisibleProperty; // @private
      this.scaleProperty = options.scaleProperty; // @private
      this.tipToBaseDistance = options.unrolledTapeDistance; // @private

      this.basePositionProperty = new Property( options.basePosition );
      var tapeDistance = options.modelViewTransform.modelToViewDeltaX( options.unrolledTapeDistance );

      this.tipPositionProperty = new Property( options.basePosition.plus( Vector2.createPolar( tapeDistance, options.angle ) ) );

      var crosshairShape = new Shape().
        moveTo( -options.crosshairSize, 0 ).
        moveTo( -options.crosshairSize, 0 ).
        lineTo( options.crosshairSize, 0 ).
        moveTo( 0, -options.crosshairSize ).
        lineTo( 0, options.crosshairSize );

      var baseCrosshair = new Path( crosshairShape, {
        stroke: options.crosshairColor,
        lineWidth: options.crosshairLineWidth
      } );

      var tipCrosshair = new Path( crosshairShape, {
        stroke: options.crosshairColor,
        lineWidth: options.crosshairLineWidth
      } );

      var tipCircle = new Circle( options.tipCircleRadius, {fill: options.tipCircleColor} );

      var baseImage = new Image( measuringTapeImage, {
        scale: options.baseScale,
        cursor: 'pointer'
      } );

      // create tapeline (running from one crosshair to the other)
      var tapeLine = new Line( this.basePositionProperty.value, this.tipPositionProperty.value, {
        stroke: options.lineColor,
        lineWidth: options.tapeLineWidth
      } );

      // add tipCrosshair and tipCircle to the tip
      var tip = new Node( {children: [tipCircle, tipCrosshair], cursor: 'pointer'} );

      // create text
      var labelText = new Text( measuringTape.getText(), {
        font: options.textFont,
        fill: options.textColor
      } );

      // expand the area for touch
      tip.touchArea = tip.localBounds.dilatedXY( 10, 10 );
      baseImage.touchArea = baseImage.localBounds.dilatedXY( 10, 10 );

      this.addChild( tapeLine ); // tapeline going from one crosshair to the other
      this.addChild( baseCrosshair ); // crosshair near the base, (set at basePosition)
      this.addChild( baseImage ); // base of the measuring tape
      this.addChild( labelText ); // text
      this.addChild( tip ); // crosshair and circle at the tip (set at tipPosition)

      var startOffset;
      baseImage.addInputListener( new SimpleDragHandler( {
          allowTouchSnag: true,
          start: function( event, trail ) {
            startOffset = event.currentTarget.globalToParentPoint( event.pointer.point ).minus( measuringTape.basePositionProperty.value );
          },

          drag: function( event ) {
            var parentPoint = event.currentTarget.globalToParentPoint( event.pointer.point ).minus( startOffset );
            var constrainedLocation = constrainBounds( parentPoint, options.dragBounds );

            // translation of the basePosition (subject to the constraining bounds)
            var translationDelta = constrainedLocation.minus( measuringTape.basePositionProperty.value );

            measuringTape.basePositionProperty.value = constrainedLocation;

            // translate the position of the tip if it is not being dragged
            if ( !isDraggingTip ) {
              measuringTape.tipPositionProperty.value = measuringTape.tipPositionProperty.value.plus( translationDelta );
            }
          }

        } )
      );

      // when the user is not holding onto the tip, dragging the body will also drag the tip
      var isDraggingTip = false;

      // init drag and drop for tip
      tip.addInputListener( new SimpleDragHandler( {
        allowTouchSnag: true,

        start: function( event, trail ) {
          isDraggingTip = true;
        },

        translate: function( translationParams ) {
          measuringTape.tipPositionProperty.value = measuringTape.tipPositionProperty.value.plus( translationParams.delta );
        },

        end: function( event, trail ) {
          isDraggingTip = false;
        }
      } ) );

      // link the positions of base and tip to the scenery nodes such as crosshair, tip, labelText, tapeLine, baseImage
      Property.multilink( [this.basePositionProperty, this.tipPositionProperty], function( basePosition, tipPosition ) {
        // calculate the orientation and change of orientation of the Measuring tape
        var oldAngle = baseImage.getRotation();
        var angle = Math.atan2( tipPosition.y - basePosition.y, tipPosition.x - basePosition.x );
        var deltaAngle = angle - oldAngle;

        // set position of the tip and the base crosshair
        baseCrosshair.center = basePosition;
        tip.center = tipPosition;

        // in order to avoid all kind of geometrical issues with position, let's reset the baseImage upright and then set its position and rotation
        baseImage.setRotation( 0 );
        baseImage.rightBottom = basePosition;
        baseImage.rotateAround( basePosition, angle );

        // reset the text
        measuringTape.tipToBaseDistance = tipPosition.distance( basePosition );
        labelText.setText( measuringTape.getText() );
        labelText.centerTop = baseImage.center.plus( options.textPosition.times( options.baseScale ) );

        // reposition the tapeline
        tapeLine.setLine( basePosition.x, basePosition.y, tipPosition.x, tipPosition.y );

        // rotate the crosshairs
        if ( options.isTipCrosshairRotating ) {
          tip.rotateAround( tip.center, deltaAngle );
        }
        if ( options.isBaseCrosshairRotating ) {
          baseCrosshair.rotateAround( baseCrosshair.center, deltaAngle );
        }

      } );

      // @private
      this.isVisiblePropertyObserver = function( isVisible ) {
        measuringTape.visible = isVisible;
      };
      this.isVisibleProperty.link( this.isVisiblePropertyObserver ); // must be unlinked in dispose

      // @private set Text on on labelText
      this.unitsPropertyObserver = function() {
        labelText.setText( measuringTape.getText() );
      };
      // link change of units to the text
      this.unitsProperty.link( this.unitsPropertyObserver ); // must be unlinked in dispose

      // @private length of the unrolled tape scales with the scaleProperty (but text stays the same).
      this.scalePropertyObserver = function( scale, oldScale ) {
        // make sure that the oldScale exists, if not set to 1.
        if ( oldScale === null ) {
          oldScale = 1;
        }
        // update the position of the tip
        var displacementVector = measuringTape.tipPositionProperty.value.minus( measuringTape.basePositionProperty.value );
        var scaledDisplacementVector = displacementVector.timesScalar( scale / oldScale );
        measuringTape.tipPositionProperty.value = measuringTape.basePositionProperty.value.plus( scaledDisplacementVector );
      };
      // scaleProperty is analogous to a zoom in/zoom out function
      this.scaleProperty.link( this.scalePropertyObserver ); // must be unlinked in dispose

      /**
       * Constrains a point to some bounds.
       *
       * @param {Vector2} point
       * @param {Bounds2} bounds
       * @returns {Vector2}
       */
      function constrainBounds( point, bounds ) {
        if ( _.isUndefined( bounds ) || bounds.containsPoint( point ) ) {
          return point;
        }
        else {
          var xConstrained = Math.max( Math.min( point.x, bounds.maxX ), bounds.minX );
          var yConstrained = Math.max( Math.min( point.y, bounds.maxY ), bounds.minY );
          return new Vector2( xConstrained, yConstrained );
        }
      }

      this.mutate( options );
    }

    return inherit( Node, MeasuringTape, {
      /**
       * reset the MeasuringTape to its initial configuration
       * @public
       */
      reset: function() {
        this.basePositionProperty.reset();
        this.tipPositionProperty.reset();
      },

      /**
       * returns a readout of the current measurement
       * @public
       * @returns {string}
       */
      getText: function() {
        return Util.toFixed( this.unitsProperty.value.multiplier * this.tipToBaseDistance / this.modelToViewScale / this.scaleProperty.value,
            this.significantFigures ) + ' ' + this.unitsProperty.value.name;
      },

      // Ensures that this node is eligible for GC.
      dispose: function() {
        this.isVisibleProperty.unlink( this.isVisiblePropertyObserver );
        this.unitsProperty.unlink( this.unitsPropertyObserver );
        this.scaleProperty.unlink( this.scalePropertyObserver );
      }
    } );
  }
)
;

<commit_msg>#129 fix file formatting that was mangled by previous commitp<commit_after>// Copyright 2002-2014, University of Colorado Boulder

/**
 * A scenery node that is used to represent a draggable Measuring Tape.
 * It contains a tip and a base that can be dragged separately,
 * with a text indicating the measurement.
 * The motion of the measuring tape can be confined by drag bounds.
 * It assumes that the position of this node is set to (0,0) in the parent Node.
 *
 * @author Vasily Shakhov (Mlearner)
 * @author Siddhartha Chinthapally (ActualConcepts)
 * @author Aaron Davis (PhET)
 * @author Martin Veillette (Berea College)
 */
define( function( require ) {
  'use strict';

  // modules
  var Bounds2 = require( 'DOT/Bounds2' );
  var Circle = require( 'SCENERY/nodes/Circle' );
  var Image = require( 'SCENERY/nodes/Image' );
  var inherit = require( 'PHET_CORE/inherit' );
  var Line = require( 'SCENERY/nodes/Line' );
  var ModelViewTransform2 = require( 'PHETCOMMON/view/ModelViewTransform2' );
  var Node = require( 'SCENERY/nodes/Node' );
  var Path = require( 'SCENERY/nodes/Path' );
  var PhetFont = require( 'SCENERY_PHET/PhetFont' );
  var Property = require( 'AXON/Property' );
  var Shape = require( 'KITE/Shape' );
  var SimpleDragHandler = require( 'SCENERY/input/SimpleDragHandler' );
  var Text = require( 'SCENERY/nodes/Text' );
  var Util = require( 'DOT/Util' );
  var Vector2 = require( 'DOT/Vector2' );

  // images
  var measuringTapeImage = require( 'image!SCENERY_PHET/measuringTape.png' );

  /**
   * Constructor for the measuring Tape
   * @param {Property.<Object>} unitsProperty - it has two fields, (1) name <string> and (2) multiplier <number>, eg. {name: 'cm', multiplier: 100},
   * @param {Property.<boolean>} isVisibleProperty
   * @param {Object} [options]
   * @constructor
   */
  function MeasuringTape( unitsProperty, isVisibleProperty, options ) {
    var measuringTape = this;

    Node.call( this );
    options = _.extend( {
      basePosition: new Vector2( 40, 40 ), // base Position in view coordinates (rightBottom position of the measuring tape image)
      unrolledTapeDistance: 1, // in model coordinates
      angle: 0.0, // angle of the tape in radians, recall that in the view, a positive angle means clockwise rotation.
      textPosition: new Vector2( 0, 30 ), // position of the text relative to center of the base image in view units
      modelViewTransform: ModelViewTransform2.createIdentity(),
      dragBounds: Bounds2.EVERYTHING,// bounds for the measuring tape (in the parent Node Coordinates reference frame), default value is no (effective) bounds
      scaleProperty: new Property( 1 ), // scale the apparent length of the unrolled Tape, without changing the measurement, analogous to a zoom factor
      significantFigures: 1, // number of significant figures in the length measurement
      textColor: 'white', // color of the length measurement and unit
      textFont: new PhetFont( {size: 16, weight: 'bold'} ), // font for the measurement text
      baseScale: 0.8, // control the size of the measuringTape Image (the base)
      lineColor: 'gray', // color of the tapeline itself
      tapeLineWidth: 2, // linewidth of the tape line
      tipCircleColor: 'rgba(0,0,0,0.1)', // color of the circle at the tip
      tipCircleRadius: 10, // radius of the circle on the tip
      crosshairColor: 'rgb(224, 95, 32)', // orange, color of the two crosshairs
      crosshairSize: 5, // size of the crosshairs in scenery coordinates ( measured from center)
      crosshairLineWidth: 2, // linewidth of the crosshairs
      isBaseCrosshairRotating: true, // do crosshairs rotate around their own axis to line up with the tapeline
      isTipCrosshairRotating: true // do crosshairs rotate around their own axis to line up with the tapeline
    }, options );

    assert && assert( Math.abs( options.modelViewTransform.modelToViewDeltaX( 1 ) ) === Math.abs( options.modelViewTransform.modelToViewDeltaY( 1 ) ), 'The y and x scale factor are not identical' );
    this.modelToViewScale = options.modelViewTransform.modelToViewDeltaX( 1 ); // private

    this.significantFigures = options.significantFigures; // @private
    this.unitsProperty = unitsProperty; // @private
    this.isVisibleProperty = isVisibleProperty; // @private
    this.scaleProperty = options.scaleProperty; // @private
    this.tipToBaseDistance = options.unrolledTapeDistance; // @private

    this.basePositionProperty = new Property( options.basePosition );
    var tapeDistance = options.modelViewTransform.modelToViewDeltaX( options.unrolledTapeDistance );

    this.tipPositionProperty = new Property( options.basePosition.plus( Vector2.createPolar( tapeDistance, options.angle ) ) );

    var crosshairShape = new Shape().
      moveTo( -options.crosshairSize, 0 ).
      moveTo( -options.crosshairSize, 0 ).
      lineTo( options.crosshairSize, 0 ).
      moveTo( 0, -options.crosshairSize ).
      lineTo( 0, options.crosshairSize );

    var baseCrosshair = new Path( crosshairShape, {
      stroke: options.crosshairColor,
      lineWidth: options.crosshairLineWidth
    } );

    var tipCrosshair = new Path( crosshairShape, {
      stroke: options.crosshairColor,
      lineWidth: options.crosshairLineWidth
    } );

    var tipCircle = new Circle( options.tipCircleRadius, {fill: options.tipCircleColor} );

    var baseImage = new Image( measuringTapeImage, {
      scale: options.baseScale,
      cursor: 'pointer'
    } );

    // create tapeline (running from one crosshair to the other)
    var tapeLine = new Line( this.basePositionProperty.value, this.tipPositionProperty.value, {
      stroke: options.lineColor,
      lineWidth: options.tapeLineWidth
    } );

    // add tipCrosshair and tipCircle to the tip
    var tip = new Node( {children: [tipCircle, tipCrosshair], cursor: 'pointer'} );

    // create text
    var labelText = new Text( measuringTape.getText(), {
      font: options.textFont,
      fill: options.textColor
    } );

    // expand the area for touch
    tip.touchArea = tip.localBounds.dilatedXY( 10, 10 );
    baseImage.touchArea = baseImage.localBounds.dilatedXY( 10, 10 );

    this.addChild( tapeLine ); // tapeline going from one crosshair to the other
    this.addChild( baseCrosshair ); // crosshair near the base, (set at basePosition)
    this.addChild( baseImage ); // base of the measuring tape
    this.addChild( labelText ); // text
    this.addChild( tip ); // crosshair and circle at the tip (set at tipPosition)

    var startOffset;
    baseImage.addInputListener( new SimpleDragHandler( {
        allowTouchSnag: true,
        start: function( event, trail ) {
          startOffset = event.currentTarget.globalToParentPoint( event.pointer.point ).minus( measuringTape.basePositionProperty.value );
        },

        drag: function( event ) {
          var parentPoint = event.currentTarget.globalToParentPoint( event.pointer.point ).minus( startOffset );
          var constrainedLocation = constrainBounds( parentPoint, options.dragBounds );

          // translation of the basePosition (subject to the constraining bounds)
          var translationDelta = constrainedLocation.minus( measuringTape.basePositionProperty.value );

          measuringTape.basePositionProperty.value = constrainedLocation;

          // translate the position of the tip if it is not being dragged
          if ( !isDraggingTip ) {
            measuringTape.tipPositionProperty.value = measuringTape.tipPositionProperty.value.plus( translationDelta );
          }
        }

      } )
    );

    // when the user is not holding onto the tip, dragging the body will also drag the tip
    var isDraggingTip = false;

    // init drag and drop for tip
    tip.addInputListener( new SimpleDragHandler( {
      allowTouchSnag: true,

      start: function( event, trail ) {
        isDraggingTip = true;
      },

      translate: function( translationParams ) {
        measuringTape.tipPositionProperty.value = measuringTape.tipPositionProperty.value.plus( translationParams.delta );
      },

      end: function( event, trail ) {
        isDraggingTip = false;
      }
    } ) );

    // link the positions of base and tip to the scenery nodes such as crosshair, tip, labelText, tapeLine, baseImage
    Property.multilink( [this.basePositionProperty, this.tipPositionProperty], function( basePosition, tipPosition ) {
      // calculate the orientation and change of orientation of the Measuring tape
      var oldAngle = baseImage.getRotation();
      var angle = Math.atan2( tipPosition.y - basePosition.y, tipPosition.x - basePosition.x );
      var deltaAngle = angle - oldAngle;

      // set position of the tip and the base crosshair
      baseCrosshair.center = basePosition;
      tip.center = tipPosition;

      // in order to avoid all kind of geometrical issues with position, let's reset the baseImage upright and then set its position and rotation
      baseImage.setRotation( 0 );
      baseImage.rightBottom = basePosition;
      baseImage.rotateAround( basePosition, angle );

      // reset the text
      measuringTape.tipToBaseDistance = tipPosition.distance( basePosition );
      labelText.setText( measuringTape.getText() );
      labelText.centerTop = baseImage.center.plus( options.textPosition.times( options.baseScale ) );

      // reposition the tapeline
      tapeLine.setLine( basePosition.x, basePosition.y, tipPosition.x, tipPosition.y );

      // rotate the crosshairs
      if ( options.isTipCrosshairRotating ) {
        tip.rotateAround( tip.center, deltaAngle );
      }
      if ( options.isBaseCrosshairRotating ) {
        baseCrosshair.rotateAround( baseCrosshair.center, deltaAngle );
      }

    } );

    // @private
    this.isVisiblePropertyObserver = function( isVisible ) {
      measuringTape.visible = isVisible;
    };
    this.isVisibleProperty.link( this.isVisiblePropertyObserver ); // must be unlinked in dispose

    // @private set Text on on labelText
    this.unitsPropertyObserver = function() {
      labelText.setText( measuringTape.getText() );
    };
    // link change of units to the text
    this.unitsProperty.link( this.unitsPropertyObserver ); // must be unlinked in dispose

    // @private length of the unrolled tape scales with the scaleProperty (but text stays the same).
    this.scalePropertyObserver = function( scale, oldScale ) {
      // make sure that the oldScale exists, if not set to 1.
      if ( oldScale === null ) {
        oldScale = 1;
      }
      // update the position of the tip
      var displacementVector = measuringTape.tipPositionProperty.value.minus( measuringTape.basePositionProperty.value );
      var scaledDisplacementVector = displacementVector.timesScalar( scale / oldScale );
      measuringTape.tipPositionProperty.value = measuringTape.basePositionProperty.value.plus( scaledDisplacementVector );
    };
    // scaleProperty is analogous to a zoom in/zoom out function
    this.scaleProperty.link( this.scalePropertyObserver ); // must be unlinked in dispose

    /**
     * Constrains a point to some bounds.
     *
     * @param {Vector2} point
     * @param {Bounds2} bounds
     * @returns {Vector2}
     */
    function constrainBounds( point, bounds ) {
      if ( _.isUndefined( bounds ) || bounds.containsPoint( point ) ) {
        return point;
      }
      else {
        var xConstrained = Math.max( Math.min( point.x, bounds.maxX ), bounds.minX );
        var yConstrained = Math.max( Math.min( point.y, bounds.maxY ), bounds.minY );
        return new Vector2( xConstrained, yConstrained );
      }
    }

    this.mutate( options );
  }

  return inherit( Node, MeasuringTape, {
    /**
     * reset the MeasuringTape to its initial configuration
     * @public
     */
    reset: function() {
      this.basePositionProperty.reset();
      this.tipPositionProperty.reset();
    },

    /**
     * returns a readout of the current measurement
     * @public
     * @returns {string}
     */
    getText: function() {
      return Util.toFixed( this.unitsProperty.value.multiplier * this.tipToBaseDistance / this.modelToViewScale / this.scaleProperty.value,
        this.significantFigures ) + ' ' + this.unitsProperty.value.name;
    },

    // Ensures that this node is eligible for GC.
    dispose: function() {
      this.isVisibleProperty.unlink( this.isVisiblePropertyObserver );
      this.unitsProperty.unlink( this.unitsPropertyObserver );
      this.scaleProperty.unlink( this.scalePropertyObserver );
    }
  } );
} );

<|endoftext|>


<commit_before>var Tree = require("./tree");
var Substance = require("substance");

var SubjectsModel = function(doc, subjects) {
  this.doc = doc;

  // Convert subjects to hash
  this.subjects = {};

  Substance.each(subjects, function(subject) {
    this.subjects[subject.id] = subject;
  }, this);

  this.tree = new Tree(this.subjects);
};

// Get tree representation suitable for jsTree widget
// ----------------

SubjectsModel.prototype.getTree = function() {
  var tree = this.tree;

  function getChildren(parentId) {
    var res = [];
    var nodes = tree.getChildren(parentId);
    if (nodes.length === 0) return res; // exit condition

    Substance.each(nodes, function(node) {
      var entry = {
        id: node.id,
        text: node.name,
        children: getChildren(node.id) // get children for subj
      };
      res.push(entry);
    });
    return res;
  }

  return getChildren("root");
};

SubjectsModel.prototype.getAllReferencedSubjects = function() {
  var doc = this.doc;
  var subjectRefs = doc.subjectReferencesIndex.get();
  var subjects = [];

  Substance.each(subjectRefs, function(subjectRef) {
    Substance.each(subjectRef.target, function(subjectId) {
      var subject = this.tree.get(subjectId);
      if (!Substance.includes(subjects, subject)) {
        subjects.push(subject);
      }
    }, this);
  }, this);

  return subjects;
};

SubjectsModel.prototype.getFullPathForSubject = function(subjectId) {
  var tree = this.tree;
  var res = [];

  function getParent(nodeId) {
    var node = tree.get(nodeId);
    var parent = tree.getParent(nodeId);
    if (parent) getParent(parent.id);

    res.push(node.name);
    return res;
  }
  return getParent(subjectId);
};

module.exports = SubjectsModel;<commit_msg>cherry picking<commit_after>var Tree = require("./tree");
var Substance = require("substance");

var SubjectsModel = function(doc, subjects) {
  this.doc = doc;

  // Convert subjects to hash
  this.subjects = {};

  Substance.each(subjects, function(subject) {
    this.subjects[subject.id] = subject;
  }, this);

  this.tree = new Tree(this.subjects);
};

// Get tree representation suitable for jsTree widget
// ----------------

SubjectsModel.prototype.getTree = function() {
  var tree = this.tree;

  function getChildren(parentId) {
    var res = [];
    var nodes = tree.getChildren(parentId);
    if (nodes.length === 0) return res; // exit condition

    Substance.each(nodes, function(node) {
      var entry = {
        id: node.id,
        text: node.name,
        children: getChildren(node.id) // get children for subj
      };
      res.push(entry);
    });
    return res;
  }

  return getChildren("root");
};

SubjectsModel.prototype.getAllReferencedSubjects = function() {
  var doc = this.doc;
  var subjectRefs = doc.subjectReferencesIndex.get();
  var subjects = [];

  Substance.each(subjectRefs, function(subjectRef) {
    Substance.each(subjectRef.target, function(subjectId) {
      var subject = this.tree.get(subjectId);
      if (!Substance.includes(subjects, subject)) {
        if(subject === undefined) {
          console.log('You have outdated subjects in this interview')
        } else {
          subjects.push(subject);
        }
      }
    }, this);
  }, this);

  return subjects;
};

SubjectsModel.prototype.getFullPathForSubject = function(subjectId) {
  var tree = this.tree;
  var res = [];

  function getParent(nodeId) {
    var node = tree.get(nodeId);
    var parent = tree.getParent(nodeId);
    if (parent) getParent(parent.id);

    res.push(node.name);
    return res;
  }
  return getParent(subjectId);
};

module.exports = SubjectsModel;<|endoftext|>


<commit_before>// Copyright 2002-2014, University of Colorado Boulder

/**
 * Measuring tape
 *
 * @author Vasily Shakhov (Mlearner)
 * @author Siddhartha Chinthapally (ActualConcepts)
 * @author Aaron Davis (PhET)
 * @author Martin Veillette (Berea College)
 */
define( function( require ) {
  'use strict';

  // modules
  var Circle = require( 'SCENERY/nodes/Circle' );
  var Image = require( 'SCENERY/nodes/Image' );
  var inherit = require( 'PHET_CORE/inherit' );
  var Line = require( 'SCENERY/nodes/Line' );
  var Node = require( 'SCENERY/nodes/Node' );
  var Path = require( 'SCENERY/nodes/Path' );
  var PhetFont = require( 'SCENERY_PHET/PhetFont' );
  var ScreenView = require( 'JOIST/ScreenView' );
  var Shape = require( 'KITE/Shape' );
  var SimpleDragHandler = require( 'SCENERY/input/SimpleDragHandler' );
  var Text = require( 'SCENERY/nodes/Text' );
  var Util = require( 'DOT/Util' );
  var Vector2 = require( 'DOT/Vector2' );

  // images
  var measuringTapeImage = require( 'image!SCENERY_PHET/measuringTape.png' );

  /**
   * Constructor for the measuring tape
   * @param {Bounds2} dragBounds for the measuring tape (in the ScreenView Coordinates reference frame)
   * @param {Property.<number>} scaleProperty  , number of pixels (view) in the base unit (model)
   * @param {Property.<object>} unitsProperty has two fields, (1) name <string>  and (2) multiplier <number>, eg. {name: 'cm', multiplier: 100},
   * @constructor
   */
  function MeasuringTape( dragBounds, scaleProperty, unitsProperty, isVisibleProperty, options ) {
    var measuringTape = this;

    Node.call( this );
    this.options = _.extend( {
      basePosition: new Vector2( 300, 300 ), // base Position in View units (rightBottom position of the measuring tape image)
      unrolledTapeDistance: 100, // in pixel units
      angle: 0.0, // angle of the tape in radians, recall that in the view, a positive angle means clockwise rotation.
      textPosition: new Vector2( 0, 10 ), // position of the text relative to center bottom of the image in view units
      significantFigures: 1,  // number of significant figures in the length measurement
      textColor: 'white',  // color of the length measurement and unit
      textFont: new PhetFont( 16 ), // font for the measurement text
      textFontWeight: 'bold',
      imageScale: 0.8, // control the size of the measuringTape Image (the base)
      lineColor: 'gray',  // color of the tapeline itself
      lineWidth: 2, // linewidth of the tape line and the crosshairs
      tipCircleColor: 'rgba(0,0,0,0.1)', // transparent by default
      tipCircleRadius: 10, // radius of the circle on the tip
      crosshairColor: 'rgb(224, 95, 32)', // orange, color of the two crosshairs
      crosshairSize: 5,  // size of the crosshairs in pixels ( measured from center)
      isBaseCrosshairRotating: true, // do crosshairs rotate around their own axis to line up with the tapeline
      isTipCrosshairRotating: true // do crosshairs rotate around their own axis to line up with the tapeline
    }, options );

    var erodedDragBounds = dragBounds.eroded( 4 );

    this.unitsProperty = unitsProperty; // @private
    this.scaleProperty = scaleProperty;  // @private

    this.tipToBaseDistance = this.options.unrolledTapeDistance; // @private
    this.basePosition = this.options.basePosition; // @private
    this.tipPosition = this.basePosition.plus( Vector2.createPolar( this.options.unrolledTapeDistance, this.options.angle ) ); // @private
    this.angle = this.options.angle; // @private

    var crosshairShape = new Shape().
      moveTo( -this.options.crosshairSize, 0 ).
      lineTo( this.options.crosshairSize, 0 ).
      moveTo( 0, -this.options.crosshairSize ).
      lineTo( 0, this.options.crosshairSize );

    var baseCrosshair = new Path( crosshairShape, {
      stroke: this.options.crosshairColor,
      lineWidth: this.options.lineWidth,
      center: this.basePosition
    } );

    var tipCrosshair = new Path( crosshairShape, {
      stroke: this.options.crosshairColor,
      lineWidth: this.options.lineWidth
    } );

    var tipCircle = new Circle( this.options.tipCircleRadius, {fill: this.options.tipCircleColor} );

    var baseImage = new Node( {
      children: [new Image( measuringTapeImage )],
      scale: this.options.imageScale,
      rightBottom: this.basePosition,
      cursor: 'pointer'
    } );

    // create tapeline (running from one crosshair to the other)
    var tapeLine = new Line( this.basePosition, this.tipPosition, {
      stroke: this.options.lineColor,
      lineWidth: this.options.lineWidth
    } );

    // add tipCrosshair and tipCircle to the tip
    var tip = new Node( {children: [tipCircle, tipCrosshair], center: this.tipPosition, cursor: 'pointer'} );

    // rotate crosshairs (if requested) and the baseImage
    baseImage.rotateAround( baseCrosshair.center, this.angle );
    if ( measuringTape.options.isBaseCrosshairRotating ) {
      baseCrosshair.rotateAround( baseCrosshair.center, this.angle );
    }
    if ( measuringTape.options.isTipCrosshairRotating ) {
      tipCrosshair.rotateAround( tipCrosshair.center, this.angle );
    }

    // create and add text
    var labelText = new Text( measuringTape.getText(), {
      font: this.options.textFont,
      fontWeight: this.options.textFontWeight,
      fill: this.options.textColor,
      centerTop: baseImage.centerBottom.plus( measuringTape.options.textPosition )
    } );

    tip.touchArea = tip.localBounds.dilatedXY( 10, 10 );
    tip.mouseArea = tip.localBounds.dilatedXY( 0, 0 );

    baseImage.touchArea = baseImage.localBounds.dilatedXY( 10, 10 );
    baseImage.mouseArea = baseImage.localBounds.dilatedXY( 0, 0 );

    this.addChild( tapeLine ); // tapeline going from one crosshair to the other
    this.addChild( baseCrosshair ); // crosshair near the base, (set at basePosition)
    this.addChild( baseImage );  // base of the measuring tape
    this.addChild( labelText ); // text
    this.addChild( tip ); // crosshair and circle at the tip (set at tipPosition)

    var startOffset;
    baseImage.addInputListener( new SimpleDragHandler( {
        allowTouchSnag: true,
        start: function( event, trail ) {

          // we want to find the ScreenView Node in order to enforce Bound constraints. Let's not make the assumption that it is the parent Node

          // Find the parent screen by moving up the scene graph.
          var testNode = measuringTape;
          while ( testNode !== null ) {
            if ( testNode instanceof ScreenView ) {
              this.parentScreen = testNode;
              break;
            }
            testNode = testNode.parents[0]; // Move up the scene graph by one level
          }

          // Determine the initial position of the new element as a function of the event position and this node's bounds.
          var upperLeftCornerGlobal = measuringTape.parentToGlobalPoint( measuringTape.basePosition );
          var initialPositionOffset = upperLeftCornerGlobal.minus( event.pointer.point );
          // initial position in screenView coordinates
          var initialPosition = this.parentScreen.globalToLocalPoint( event.pointer.point.plus( initialPositionOffset ) );

          startOffset = initialPosition.minus( measuringTape.basePosition );
        },

        translate: function( translationParams ) {
          // keep a reference to the old position
          var oldPosition = measuringTape.basePosition;

          // position of the base crosshair (in screen View coordinates) if we didn't enforce bounds
          var screenViewBasePosition = oldPosition.plus( translationParams.delta ).plus( startOffset );

          // constrained  basePosition (aka. the base crosshair location) to be within bounds
          var constrainedLocation = constrainBounds( screenViewBasePosition, erodedDragBounds );

          // recast the position in the local coordinate frame
          measuringTape.basePosition = constrainedLocation.minus( startOffset );

          // if the tip is not being dragged then let's move the position of the tip as well
          if ( !isDraggingTip ) {
            var deltaPosition = measuringTape.basePosition.minus( oldPosition );
            measuringTape.tipPosition = measuringTape.tipPosition.plus( deltaPosition );
          }

          // update positions of the crosshairs, text, tapeline and rotation
          measuringTape.update( measuringTape.basePosition, measuringTape.tipPosition );
        }
      } )
    );

    // when the user is not holding onto the tip, dragging the body will also drag the tip
    var isDraggingTip = false;

    // init drag and drop for tip
    tip.addInputListener( new SimpleDragHandler( {
      allowTouchSnag: true,

      start: function( event, trail ) {
        isDraggingTip = true;
      },

      translate: function( translationParams ) {
        measuringTape.tipPosition = measuringTape.tipPosition.plus( translationParams.delta );

        // update positions of the crosshairs, text, tapeline and rotation
        measuringTape.update( measuringTape.basePosition, measuringTape.tipPosition );
      },

      end: function( event, trail ) {
        isDraggingTip = false;
      }
    } ) );

    /**
     * @public
     *
     * @param {Vector2} basePosition
     * @param {Vector2} tipPosition
     */
    this.update = function( basePosition, tipPosition ) {
      measuringTape.oldAngle = measuringTape.angle;
      measuringTape.angle = Math.atan2( tipPosition.y - basePosition.y, tipPosition.x - basePosition.x );
      var deltaAngle = measuringTape.angle - measuringTape.oldAngle;

      baseCrosshair.center = basePosition;
      tip.center = tipPosition;

      // in order to avoid all kind of geometrical issues with position, let's reset the baseImage upright and then set the position
      baseImage.setRotation( 0 );
      baseImage.rightBottom = basePosition;
      baseImage.rotateAround( basePosition, measuringTape.angle );

      measuringTape.tipToBaseDistance = tipPosition.distance( basePosition );
      labelText.centerTop = baseImage.centerBottom.plus( measuringTape.options.textPosition );
      labelText.setText( measuringTape.getText() );

      // reposition the tapeline
      tapeLine.setLine( basePosition.x, basePosition.y, tipPosition.x, tipPosition.y );

      if ( measuringTape.options.isTipCrosshairRotating ) {
        tip.rotateAround( tip.center, deltaAngle );
      }
      if ( measuringTape.options.isBaseCrosshairRotating ) {
        baseCrosshair.rotateAround( baseCrosshair.center, deltaAngle );
      }
    };

    // add
    isVisibleProperty.linkAttribute( this, 'visible' );

    unitsProperty.link( function( units ) {
      labelText.setText( measuringTape.getText() );
    } );

    scaleProperty.link( function( scale ) {
      labelText.setText( measuringTape.getText() );
    } );

    /**
     * Constrains a point to some bounds.
     * @param {Vector2} point
     * @param {Bounds2} bounds
     */
    var constrainBounds = function( point, bounds ) {
      if ( _.isUndefined( bounds ) || bounds.containsPoint( point ) ) {
        return point;
      }
      else {
        var xConstrained = Math.max( Math.min( point.x, bounds.maxX ), bounds.minX );
        var yConstrained = Math.max( Math.min( point.y, bounds.maxY ), bounds.minY );
        return new Vector2( xConstrained, yConstrained );
      }
    };

  }

  return inherit( Node, MeasuringTape, {
    // @ public
    reset: function() {
      this.basePosition = this.options.basePosition;
      this.tipPosition = this.basePosition.plus( Vector2.createPolar( this.options.unrolledTapeDistance, this.options.angle ) );
      this.update( this.basePosition, this.tipPosition );
    },
    // @ public
    getText: function() {
      return Util.toFixed( this.unitsProperty.value.multiplier * this.tipToBaseDistance / this.scaleProperty.value,
        this.options.significantFigures ) + ' ' + this.unitsProperty.value.name;
    }

  } );
} );

<commit_msg>improve the JSDoc of measuring tape (see #94)<commit_after>// Copyright 2002-2014, University of Colorado Boulder

/**
 * A scenery node that is used to represent a draggable Measuring Tape.
 * It contains a tip and a base that can be dragged separately,
 * with a text indicating the measurement.
 * The motion of the measuring tape can be confined by drag bounds.
 *
 * @author Vasily Shakhov (Mlearner)
 * @author Siddhartha Chinthapally (ActualConcepts)
 * @author Aaron Davis (PhET)
 * @author Martin Veillette (Berea College)
 */
define( function( require ) {
  'use strict';

  // modules
  var Circle = require( 'SCENERY/nodes/Circle' );
  var Image = require( 'SCENERY/nodes/Image' );
  var inherit = require( 'PHET_CORE/inherit' );
  var Line = require( 'SCENERY/nodes/Line' );
  var Node = require( 'SCENERY/nodes/Node' );
  var Path = require( 'SCENERY/nodes/Path' );
  var PhetFont = require( 'SCENERY_PHET/PhetFont' );
  var ScreenView = require( 'JOIST/ScreenView' );
  var Shape = require( 'KITE/Shape' );
  var SimpleDragHandler = require( 'SCENERY/input/SimpleDragHandler' );
  var Text = require( 'SCENERY/nodes/Text' );
  var Util = require( 'DOT/Util' );
  var Vector2 = require( 'DOT/Vector2' );

  // images
  var measuringTapeImage = require( 'image!SCENERY_PHET/measuringTape.png' );

  /**
   * Constructor for the measuring tape
   * @param {Bounds2} dragBounds for the measuring tape (in the ScreenView Coordinates reference frame)
   * @param {Property.<number>} scaleProperty  , number of pixels (view) in the base unit (model)
   * @param {Property.<object>} unitsProperty has two fields, (1) name <string>  and (2) multiplier <number>, eg. {name: 'cm', multiplier: 100},
   * @constructor
   */
  function MeasuringTape( dragBounds, scaleProperty, unitsProperty, isVisibleProperty, options ) {
    var measuringTape = this;

    Node.call( this );
    this.options = _.extend( {
      basePosition: new Vector2( 300, 300 ), // base Position in View units (rightBottom position of the measuring tape image)
      unrolledTapeDistance: 100, // in pixel units
      angle: 0.0, // angle of the tape in radians, recall that in the view, a positive angle means clockwise rotation.
      textPosition: new Vector2( 0, 10 ), // position of the text relative to center bottom of the image in view units
      significantFigures: 1,  // number of significant figures in the length measurement
      textColor: 'white',  // color of the length measurement and unit
      textFont: new PhetFont( 16 ), // font for the measurement text
      textFontWeight: 'bold',
      imageScale: 0.8, // control the size of the measuringTape Image (the base)
      lineColor: 'gray',  // color of the tapeline itself
      lineWidth: 2, // linewidth of the tape line and the crosshairs
      tipCircleColor: 'rgba(0,0,0,0.1)', // transparent by default
      tipCircleRadius: 10, // radius of the circle on the tip
      crosshairColor: 'rgb(224, 95, 32)', // orange, color of the two crosshairs
      crosshairSize: 5,  // size of the crosshairs in pixels ( measured from center)
      isBaseCrosshairRotating: true, // do crosshairs rotate around their own axis to line up with the tapeline
      isTipCrosshairRotating: true // do crosshairs rotate around their own axis to line up with the tapeline
    }, options );

    var erodedDragBounds = dragBounds.eroded( 4 );

    this.unitsProperty = unitsProperty; // @private
    this.scaleProperty = scaleProperty;  // @private

    this.tipToBaseDistance = this.options.unrolledTapeDistance; // @private
    this.basePosition = this.options.basePosition; // @private
    this.tipPosition = this.basePosition.plus( Vector2.createPolar( this.options.unrolledTapeDistance, this.options.angle ) ); // @private
    this.angle = this.options.angle; // @private

    var crosshairShape = new Shape().
      moveTo( -this.options.crosshairSize, 0 ).
      lineTo( this.options.crosshairSize, 0 ).
      moveTo( 0, -this.options.crosshairSize ).
      lineTo( 0, this.options.crosshairSize );

    var baseCrosshair = new Path( crosshairShape, {
      stroke: this.options.crosshairColor,
      lineWidth: this.options.lineWidth,
      center: this.basePosition
    } );

    var tipCrosshair = new Path( crosshairShape, {
      stroke: this.options.crosshairColor,
      lineWidth: this.options.lineWidth
    } );

    var tipCircle = new Circle( this.options.tipCircleRadius, {fill: this.options.tipCircleColor} );

    var baseImage = new Node( {
      children: [new Image( measuringTapeImage )],
      scale: this.options.imageScale,
      rightBottom: this.basePosition,
      cursor: 'pointer'
    } );

    // create tapeline (running from one crosshair to the other)
    var tapeLine = new Line( this.basePosition, this.tipPosition, {
      stroke: this.options.lineColor,
      lineWidth: this.options.lineWidth
    } );

    // add tipCrosshair and tipCircle to the tip
    var tip = new Node( {children: [tipCircle, tipCrosshair], center: this.tipPosition, cursor: 'pointer'} );

    // rotate crosshairs (if requested) and the baseImage
    baseImage.rotateAround( baseCrosshair.center, this.angle );
    if ( measuringTape.options.isBaseCrosshairRotating ) {
      baseCrosshair.rotateAround( baseCrosshair.center, this.angle );
    }
    if ( measuringTape.options.isTipCrosshairRotating ) {
      tipCrosshair.rotateAround( tipCrosshair.center, this.angle );
    }

    // create and add text
    var labelText = new Text( measuringTape.getText(), {
      font: this.options.textFont,
      fontWeight: this.options.textFontWeight,
      fill: this.options.textColor,
      centerTop: baseImage.centerBottom.plus( measuringTape.options.textPosition )
    } );

    tip.touchArea = tip.localBounds.dilatedXY( 10, 10 );
    tip.mouseArea = tip.localBounds.dilatedXY( 0, 0 );

    baseImage.touchArea = baseImage.localBounds.dilatedXY( 10, 10 );
    baseImage.mouseArea = baseImage.localBounds.dilatedXY( 0, 0 );

    this.addChild( tapeLine ); // tapeline going from one crosshair to the other
    this.addChild( baseCrosshair ); // crosshair near the base, (set at basePosition)
    this.addChild( baseImage );  // base of the measuring tape
    this.addChild( labelText ); // text
    this.addChild( tip ); // crosshair and circle at the tip (set at tipPosition)

    var startOffset;
    baseImage.addInputListener( new SimpleDragHandler( {
        allowTouchSnag: true,
        start: function( event, trail ) {

          // we want to find the ScreenView Node in order to enforce Bound constraints. Let's not make the assumption that it is the parent Node

          // Find the parent screen by moving up the scene graph.
          var testNode = measuringTape;
          while ( testNode !== null ) {
            if ( testNode instanceof ScreenView ) {
              this.parentScreen = testNode;
              break;
            }
            testNode = testNode.parents[0]; // Move up the scene graph by one level
          }

          // Determine the initial position of the new element as a function of the event position and this node's bounds.
          var upperLeftCornerGlobal = measuringTape.parentToGlobalPoint( measuringTape.basePosition );
          var initialPositionOffset = upperLeftCornerGlobal.minus( event.pointer.point );
          // initial position in screenView coordinates
          var initialPosition = this.parentScreen.globalToLocalPoint( event.pointer.point.plus( initialPositionOffset ) );

          startOffset = initialPosition.minus( measuringTape.basePosition );
        },

        translate: function( translationParams ) {
          // keep a reference to the old position
          var oldPosition = measuringTape.basePosition;

          // position of the base crosshair (in screen View coordinates) if we didn't enforce bounds
          var screenViewBasePosition = oldPosition.plus( translationParams.delta ).plus( startOffset );

          // constrained  basePosition (aka. the base crosshair location) to be within bounds
          var constrainedLocation = constrainBounds( screenViewBasePosition, erodedDragBounds );

          // recast the position in the local coordinate frame
          measuringTape.basePosition = constrainedLocation.minus( startOffset );

          // if the tip is not being dragged then let's move the position of the tip as well
          if ( !isDraggingTip ) {
            var deltaPosition = measuringTape.basePosition.minus( oldPosition );
            measuringTape.tipPosition = measuringTape.tipPosition.plus( deltaPosition );
          }

          // update positions of the crosshairs, text, tapeline and rotation
          measuringTape.update( measuringTape.basePosition, measuringTape.tipPosition );
        }
      } )
    );

    // when the user is not holding onto the tip, dragging the body will also drag the tip
    var isDraggingTip = false;

    // init drag and drop for tip
    tip.addInputListener( new SimpleDragHandler( {
      allowTouchSnag: true,

      start: function( event, trail ) {
        isDraggingTip = true;
      },

      translate: function( translationParams ) {
        measuringTape.tipPosition = measuringTape.tipPosition.plus( translationParams.delta );

        // update positions of the crosshairs, text, tapeline and rotation
        measuringTape.update( measuringTape.basePosition, measuringTape.tipPosition );
      },

      end: function( event, trail ) {
        isDraggingTip = false;
      }
    } ) );

    /**
     * @public
     *
     * @param {Vector2} basePosition
     * @param {Vector2} tipPosition
     */
    this.update = function( basePosition, tipPosition ) {
      measuringTape.oldAngle = measuringTape.angle;
      measuringTape.angle = Math.atan2( tipPosition.y - basePosition.y, tipPosition.x - basePosition.x );
      var deltaAngle = measuringTape.angle - measuringTape.oldAngle;

      baseCrosshair.center = basePosition;
      tip.center = tipPosition;

      // in order to avoid all kind of geometrical issues with position, let's reset the baseImage upright and then set the position
      baseImage.setRotation( 0 );
      baseImage.rightBottom = basePosition;
      baseImage.rotateAround( basePosition, measuringTape.angle );

      measuringTape.tipToBaseDistance = tipPosition.distance( basePosition );
      labelText.centerTop = baseImage.centerBottom.plus( measuringTape.options.textPosition );
      labelText.setText( measuringTape.getText() );

      // reposition the tapeline
      tapeLine.setLine( basePosition.x, basePosition.y, tipPosition.x, tipPosition.y );

      if ( measuringTape.options.isTipCrosshairRotating ) {
        tip.rotateAround( tip.center, deltaAngle );
      }
      if ( measuringTape.options.isBaseCrosshairRotating ) {
        baseCrosshair.rotateAround( baseCrosshair.center, deltaAngle );
      }
    };

    // add
    isVisibleProperty.linkAttribute( this, 'visible' );

    unitsProperty.link( function( units ) {
      labelText.setText( measuringTape.getText() );
    } );

    scaleProperty.link( function( scale ) {
      labelText.setText( measuringTape.getText() );
    } );

    /**
     * Constrains a point to some bounds.
     * @param {Vector2} point
     * @param {Bounds2} bounds
     */
    var constrainBounds = function( point, bounds ) {
      if ( _.isUndefined( bounds ) || bounds.containsPoint( point ) ) {
        return point;
      }
      else {
        var xConstrained = Math.max( Math.min( point.x, bounds.maxX ), bounds.minX );
        var yConstrained = Math.max( Math.min( point.y, bounds.maxY ), bounds.minY );
        return new Vector2( xConstrained, yConstrained );
      }
    };

  }

  return inherit( Node, MeasuringTape, {
    // @ public
    reset: function() {
      this.basePosition = this.options.basePosition;
      this.tipPosition = this.basePosition.plus( Vector2.createPolar( this.options.unrolledTapeDistance, this.options.angle ) );
      this.update( this.basePosition, this.tipPosition );
    },
    // @ public
    getText: function() {
      return Util.toFixed( this.unitsProperty.value.multiplier * this.tipToBaseDistance / this.scaleProperty.value,
        this.options.significantFigures ) + ' ' + this.unitsProperty.value.name;
    }

  } );
} );

<|endoftext|>


<commit_before>// Copyright 2002-2013, University of Colorado Boulder

/**
 * MultiLine plain text, with alignment.
 * The line break character is '\n'.
 * Specify alignment via the 'align' option.
 * Text node options can be specified to style the text.
 *

 * Example: new MultiLineText( 'Hello\nWorld', { align: 'left', font: new PhetFont(20), fill: 'red' } );
 *
 * @author Sam Reid
 * @author Chris Malley (PixelZoom, Inc.)
 */
define( function( require ) {
  'use strict';

  // modules
  var inherit = require( 'PHET_CORE/inherit' );
  var Node = require( 'SCENERY/nodes/Node' );
  var Text = require( 'SCENERY/nodes/Text' );
  var VBox = require( 'SCENERY/nodes/VBox' );

  /**
   *
   * @param {string} text
   * @param {Object} [options]
   * @constructor
   */
  function MultiLineText( text, options ) {

    var thisNode = this;

    thisNode._options = options = _.extend( {
      align: 'center' // 'center', 'left' or 'right' (as supported by VBox)
    }, options );

    Node.call( thisNode );
    thisNode.text = text;
    thisNode.mutate( _.omit( options, 'align' ) ); // mutate after removing options that are specific to this subtype
  }

  inherit( Node, MultiLineText, {
      get text() {
        return this._text;
      },
      set text( string ) {
        var thisNode = this;
        thisNode._text = string;
        thisNode.children = [ new VBox( {
          children: string.split( '\n' ).map( function( line ) {
            if ( line.length === 0 ) { line = ' '; }  // creates a blank line between consecutive line breaks
            return new Text( line, _.omit( thisNode._options, 'align' ) );
          } ),
          align: thisNode._options.align
        } ) ];
      },
      /**
       * Returns an Array of the text nodes that comprise this multi line text, in case they need to be modified.
       * @private
       * @returns {Array[Text]}
       */
      getTextNodes: function() {
        var vbox = this.children[ 0 ];
        return vbox.getChildren();
      },

      /**
       * Sets the fill for all Text nodes.
       */
      setFill: function( fill ) {
        var children = this.getTextNodes();
        for ( var i = 0; i < children.length; i++ ) {
          children[ i ].setFill( fill );
        }
      }
    }
  );

  return MultiLineText;
} );
<commit_msg>#160 set default font to PhetFont for MultilineText<commit_after>// Copyright 2002-2013, University of Colorado Boulder

/**
 * MultiLine plain text, with alignment.
 * The line break character is '\n'.
 * Specify alignment via the 'align' option.
 * Text node options can be specified to style the text.
 *

 * Example: new MultiLineText( 'Hello\nWorld', { align: 'left', font: new PhetFont(20), fill: 'red' } );
 *
 * @author Sam Reid
 * @author Chris Malley (PixelZoom, Inc.)
 */
define( function( require ) {
  'use strict';

  // modules
  var inherit = require( 'PHET_CORE/inherit' );
  var Node = require( 'SCENERY/nodes/Node' );
  var PhetFont = require( 'SCENERY_PHET/PhetFont' );
  var Text = require( 'SCENERY/nodes/Text' );
  var VBox = require( 'SCENERY/nodes/VBox' );

  /**
   *
   * @param {string} text
   * @param {Object} [options]
   * @constructor
   */
  function MultiLineText( text, options ) {

    var thisNode = this;

    thisNode._options = options = _.extend( {
      font: new PhetFont(),
      align: 'center' // 'center', 'left' or 'right' (as supported by VBox)
    }, options );

    Node.call( thisNode );
    thisNode.text = text;
    thisNode.mutate( _.omit( options, 'align' ) ); // mutate after removing options that are specific to this subtype
  }

  inherit( Node, MultiLineText, {
      get text() {
        return this._text;
      },
      set text( string ) {
        var thisNode = this;
        thisNode._text = string;
        thisNode.children = [ new VBox( {
          children: string.split( '\n' ).map( function( line ) {
            if ( line.length === 0 ) { line = ' '; }  // creates a blank line between consecutive line breaks
            return new Text( line, _.omit( thisNode._options, 'align' ) );
          } ),
          align: thisNode._options.align
        } ) ];
      },
      /**
       * Returns an Array of the text nodes that comprise this multi line text, in case they need to be modified.
       * @private
       * @returns {Array[Text]}
       */
      getTextNodes: function() {
        var vbox = this.children[ 0 ];
        return vbox.getChildren();
      },

      /**
       * Sets the fill for all Text nodes.
       */
      setFill: function( fill ) {
        var children = this.getTextNodes();
        for ( var i = 0; i < children.length; i++ ) {
          children[ i ].setFill( fill );
        }
      }
    }
  );

  return MultiLineText;
} );
<|endoftext|>