﻿/*
 * jGraduate 0.4
 *
 * jQuery Plugin for a gradient picker
 *
 * Copyright (c) 2010 Jeff Schiller
 * http://blog.codedread.com/
 * Copyright (c) 2010 Alexis Deveria
 * http://a.deveria.com/
 *
 * Apache 2 License

jGraduate( options, okCallback, cancelCallback )

where options is an object literal:
	{
		window: { title: "Pick the start color and opacity for the gradient" },
		images: { clientPath: "images/" },
		paint: a Paint object,
		newstop: String of value "same", "inverse", "black" or "white" 
				 OR object with one or both values {color: #Hex color, opac: number 0-1}
	}
 
- the Paint object is:
	Paint {
		type: String, // one of "none", "solidColor", "linearGradient", "radialGradient"
		alpha: Number representing opacity (0-100),
		solidColor: String representing #RRGGBB hex of color,
		linearGradient: object of interface SVGLinearGradientElement,
		radialGradient: object of interface SVGRadialGradientElement,
	}

$.jGraduate.Paint() -> constructs a 'none' color
$.jGraduate.Paint({copy: o}) -> creates a copy of the paint o
$.jGraduate.Paint({hex: "#rrggbb"}) -> creates a solid color paint with hex = "#rrggbb"
$.jGraduate.Paint({linearGradient: o, a: 50}) -> creates a linear gradient paint with opacity=0.5
$.jGraduate.Paint({radialGradient: o, a: 7}) -> creates a radial gradient paint with opacity=0.07
$.jGraduate.Paint({hex: "#rrggbb", linearGradient: o}) -> throws an exception?

- picker accepts the following object as input:
	{
		okCallback: function to call when Ok is pressed
		cancelCallback: function to call when Cancel is pressed
		paint: object describing the paint to display initially, if not set, then default to opaque white
	}

- okCallback receives a Paint object

 *
 */
 
