<commit_before>// A cubic time algorithm which simply tries all possible lines,
// and checks whether or not it is a good one.
function NaiveAlgorithm(PointGroupOne, PointGroupTwo, returnMostHorizontal)
{
    returnMostHorizontal = returnMostHorizontal || false;
	// When there is an even number of points, leave out the last point
    var groupOneLength = PointGroupOne.length + (PointGroupOne.length%2 - 1);
	var groupTwoLength = PointGroupTwo.length + (PointGroupTwo.length%2 - 1);
    var candidateList = [];

	for(var i = 0; i < groupOneLength; i++)
	{
		for(var j = 0; j < groupTwoLength; j++)
		{
			var p1 = PointGroupOne[i].clone();
			var p2 = PointGroupTwo[j].clone();
			var candidateLine = new Line(p1, p2);

			// We allow for a difference of one if the groups are of even size
			// the naive algorithm could be expanded to not need this.
			var allowOneDifference = false;
			var isProperCut = NaiveCheckIfLineIsProperCut(candidateLine, PointGroupOne.slice(0,groupOneLength), PointGroupTwo.slice(0,groupTwoLength), allowOneDifference);
			if(isProperCut)
			{
				// If for both group 1 and group 2, there are as many points left as right of the line,
				// it is the line we're looking for - and can stop looking.

                // When there is an even number of points, search the point that is closest to the candidate line (on the side of the left out point)
                var minDistOne = 0;
                var closestPoint = p1;
                var p1median = p1.clone();
                if(PointGroupOne.length%2 == 0)
                {
                    for(var k = 0; k < groupOneLength+1; k++)
                    {
                         var distanceToLine = candidateLine.distance(PointGroupOne[k])*candidateLine.orientation(PointGroupOne[k])*candidateLine.orientation(PointGroupOne[groupOneLength]);
                         if(k != i && (distanceToLine < minDistOne || minDistOne == 0) && distanceToLine > 0) {
                                 minDistOne = distanceToLine;
                                 closestPoint = PointGroupOne[k];
                         }
                    }
                    p1median.x = (closestPoint.x+p1.x)/2;
                    p1median.y = (closestPoint.y+p1.y)/2;
                }

                var minDistTwo = 0;
                closestPoint = p2;
                var p2median = p2.clone();
                if(PointGroupTwo.length%2 == 0)
                {
                    for(var l = 0; l < groupTwoLength+1; l++)
                    {
                         var distanceToLine = candidateLine.distance(PointGroupTwo[l])*candidateLine.orientation(PointGroupTwo[l])*candidateLine.orientation(PointGroupTwo[groupTwoLength]);
                         if(l != j && (distanceToLine < minDistTwo || minDistTwo == 0) && distanceToLine > 0) {
                                 minDistTwo = distanceToLine;
                                 var closestPoint = PointGroupTwo[l];
                         }
                    }
                    p2median.x = (closestPoint.x+p2.x)/2;
                    p2median.y = (closestPoint.y+p2.y)/2;
                }

                // First check whether a line through the medians is possible
                candidateLine = new Line(p1median,p2median);
                if(NaiveCheckIfLineIsProperCut(candidateLine, PointGroupOne, PointGroupTwo, allowOneDifference))
                {
                    candidateList.push(candidateLine.clone());
                }
                else
                {
                    var minDist = Math.min(minDistOne,minDistTwo);
                    p1median.x = p1.x+(p1median.x-p1.x)*minDist/minDistOne;
                    p1median.y = p1.y+(p1median.y-p1.y)*minDist/minDistOne;
                    p2median.x = p2.x+(p2median.x-p2.x)*minDist/minDistTwo;
                    p2median.y = p2.y+(p2median.y-p2.y)*minDist/minDistTwo;

                    // Otherwise check whether the line can be shifted perpendicular to the median with the smallest distance
                    var oldLine = new Line(p1,p2);
                    candidateLine = new Line(p1median,p2median);
                    if(oldLine.orientation(PointGroupOne[groupOneLength]) == oldLine.orientation(PointGroupTwo[groupTwoLength])
                        && NaiveCheckIfLineIsProperCut(candidateLine, PointGroupOne, PointGroupTwo, allowOneDifference))
                    {
                        candidateList.push(candidateLine.clone());
                    }
                    else
                    {
                        candidateList.push(new Line(p1,p2));
                    }
                }
                candidateList.push(new Line(p1,p2));
			}

            if(!returnMostHorizontal && candidateList.length > 0)
                return candidateList[0];
		}
	}

    var mostHorizontalLine = candidateList[0];
    for(var i = 1; i < candidateList.length; i++)
    {
        if(Math.abs(mostHorizontalLine.slope()) > Math.abs(candidateList[i].slope))
        {
            mostHorizontalLine = candidateList[i];
        }
    }
    return mostHorizontalLine;
}

// A function which checks whether or not the given line divides both
// point groups in half, using a linear time algorithm
function NaiveCheckIfLineIsProperCut(line, pointGroupOne, pointGroupTwo, allowOneDifference)
{
	allowOneDifference = allowOneDifference || false;
	var groupOneLength = pointGroupOne.length;
	var groupTwoLength = pointGroupTwo.length;

	// Stores how many points are left/on/right of the line
	var groupOnePositions = [0,0,0];
	var groupTwoPositions = [0,0,0];

	// Given we have a candidate line, we can just count all other points
	// to see whether or not the candidate line is a good split
	// Note that even if we evaluate the points which make up the line,
	// it doesn't matter as they'll be on the line.
	for(var k = 0; k < groupOneLength; k++)
	{
		var p = pointGroupOne[k];
		var pos = line.orientation(p);
		groupOnePositions[pos + 1]++;
	}

	for(var k = 0; k < groupTwoLength; k++)
	{
		var p = pointGroupTwo[k];
		var pos = line.orientation(p);
		groupTwoPositions[pos + 1]++;
	}

	// The groups are split well if both have equally many points on either side
	var groupOneWellSplit = (groupOnePositions[0] == groupOnePositions[2]);
	var groupTwoWellSplit = (groupTwoPositions[0] == groupTwoPositions[2]);

	// However, if a group is of even size, then there won't be a split with as many points
	// to the left as to the right, so we allow for a difference of one (if we evaluate a slice through two points)
	if(allowOneDifference)
	{
		if(groupOneLength % 2 == 0)
		{
			groupOneWellSplit = ((groupOnePositions[0] + 1 == groupOnePositions[2]) || (groupOnePositions[0] - 1 == groupOnePositions[2]));
		}
		if(groupTwoLength % 2 == 0)
		{
			groupTwoWellSplit = ((groupTwoPositions[0] + 1 == groupTwoPositions[2]) || (groupTwoPositions[0] - 1 == groupTwoPositions[2]));
		}
	}

	// If for both group 1 and group 2, there are as many points left as right of the line, it is a proper cut
	return groupOneWellSplit && groupTwoWellSplit;
}<commit_msg>First implementation of duality algorithm. Does not deal with degenerate cases, not thoroughly tested, but it worked for the one single test instance I tried.<commit_after>function DualityAlgorithm(pointGroupOne, pointGroupTwo)
{
    // Extract one point from even-sized groups
    // ~later

    // Convert to dual represenation
    // WARNING: It says not to use dual() for the algorithm - why not?
    dualGroupOne = pointGroupOne.map(function(obj){return obj.dual();});
    dualGroupTwo = pointGroupTwo.map(function(obj){return obj.dual();});
    //console.log(dualGroupOne[0].x1, dualGroupOne[0].y1, dualGroupOne[0].x2, dualGroupOne[0].y2)

    // Find intersections of n-level
    var findAllIntersections = function(obj, lines)
    {
        var intersections = [];
        for(var i = 0; i < lines.length; i++)
        {
            // Only non-paralle
            if(obj.slope() != lines[i].slope())
            {
                intersections.push([obj.intersectionWith(lines[i]),i]);
            }
        }
        return intersections;
    };
    intersectionGroupOne = dualGroupOne.map(function(obj){return findAllIntersections(obj, dualGroupOne);});
    intersectionGroupTwo = dualGroupTwo.map(function(obj){return findAllIntersections(obj, dualGroupTwo);});
    crossGroupIntersections = dualGroupOne.map(function(obj){return findAllIntersections(obj, dualGroupTwo);});

    // Find the left-most intersection for both groups, and determine the median line
    // just to the left of that intersection.
    x1s = [].concat(...intersectionGroupOne).map(function(obj){return obj[0].x;});
    x2s = [].concat(...intersectionGroupTwo).map(function(obj){return obj[0].x;});
    xCross = [].concat(...crossGroupIntersections).map(function(obj){return obj[0].x;});
    smallestX1 = Math.min(...x1s);
    smallestX2 = Math.min(...x2s);
    smallestCrossX = Math.min(...xCross);

    indexOfMedianY1 = Math.floor(dualGroupOne.length/2); // floor, because of 0-indexing
    indexOfMedianY2 = Math.floor(dualGroupTwo.length/2);
    y1s =  dualGroupOne.map(function(obj){return obj.atX(smallestX1 - 0.5);});
    y2s = dualGroupTwo.map(function(obj){return obj.atX(smallestX2 - 0.5);});

    compareNumbers = function(a,b){return a-b;};
    mediany1 = y1s.slice().sort(compareNumbers)[indexOfMedianY1];
    mediany2 = y2s.slice().sort(compareNumbers)[indexOfMedianY2];

    indexOfMedianLine1 = y1s.indexOf(mediany1);
    indexOfMedianLine2 = y2s.indexOf(mediany2);

    // Trace the median line along intersections, until the two medians intersect
    triedAllIntersections = false;
    // Include cross-group intersections
    xOfLastIntersect = Math.min(smallestX1, smallestX2, smallestCrossX) - 0.5;
    while(!triedAllIntersections)
    {
        currentMedianLine1 = dualGroupOne[indexOfMedianLine1];
        currentMedianLine2 = dualGroupTwo[indexOfMedianLine2];
        nextIntersect1 = intersectionGroupOne[indexOfMedianLine1]
                            .sort(function(a,b){return a[0].x - b[0].x;})
                            .find(function(obj){return obj[0].x > xOfLastIntersect});
        nextIntersect2 = intersectionGroupTwo[indexOfMedianLine2]
                            .sort(function(a,b){return a[0].x - b[0].x;})
                            .find(function(obj){return obj[0].x > xOfLastIntersect});

        if(currentMedianLine1.slope() != currentMedianLine2.slope())
        {
            // If the medians are non-parallel, they will intersect
            // if they intersect before they intersect with another line in their group
            // they are median lines when they intersect, and thus found our point!
            medianIntersect = currentMedianLine1.intersectionWith(currentMedianLine2);
            if(     xOfLastIntersect < medianIntersect.x
                && (medianIntersect.x < nextIntersect1[0].x || nextIntersect1 == undefined)
                && (medianIntersect.x < nextIntersect2[0].x || nextIntersect2 == undefined))
            {
                return medianIntersect.dual();
            }
        }

        if((nextIntersect2 == undefined && nextIntersect1 != undefined) || nextIntersect1[0].x <= nextIntersect2[0].x)
        {
            // intersect point + slope!=
            xOfLastIntersect = nextIntersect1[0].x;
            indexOfMedianLine1 = nextIntersect1[1];

        }
        if((nextIntersect1 == undefined && nextIntersect2 != undefined) || nextIntersect1[0].x > nextIntersect2[0].x )
        {
            // intersect point + slope!=
            xOfLastIntersect = nextIntersect2[0].x;
            indexOfMedianLine2 = nextIntersect2[1];
        }
        if(nextIntersect1 == undefined && nextIntersect2 != undefined)
        {
            triedAllIntersections = true;
        }
    }

    // Convert intersection back to normal level
    return new Line(0,0,0,0);
}

