/*
 * 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('
' +
                    '- 颜色
 ' +
                  '- 线性渐变
 ' +
                    '- 径向渐变
 ' +
                  '
' +
                  '' +
                  '' +
            '' +
            ''
                  );
      var colPicker = $(idref + '> .jGraduate_colPick');
      var gradPicker = $(idref + '> .jGraduate_gradPick');
            gradPicker.html(
              '' +
                '
' + $settings.window.pickerTitle + '
' +
                '
' +
                '
' +
              '
 ' +
              '' +
              '' +
        '' +
          '
' +
          '
' +
            '' +
          '
' +
        '
 ' +
              '' +
              '' +
                '' +
                '' +
              '
');
      // --------------
            // 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',
            x1: 0,
            y1: 0,
            x2: 1,
            y2: 0
          }, 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 = '';
              if (color.length === 3) {
                color = color.split("").map(function(d){return d + "" + d}).join("");
              }
              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 = $('').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 = $('').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');
      $.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).on("click touchstart", 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 touchstart', function() {
        $this.paint.type = curType;
        $this.paint[curType] = curGradient.cloneNode(true);;
        $this.paint.solidColor = null;
        okClicked();
      });
      $('#'+id+'_jGraduate_Cancel').bind('click touchstart', 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.on("click touchstart", 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);
    });
  };
})();