(function() {
 
var ns = { svg: 'http://www.w3.org/2000/svg', xlink: 'http://www.w3.org/1999/xlink' };
if(!window.console) {
  window.console = new function() {
    this.log = function(str) {};
    this.dir = function(str) {};
  };
}

$.jGraduate = { 
	Paint:
		function(opt) {
			var options = opt || {};
			this.alpha = isNaN(options.alpha) ? 100 : options.alpha;
			// copy paint object
    		if (options.copy) {
    			this.type = options.copy.type;
    			this.alpha = options.copy.alpha;
				this.solidColor = null;
				this.linearGradient = null;
				this.radialGradient = null;

    			switch(this.type) {
    				case "none":
    					break;
    				case "solidColor":
    					this.solidColor = options.copy.solidColor;
    					break;
    				case "linearGradient":
    					this.linearGradient = options.copy.linearGradient.cloneNode(true);
    					break;
    				case "radialGradient":
    					this.radialGradient = options.copy.radialGradient.cloneNode(true);
    					break;
    			}
    		}
    		// create linear gradient paint
    		else if (options.linearGradient) {
    			this.type = "linearGradient";
    			this.solidColor = null;
    			this.radialGradient = null;
    			this.linearGradient = options.linearGradient.cloneNode(true);
    		}
    		// create linear gradient paint
    		else if (options.radialGradient) {
    			this.type = "radialGradient";
    			this.solidColor = null;
    			this.linearGradient = null;
    			this.radialGradient = options.radialGradient.cloneNode(true);
    		}
    		// create solid color paint
    		else if (options.solidColor) {
    			this.type = "solidColor";
    			this.solidColor = options.solidColor;
    		}
    		// create empty paint
	    	else {
	    		this.type = "none";
    			this.solidColor = null;
    			this.linearGradient = null;
    			this.radialGradient = null;
	    	}
		}
};

jQuery.fn.jGraduateDefaults = {
	paint: new $.jGraduate.Paint(),
	window: {
		pickerTitle: "Drag markers to pick a paint"
	},
	images: {
		clientPath: "images/"
	},
	newstop: 'inverse' // same, inverse, black, white
};

var isGecko = navigator.userAgent.indexOf('Gecko/') >= 0;

function setAttrs(elem, attrs) {
	if(isGecko) {
		for (var aname in attrs) elem.setAttribute(aname, attrs[aname]);
	} else {
		for (var aname in attrs) {
			var val = attrs[aname], prop = elem[aname];
			if(prop && prop.constructor === 'SVGLength') {
				prop.baseVal.value = val;
			} else {
				elem.setAttribute(aname, val);
			}
		}
	}
}

function mkElem(name, attrs, newparent) {
	var elem = document.createElementNS(ns.svg, name);
	setAttrs(elem, attrs);
	if(newparent) newparent.appendChild(elem);
	return elem;
}

jQuery.fn.jGraduate =
	function(options) {
	 	var $arguments = arguments;
		return this.each( function() {
			var $this = $(this), $settings = $.extend(true, {}, jQuery.fn.jGraduateDefaults, options),
				id = $this.attr('id'),
				idref = '#'+$this.attr('id')+' ';
			
            if (!idref)
            {
              alert('Container element must have an id attribute to maintain unique id strings for sub-elements.');
              return;
            }
            
            var okClicked = function() {
	            switch ( $this.paint.type ) {
	            	case "radialGradient":
	            		$this.paint.linearGradient = null;
	            		break;
	            	case "linearGradient":
	            		$this.paint.radialGradient = null;
	            		break;
	            	case "solidColor":
	            		$this.paint.radialGradient = $this.paint.linearGradient = null;
	            		break;
	            }
            	$.isFunction($this.okCallback) && $this.okCallback($this.paint);
            	$this.hide();
            },
            cancelClicked = function() {
            	$.isFunction($this.cancelCallback) && $this.cancelCallback();
            	$this.hide();
            };

            $.extend(true, $this, // public properties, methods, and callbacks
              {
              	// make a copy of the incoming paint
                paint: new $.jGraduate.Paint({copy: $settings.paint}),
                okCallback: $.isFunction($arguments[1]) && $arguments[1] || null,
                cancelCallback: $.isFunction($arguments[2]) && $arguments[2] || null
              });

			var pos = $this.position(),
				color = null;
			var $win = $(window);

			if ($this.paint.type == "none") {
				$this.paint = $.jGraduate.Paint({solidColor: 'ffffff'});
			}
			
            $this.addClass('jGraduate_Picker');
            $this.html('<ul class="jGraduate_tabs">' +
            				'<li class="jGraduate_tab_color jGraduate_tab_current" data-type="col">Solid Color</li>' +
            				'<li class="jGraduate_tab_lingrad" data-type="lg">Linear Gradient</li>' +
            				'<li class="jGraduate_tab_radgrad" data-type="rg">Radial Gradient</li>' +
            			'</ul>' +
            			'<div class="jGraduate_colPick"></div>' +
            			'<div class="jGraduate_gradPick"></div>' +
						'<div class="jGraduate_LightBox"></div>' +
						'<div id="' + id + '_jGraduate_stopPicker" class="jGraduate_stopPicker"></div>'
            			
            			
            			);
			var colPicker = $(idref + '> .jGraduate_colPick');
			var gradPicker = $(idref + '> .jGraduate_gradPick');
			
            gradPicker.html(
            	'<div id="' + id + '_jGraduate_Swatch" class="jGraduate_Swatch">' +
            		'<h2 class="jGraduate_Title">' + $settings.window.pickerTitle + '</h2>' +
            		'<div id="' + id + '_jGraduate_GradContainer" class="jGraduate_GradContainer"></div>' +
            		'<div id="' + id + '_jGraduate_StopSlider" class="jGraduate_StopSlider"></div>' +
            	'</div>' + 
            	'<div class="jGraduate_Form jGraduate_Points jGraduate_lg_field">' +
            		'<div class="jGraduate_StopSection">' +
	            		'<label class="jGraduate_Form_Heading">Begin Point</label>' +
    	        		'<div class="jGraduate_Form_Section">' +
        	    			'<label>x:</label>' +
            				'<input type="text" id="' + id + '_jGraduate_x1" size="3" title="Enter starting x value between 0.0 and 1.0"/>' +
            				'<label> y:</label>' +
            				'<input type="text" id="' + id + '_jGraduate_y1" size="3" title="Enter starting y value between 0.0 and 1.0"/>' +
        	   			'</div>' +
        	   		'</div>' +
        	   		'<div class="jGraduate_StopSection">' +
	            		'<label class="jGraduate_Form_Heading">End Point</label>' +
    	        		'<div class="jGraduate_Form_Section">' +
	    	        		'<label>x:</label>' +
		    	        	'<input type="text" id="' + id + '_jGraduate_x2" size="3" title="Enter ending x value between 0.0 and 1.0"/>' +
    		    	    	'<label> y:</label>' +
        		    		'<input type="text" id="' + id + '_jGraduate_y2" size="3" title="Enter ending y value between 0.0 and 1.0"/>' +
    	    	    	'</div>' +
    	    	    '</div>' +
    	       	'</div>' +
            	'<div class="jGraduate_Form jGraduate_Points jGraduate_rg_field">' +
					'<div class="jGraduate_StopSection">' +
						'<label class="jGraduate_Form_Heading">Center Point</label>' +
						'<div class="jGraduate_Form_Section">' +
							'<label>x:</label>' +
							'<input type="text" id="' + id + '_jGraduate_cx" size="3" title="Enter x value between 0.0 and 1.0"/>' +
							'<label> y:</label>' +
							'<input type="text" id="' + id + '_jGraduate_cy" size="3" title="Enter y value between 0.0 and 1.0"/>' +
						'</div>' +
					'</div>' +
					'<div class="jGraduate_StopSection">' +
						'<label class="jGraduate_Form_Heading">Focal Point</label>' +
						'<div class="jGraduate_Form_Section">' +
							'<label>Match center: <input type="checkbox" checked="checked" id="' + id + '_jGraduate_match_ctr"/></label><br/>' +
							'<label>x:</label>' +
							'<input type="text" id="' + id + '_jGraduate_fx" size="3" title="Enter x value between 0.0 and 1.0"/>' +
							'<label> y:</label>' +
							'<input type="text" id="' + id + '_jGraduate_fy" size="3" title="Enter y value between 0.0 and 1.0"/>' +
						'</div>' +
					'</div>' +
    	       	'</div>' +
				'<div class="jGraduate_StopSection jGraduate_SpreadMethod">' +
					'<label class="jGraduate_Form_Heading">Spread method</label>' +
					'<div class="jGraduate_Form_Section">' +
						'<select class="jGraduate_spreadMethod">' +
							'<option value=pad selected>Pad</option>' +
							'<option value=reflect>Reflect</option>' +
							'<option value=repeat>Repeat</option>' +
						'</select>' + 
					'</div>' +
				'</div>' +
            	'<div class="jGraduate_Form">' +
        	   		'<div class="jGraduate_Slider jGraduate_RadiusField jGraduate_rg_field">' +
						'<label class="prelabel">Radius:</label>' +
						'<div id="' + id + '_jGraduate_Radius" class="jGraduate_SliderBar jGraduate_Radius" title="Click to set radius">' +
							'<img id="' + id + '_jGraduate_RadiusArrows" class="jGraduate_RadiusArrows" src="' + $settings.images.clientPath + 'rangearrows2.gif">' +
						'</div>' +
						'<label><input type="text" id="' + id + '_jGraduate_RadiusInput" size="3" value="100"/>%</label>' + 
    	    	    '</div>' +
        	   		'<div class="jGraduate_Slider jGraduate_EllipField jGraduate_rg_field">' +
						'<label class="prelabel">Ellip:</label>' +
						'<div id="' + id + '_jGraduate_Ellip" class="jGraduate_SliderBar jGraduate_Ellip" title="Click to set Ellip">' +
							'<img id="' + id + '_jGraduate_EllipArrows" class="jGraduate_EllipArrows" src="' + $settings.images.clientPath + 'rangearrows2.gif">' +
						'</div>' +
						'<label><input type="text" id="' + id + '_jGraduate_EllipInput" size="3" value="0"/>%</label>' + 
    	    	    '</div>' +
        	   		'<div class="jGraduate_Slider jGraduate_AngleField jGraduate_rg_field">' +
						'<label class="prelabel">Angle:</label>' +
						'<div id="' + id + '_jGraduate_Angle" class="jGraduate_SliderBar jGraduate_Angle" title="Click to set Angle">' +
							'<img id="' + id + '_jGraduate_AngleArrows" class="jGraduate_AngleArrows" src="' + $settings.images.clientPath + 'rangearrows2.gif">' +
						'</div>' +
						'<label><input type="text" id="' + id + '_jGraduate_AngleInput" size="3" value="0"/>deg</label>' + 
    	    	    '</div>' +
        	   		'<div class="jGraduate_Slider jGraduate_OpacField">' +
						'<label class="prelabel">Opac:</label>' +
						'<div id="' + id + '_jGraduate_Opac" class="jGraduate_SliderBar jGraduate_Opac" title="Click to set Opac">' +
							'<img id="' + id + '_jGraduate_OpacArrows" class="jGraduate_OpacArrows" src="' + $settings.images.clientPath + 'rangearrows2.gif">' +
						'</div>' +
						'<label><input type="text" id="' + id + '_jGraduate_OpacInput" size="3" value="100"/>%</label>' + 
    	    	    '</div>' +
    	       	'</div>' +
        	    '<div class="jGraduate_OkCancel">' +
            		'<input type="button" id="' + id + '_jGraduate_Ok" class="jGraduate_Ok" value="OK"/>' +
            		'<input type="button" id="' + id + '_jGraduate_Cancel" class="jGraduate_Cancel" value="Cancel"/>' +
            	'</div>');
            	
			// --------------
            // Set up all the SVG elements (the gradient, stops and rectangle)
            var MAX = 256, MARGINX = 0, MARGINY = 0, STOP_RADIUS = 15/2,
            	SIZEX = MAX - 2*MARGINX, SIZEY = MAX - 2*MARGINY;
            	
            var curType, curGradient, previewRect;	
            
			var attr_input = {};
            
            var SLIDERW = 145;
            $('.jGraduate_SliderBar').width(SLIDERW);
			
			var container = $('#' + id+'_jGraduate_GradContainer')[0];
			
			var svg = mkElem('svg', {
				id: id + '_jgraduate_svg',
				width: MAX,
				height: MAX,
				xmlns: ns.svg
			}, container);
			
			// if we are sent a gradient, import it 
			
			curType = curType || $this.paint.type;
			
			var grad = curGradient = $this.paint[curType];
			
			var gradalpha = $this.paint.alpha;
			
			var isSolid = curType === 'solidColor';
			
			// Make any missing gradients
			switch ( curType ) {
				case "solidColor":
					// fall through
				case "linearGradient":
					if(!isSolid) {
						curGradient.id = id+'_lg_jgraduate_grad';
						grad = curGradient = svg.appendChild(curGradient);//.cloneNode(true));
					}
					mkElem('radialGradient', {
						id: id + '_rg_jgraduate_grad'
					}, svg);
					if(curType === "linearGradient") break;
				case "radialGradient":
					if(!isSolid) {
						curGradient.id = id+'_rg_jgraduate_grad';
						grad = curGradient = svg.appendChild(curGradient);//.cloneNode(true));
					}
					mkElem('linearGradient', {
						id: id + '_lg_jgraduate_grad'
					}, svg);
			}
			
			if(isSolid) {
				grad = curGradient = $('#' + id + '_lg_jgraduate_grad')[0];
				var color = $this.paint[curType];
				mkStop(0, '#' + color, 1);
				
				var type = typeof $settings.newstop;
				
				if(type === 'string') {
					switch ( $settings.newstop ) {
						case 'same':
							mkStop(1, '#' + color, 1);				
							break;

						case 'inverse':
							// Invert current color for second stop
							var inverted = '';
							
							for(var i = 0; i < 6; i += 2) {
								var ch = color.substr(i, 2);
								var inv = (255 - parseInt(color.substr(i, 2), 16)).toString(16);
								if(inv.length < 2) inv = 0 + inv;
								inverted += inv;
							}
							mkStop(1, '#' + inverted, 1);
							break;
						
						case 'white':
							mkStop(1, '#ffffff', 1);
							break;
	
						case 'black':
							mkStop(1, '#000000', 1);
							break;
					}
				} else if(type === 'object'){
					var opac = ('opac' in $settings.newstop) ? $settings.newstop.opac : 1;
					mkStop(1, ($settings.newstop.color || '#' + color), opac);
				}
			}

			
			var x1 = parseFloat(grad.getAttribute('x1')||0.0),
				y1 = parseFloat(grad.getAttribute('y1')||0.0),
				x2 = parseFloat(grad.getAttribute('x2')||1.0),
				y2 = parseFloat(grad.getAttribute('y2')||0.0);
				
			var cx = parseFloat(grad.getAttribute('cx')||0.5),
				cy = parseFloat(grad.getAttribute('cy')||0.5),
				fx = parseFloat(grad.getAttribute('fx')|| cx),
				fy = parseFloat(grad.getAttribute('fy')|| cy);

			
			var previewRect = mkElem('rect', {
				id: id + '_jgraduate_rect',
				x: MARGINX,
				y: MARGINY,
				width: SIZEX,
				height: SIZEY,
				fill: 'url(#'+id+'_jgraduate_grad)',
				'fill-opacity': gradalpha/100
			}, svg);
			
			// stop visuals created here
			var beginCoord = $('<div/>').attr({
				'class': 'grad_coord jGraduate_lg_field',
				title: 'Begin Stop'
			}).text(1).css({
				top: y1 * MAX,
				left: x1 * MAX
			}).data('coord', 'start').appendTo(container);
			
			var endCoord = beginCoord.clone().text(2).css({
				top: y2 * MAX,
				left: x2 * MAX
			}).attr('title', 'End stop').data('coord', 'end').appendTo(container);
		
			var centerCoord = $('<div/>').attr({
				'class': 'grad_coord jGraduate_rg_field',
				title: 'Center stop'
			}).text('C').css({
				top: cy * MAX,
				left: cx * MAX
			}).data('coord', 'center').appendTo(container);
			
			var focusCoord = centerCoord.clone().text('F').css({
				top: fy * MAX,
				left: fx * MAX,
				display: 'none'
			}).attr('title', 'Focus point').data('coord', 'focus').appendTo(container);
			
			focusCoord[0].id = id + '_jGraduate_focusCoord';
			
			var coords = $(idref + ' .grad_coord');
			
// 			$(container).hover(function() {
// 				coords.animate({
// 					opacity: 1
// 				}, 500);
// 			}, function() {
// 				coords.animate({
// 					opacity: .2
// 				}, 500);				
// 			});
			
			$.each(['x1', 'y1', 'x2', 'y2', 'cx', 'cy', 'fx', 'fy'], function(i, attr) {
				var attrval = curGradient.getAttribute(attr);
				
				var isRadial = isNaN(attr[1]);
				
				if(!attrval) {
					// Set defaults
					if(isRadial) {
						// For radial points
						attrval = "0.5";
					} else {
						// Only x2 is 1
						attrval = attr === 'x2' ? "1.0" : "0.0";
					}
				}

				attr_input[attr] = $('#'+id+'_jGraduate_' + attr)
					.val(attrval)
					.change(function() {
						// TODO: Support values < 0 and > 1 (zoomable preview?)
						if (isNaN(parseFloat(this.value)) || this.value < 0) {
							this.value = 0.0; 
						} else if(this.value > 1) {
							this.value = 1.0;
						}
						
						if(!(attr[0] === 'f' && !showFocus)) {
							if(isRadial && curType === 'radialGradient' || !isRadial && curType === 'linearGradient') {
								curGradient.setAttribute(attr, this.value);
							}
						}
						
						if(isRadial) {
							var $elem = attr[0] === "c" ? centerCoord : focusCoord;
						} else {
							var $elem = attr[1] === "1" ? beginCoord : endCoord;						
						}
						
						var cssName = attr.indexOf('x') >= 0 ? 'left' : 'top';
						
						$elem.css(cssName, this.value * MAX);
				}).change();
			});


			
			function mkStop(n, color, opac, sel, stop_elem) {
				var stop = stop_elem || mkElem('stop',{'stop-color':color,'stop-opacity':opac,offset:n}, curGradient);
				if(stop_elem) {
					color = stop_elem.getAttribute('stop-color');
					opac = stop_elem.getAttribute('stop-opacity');
					n = stop_elem.getAttribute('offset');
				} else {
					curGradient.appendChild(stop);
				}
				if(opac === null) opac = 1;
				
				var picker_d = 'M-6.2,0.9c3.6-4,6.7-4.3,6.7-12.4c-0.2,7.9,3.1,8.8,6.5,12.4c3.5,3.8,2.9,9.6,0,12.3c-3.1,2.8-10.4,2.7-13.2,0C-9.6,9.9-9.4,4.4-6.2,0.9z';
				
				var pathbg = mkElem('path',{
					d: picker_d,
					fill: 'url(#jGraduate_trans)',
					transform: 'translate(' + (10 + n * MAX) + ', 26)'
				}, stopGroup);
				
				var path = mkElem('path',{
					d: picker_d,
					fill: color,
					'fill-opacity': opac,
					transform: 'translate(' + (10 + n * MAX) + ', 26)',
					stroke: '#000',
					'stroke-width': 1.5
				}, stopGroup);

				$(path).mousedown(function(e) {
					selectStop(this);
					drag = cur_stop;
					$win.mousemove(dragColor).mouseup(remDrags);
					stop_offset = stopMakerDiv.offset();
					e.preventDefault();
					return false;
				}).data('stop', stop).data('bg', pathbg).dblclick(function() {
					$('div.jGraduate_LightBox').show();			
					var colorhandle = this;
					var stopOpacity = +stop.getAttribute('stop-opacity') || 1;
					var stopColor = stop.getAttribute('stop-color') || 1;
					var thisAlpha = (parseFloat(stopOpacity)*255).toString(16);
					while (thisAlpha.length < 2) { thisAlpha = "0" + thisAlpha; }
					color = stopColor.substr(1) + thisAlpha;
					$('#'+id+'_jGraduate_stopPicker').css({'left': 100, 'bottom': 15}).jPicker({
							window: { title: "Pick the start color and opacity for the gradient" },
							images: { clientPath: $settings.images.clientPath },
							color: { active: color, alphaSupport: true }
						}, function(color, arg2){
							stopColor = color.val('hex') ? ('#'+color.val('hex')) : "none";
							stopOpacity = color.val('a') !== null ? color.val('a')/256 : 1;
							colorhandle.setAttribute('fill', stopColor);
							colorhandle.setAttribute('fill-opacity', stopOpacity);
							stop.setAttribute('stop-color', stopColor);
							stop.setAttribute('stop-opacity', stopOpacity);
							$('div.jGraduate_LightBox').hide();
							$('#'+id+'_jGraduate_stopPicker').hide();
						}, null, function() {
							$('div.jGraduate_LightBox').hide();
							$('#'+id+'_jGraduate_stopPicker').hide();
						});
				});
				
				$(curGradient).find('stop').each(function() {
					var cur_s = $(this);
					if(+this.getAttribute('offset') > n) {
						if(!color) {
							var newcolor = this.getAttribute('stop-color');
							var newopac = this.getAttribute('stop-opacity');
							stop.setAttribute('stop-color', newcolor);
							path.setAttribute('fill', newcolor);
							stop.setAttribute('stop-opacity', newopac === null ? 1 : newopac);
							path.setAttribute('fill-opacity', newopac === null ? 1 : newopac);
						}
						cur_s.before(stop);
						return false;
					}
				});
				if(sel) selectStop(path);
				return stop;
			}
			
			function remStop() {
				delStop.setAttribute('display', 'none');
				var path = $(cur_stop);
				var stop = path.data('stop');
				var bg = path.data('bg');
				$([cur_stop, stop, bg]).remove();
			}
			
				
			var stops, stopGroup;
			
			var stopMakerDiv = $('#' + id + '_jGraduate_StopSlider');

			var cur_stop, stopGroup, stopMakerSVG, drag;
			
			var delStop = mkElem('path',{
				d:'m9.75,-6l-19.5,19.5m0,-19.5l19.5,19.5',
				fill:'none',
				stroke:'#D00',
				'stroke-width':5,
				display:'none'
			}, stopMakerSVG);

			
			function selectStop(item) {
				if(cur_stop) cur_stop.setAttribute('stroke', '#000');
				item.setAttribute('stroke', 'blue');
				cur_stop = item;
				cur_stop.parentNode.appendChild(cur_stop);
			// 	stops = $('stop');
			// 	opac_select.val(cur_stop.attr('fill-opacity') || 1);
			// 	root.append(delStop);
			}
			
			var stop_offset;
			
			function remDrags() {
				$win.unbind('mousemove', dragColor);
				if(delStop.getAttribute('display') !== 'none') {
					remStop();
				}
				drag = null;
			}
			
			var scale_x = 1, scale_y = 1, angle = 0;
			var c_x = cx;
			var c_y = cy;
			
			function xform() {
				var rot = angle?'rotate(' + angle + ',' + c_x + ',' + c_y + ') ':'';
				if(scale_x === 1 && scale_y === 1) {
					curGradient.removeAttribute('gradientTransform');
// 					$('#ang').addClass('dis');
				} else {
					var x = -c_x * (scale_x-1);
					var y = -c_y * (scale_y-1);
					curGradient.setAttribute('gradientTransform', rot + 'translate(' + x + ',' + y + ') scale(' + scale_x + ',' + scale_y + ')');
// 					$('#ang').removeClass('dis');
				}
			}
			
			function dragColor(evt) {

				var x = evt.pageX - stop_offset.left;
				var y = evt.pageY - stop_offset.top;
				x = x < 10 ? 10 : x > MAX + 10 ? MAX + 10: x;

				var xf_str = 'translate(' + x + ', 26)';
					if(y < -60 || y > 130) {
						delStop.setAttribute('display', 'block');
						delStop.setAttribute('transform', xf_str);
					} else {
						delStop.setAttribute('display', 'none');
					}
				
				drag.setAttribute('transform', xf_str);
				$.data(drag, 'bg').setAttribute('transform', xf_str);
				var stop = $.data(drag, 'stop');
				var s_x = (x - 10) / MAX;
				
				stop.setAttribute('offset', s_x);
				var last = 0;
				
				$(curGradient).find('stop').each(function(i) {
					var cur = this.getAttribute('offset');
					var t = $(this);
					if(cur < last) {
						t.prev().before(t);
						stops = $(curGradient).find('stop');
					}
					last = cur;
				});
				
			}
			
			stopMakerSVG = mkElem('svg', {
				width: '100%',
				height: 45
			}, stopMakerDiv[0]);
			
			var trans_pattern = mkElem('pattern', {
				width: 16,
				height: 16,
				patternUnits: 'userSpaceOnUse',
				id: 'jGraduate_trans'
			}, stopMakerSVG);
			
			var trans_img = mkElem('image', {
				width: 16,
				height: 16
			}, trans_pattern);
			
			var bg_image = $settings.images.clientPath + 'map-opacity.png';

			trans_img.setAttributeNS(ns.xlink, 'xlink:href', bg_image);
			
			$(stopMakerSVG).click(function(evt) {
				stop_offset = stopMakerDiv.offset();
				var target = evt.target;
				if(target.tagName === 'path') return;
				var x = evt.pageX - stop_offset.left - 8;
				x = x < 10 ? 10 : x > MAX + 10 ? MAX + 10: x;
				mkStop(x / MAX, 0, 0, true);
				evt.stopPropagation();
			});
			
			$(stopMakerSVG).mouseover(function() {
				stopMakerSVG.appendChild(delStop);
			});
			
			stopGroup = mkElem('g', {}, stopMakerSVG);
			
			mkElem('line', {
				x1: 10,
				y1: 15,
				x2: MAX + 10,
				y2: 15,
				'stroke-width': 2,
				stroke: '#000'
			}, stopMakerSVG);
			
			
			var spreadMethodOpt = gradPicker.find('.jGraduate_spreadMethod').change(function() {
				curGradient.setAttribute('spreadMethod', $(this).val());
			});
			
		
			// handle dragging the stop around the swatch
			var draggingCoord = null;
			
			var onCoordDrag = function(evt) {
				var x = evt.pageX - offset.left;
				var y = evt.pageY - offset.top;

				// clamp stop to the swatch
				x = x < 0 ? 0 : x > MAX ? MAX : x;
				y = y < 0 ? 0 : y > MAX ? MAX : y;
				
				draggingCoord.css('left', x).css('top', y);

				// calculate stop offset            		
				var fracx = x / SIZEX;
				var fracy = y / SIZEY;
				
				var type = draggingCoord.data('coord');
				var grad = curGradient;
				
				switch ( type ) {
					case 'start':
						attr_input.x1.val(fracx);
						attr_input.y1.val(fracy);
						grad.setAttribute('x1', fracx);
						grad.setAttribute('y1', fracy);
						break;
					case 'end':
						attr_input.x2.val(fracx);
						attr_input.y2.val(fracy);
						grad.setAttribute('x2', fracx);
						grad.setAttribute('y2', fracy);
						break;
					case 'center':
						attr_input.cx.val(fracx);
						attr_input.cy.val(fracy);
						grad.setAttribute('cx', fracx);
						grad.setAttribute('cy', fracy);
						c_x = fracx;
						c_y = fracy;
						xform();
						break;
					case 'focus':
						attr_input.fx.val(fracx);
						attr_input.fy.val(fracy);
						grad.setAttribute('fx', fracx);
						grad.setAttribute('fy', fracy);
						xform();
				}
				
				evt.preventDefault();
			}
			
			var onCoordUp = function() {
				draggingCoord = null;
				$win.unbind('mousemove', onCoordDrag).unbind('mouseup', onCoordUp);
			}
			
			// Linear gradient
// 			(function() {

			
			stops = curGradient.getElementsByTagNameNS(ns.svg, 'stop');

			// if there are not at least two stops, then 
			if (numstops < 2) {
				while (numstops < 2) {
					curGradient.appendChild( document.createElementNS(ns.svg, 'stop') );
					++numstops;
				}
				stops = curGradient.getElementsByTagNameNS(ns.svg, 'stop');
			}
			
			var numstops = stops.length;				
			for(var i = 0; i < numstops; i++) {
				mkStop(0, 0, 0, 0, stops[i]);
			}
			
			spreadMethodOpt.val(curGradient.getAttribute('spreadMethod') || 'pad');

			var offset;
			
			// No match, so show focus point
			var showFocus = false; 
			
			previewRect.setAttribute('fill-opacity', gradalpha/100);

			
			$('#' + id + ' div.grad_coord').mousedown(function(evt) {
				evt.preventDefault();
				draggingCoord = $(this);
				var s_pos = draggingCoord.offset();
				offset = draggingCoord.parent().offset();
				$win.mousemove(onCoordDrag).mouseup(onCoordUp);
			});
			
			// bind GUI elements
			$('#'+id+'_jGraduate_Ok').bind('click', function() {
				$this.paint.type = curType;
				$this.paint[curType] = curGradient.cloneNode(true);;
				$this.paint.solidColor = null;
				okClicked();
			});
			$('#'+id+'_jGraduate_Cancel').bind('click', function(paint) {
				cancelClicked();
			});

			if(curType === 'radialGradient') {
				if(showFocus) {
					focusCoord.show();				
				} else {
					focusCoord.hide();
					attr_input.fx.val("");
					attr_input.fy.val("");
				}
			}

			$("#" + id + "_jGraduate_match_ctr")[0].checked = !showFocus;
			
			var lastfx, lastfy;
			
			$("#" + id + "_jGraduate_match_ctr").change(function() {
				showFocus = !this.checked;
				focusCoord.toggle(showFocus);
				attr_input.fx.val('');
				attr_input.fy.val('');
				var grad = curGradient;
				if(!showFocus) {
					lastfx = grad.getAttribute('fx');
					lastfy = grad.getAttribute('fy');
					grad.removeAttribute('fx');
					grad.removeAttribute('fy');
				} else {
					var fx = lastfx || .5;
					var fy = lastfy || .5;
					grad.setAttribute('fx', fx);
					grad.setAttribute('fy', fy);
					attr_input.fx.val(fx);
					attr_input.fy.val(fy);
				}
			});
			
			var stops = curGradient.getElementsByTagNameNS(ns.svg, 'stop');
			var numstops = stops.length;
			// if there are not at least two stops, then 
			if (numstops < 2) {
				while (numstops < 2) {
					curGradient.appendChild( document.createElementNS(ns.svg, 'stop') );
					++numstops;
				}
				stops = curGradient.getElementsByTagNameNS(ns.svg, 'stop');
			}
			
			var slider;
			
			var setSlider = function(e) {
				var offset = slider.offset;
				var div = slider.parent;
				var x = (e.pageX - offset.left - parseInt(div.css('border-left-width')));
				if (x > SLIDERW) x = SLIDERW;
				if (x <= 0) x = 0;
				var posx = x - 5;
				x /= SLIDERW;
				
				switch ( slider.type ) {
					case 'radius':
						x = Math.pow(x * 2, 2.5);
						if(x > .98 && x < 1.02) x = 1;
						if (x <= .01) x = .01;
						curGradient.setAttribute('r', x);
						break;
					case 'opacity':
						$this.paint.alpha = parseInt(x*100);
						previewRect.setAttribute('fill-opacity', x);
						break;
					case 'ellip':
						scale_x = 1, scale_y = 1;
						if(x < .5) {
							x /= .5; // 0.001
							scale_x = x <= 0 ? .01 : x;
						} else if(x > .5) {
							x /= .5; // 2
							x = 2 - x;
							scale_y = x <= 0 ? .01 : x;
						} 
						xform();
						x -= 1;
						if(scale_y === x + 1) {
							x = Math.abs(x);
						}
						break;
					case 'angle':
						x = x - .5;
						angle = x *= 180;
						xform();
						x /= 100;
						break;
				}
				slider.elem.css({'margin-left':posx});
				x = Math.round(x*100);
				slider.input.val(x);
			};
			
			var ellip_val = 0, angle_val = 0;
			
			if(curType === 'radialGradient') {
				var tlist = curGradient.gradientTransform.baseVal;
				if(tlist.numberOfItems === 2) {
					var t = tlist.getItem(0);
					var s = tlist.getItem(1);
					if(t.type === 2 && s.type === 3) {
						var m = s.matrix;
						if(m.a !== 1) {
							ellip_val = Math.round(-(1 - m.a) * 100);	
						} else if(m.d !== 1) {
							ellip_val = Math.round((1 - m.d) * 100);
						} 
					}
				} else if(tlist.numberOfItems === 3) {
					// Assume [R][T][S]
					var r = tlist.getItem(0);
					var t = tlist.getItem(1);
					var s = tlist.getItem(2);
					
					if(r.type === 4 
						&& t.type === 2 
						&& s.type === 3) {

						angle_val = Math.round(r.angle);
						var m = s.matrix;
						if(m.a !== 1) {
							ellip_val = Math.round(-(1 - m.a) * 100);	
						} else if(m.d !== 1) {
							ellip_val = Math.round((1 - m.d) * 100);
						} 
						
					}
				}
			}
			
			var sliders = {
				radius: {
					handle: '#' + id + '_jGraduate_RadiusArrows',
					input: '#' + id + '_jGraduate_RadiusInput',
					val: (curGradient.getAttribute('r') || .5) * 100
				},
				opacity: {
					handle: '#' + id + '_jGraduate_OpacArrows',
					input: '#' + id + '_jGraduate_OpacInput',
					val: $this.paint.alpha || 100
				},
				ellip: {
					handle: '#' + id + '_jGraduate_EllipArrows',
					input: '#' + id + '_jGraduate_EllipInput',
					val: ellip_val
				},
				angle: {
					handle: '#' + id + '_jGraduate_AngleArrows',
					input: '#' + id + '_jGraduate_AngleInput',
					val: angle_val
				}
			}
			
			$.each(sliders, function(type, data) {
				var handle = $(data.handle);
				handle.mousedown(function(evt) {
					var parent = handle.parent();
					slider = {
						type: type,
						elem: handle,
						input: $(data.input),
						parent: parent,
						offset: parent.offset()
					};
					$win.mousemove(dragSlider).mouseup(stopSlider);
					evt.preventDefault();
				});
				
				$(data.input).val(data.val).change(function() {
					var val = +this.value;
					var xpos = 0;
					var isRad = curType === 'radialGradient';
					switch ( type ) {
						case 'radius':
							if(isRad) curGradient.setAttribute('r', val / 100);
							xpos = (Math.pow(val / 100, 1 / 2.5) / 2) * SLIDERW;
							break;
						
						case 'opacity':
							$this.paint.alpha = val;
							previewRect.setAttribute('fill-opacity', val / 100);
							xpos = val * (SLIDERW / 100);
							break;
							
						case 'ellip':
							scale_x = scale_y = 1;
							if(val === 0) {
								xpos = SLIDERW * .5;
								break;
							}
							if(val > 99.5) val = 99.5;
							if(val > 0) {
								scale_y = 1 - (val / 100);
							} else {
								scale_x = - (val / 100) - 1;
							}

							xpos = SLIDERW * ((val + 100) / 2) / 100;
							if(isRad) xform();
							break;
						
						case 'angle':
							angle = val;
							xpos = angle / 180;
							xpos += .5;
							xpos *= SLIDERW;
							if(isRad) xform();
					}
					if(xpos > SLIDERW) {
						xpos = SLIDERW;
					} else if(xpos < 0) {
						xpos = 0;
					}
					handle.css({'margin-left': xpos - 5});
				}).change();
			});
			
			var dragSlider = function(evt) {
				setSlider(evt);
				evt.preventDefault();
			};
			
			var stopSlider = function(evt) {
				$win.unbind('mousemove', dragSlider).unbind('mouseup', stopSlider);
				slider = null;
			};
			
			
			// --------------
			var thisAlpha = ($this.paint.alpha*255/100).toString(16);
			while (thisAlpha.length < 2) { thisAlpha = "0" + thisAlpha; }
			thisAlpha = thisAlpha.split(".")[0];
			color = $this.paint.solidColor == "none" ? "" : $this.paint.solidColor + thisAlpha;
			
			if(!isSolid) {
				color = stops[0].getAttribute('stop-color');
			}
			
			// This should be done somewhere else, probably
			$.extend($.fn.jPicker.defaults.window, {
				alphaSupport: true, effects: {type: 'show',speed: 0}
			});
			
			colPicker.jPicker(
				{
					window: { title: $settings.window.pickerTitle },
					images: { clientPath: $settings.images.clientPath },
					color: { active: color, alphaSupport: true }
				},
				function(color) {
					$this.paint.type = "solidColor";
					$this.paint.alpha = color.val('ahex') ? Math.round((color.val('a') / 255) * 100) : 100;
					$this.paint.solidColor = color.val('hex') ? color.val('hex') : "none";
					$this.paint.radialGradient = null;
					okClicked(); 
				},
				null,
				function(){ cancelClicked(); }
				);

			
			var tabs = $(idref + ' .jGraduate_tabs li');
			tabs.click(function() {
				tabs.removeClass('jGraduate_tab_current');
				$(this).addClass('jGraduate_tab_current');
				$(idref + " > div").hide();
				var type = $(this).attr('data-type');
				var container = $(idref + ' .jGraduate_gradPick').show();
				if(type === 'rg' || type === 'lg') {
					// Show/hide appropriate fields
					$('.jGraduate_' + type + '_field').show();
					$('.jGraduate_' + (type === 'lg' ? 'rg' : 'lg') + '_field').hide();
					
					$('#' + id + '_jgraduate_rect')[0].setAttribute('fill', 'url(#' + id + '_' + type + '_jgraduate_grad)');
					
					// Copy stops
					
					curType = type === 'lg' ? 'linearGradient' : 'radialGradient';
					
					$('#' + id + '_jGraduate_OpacInput').val($this.paint.alpha).change();
					
					var newGrad = $('#' + id + '_' + type + '_jgraduate_grad')[0];
					
					if(curGradient !== newGrad) {
						var cur_stops = $(curGradient).find('stop');	
						$(newGrad).empty().append(cur_stops);
						curGradient = newGrad;
						var sm = spreadMethodOpt.val();
						curGradient.setAttribute('spreadMethod', sm);
					}
					showFocus = type === 'rg' && curGradient.getAttribute('fx') != null && !(cx == fx && cy == fy);
					$('#' + id + '_jGraduate_focusCoord').toggle(showFocus);
					if(showFocus) {
						$('#' + id + '_jGraduate_match_ctr')[0].checked = false;
					}
				} else {
					$(idref + ' .jGraduate_gradPick').hide();
					$(idref + ' .jGraduate_colPick').show();
				}
			});
			$(idref + " > div").hide();
			tabs.removeClass('jGraduate_tab_current');
			var tab;
			switch ( $this.paint.type ) {
				case 'linearGradient':
					tab = $(idref + ' .jGraduate_tab_lingrad');
					break;
				case 'radialGradient':
					tab = $(idref + ' .jGraduate_tab_radgrad');
					break;
				default:
					tab = $(idref + ' .jGraduate_tab_color');
					break;
			}
			$this.show();
			
			// jPicker will try to show after a 0ms timeout, so need to fire this after that
			setTimeout(function() {
				tab.addClass('jGraduate_tab_current').click();	
			}, 10);
		});
	};
})();