// A cubic time algorithm which simply tries all possible lines,
// and checks whether or not it is a good one.
function NaiveAlgorithm(PointGroupOne, PointGroupTwo, returnMostHorizontal)
{
    returnMostHorizontal = returnMostHorizontal || false;
	// When there is an even number of points, leave out the last point
    var groupOneLength = PointGroupOne.length + (PointGroupOne.length%2 - 1);
	var groupTwoLength = PointGroupTwo.length + (PointGroupTwo.length%2 - 1);
    var candidateList = [];

	for(var i = 0; i < groupOneLength; i++)
	{
		for(var j = 0; j < groupTwoLength; j++)
		{
			var p1 = PointGroupOne[i].clone();
			var p2 = PointGroupTwo[j].clone();
			var candidateLine = new Line(p1, p2);

			// We allow for a difference of one if the groups are of even size
			// the naive algorithm could be expanded to not need this.
			var allowOneDifference = false;
			var isProperCut = NaiveCheckIfLineIsProperCut(candidateLine, PointGroupOne.slice(0,groupOneLength), PointGroupTwo.slice(0,groupTwoLength), allowOneDifference);
			if(isProperCut)
			{
				// If for both group 1 and group 2, there are as many points left as right of the line,
				// it is the line we're looking for - and can stop looking.

                // When there is an even number of points, search the point that is closest to the candidate line (on the side of the left out point)
                var minDistOne = 0;
                var closestPoint = p1;
                var p1median = p1.clone();
                if(PointGroupOne.length%2 == 0)
                {
                    for(var k = 0; k < groupOneLength+1; k++)
                    {
                         var distanceToLine = candidateLine.distance(PointGroupOne[k])*candidateLine.orientation(PointGroupOne[k])*candidateLine.orientation(PointGroupOne[groupOneLength]);
                         if(k != i && (distanceToLine < minDistOne || minDistOne == 0) && distanceToLine > 0) {
                                 minDistOne = distanceToLine;
                                 closestPoint = PointGroupOne[k];
                         }
                    }
                    p1median.x = (closestPoint.x+p1.x)/2;
                    p1median.y = (closestPoint.y+p1.y)/2;
                }

                var minDistTwo = 0;
                closestPoint = p2;
                var p2median = p2.clone();
                if(PointGroupTwo.length%2 == 0)
                {
                    for(var l = 0; l < groupTwoLength+1; l++)
                    {
                         var distanceToLine = candidateLine.distance(PointGroupTwo[l])*candidateLine.orientation(PointGroupTwo[l])*candidateLine.orientation(PointGroupTwo[groupTwoLength]);
                         if(l != j && (distanceToLine < minDistTwo || minDistTwo == 0) && distanceToLine > 0) {
                                 minDistTwo = distanceToLine;
                                 var closestPoint = PointGroupTwo[l];
                         }
                    }
                    p2median.x = (closestPoint.x+p2.x)/2;
                    p2median.y = (closestPoint.y+p2.y)/2;
                }

                // First check whether a line through the medians is possible
                candidateLine = new Line(p1median,p2median);
                if(NaiveCheckIfLineIsProperCut(candidateLine, PointGroupOne, PointGroupTwo, allowOneDifference))
                {
                    candidateList.push(candidateLine.clone());
                }
                else
                {
                    var minDist = Math.min(minDistOne,minDistTwo);
                    p1median.x = p1.x+(p1median.x-p1.x)*minDist/minDistOne;
                    p1median.y = p1.y+(p1median.y-p1.y)*minDist/minDistOne;
                    p2median.x = p2.x+(p2median.x-p2.x)*minDist/minDistTwo;
                    p2median.y = p2.y+(p2median.y-p2.y)*minDist/minDistTwo;

                    // Otherwise check whether the line can be shifted perpendicular to the median with the smallest distance
                    var oldLine = new Line(p1,p2);
                    candidateLine = new Line(p1median,p2median);
                    if(oldLine.orientation(PointGroupOne[groupOneLength]) == oldLine.orientation(PointGroupTwo[groupTwoLength])
                        && NaiveCheckIfLineIsProperCut(candidateLine, PointGroupOne, PointGroupTwo, allowOneDifference))
                    {
                        candidateList.push(candidateLine.clone());
                    }
                    else
                    {
                        candidateList.push(new Line(p1,p2));
                    }
                }
                candidateList.push(new Line(p1,p2));
			}

            if(!returnMostHorizontal && candidateList.length > 0)
                return candidateList[0];
		}
	}

    var mostHorizontalLine = candidateList[0];
    for(var i = 1; i < candidateList.length; i++)
    {
        if(Math.abs(mostHorizontalLine.slope()) > Math.abs(candidateList[i].slope))
        {
            mostHorizontalLine = candidateList[i];
        }
    }
    return mostHorizontalLine;
}

// A function which checks whether or not the given line divides both
// point groups in half, using a linear time algorithm
function NaiveCheckIfLineIsProperCut(line, pointGroupOne, pointGroupTwo, allowOneDifference)
{
	allowOneDifference = allowOneDifference || false;
	var groupOneLength = pointGroupOne.length;
	var groupTwoLength = pointGroupTwo.length;

	// Stores how many points are left/on/right of the line
	var groupOnePositions = [0,0,0];
	var groupTwoPositions = [0,0,0];

	// Given we have a candidate line, we can just count all other points
	// to see whether or not the candidate line is a good split
	// Note that even if we evaluate the points which make up the line,
	// it doesn't matter as they'll be on the line.
	for(var k = 0; k < groupOneLength; k++)
	{
		var p = pointGroupOne[k];
		var pos = line.orientation(p);
		groupOnePositions[pos + 1]++;
	}

	for(var k = 0; k < groupTwoLength; k++)
	{
		var p = pointGroupTwo[k];
		var pos = line.orientation(p);
		groupTwoPositions[pos + 1]++;
	}

	// The groups are split well if both have equally many points on either side
	var groupOneWellSplit = (groupOnePositions[0] == groupOnePositions[2]);
	var groupTwoWellSplit = (groupTwoPositions[0] == groupTwoPositions[2]);

	// However, if a group is of even size, then there won't be a split with as many points
	// to the left as to the right, so we allow for a difference of one (if we evaluate a slice through two points)
	if(allowOneDifference)
	{
		if(groupOneLength % 2 == 0)
		{
			groupOneWellSplit = ((groupOnePositions[0] + 1 == groupOnePositions[2]) || (groupOnePositions[0] - 1 == groupOnePositions[2]));
		}
		if(groupTwoLength % 2 == 0)
		{
			groupTwoWellSplit = ((groupTwoPositions[0] + 1 == groupTwoPositions[2]) || (groupTwoPositions[0] - 1 == groupTwoPositions[2]));
		}
	}

	// If for both group 1 and group 2, there are as many points left as right of the line, it is a proper cut
	return groupOneWellSplit && groupTwoWellSplit;
}<|endoftext|>


<commit_before>if (!Spaz.dock) Spaz.dock = {};

/***********
Spaz.dock takes care of the docking
***********/

Spaz.dock.init = function() {

   // Only on OSX
   Spaz.dock.active = air.NativeApplication.supportsDockIcon;

   //
   if (!Spaz.dock.active)
   {
      return;
   }

   // Create the text format
   var format = new window.runtime.flash.text.TextFormat();
   format.size = 25;
   format.color = 0xFFFFFF;
   format.bold = true;
   format.font = 'Arial';
   air.trace("Dock text format created");

   // Load the image async
   var imageURL = 'images/spaz-icon-alpha.png';
   air.trace("Want to load image " + imageURL);
   var loader = new air.Loader();
   loader.contentLoaderInfo.addEventListener(air.Event.COMPLETE, function(event)
   {
      air.trace("Loaded image " + imageURL);
      Spaz.dock.bitmapData = event.target.content.bitmapData;
   }, false, 0, true);
   loader.load(new air.URLRequest(imageURL));

   // Save for later use
   Spaz.dock.icon = air.NativeApplication.nativeApplication.icon;
   Spaz.dock.textFormat = format;
   Spaz.dock.padding = 7;
   Spaz.dock.border = 5;

   //
   Spaz.dock.sync();
}

Spaz.dock.sync = function()
{
   if (!Spaz.dock.active)
   {
      return;
   }

   //
   var reloadID = Spaz.dock.reloadID;
   if (reloadID != null)
   {
      air.trace("Stopping dock refresh thread");
      window.clearInterval(reloadID);
   }
   if (Spaz.Prefs.getDockDisplayUnreadBadge())
   {
      var refresh = Spaz.Prefs.getDockRefreshInterval();
      air.trace("Starting dock refresh thread with refresh rate of " + refresh + " ms");
      Spaz.dock.reloadID = window.setInterval(Spaz.dock.refresh, refresh);
   }
}

Spaz.dock.refresh = function(unreadCount)
{
   if (!Spaz.dock.active)
   {
      return;
   }

   //
   if (typeof unreadCount == 'undefined')
   {
      unreadCount = $("#timeline-friends div.timeline-entry").size() - $("#timeline-friends div.timeline-entry.read").size();
   }

   //
   air.trace("Refreshing unread badge count");

   //
   if (unreadCount == Spaz.dock.lastUnreadCount)
   {
      return;
   }

   // Use the original icon if count is zero
   if (unreadCount == 0)
   {
      // Update state
      Spaz.dock.icon.bitmaps = [Spaz.dock.bitmapData];
      Spaz.dock.lastUnreadCount = 0;
      return;
   }

   //
   var tf = new window.runtime.flash.text.TextField();
   tf.defaultTextFormat = Spaz.dock.textFormat;
   tf.text = "" + unreadCount;
   tf.wordWrap = true;

   // Clone the bitmap
   var clone = Spaz.dock.bitmapData.clone();

   // Draw the text in a buffer
   var buffer = new window.runtime.flash.display.BitmapData(
      256,
      256,
      true,
      0x000000);
   buffer.draw(tf);

   // Compute the bounding box, we cannot use TextField.textWidth and TextField.textHeight as
   // they return approximation which results into incorrect display
   var rect = buffer.getColorBoundsRect(0xFFFFFFFF, 0xFFFFFFFF, true);
   var xmin = rect.left;
   var ymin = rect.top;
   var xmax = rect.right;
   var ymax = rect.bottom;

   //
   var width = xmax - xmin;
   var height = ymax - ymin;

   // Compute the ellipse dimensions
   var eH = (width / (Math.sqrt(2)));
   var eY = (height / (Math.sqrt(2)));

   // Correct the dimension if the ellipse width is lesser than the ellipse height
   if (eH < eY)
   {
      eH = eY;
   }

   // Compute the inner and outter ellipses
   var innerW = eH + Spaz.dock.padding;
   var innerH = eY + Spaz.dock.padding;
   var outterW = eH + Spaz.dock.padding + Spaz.dock.border;
   var outterH = eY + Spaz.dock.padding + Spaz.dock.border;

   // Draw the shape
   var shape = new window.runtime.flash.display.Shape();
   Spaz.dock.drawPolygon(
      shape.graphics,
      outterW,
      outterH,
      innerW,
      innerH,
      outterW,
      outterH,
      30);
   clone.draw(shape);

   // Copy the text on top of the shape
   clone.copyPixels(
      buffer,
      new window.runtime.flash.geom.Rectangle(xmin, ymin, width, height),
      new window.runtime.flash.geom.Point(outterW - width / 2, outterH - height / 2),
      null,
      null,
      true);

   // Update state
   Spaz.dock.icon.bitmaps = [clone];
   Spaz.dock.lastUnreadCount = unreadCount;
}

Spaz.dock.drawPolygon = function(graphics, centerX, centerY, innerW, innerH, outterW, outterH, size)
{
   var delta = (Math.PI * 2) / size;
   var angle = 0;
   var xs = [];
   var ys = [];
   for (var i = 0;i < size;i++)
   {
      angle = delta * i + (Math.PI / 2);
      var x1 = centerX + outterW * Math.cos(angle) + 0.5;
      var y1 = centerY - outterH * Math.sin(angle) + 0.5;
      angle += delta / 2;
      var x2 = centerX + innerW * Math.cos(angle) + 0.5;
      var y2 = centerY - innerH * Math.sin(angle) + 0.5;

      //
      xs.push(x1);
      ys.push(y1);
      xs.push(x2);
      ys.push(y2);
   }

   // Draw the inner
   var fillType = window.runtime.flash.display.GradientType.LINEAR;
   var colors = [0xFF0000, 0xAA0000];
   var alphas = [1, 1];
   var ratios = [0x00, 0xFF];
   var matr = new window.runtime.flash.geom.Matrix();
   matr.createGradientBox(outterW * 2, 0, 0, 0, 0);
   var spreadMethod = window.runtime.flash.display.SpreadMethod.PAD;
   graphics.beginGradientFill(fillType, colors, alphas, ratios, matr, spreadMethod);

   //
   graphics.lineStyle(
      1,
      0x660000,
      1,
      false,
      window.runtime.flash.display.LineScaleMode.NORMAL,
      window.runtime.flash.display.CapsStyle.NONE,
      window.runtime.flash.display.JointStyle.MITER,
      3);

   //
   for (var j in xs)
   {
      if (j == 0)
      {
         graphics.moveTo(xs[j], ys[j]);
      }
      else
      {
         graphics.lineTo(xs[j], ys[j]);
      }
   }

   //
   graphics.endFill();
}
<commit_msg>a bit of documentation<commit_after>if (!Spaz.dock) Spaz.dock = {};

/***********
Spaz.dock takes care of the docking
***********/

/* Initiliaze everything.
 */
Spaz.dock.init = function() {

   // Only on OSX
   Spaz.dock.active = air.NativeApplication.supportsDockIcon;

   //
   if (!Spaz.dock.active)
   {
      return;
   }

   // Create the text format
   var format = new window.runtime.flash.text.TextFormat();
   format.size = 25;
   format.color = 0xFFFFFF;
   format.bold = true;
   format.font = 'Arial';
   air.trace("Dock text format created");

   // Load the image async
   var imageURL = 'images/spaz-icon-alpha.png';
   air.trace("Want to load image " + imageURL);
   var loader = new air.Loader();
   loader.contentLoaderInfo.addEventListener(air.Event.COMPLETE, function(event)
   {
      air.trace("Loaded image " + imageURL);
      Spaz.dock.bitmapData = event.target.content.bitmapData;
   }, false, 0, true);
   loader.load(new air.URLRequest(imageURL));

   // Save for later use
   Spaz.dock.icon = air.NativeApplication.nativeApplication.icon;
   Spaz.dock.textFormat = format;
   Spaz.dock.padding = 7;
   Spaz.dock.border = 5;

   //
   Spaz.dock.sync();
}

/* Synchronize the dock with its the prefs. It also takes care of starting/stopping the refresh thread.
 */
Spaz.dock.sync = function()
{
   if (!Spaz.dock.active)
   {
      return;
   }

   //
   var reloadID = Spaz.dock.reloadID;
   if (reloadID != null)
   {
      air.trace("Stopping dock refresh thread");
      window.clearInterval(reloadID);
   }
   if (Spaz.Prefs.getDockDisplayUnreadBadge())
   {
      var refresh = Spaz.Prefs.getDockRefreshInterval();
      air.trace("Starting dock refresh thread with refresh rate of " + refresh + " ms");
      Spaz.dock.reloadID = window.setInterval(Spaz.dock.refresh, refresh);
   }
}

/* Performs a visual refresh. The unreadCount argument is optional. If it is not provided
 * then the value will be computed from the div elements non marked as read in the timeline of friends.
 */
Spaz.dock.refresh = function(unreadCount)
{
   if (!Spaz.dock.active)
   {
      return;
   }

   //
   if (typeof unreadCount == 'undefined')
   {
      unreadCount = $("#timeline-friends div.timeline-entry").size() - $("#timeline-friends div.timeline-entry.read").size();
   }

   //
   air.trace("Refreshing unread badge count");

   //
   if (unreadCount == Spaz.dock.lastUnreadCount)
   {
      return;
   }

   // Use the original icon if count is zero
   if (unreadCount == 0)
   {
      // Update state
      Spaz.dock.icon.bitmaps = [Spaz.dock.bitmapData];
      Spaz.dock.lastUnreadCount = 0;
      return;
   }

   //
   var tf = new window.runtime.flash.text.TextField();
   tf.defaultTextFormat = Spaz.dock.textFormat;
   tf.text = "" + unreadCount;
   tf.wordWrap = true;

   // Clone the bitmap
   var clone = Spaz.dock.bitmapData.clone();

   // Draw the text in a buffer
   var buffer = new window.runtime.flash.display.BitmapData(
      256,
      256,
      true,
      0x000000);
   buffer.draw(tf);

   // Compute the bounding box, we cannot use TextField.textWidth and TextField.textHeight as
   // they return approximation which results into incorrect display
   var rect = buffer.getColorBoundsRect(0xFFFFFFFF, 0xFFFFFFFF, true);
   var xmin = rect.left;
   var ymin = rect.top;
   var xmax = rect.right;
   var ymax = rect.bottom;

   //
   var width = xmax - xmin;
   var height = ymax - ymin;

   // Compute the ellipse dimensions
   var eH = (width / (Math.sqrt(2)));
   var eY = (height / (Math.sqrt(2)));

   // Correct the dimension if the ellipse width is lesser than the ellipse height
   if (eH < eY)
   {
      eH = eY;
   }

   // Compute the inner and outter ellipses
   var innerW = eH + Spaz.dock.padding;
   var innerH = eY + Spaz.dock.padding;
   var outterW = eH + Spaz.dock.padding + Spaz.dock.border;
   var outterH = eY + Spaz.dock.padding + Spaz.dock.border;

   // Draw the shape
   var shape = new window.runtime.flash.display.Shape();
   Spaz.dock.drawPolygon(
      shape.graphics,
      outterW,
      outterH,
      innerW,
      innerH,
      outterW,
      outterH,
      30);
   clone.draw(shape);

   // Copy the text on top of the shape
   clone.copyPixels(
      buffer,
      new window.runtime.flash.geom.Rectangle(xmin, ymin, width, height),
      new window.runtime.flash.geom.Point(outterW - width / 2, outterH - height / 2),
      null,
      null,
      true);

   // Update state
   Spaz.dock.icon.bitmaps = [clone];
   Spaz.dock.lastUnreadCount = unreadCount;
}

/* Draw a star polygon on the specified graphics.
 * graphics: the graphics
 * centerX: the x coordinate of the center of the polygon
 * centerY: the y coordinate of the center of the polygon
 * innerW: the width of the inner ellipse
 * innerH: the height of the inner ellipse
 * outterW: the width of the outter ellipse
 * outterH: the height of the outter ellipse
 * size: the number of branch in the star
 */
Spaz.dock.drawPolygon = function(graphics, centerX, centerY, innerW, innerH, outterW, outterH, size)
{
   var delta = (Math.PI * 2) / size;
   var angle = 0;
   var xs = [];
   var ys = [];
   for (var i = 0;i < size;i++)
   {
      angle = delta * i + (Math.PI / 2);
      var x1 = centerX + outterW * Math.cos(angle) + 0.5;
      var y1 = centerY - outterH * Math.sin(angle) + 0.5;
      angle += delta / 2;
      var x2 = centerX + innerW * Math.cos(angle) + 0.5;
      var y2 = centerY - innerH * Math.sin(angle) + 0.5;

      //
      xs.push(x1);
      ys.push(y1);
      xs.push(x2);
      ys.push(y2);
   }

   // Draw the inner
   var fillType = window.runtime.flash.display.GradientType.LINEAR;
   var colors = [0xFF0000, 0xAA0000];
   var alphas = [1, 1];
   var ratios = [0x00, 0xFF];
   var matr = new window.runtime.flash.geom.Matrix();
   matr.createGradientBox(outterW * 2, 0, 0, 0, 0);
   var spreadMethod = window.runtime.flash.display.SpreadMethod.PAD;
   graphics.beginGradientFill(fillType, colors, alphas, ratios, matr, spreadMethod);

   //
   graphics.lineStyle(
      1,
      0x660000,
      1,
      false,
      window.runtime.flash.display.LineScaleMode.NORMAL,
      window.runtime.flash.display.CapsStyle.NONE,
      window.runtime.flash.display.JointStyle.MITER,
      3);

   //
   for (var j in xs)
   {
      if (j == 0)
      {
         graphics.moveTo(xs[j], ys[j]);
      }
      else
      {
         graphics.lineTo(xs[j], ys[j]);
      }
   }

   //
   graphics.endFill();
}
<|endoftext|>


<commit_before>var nock = require('nock')
  , scopes = require('./scopes.js')
  , expect = require('chai').expect
  , Zencoder = require('../index');

var apiKey = '1234567abcde';

var scope = nock('https://app.zencoder.com')
              .matchHeader('Zencoder-Api-Key', apiKey)
              .get('/api/v2/account').times(2)
              .reply(200, scopes.accounts.details)
              .post('/api/v90/jobs')
              .reply(301, scopes.moved_permanently);

describe('The Zencoder REST Client setup', function () {

  it('should not require new', function(done) {
    process.env.ZENCODER_API_KEY = apiKey;
    var client = Zencoder();
    expect(client.apiKey).to.equal(apiKey);
    client.Account.details(function(err, data, response) {
      expect(response.statusCode).to.equal(200);
      done();
    });
  });

  it('should use an environment variable if API key is not included', function(done) {
    process.env.ZENCODER_API_KEY = apiKey;
    var client = new Zencoder();
    expect(client.apiKey).to.equal(apiKey);
    client.Account.details(function(err, data, response) {
      expect(response.statusCode).to.equal(200);
      done();
    });
  });

  it('should use default base url if not specified', function() {
    var client = new Zencoder(apiKey);
    expect(client.baseUrl).to.equal('https://app.zencoder.com/api/v2');
  });

  it('should use different base url if specified', function() {
    var newBase = 'https://app.zencoder.com/api/v3';
    var client = new Zencoder(apiKey, newBase);
    expect(client.baseUrl).to.equal(newBase);
  });

  it('should adjust timeout if timeout specified in options', function() {
    var options = {timeout: 5000};
    var client = new Zencoder(apiKey, null, {timeout: 5000});
    expect(client.options.timeout).to.equal(5000);
  });

  it('should return an error object as error param', function() {
    var client = new Zencoder(apiKey, 'https://app.zencoder.com/api/v90');
    client.Job.create({}, function(err) {
      expect(err.name).to.equal('Error')
    });
  });

  it('should return a promise if no callback is supplied', function() {
    process.env.ZENCODER_API_KEY = apiKey;
    var client = new Zencoder();
    expect(client.Account.details().constructor).to.equal(Promise)
  })

});
<commit_msg>Added additional test conditions<commit_after>var nock = require('nock')
  , scopes = require('./scopes.js')
  , expect = require('chai').expect
  , Zencoder = require('../index');

var apiKey = '1234567abcde';

var scope = nock('https://app.zencoder.com')
              .matchHeader('Zencoder-Api-Key', apiKey)
              .get('/api/v2/account').times(3)
              .reply(200, scopes.accounts.details)
              .post('/api/v90/jobs').times(2)
              .reply(301, scopes.moved_permanently);

describe('The Zencoder REST Client setup', function () {

  it('should not require new', function(done) {
    process.env.ZENCODER_API_KEY = apiKey;
    var client = Zencoder();
    expect(client.apiKey).to.equal(apiKey);
    client.Account.details(function(err, data, response) {
      expect(response.statusCode).to.equal(200);
      done();
    });
  });

  it('should use an environment variable if API key is not included', function(done) {
    process.env.ZENCODER_API_KEY = apiKey;
    var client = new Zencoder();
    expect(client.apiKey).to.equal(apiKey);
    client.Account.details(function(err, data, response) {
      expect(response.statusCode).to.equal(200);
      done();
    });
  });

  it('should use default base url if not specified', function() {
    var client = new Zencoder(apiKey);
    expect(client.baseUrl).to.equal('https://app.zencoder.com/api/v2');
  });

  it('should use different base url if specified', function() {
    var newBase = 'https://app.zencoder.com/api/v3';
    var client = new Zencoder(apiKey, newBase);
    expect(client.baseUrl).to.equal(newBase);
  });

  it('should adjust timeout if timeout specified in options', function() {
    var options = {timeout: 5000};
    var client = new Zencoder(apiKey, null, {timeout: 5000});
    expect(client.options.timeout).to.equal(5000);
  });

  it('should return an error object as error param', function() {
    var client = new Zencoder(apiKey, 'https://app.zencoder.com/api/v90');
    client.Job.create({}, function(err) {
      expect(err.name).to.equal('Error')
    });
  });

  it('should return a promise if no callback is supplied', function(done) {
    process.env.ZENCODER_API_KEY = apiKey;
    var client = new Zencoder();
    var resPromise = client.Account.details();
    expect(resPromise.constructor).to.equal(Promise);
    resPromise.then(function({data, body}) {
      expect(data).to.be.defined;
      expect(body).to.be.defined;
      return done();
    })
  })

});
<|endoftext|>


<commit_before>var assert = require('assert');
var fs = require('fs');
var spawn = require('child_process').spawn;
var getPixels = require('get-pixels');
var ndarray = require('ndarray');
var request = require('request');
var savePixels = require('save-pixels');

describe('phantomjs-pixel-server', function () {
  before(function (done) {
    this.child = spawn('phantomjs', [__dirname + '/../lib/phantomjs-pixel-server.js'], {stdio: [0, 1, 2]});
    setTimeout(function () {
      // TODO: We could wait for 200 from server
      done();
    }, 1000);
  });
  after(function (done) {
    this.child.kill();
    this.child.on('exit', function (code, signal) {
      done();
    });
  });

  describe('given a set of commands on a canvas', function () {
    before(function (done) {
      // Stringify our argument for phantomjs
      var params = {
        width: 10,
        height: 10,
        // TODO: Move to function over vanilla JS so the callback makes sense
        js: [
          // Draw a white on black checkerboard
          'context.fillStyle = "#000000";',
          'context.fillRect(0, 0, 10, 10);',
          'context.fillStyle = "#FFFFFF";',
          'context.fillRect(0, 0, 5, 5);',
          'context.fillRect(5, 5, 5, 5);',
          'cb();'
        ].join('\n')
      };
      var arg = JSON.stringify(params);
      var encodedArg = encodeURIComponent(arg);

      // Request to our server
      var that = this;
      request({
        url: 'http://localhost:9090/',
        method: 'POST',
        headers: {
          // DEV: PhantomJS looks for Proper-Case headers, request is lower-case =(
          // TODO: Note this in the README
          'Content-Length': encodedArg.length
        },
        body: encodedArg,
      }, function handlePhantomResponse (err, res, body) {
        that.res = res;
        that.body = body;
        done(err);
      });
    });
    before(function () {
      try {
        this.actualPixels = JSON.parse(this.body);
      } catch (e) {
        console.log('Body was ', this.body);
        throw e;
      }
    });

    if (process.env.DEBUG_TEST) {
      before(function (done) {
        var png = savePixels(ndarray(this.actualPixels, [10, 10, 4], [4 * 10, 4, 1], 0), 'png');
        try { fs.mkdirSync(__dirname + '/actual-files'); } catch (e) {}
        png.pipe(fs.createWriteStream(__dirname + '/actual-files/checkerboard.png'));
        png.on('end', done);
      });
    }
    before(function (done) {
      var that = this;
      getPixels(__dirname + '/expected-files/checkerboard.png', function (err, expectedPixels) {
        that.expectedPixels = expectedPixels;
        done(err);
      });
    });

    it('returns an array of pixel values', function () {
      assert.deepEqual(this.actualPixels, [].slice.call(this.expectedPixels.data));
    });
  });

  describe.skip('given commands which encrypt as a string', function () {
    // DEV: These were optimal for gifsockets but proved troublesome on Windows with stdout
    // TODO: Look into if Windows + stdout gives us the same trouble
    // TODO: Will the output have to be offset since we are over HTTP?
    it('gives an encoded string which corresponds to pixel values', function () {

    });
  });
});
<commit_msg>Made utilities for test<commit_after>var assert = require('assert');
var fs = require('fs');
var spawn = require('child_process').spawn;
var getPixels = require('get-pixels');
var ndarray = require('ndarray');
var request = require('request');
var savePixels = require('save-pixels');

function makeRequest(params) {
  before(function (done) {
    // Stringify our argument for phantomjs
    var arg = JSON.stringify(params);
    var encodedArg = encodeURIComponent(arg);

    // Request to our server
    var that = this;
    request({
      url: 'http://localhost:9090/',
      method: 'POST',
      headers: {
        // DEV: PhantomJS looks for Proper-Case headers, request is lower-case =(
        // TODO: Note this in the README
        'Content-Length': encodedArg.length
      },
      body: encodedArg,
    }, function handlePhantomResponse (err, res, body) {
      that.res = res;
      that.body = body;
      done(err);
    });
  });
}
function parseResponse() {
  before(function () {
    try {
      this.actualPixels = JSON.parse(this.body);
    } catch (e) {
      console.log('Body was ', this.body);
      throw e;
    }
  });
}
function debugActual(filename) {
  if (process.env.DEBUG_TEST) {
    before(function (done) {
      var png = savePixels(ndarray(this.actualPixels, [10, 10, 4], [4 * 10, 4, 1], 0), 'png');
      try { fs.mkdirSync(__dirname + '/actual-files'); } catch (e) {}
      png.pipe(fs.createWriteStream(__dirname + '/actual-files/' + filename));
      png.on('end', done);
    });
  }
}
function loadExpected(filename) {
  before(function (done) {
    var that = this;
    getPixels(__dirname + '/expected-files/' + filename, function (err, expectedPixels) {
      that.expectedPixels = expectedPixels;
      done(err);
    });
  });
}

describe('phantomjs-pixel-server', function () {
  before(function (done) {
    this.child = spawn('phantomjs', [__dirname + '/../lib/phantomjs-pixel-server.js'], {stdio: [0, 1, 2]});
    setTimeout(function () {
      // TODO: We could wait for 200 from server
      done();
    }, 1000);
  });
  after(function (done) {
    this.child.kill();
    this.child.on('exit', function (code, signal) {
      done();
    });
  });

  describe('given a set of commands on a canvas', function () {
    makeRequest({
      width: 10,
      height: 10,
      // TODO: Move to function over vanilla JS so the callback makes sense
      js: [
        // Draw a white on black checkerboard
        'context.fillStyle = "#000000";',
        'context.fillRect(0, 0, 10, 10);',
        'context.fillStyle = "#FFFFFF";',
        'context.fillRect(0, 0, 5, 5);',
        'context.fillRect(5, 5, 5, 5);',
        'cb();'
      ].join('\n')
    });
    parseResponse();
    debugActual('checkerboard.png');
    loadExpected('checkerboard.png');

    it('returns an array of pixel values', function () {
      assert.deepEqual(this.actualPixels, [].slice.call(this.expectedPixels.data));
    });
  });

  describe.skip('given commands which encrypt as a string', function () {
    // DEV: These were optimal for gifsockets but proved troublesome on Windows with stdout
    // TODO: Look into if Windows + stdout gives us the same trouble
    // TODO: Will the output have to be offset since we are over HTTP?
    it('gives an encoded string which corresponds to pixel values', function () {

    });
  });
});
<|endoftext|>


<commit_before>/*
 * Copyright (C) 2012 B3Partners B.V.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
/**
 * DataSelection and Filter component
 * Creates a dialog where filter and selection settings can be set.
 * A dataselectionfilter contains 1..n filters, a filter contains 1..n attributefilters.
 * @author Meine Toonen
 */
Ext.define ("viewer.components.DataSelection",{
    extend: "viewer.components.Component",
    dataTab : null,
    filterTab : null,
    attributes:null,
    layerSelector:null,
    appLayer: null,
    filters:null,
    filterActive:null,
    config: {
        layers:null,
        title:null,
        iconUrl:null,
        tooltip:null,
        details:{
            width: null,
            height:null
        }
    },
    constructor: function (conf){
        this.attributes =[];
        viewer.components.DataSelection.superclass.constructor.call(this, conf);
        this.filters = new Array();
        this.initConfig(conf);
        var me = this;
        this.renderButton({
            handler: function(){
                me.showWindow();
            },
            text: me.title,
            icon: me.iconUrl,
            tooltip: me.tooltip
        });
        this.loadWindow();
        return this;
    },
    showWindow : function (){
        this.layerSelector.initLayers();
        this.popup.show();
    },
    showAndForceLayer : function (layer){
        this.layerSelector.addForcedLayer(layer);
        this.showWindow();
    },
    removeForcedLayer : function (layer){
        this.layerSelector.removeForcedLayer(layer);
        this.layerSelector.initLayers();
    },
    loadWindow : function(){
        var config = {
            viewerController : this.viewerController,
            div: this.getContentDiv(),
            layers : this.layers
        };
        this.layerSelector = Ext.create("viewer.components.LayerSelector",config);
        this.layerSelector.addListener(viewer.viewercontroller.controller.Event.ON_LAYERSELECTOR_CHANGE,this.layerChanged,this);

        // Make the tabs
        this.tabpanel = Ext.create('Ext.tab.Panel', {
            height: parseInt(this.details.height) - 100,
            width: parseInt(this.details.width)-40,
            hideMode: 'offsets',
            autoScroll:true,
            layout: {
                type: 'fit'
            },
            tabBar:{
                style: 'background:#fff;'
            },
            items: [
            {
                id   : 'filterTab',
                title: 'Filter',// TODO Do renaming of variables dataselection-->filter
                hideMode: 'offsets',
                autoScroll:true,
                html: "

"
            },
            {
                id : "dataTab",
                title: 'Dataselectie',// TODO Do renaming of variables filter->dataselection
                hideMode: 'offsets',
                autoScroll:true,
                html: "

"
            }
            ],
            activeTab : "dataTab",
            renderTo : this.getContentDiv()
        });

        // Make panels in tabs and save them
        this.dataTab = Ext.create('Ext.panel.Panel', {
            autoScroll: true,
            renderTo: 'dataTabDiv'
        });

        this.filterTab = Ext.create('Ext.panel.Panel', {
            autoScroll: true,
            renderTo: 'filterTabDiv'
        });

        // Add the button to the dataselectiontab
        var addFilter = Ext.create('Ext.Button', {
            text : 'Voeg filter toe',
            listeners: {
                click:{
                    scope: this,
                    fn: this.addFilter
                }
            }
        });
        this.filterTab.add(addFilter);
        this.filterActive = Ext.create ("Ext.form.field.Checkbox",{
            boxLabel  : 'Filter is actief',
            name      : 'filterActive',
            inputValue: true,
            checked   : false

        });
        this.filterTab.add(this.filterActive);
        // Add the first filter
        this.addFilter();
        // Make lower buttons
        this.button1 = Ext.create('Ext.Button', {
            text : 'Toepassen',
            renderTo: this.getContentDiv(),
            listeners: {
                click:{
                    scope: this,
                    fn: this.applyFilter
                }
            }
        });

        this.button2 = Ext.create('Ext.Button', {
            text : 'Annuleren',
            renderTo: this.getContentDiv(),
            listeners: {
                click:{
                    scope: this,
                    fn: this.cancel
                }
            }
        });
    },
    createDataTab : function (appLayer){
        var attributes = appLayer.attributes;
        var dataSelectieAttributes = new Array();
        for(var i= 0 ; i < attributes.length ;i++){
            var attribute = attributes[i];
            if(attribute.selectable){
                var defaultVal = "";
                if(attribute.defaultValue != undefined){
                    defaultVal = attribute.defaultValue;
                }
                dataSelectieAttributes.push({
                    xtype: "textfield",
                    id: attribute.name,
                    dataType: attribute.type,
                    name: attribute.name,
                    fieldLabel: attribute.alias || attribute.name,
                    labelWidth:200,
                    value : defaultVal
                });
            }
        }

        this.dataTab.removeAll();
        this.dataTab.add(dataSelectieAttributes);
    },
    /**
     *  Add a filter to the current filterlist.
     */
    addFilter : function (){
        var logicOperator = null;
        if(this.filters.length != 0){
            var logicStore = Ext.create('Ext.data.Store', {
                fields: ['id','title'],
                data : [{
                    id:"OR",
                    title:"of"
                }, {
                    id:"AND",
                    title:"en"
                }]
            });

            logicOperator = Ext.create('Ext.form.ComboBox', {
                fieldLabel: '',
                store: logicStore,
                queryMode: 'local',
                displayField: 'title',
                width:50,
                value:'OR',
                valueField: 'id'
            });
            // Insert before the checkbox
            this.filterTab.insert(this.filterTab.items.length - 1,logicOperator);

        }
        var config = {
            width: parseInt(this.details.width),
            height: parseInt(this.details.height),
            attributes:this.attributes,
            logicOperator:logicOperator
        };
        var filter = Ext.create("viewer.components.Filter",config);
        this.filters.push(filter);
        // Insert before the checkbox
        this.filterTab.insert(this.filterTab.items.length - 1,filter.getUI());
    },
    applyFilter : function (){
        var cql = "";

        cql += this.getDataTabCQL();
        if(this.filterActive.getValue()){
            if(cql != ""){
                cql += " AND ";
            }
            for ( var i = 0 ; i < this.filters.length;i++){
                var filter = this.filters[i];
                cql += filter.getCQL();
            }

        }
        if(cql != ""){
            cql = "(" + cql + ")";
        }
        var layerObj = this.layerSelector.getValue();
        var layer = this.viewerController.getApplayer(layerObj.serviceId, layerObj.name);

        var filterWrapper =  Ext.create("viewer.components.CQLFilterWrapper",{
            id: this.name + this.layerSelector.getValue(),
            cql: cql,
            operator : "AND"
        });

        this.viewerController.setFilter(filterWrapper,layer);

    //console.log("CQL: " + layer.filter.getCQL());
    },
    getDataTabCQL : function (){
        var items = this.dataTab.items.items;
        var cql = "";
        for ( var i = 0 ; i < items.length;i++){
            var item = items[i];
            if(item.getValue() != ""){
                if(i != 0 ){
                    cql += " AND ";
                }
                cql += "\"" +item.id + "\"=";
                var attributeType = item.dataType;
                if(attributeType && attributeType.toLowerCase() == "string"){
                    cql += "\'";
                }
                cql += item.getValue();
                if(attributeType && attributeType.toLowerCase() == "string"){
                    cql += "\'";
                }
            }
        }
        return cql;
    },
    cancel : function (){
        this.popup.hide();
    },
    /**
     *  Reset all comboboxes when a different layer is selected
     */
    layerChanged : function (item,prev){
        this.appLayer = this.viewerController.getApplayer (item.serviceId,item.name);

        if(this.appLayer != null){
            this.featureService = this.viewerController.getAppLayerFeatureService(this.appLayer);
            var me = this;
            // check if featuretype was loaded
            if(this.appLayer.attributes == undefined) {
                this.featureService.loadAttributes(me.appLayer, function(attributes) {
                    me.changeAttributes(me.appLayer);
                });
            } else {
                this.changeAttributes(me.appLayer);
            }
        }

        if(prev != undefined){
            var prevLayer = this.viewerController.getLayer (prev.serviceId,prev.name);
            prevLayer.setQuery(null);
        }
    },
    // Change the comboboxes of the attributefilters. Happens when a new layer is chosen.
    changeAttributes : function (appLayer){
        var attributes = appLayer.attributes;
        var attributeList = new Array();
        for(var i= 0 ; i < attributes.length ;i++){
            var attribute = attributes[i];
            if(attribute.filterable){
                attributeList.push({
                    id: attribute.id,
                    title: attribute.alias || attribute.name,
                    value: attribute.name,
                    type: attribute.type
                });
            }
        }
        this.attributes = attributeList;
        for (var j = 0 ; j < this.filters.length ;j++){
            var filter = this.filters[j];
            filter.setNewAttributeList(attributeList);
        }
        this.createDataTab(appLayer);
    },
    getExtComponents: function() {
        return Ext.Array.merge(
            this.layerSelector.getExtComponents(),
            [
            this.tabpanel.getId(),
            this.dataTab.getId(),
            this.filterTab.getId(),
            this.button1.getId(),
            this.button2.getId()
            ]
            );
    }
});
<commit_msg>Correct call of getLayer function<commit_after>/*
 * Copyright (C) 2012 B3Partners B.V.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
/**
 * DataSelection and Filter component
 * Creates a dialog where filter and selection settings can be set.
 * A dataselectionfilter contains 1..n filters, a filter contains 1..n attributefilters.
 * @author Meine Toonen
 */
Ext.define ("viewer.components.DataSelection",{
    extend: "viewer.components.Component",
    dataTab : null,
    filterTab : null,
    attributes:null,
    layerSelector:null,
    appLayer: null,
    filters:null,
    filterActive:null,
    config: {
        layers:null,
        title:null,
        iconUrl:null,
        tooltip:null,
        details:{
            width: null,
            height:null
        }
    },
    constructor: function (conf){
        this.attributes =[];
        viewer.components.DataSelection.superclass.constructor.call(this, conf);
        this.filters = new Array();
        this.initConfig(conf);
        var me = this;
        this.renderButton({
            handler: function(){
                me.showWindow();
            },
            text: me.title,
            icon: me.iconUrl,
            tooltip: me.tooltip
        });
        this.loadWindow();
        return this;
    },
    showWindow : function (){
        this.layerSelector.initLayers();
        this.popup.show();
    },
    showAndForceLayer : function (layer){
        this.layerSelector.addForcedLayer(layer);
        this.showWindow();
    },
    removeForcedLayer : function (layer){
        this.layerSelector.removeForcedLayer(layer);
        this.layerSelector.initLayers();
    },
    loadWindow : function(){
        var config = {
            viewerController : this.viewerController,
            div: this.getContentDiv(),
            layers : this.layers
        };
        this.layerSelector = Ext.create("viewer.components.LayerSelector",config);
        this.layerSelector.addListener(viewer.viewercontroller.controller.Event.ON_LAYERSELECTOR_CHANGE,this.layerChanged,this);

        // Make the tabs
        this.tabpanel = Ext.create('Ext.tab.Panel', {
            height: parseInt(this.details.height) - 100,
            width: parseInt(this.details.width)-40,
            hideMode: 'offsets',
            autoScroll:true,
            layout: {
                type: 'fit'
            },
            tabBar:{
                style: 'background:#fff;'
            },
            items: [
            {
                id   : 'filterTab',
                title: 'Filter',// TODO Do renaming of variables dataselection-->filter
                hideMode: 'offsets',
                autoScroll:true,
                html: "

"
            },
            {
                id : "dataTab",
                title: 'Dataselectie',// TODO Do renaming of variables filter->dataselection
                hideMode: 'offsets',
                autoScroll:true,
                html: "

"
            }
            ],
            activeTab : "dataTab",
            renderTo : this.getContentDiv()
        });

        // Make panels in tabs and save them
        this.dataTab = Ext.create('Ext.panel.Panel', {
            autoScroll: true,
            renderTo: 'dataTabDiv'
        });

        this.filterTab = Ext.create('Ext.panel.Panel', {
            autoScroll: true,
            renderTo: 'filterTabDiv'
        });

        // Add the button to the dataselectiontab
        var addFilter = Ext.create('Ext.Button', {
            text : 'Voeg filter toe',
            listeners: {
                click:{
                    scope: this,
                    fn: this.addFilter
                }
            }
        });
        this.filterTab.add(addFilter);
        this.filterActive = Ext.create ("Ext.form.field.Checkbox",{
            boxLabel  : 'Filter is actief',
            name      : 'filterActive',
            inputValue: true,
            checked   : false

        });
        this.filterTab.add(this.filterActive);
        // Add the first filter
        this.addFilter();
        // Make lower buttons
        this.button1 = Ext.create('Ext.Button', {
            text : 'Toepassen',
            renderTo: this.getContentDiv(),
            listeners: {
                click:{
                    scope: this,
                    fn: this.applyFilter
                }
            }
        });

        this.button2 = Ext.create('Ext.Button', {
            text : 'Annuleren',
            renderTo: this.getContentDiv(),
            listeners: {
                click:{
                    scope: this,
                    fn: this.cancel
                }
            }
        });
    },
    createDataTab : function (appLayer){
        var attributes = appLayer.attributes;
        var dataSelectieAttributes = new Array();
        for(var i= 0 ; i < attributes.length ;i++){
            var attribute = attributes[i];
            if(attribute.selectable){
                var defaultVal = "";
                if(attribute.defaultValue != undefined){
                    defaultVal = attribute.defaultValue;
                }
                dataSelectieAttributes.push({
                    xtype: "textfield",
                    id: attribute.name,
                    dataType: attribute.type,
                    name: attribute.name,
                    fieldLabel: attribute.alias || attribute.name,
                    labelWidth:200,
                    value : defaultVal
                });
            }
        }

        this.dataTab.removeAll();
        this.dataTab.add(dataSelectieAttributes);
    },
    /**
     *  Add a filter to the current filterlist.
     */
    addFilter : function (){
        var logicOperator = null;
        if(this.filters.length != 0){
            var logicStore = Ext.create('Ext.data.Store', {
                fields: ['id','title'],
                data : [{
                    id:"OR",
                    title:"of"
                }, {
                    id:"AND",
                    title:"en"
                }]
            });

            logicOperator = Ext.create('Ext.form.ComboBox', {
                fieldLabel: '',
                store: logicStore,
                queryMode: 'local',
                displayField: 'title',
                width:50,
                value:'OR',
                valueField: 'id'
            });
            // Insert before the checkbox
            this.filterTab.insert(this.filterTab.items.length - 1,logicOperator);

        }
        var config = {
            width: parseInt(this.details.width),
            height: parseInt(this.details.height),
            attributes:this.attributes,
            logicOperator:logicOperator
        };
        var filter = Ext.create("viewer.components.Filter",config);
        this.filters.push(filter);
        // Insert before the checkbox
        this.filterTab.insert(this.filterTab.items.length - 1,filter.getUI());
    },
    applyFilter : function (){
        var cql = "";

        cql += this.getDataTabCQL();
        if(this.filterActive.getValue()){
            if(cql != ""){
                cql += " AND ";
            }
            for ( var i = 0 ; i < this.filters.length;i++){
                var filter = this.filters[i];
                cql += filter.getCQL();
            }

        }
        if(cql != ""){
            cql = "(" + cql + ")";
        }
        var layerObj = this.layerSelector.getValue();
        var layer = this.viewerController.getApplayer(layerObj.serviceId, layerObj.name);

        var filterWrapper =  Ext.create("viewer.components.CQLFilterWrapper",{
            id: this.name + this.layerSelector.getValue(),
            cql: cql,
            operator : "AND"
        });

        this.viewerController.setFilter(filterWrapper,layer);

    //console.log("CQL: " + layer.filter.getCQL());
    },
    getDataTabCQL : function (){
        var items = this.dataTab.items.items;
        var cql = "";
        for ( var i = 0 ; i < items.length;i++){
            var item = items[i];
            if(item.getValue() != ""){
                if(i != 0 ){
                    cql += " AND ";
                }
                cql += "\"" +item.id + "\"=";
                var attributeType = item.dataType;
                if(attributeType && attributeType.toLowerCase() == "string"){
                    cql += "\'";
                }
                cql += item.getValue();
                if(attributeType && attributeType.toLowerCase() == "string"){
                    cql += "\'";
                }
            }
        }
        return cql;
    },
    cancel : function (){
        this.popup.hide();
    },
    /**
     *  Reset all comboboxes when a different layer is selected
     */
    layerChanged : function (item,prev){
        this.appLayer = this.viewerController.getApplayer (item.serviceId,item.name);

        if(this.appLayer != null){
            this.featureService = this.viewerController.getAppLayerFeatureService(this.appLayer);
            var me = this;
            // check if featuretype was loaded
            if(this.appLayer.attributes == undefined) {
                this.featureService.loadAttributes(me.appLayer, function(attributes) {
                    me.changeAttributes(me.appLayer);
                });
            } else {
                this.changeAttributes(me.appLayer);
            }
        }

        if(prev != undefined){
            var prevLayer = this.viewerController.getLayer(this.appLayer);
            prevLayer.setQuery(null);
        }
    },
    // Change the comboboxes of the attributefilters. Happens when a new layer is chosen.
    changeAttributes : function (appLayer){
        var attributes = appLayer.attributes;
        var attributeList = new Array();
        for(var i= 0 ; i < attributes.length ;i++){
            var attribute = attributes[i];
            if(attribute.filterable){
                attributeList.push({
                    id: attribute.id,
                    title: attribute.alias || attribute.name,
                    value: attribute.name,
                    type: attribute.type
                });
            }
        }
        this.attributes = attributeList;
        for (var j = 0 ; j < this.filters.length ;j++){
            var filter = this.filters[j];
            filter.setNewAttributeList(attributeList);
        }
        this.createDataTab(appLayer);
    },
    getExtComponents: function() {
        return Ext.Array.merge(
            this.layerSelector.getExtComponents(),
            [
            this.tabpanel.getId(),
            this.dataTab.getId(),
            this.filterTab.getId(),
            this.button1.getId(),
            this.button2.getId()
            ]
            );
    }
});
<|endoftext|>


<commit_before>// @flow
/*
 *
 * Marionette-based Backbone.Form editor. MUST NOT be used directly. Use EditorBase*View base views instead while implementing Marionette editors.
 *
 * */

import formRepository from '../../formRepository';
import { keyCode } from 'utils';

const classes = {
    disabled: 'editor_disabled',
    readonly: 'editor_readonly',
    hidden: 'editor_hidden',
    FOCUSED: 'editor_focused',
    EMPTY: 'editor_empty',
    ERROR: 'js-editor_error'
};

const onRender = function() {
    if (this.id) {
        this.el.setAttribute('id', this.id);
    }
    this.setPermissions(this.enabled, this.readonly);
    this.setHidden(this.hidden);
    this.setValue(this.value);
    if (this.focusElement) {
        this.$el.on('focus', this.focusElement, this.onFocus);
        this.$el.on('blur', this.focusElement, this.onBlur);
        this.$el.on('keyup', this.focusElement, this.onKeyup);
    } else if (this.focusElement !== null) {
        this.$el.on('focus', this.onFocus);
        this.$el.on('blur', this.onBlur);
        this.$el.on('keyup', this.onKeyup);
    }
    this.__updateEmpty();

    // revalidate if model isInvalid
    if (this.model && this.model.validationResult) {
        if (this.field) {
            this.field.validate();
        } else {
            this.validate();
        }
    }
};

const onChange = function() {
    if (this.model && this.schema.autocommit) {
        this.commit();
    }
    if (this.__validatedOnce) {
        if (this.form) {
            this.form.validate();
        } else if (this.field) {
            this.field.validate();
        }
    }
    this.__updateEmpty();
};

/**
 * @name BaseEditorView
 * @memberof module:core.form.editors.base
 * @class Base class for all editors in the library.
 * While implementing editors, inherit from one of the following classes which in turn are inherited from this one:

 *
BaseCollectionEditorView

 *
BaseCompositeEditorView

 *
BaseEditorView

 * Possible events:

 *
'change' (thisEditorView) - fires when the value inside the editor is changed.
 * This event doesn't imply any change in model (!).

 *
'enabled' (enabled) - fires when the property enabled is changed.

 *
'readonly' (readonly) - fires when the property readonly is changed.

 *
'<key>:committed' (thisEditorView, model, value) - fires when the value is committed into the model.

 *
'value:committed' (thisEditorView, model, key, value) - fires when the value is committed into the model.

 *

 * @constructor
 * @extends Marionette.View
 * @param {Object} options Options object.
 * @param {Boolean} [options.autocommit=false] Indicates whether to call commit() method on the value change.
 * @param {Boolean} [options.forceCommit=false] Indicated whether to commit the value into the model if editors validation has failed (not recommended).
 * @param {Boolean} [options.enabled=true] The initial value of enabled flag. Can be changed later on by calling setEnabled() method.
 * @param {Boolean} [options.readonly=false] The initial value of readonly flag.
 *                                           Can be changed later on by calling setReadonly() method.
 * @param {Boolean} [options.model] A model which contains an attributes edited by this editor.
 *                                  Can only be used if the editor is created standalone (without a form).
 *                                   Must be used together with  key options.
 * @param {Boolean} [options.key] The name of an attribute in options.model edited by this editor.
 *                                Can only be used if the editor is created standalone (without a form).
 *                                Must be used together with  model options.
 * @param {Function[]} [options.validators] An array of validator function which look like the following:
 * function(value, formValues) -> ({type, message}|undefined)
 * @param {Object} [options.schema] When created implicitly by form, all the editor options are passed through this option.
 * Не использовать явно.
 * */

export default {
    create(viewClass: Marionette.View | Marionette.CollectionView | Marionette.CompositeView) {
        return {
            classes: {
                disabled: 'editor_disabled',
                readonly: 'editor_readonly',
                hidden: 'editor_hidden',
                FOCUSED: 'editor_focused',
                EMPTY: 'editor_empty'
            },

            hasFocus: false,

            constructor(options: Object = {}) {
                _.bindAll(this, 'onFocus', 'onBlur', 'checkChange', 'onKeyup');

                //Set initial value
                if (options.model) {
                    this.model = options.model;
                    this.value = this.model.get(options.key);
                } else if (options.value !== undefined) {
                    this.value = options.value;
                }

                if (this.value === undefined) {
                    this.value = null;
                }

                this.key = options.key;
                this.form = options.form;
                this.field = options.field;

                const schema = (this.schema = options.schema || {});

                this.validators = options.validators || schema.validators;
                this.__validatedOnce = false;

                this.on('render', onRender.bind(this));
                this.on('change', onChange.bind(this));
                this.setValue = _.wrap(this.setValue, (fn, ...rest) => {
                    fn.call(this, ...rest);
                    if (this.$el) {
                        this.__updateEmpty();
                    }
                });

                // ToDo fix: if schema autocommit false take undefined value from options.autocommit
                schema.autocommit = schema.autocommit || options.autocommit;

                this.enabled = schema.enabled = schema.enabled || options.enabled || (schema.enabled === undefined && options.enabled === undefined);
                this.readonly = schema.readonly = schema.readonly || options.readonly || (schema.readonly !== undefined && options.readonly !== undefined);
                this.hidden = schema.hidden = schema.hidden || options.hidden || (schema.hidden !== undefined && options.hidden !== undefined);
                schema.forceCommit = options.forceCommit || schema.forceCommit;

                viewClass.prototype.constructor.apply(this, arguments);
                if (this.model) {
                    this.listenTo(this.model, `change:${this.key}`, this.updateValue);
                    this.listenTo(this.model, 'sync', this.updateValue);
                    this.listenTo(this.model, 'validate:force', (e = {}) => {
                        if (this.field) {
                            e.validationResult = this.field.validate();
                        } else {
                            e.validationResult = this.validate();
                        }
                    });
                }
            },

            __updateEmpty() {
                const isEmpty = this.isEmptyValue();
                if (this.options.fieldUpdateEmpty) {
                    this.options.fieldUpdateEmpty(isEmpty);
                }
                this.$el.toggleClass(classes.EMPTY, isEmpty);
            },

            /**
             * Manually updated editor's internal value with the value from this.model.get(this.key).
             * Shouldn't be called normally. The method is called internally on model's change event.
             * */
            updateValue() {
                this.setValue(this.getModelValue());
            },

            /**
             * Retrieves actual value of the bound attribute from the model.
             * @return {*}
             * */
            getModelValue() {
                return !this.model ? undefined : this.model.get(this.key);
            },

            __getFocusElement() {
                if (this.focusElement) {
                    return this.$el.find(this.focusElement);
                }
                return this.$el;
            },

            __triggerChange(...args: Array<any>) {
                this.trigger('change', this, ...args);
            },

            /**
             * Returns internal editor's value.
             * @return {*}
             */
            getValue() {
                return this.value;
            },

            /**
             * Sets new internal editor's value.
             * @param {*} value The new value.
             */
            setValue(value: Number | String) {
                this.value = value;
            },

            setPermissions(enabled: Boolean, readonly: Boolean) {
                this.__setEnabled(enabled);
                this.__setReadonly(readonly);
            },

            /**
             * Sets a new value of enabled flag. While disabled, the editor's value cannot be changed or copied by the user.
             * It's implied that the value doesn't make sense.
             * @param {Boolean} enabled New flag value.
             */
            setEnabled(enabled: Boolean) {
                const readonly = this.getReadonly();
                this.setPermissions(enabled, readonly);
            },

            /**
             * Sets a new value of readonly flag. While readonly, the editor's value cannot be changed but can be copied by the user.
             * @param {Boolean} readonly New flag value.
             */
            setReadonly(readonly: Boolean) {
                const enabled = this.getEnabled();
                this.setPermissions(enabled, readonly);
            },

            /**
             * Sets a new value of hidden flag.
             * @param {Boolean} hidden New flag value.
             */
            setHidden(hidden: Boolean) {
                this.hidden = hidden;
                this.$el.toggleClass(classes.hidden, hidden);
            },

            __setEnabled(enabled: Boolean) {
                this.enabled = enabled;
                this.trigger('enabled', enabled);
                if (!this.enabled) {
                    this.$el.addClass(classes.disabled);
                } else {
                    this.$el.removeClass(classes.disabled);
                }
            },

            /**
             * Returns the value of `enabled` flag.
             * @return {Boolean}
             */
            getEnabled() {
                return this.enabled;
            },

            /**
             * Returns the value of `enabled` flag.
             * @return {Boolean}
             */
            getHidden() {
                return this.hidden;
            },

            __setReadonly(readonly: Boolean) {
                this.readonly = readonly;
                this.trigger('readonly', readonly);
                if (this.readonly && this.getEnabled()) {
                    this.$el.addClass(classes.readonly);
                } else {
                    this.$el.removeClass(classes.readonly);
                }
            },

            /**
             * Returns the value of `readonly` flag.
             * @return {Boolean}
             */
            getReadonly() {
                return this.readonly;
            },

            /**
             * Sets the focus onto this editor.
             */
            focus() {
                this.__getFocusElement().focus();
                this.hasFocus = true;
            },

            /**
             * Clears the focus.
             */
            blur() {
                this.__getFocusElement().blur();
                this.hasFocus = false;
            },

            /**
             * Update the model with the internal editor's value.
             * @param {Object} [options] Options to pass to model.set().
             * @param {Boolean} [options.validate] Set to true to trigger built-in model validation.
             * @return {Object|undefined} Returns an error object { type, message } if validation fails
             * and options.forceCommit is turned off. undefined otherwise.
             */
            commit(options: Object = {}) {
                let error = this.validate(true);
                if (error && !this.schema.forceCommit) {
                    return error;
                }

                this.listenToOnce(this.model, 'invalid', (model, e) => {
                    error = e;
                });

                this.model.set(this.key, this.getValue(), {
                    silent: false,
                    validate: options.validate === true
                });

                if (error && !this.schema.forceCommit) {
                    return error;
                }
                this.trigger(`${this.key}:committed`, this, this.model, this.getValue());
                this.trigger('value:committed', this, this.model, this.key, this.getValue());
            },
            isEmptyValue() {
                return !this.getValue();
            },

            /**
             * Check validity with built-in validator functions (initially passed into constructor options).
             * @return {Object|undefined} Returns an error object { type, message } if validation fails. undefined otherwise.
             */
            validate(internal: Boolean) {
                let error = null;
                const value = this.getValue();
                const formValues = this.form ? this.form.getValue() : {};
                const validators = this.validators;
                const getValidator = this.getValidator;

                if (!internal) {
                    this.__validatedOnce = true;
                }

                if (validators) {
                    //Run through validators until an error is found
                    validators.every(validator => {
                        error = getValidator(validator)(value, formValues);
                        return !error;
                    });
                }

                if (this.isRendered() && !this.isDestroyed()) {
                    this.$el.toggleClass(classes.ERROR, !!error);
                }

                return error;
            },

            trigger(event: 'focus' | 'blur') {
                if (event === 'focus') {
                    this.hasFocus = true;
                } else if (event === 'blur') {
                    this.hasFocus = false;
                }

                return Marionette.View.prototype.trigger.apply(this, arguments);
            },

            getValidator(validator: string | Function) {
                const validators = formRepository.validators;

                //Convert regular expressions to validators
                if (_.isRegExp(validator)) {
                    return validators.regexp({ regexp: validator });
                }

                //Use a built-in validator if given a string
                if (typeof validator === 'string') {
                    if (!validators[validator]) {
                        throw new Error(`Validator "${validator}" not found`);
                    }

                    return validators[validator]();
                }

                //Functions can be used directly
                if (typeof validator === 'function') {
                    return validator;
                }

                //Use a customised built-in validator if given an object
                //noinspection JSUnresolvedVariable
                if (_.isObject(validator) && validator.type) {
                    const config = validator;

                    //noinspection JSUnresolvedVariable
                    return validators[config.type](config);
                }

                //Unknown validator type
                throw new Error('Invalid validator');
            },

            checkChange() {
                if (this.model) {
                    if (this.value !== this.model.get(this.key)) {
                        this.__triggerChange();
                    }
                }
            },

            onKeyup(event) {
                if (event.keyCode === keyCode.ENTER) {
                    this.checkChange();
                }
            },

            onFocus() {
                this.$el.addClass(classes.FOCUSED);
                this.trigger('focus', this);
            },

            onBlur(options = {}) {
                if (options.triggerChange === undefined || options.triggerChange === true) {
                    this.checkChange();
                }
                this.$el.removeClass(classes.FOCUSED);
                this.trigger('blur', this);
            },

            renderIcons(...iconTemplates) {
                this.el.insertAdjacentHTML('beforeend', iconTemplates.join(' '));
            }
        };
    }
};
<commit_msg>[#187629] Editors: fix triggerChange onBlur if editor has no model.<commit_after>// @flow
/*
 *
 * Marionette-based Backbone.Form editor. MUST NOT be used directly. Use EditorBase*View base views instead while implementing Marionette editors.
 *
 * */

import formRepository from '../../formRepository';
import { keyCode } from 'utils';

const classes = {
    disabled: 'editor_disabled',
    readonly: 'editor_readonly',
    hidden: 'editor_hidden',
    FOCUSED: 'editor_focused',
    EMPTY: 'editor_empty',
    ERROR: 'js-editor_error'
};

const onRender = function() {
    if (this.id) {
        this.el.setAttribute('id', this.id);
    }
    this.setPermissions(this.enabled, this.readonly);
    this.setHidden(this.hidden);
    this.setValue(this.value);
    if (this.focusElement) {
        this.$el.on('focus', this.focusElement, this.onFocus);
        this.$el.on('blur', this.focusElement, this.onBlur);
        this.$el.on('keyup', this.focusElement, this.onKeyup);
    } else if (this.focusElement !== null) {
        this.$el.on('focus', this.onFocus);
        this.$el.on('blur', this.onBlur);
        this.$el.on('keyup', this.onKeyup);
    }
    this.__updateEmpty();

    // revalidate if model isInvalid
    if (this.model && this.model.validationResult) {
        if (this.field) {
            this.field.validate();
        } else {
            this.validate();
        }
    }
};

const onChange = function() {
    this.__previousValue = this.value;
    if (this.model && this.schema.autocommit) {
        this.commit();
    }
    if (this.__validatedOnce) {
        if (this.form) {
            this.form.validate();
        } else if (this.field) {
            this.field.validate();
        }
    }
    this.__updateEmpty();
};

/**
 * @name BaseEditorView
 * @memberof module:core.form.editors.base
 * @class Base class for all editors in the library.
 * While implementing editors, inherit from one of the following classes which in turn are inherited from this one:

 *
BaseCollectionEditorView

 *
BaseCompositeEditorView

 *
BaseEditorView

 * Possible events:

 *
'change' (thisEditorView) - fires when the value inside the editor is changed.
 * This event doesn't imply any change in model (!).

 *
'enabled' (enabled) - fires when the property enabled is changed.

 *
'readonly' (readonly) - fires when the property readonly is changed.

 *
'<key>:committed' (thisEditorView, model, value) - fires when the value is committed into the model.

 *
'value:committed' (thisEditorView, model, key, value) - fires when the value is committed into the model.

 *

 * @constructor
 * @extends Marionette.View
 * @param {Object} options Options object.
 * @param {Boolean} [options.autocommit=false] Indicates whether to call commit() method on the value change.
 * @param {Boolean} [options.forceCommit=false] Indicated whether to commit the value into the model if editors validation has failed (not recommended).
 * @param {Boolean} [options.enabled=true] The initial value of enabled flag. Can be changed later on by calling setEnabled() method.
 * @param {Boolean} [options.readonly=false] The initial value of readonly flag.
 *                                           Can be changed later on by calling setReadonly() method.
 * @param {Boolean} [options.model] A model which contains an attributes edited by this editor.
 *                                  Can only be used if the editor is created standalone (without a form).
 *                                   Must be used together with  key options.
 * @param {Boolean} [options.key] The name of an attribute in options.model edited by this editor.
 *                                Can only be used if the editor is created standalone (without a form).
 *                                Must be used together with  model options.
 * @param {Function[]} [options.validators] An array of validator function which look like the following:
 * function(value, formValues) -> ({type, message}|undefined)
 * @param {Object} [options.schema] When created implicitly by form, all the editor options are passed through this option.
 * Не использовать явно.
 * */

export default {
    create(viewClass: Marionette.View | Marionette.CollectionView | Marionette.CompositeView) {
        return {
            classes: {
                disabled: 'editor_disabled',
                readonly: 'editor_readonly',
                hidden: 'editor_hidden',
                FOCUSED: 'editor_focused',
                EMPTY: 'editor_empty'
            },

            hasFocus: false,

            constructor(options: Object = {}) {
                _.bindAll(this, 'onFocus', 'onBlur', 'checkChange', 'onKeyup');

                //Set initial value
                if (options.model) {
                    this.model = options.model;
                    this.value = this.model.get(options.key);
                } else if (options.value !== undefined) {
                    this.value = options.value;
                }

                if (this.value === undefined) {
                    this.value = null;
                }

                this.__previousValue = this.value;

                this.key = options.key;
                this.form = options.form;
                this.field = options.field;

                const schema = (this.schema = options.schema || {});

                this.validators = options.validators || schema.validators;
                this.__validatedOnce = false;

                this.on('render', onRender.bind(this));
                this.on('change', onChange.bind(this));
                this.setValue = _.wrap(this.setValue, (fn, ...rest) => {
                    fn.call(this, ...rest);
                    if (this.$el) {
                        this.__updateEmpty();
                    }
                    this.__previousValue = this.value;
                });

                // ToDo fix: if schema autocommit false take undefined value from options.autocommit
                schema.autocommit = schema.autocommit || options.autocommit;

                this.enabled = schema.enabled = schema.enabled || options.enabled || (schema.enabled === undefined && options.enabled === undefined);
                this.readonly = schema.readonly = schema.readonly || options.readonly || (schema.readonly !== undefined && options.readonly !== undefined);
                this.hidden = schema.hidden = schema.hidden || options.hidden || (schema.hidden !== undefined && options.hidden !== undefined);
                schema.forceCommit = options.forceCommit || schema.forceCommit;

                viewClass.prototype.constructor.apply(this, arguments);
                if (this.model) {
                    this.listenTo(this.model, `change:${this.key}`, this.updateValue);
                    this.listenTo(this.model, 'sync', this.updateValue);
                    this.listenTo(this.model, 'validate:force', (e = {}) => {
                        if (this.field) {
                            e.validationResult = this.field.validate();
                        } else {
                            e.validationResult = this.validate();
                        }
                    });
                }
            },

            __updateEmpty() {
                const isEmpty = this.isEmptyValue();
                if (this.options.fieldUpdateEmpty) {
                    this.options.fieldUpdateEmpty(isEmpty);
                }
                this.$el.toggleClass(classes.EMPTY, isEmpty);
            },

            /**
             * Manually updated editor's internal value with the value from this.model.get(this.key).
             * Shouldn't be called normally. The method is called internally on model's change event.
             * */
            updateValue() {
                this.setValue(this.getModelValue());
            },

            /**
             * Retrieves actual value of the bound attribute from the model.
             * @return {*}
             * */
            getModelValue() {
                return !this.model ? undefined : this.model.get(this.key);
            },

            __getFocusElement() {
                if (this.focusElement) {
                    return this.$el.find(this.focusElement);
                }
                return this.$el;
            },

            __triggerChange(...args: Array<any>) {
                this.trigger('change', this, ...args);
            },

            /**
             * Returns internal editor's value.
             * @return {*}
             */
            getValue() {
                return this.value;
            },

            /**
             * Sets new internal editor's value.
             * @param {*} value The new value.
             */
            setValue(value: Number | String) {
                this.value = value;
            },

            setPermissions(enabled: Boolean, readonly: Boolean) {
                this.__setEnabled(enabled);
                this.__setReadonly(readonly);
            },

            /**
             * Sets a new value of enabled flag. While disabled, the editor's value cannot be changed or copied by the user.
             * It's implied that the value doesn't make sense.
             * @param {Boolean} enabled New flag value.
             */
            setEnabled(enabled: Boolean) {
                const readonly = this.getReadonly();
                this.setPermissions(enabled, readonly);
            },

            /**
             * Sets a new value of readonly flag. While readonly, the editor's value cannot be changed but can be copied by the user.
             * @param {Boolean} readonly New flag value.
             */
            setReadonly(readonly: Boolean) {
                const enabled = this.getEnabled();
                this.setPermissions(enabled, readonly);
            },

            /**
             * Sets a new value of hidden flag.
             * @param {Boolean} hidden New flag value.
             */
            setHidden(hidden: Boolean) {
                this.hidden = hidden;
                this.$el.toggleClass(classes.hidden, hidden);
            },

            __setEnabled(enabled: Boolean) {
                this.enabled = enabled;
                this.trigger('enabled', enabled);
                if (!this.enabled) {
                    this.$el.addClass(classes.disabled);
                } else {
                    this.$el.removeClass(classes.disabled);
                }
            },

            /**
             * Returns the value of `enabled` flag.
             * @return {Boolean}
             */
            getEnabled() {
                return this.enabled;
            },

            /**
             * Returns the value of `enabled` flag.
             * @return {Boolean}
             */
            getHidden() {
                return this.hidden;
            },

            __setReadonly(readonly: Boolean) {
                this.readonly = readonly;
                this.trigger('readonly', readonly);
                if (this.readonly && this.getEnabled()) {
                    this.$el.addClass(classes.readonly);
                } else {
                    this.$el.removeClass(classes.readonly);
                }
            },

            /**
             * Returns the value of `readonly` flag.
             * @return {Boolean}
             */
            getReadonly() {
                return this.readonly;
            },

            /**
             * Sets the focus onto this editor.
             */
            focus() {
                this.__getFocusElement().focus();
                this.hasFocus = true;
            },

            /**
             * Clears the focus.
             */
            blur() {
                this.__getFocusElement().blur();
                this.hasFocus = false;
            },

            /**
             * Update the model with the internal editor's value.
             * @param {Object} [options] Options to pass to model.set().
             * @param {Boolean} [options.validate] Set to true to trigger built-in model validation.
             * @return {Object|undefined} Returns an error object { type, message } if validation fails
             * and options.forceCommit is turned off. undefined otherwise.
             */
            commit(options: Object = {}) {
                let error = this.validate(true);
                if (error && !this.schema.forceCommit) {
                    return error;
                }

                this.listenToOnce(this.model, 'invalid', (model, e) => {
                    error = e;
                });

                this.model.set(this.key, this.getValue(), {
                    silent: false,
                    validate: options.validate === true
                });

                if (error && !this.schema.forceCommit) {
                    return error;
                }
                this.trigger(`${this.key}:committed`, this, this.model, this.getValue());
                this.trigger('value:committed', this, this.model, this.key, this.getValue());
            },
            isEmptyValue() {
                return !this.getValue();
            },

            /**
             * Check validity with built-in validator functions (initially passed into constructor options).
             * @return {Object|undefined} Returns an error object { type, message } if validation fails. undefined otherwise.
             */
            validate(internal: Boolean) {
                let error = null;
                const value = this.getValue();
                const formValues = this.form ? this.form.getValue() : {};
                const validators = this.validators;
                const getValidator = this.getValidator;

                if (!internal) {
                    this.__validatedOnce = true;
                }

                if (validators) {
                    //Run through validators until an error is found
                    validators.every(validator => {
                        error = getValidator(validator)(value, formValues);
                        return !error;
                    });
                }

                if (this.isRendered() && !this.isDestroyed()) {
                    this.$el.toggleClass(classes.ERROR, !!error);
                }

                return error;
            },

            trigger(event: 'focus' | 'blur') {
                if (event === 'focus') {
                    this.hasFocus = true;
                } else if (event === 'blur') {
                    this.hasFocus = false;
                }

                return Marionette.View.prototype.trigger.apply(this, arguments);
            },

            getValidator(validator: string | Function) {
                const validators = formRepository.validators;

                //Convert regular expressions to validators
                if (_.isRegExp(validator)) {
                    return validators.regexp({ regexp: validator });
                }

                //Use a built-in validator if given a string
                if (typeof validator === 'string') {
                    if (!validators[validator]) {
                        throw new Error(`Validator "${validator}" not found`);
                    }

                    return validators[validator]();
                }

                //Functions can be used directly
                if (typeof validator === 'function') {
                    return validator;
                }

                //Use a customised built-in validator if given an object
                //noinspection JSUnresolvedVariable
                if (_.isObject(validator) && validator.type) {
                    const config = validator;

                    //noinspection JSUnresolvedVariable
                    return validators[config.type](config);
                }

                //Unknown validator type
                throw new Error('Invalid validator');
            },

            checkChange() {
                if (this.__previousValue !== this.value) {
                    this.__triggerChange();
                }
            },

            onKeyup(event) {
                if (event.keyCode === keyCode.ENTER) {
                    this.checkChange();
                }
            },

            onFocus() {
                this.$el.addClass(classes.FOCUSED);
                this.trigger('focus', this);
            },

            onBlur({ triggerChange = true } = {}) {
                if (triggerChange) {
                    this.checkChange();
                }
                this.$el.removeClass(classes.FOCUSED);
                this.trigger('blur', this);
            },

            renderIcons(...iconTemplates) {
                this.el.insertAdjacentHTML('beforeend', iconTemplates.join(' '));
            }
        };
    }
};
<|endoftext|>