<commit_before>/**
 * SVG-edit Utilities
 *
 * Licensed under the Apache License, Version 2
 *
 * Copyright(c) 2010 Alexis Deveria
 * Copyright(c) 2010 Jeff Schiller
 */

// Dependencies:
// 1) jQuery

(function() {

if (window.svgedit == undefined) {
	window.svgedit = {};
}

svgedit.Utilities = {
};

// String used to encode base64.
svgedit.Utilities._keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";

// Function: svgedit.Utilities.toXml
// Converts characters in a string to XML-friendly entities.
//
// Example: "&" becomes "&"
//
// Parameters:
// str - The string to be converted
//
// Returns:
// The converted string
svgedit.Utilities.toXml = function(str) {
	return $('<p/>').text(str).html();
};

// Function: svgedit.Utilities.fromXml
// Converts XML entities in a string to single characters.
// Example: "&" becomes "&"
//
// Parameters:
// str - The string to be converted
//
// Returns:
// The converted string
svgedit.Utilities.fromXml = function(str) {
	return $('<p/>').html(str).text();
};

// This code was written by Tyler Akins and has been placed in the
// public domain.  It would be nice if you left this header intact.
// Base64 code from Tyler Akins -- http://rumkin.com

// schiller: Removed string concatenation in favour of Array.join() optimization,
//           also precalculate the size of the array needed.

// Function: Utils.encode64
// Converts a string to base64
svgedit.Utilities.encode64 = function(input) {
	// base64 strings are 4/3 larger than the original string
//	input = Utils.encodeUTF8(input); // convert non-ASCII characters
	input = Utils.convertToXMLReferences(input);
	if(window.btoa) return window.btoa(input); // Use native if available
	var _keyStr = svgedit.Utilities._keyStr;
	var output = new Array( Math.floor( (input.length + 2) / 3 ) * 4 );
	var chr1, chr2, chr3;
	var enc1, enc2, enc3, enc4;
	var i = 0, p = 0;

	do {
		chr1 = input.charCodeAt(i++);
		chr2 = input.charCodeAt(i++);
		chr3 = input.charCodeAt(i++);

		enc1 = chr1 >> 2;
		enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
		enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
		enc4 = chr3 & 63;

		if (isNaN(chr2)) {
			enc3 = enc4 = 64;
		} else if (isNaN(chr3)) {
			enc4 = 64;
		}

		output[p++] = _keyStr.charAt(enc1);
		output[p++] = _keyStr.charAt(enc2);
		output[p++] = _keyStr.charAt(enc3);
		output[p++] = _keyStr.charAt(enc4);
	} while (i < input.length);

	return output.join('');
};

// Function: Utils.decode64
// Converts a string from base64
svgedit.Utilities.decode64 = function(input) {
	if(window.atob) return window.atob(input);
	var output = "";
	var chr1, chr2, chr3 = "";
	var enc1, enc2, enc3, enc4 = "";
	var i = 0;

	 // remove all characters that are not A-Z, a-z, 0-9, +, /, or =
	 input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");

	 do {
		enc1 = _keyStr.indexOf(input.charAt(i++));
		enc2 = _keyStr.indexOf(input.charAt(i++));
		enc3 = _keyStr.indexOf(input.charAt(i++));
		enc4 = _keyStr.indexOf(input.charAt(i++));

		chr1 = (enc1 << 2) | (enc2 >> 4);
		chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
		chr3 = ((enc3 & 3) << 6) | enc4;

		output = output + String.fromCharCode(chr1);

		if (enc3 != 64) {
		   output = output + String.fromCharCode(chr2);
		}
		if (enc4 != 64) {
		   output = output + String.fromCharCode(chr3);
		}

		chr1 = chr2 = chr3 = "";
		enc1 = enc2 = enc3 = enc4 = "";

	 } while (i < input.length);
	 return unescape(output);
};

// Currently not being used, so commented out for now
// based on http://phpjs.org/functions/utf8_encode:577
// codedread:does not seem to work with webkit-based browsers on OSX
// 		"encodeUTF8": function(input) {
// 			//return unescape(encodeURIComponent(input)); //may or may not work
// 			var output = '';
// 			for (var n = 0; n < input.length; n++){
// 				var c = input.charCodeAt(n);
// 				if (c < 128) {
// 					output += input[n];
// 				}
// 				else if (c > 127) {
// 					if (c < 2048){
// 						output += String.fromCharCode((c >> 6) | 192);
// 					}
// 					else {
// 						output += String.fromCharCode((c >> 12) | 224) + String.fromCharCode((c >> 6) & 63 | 128);
// 					}
// 					output += String.fromCharCode((c & 63) | 128);
// 				}
// 			}
// 			return output;
// 		},

// Function: svgedit.Utilities.convertToXMLReferences
// Converts a string to use XML references
svgedit.Utilities.convertToXMLReferences = function(input) {
	var output = '';
	for (var n = 0; n < input.length; n++){
		var c = input.charCodeAt(n);
		if (c < 128) {
			output += input[n];
		}
		else if(c > 127) {
			output += ("&#" + c + ";");
		}
	}
	return output;
};

// Function: rectsIntersect
// Check if two rectangles (BBoxes objects) intersect each other
//
// Paramaters:
// r1 - The first BBox-like object
// r2 - The second BBox-like object
//
// Returns:
// Boolean that's true if rectangles intersect
svgedit.Utilities.rectsIntersect = function(r1, r2) {
	return r2.x < (r1.x+r1.width) &&
		(r2.x+r2.width) > r1.x &&
		r2.y < (r1.y+r1.height) &&
		(r2.y+r2.height) > r1.y;
};

// Function: snapToAngle
// Returns a 45 degree angle coordinate associated with the two given
// coordinates
//
// Parameters:
// x1 - First coordinate's x value
// x2 - Second coordinate's x value
// y1 - First coordinate's y value
// y2 - Second coordinate's y value
//
// Returns:
// Object with the following values:
// x - The angle-snapped x value
// y - The angle-snapped y value
// snapangle - The angle at which to snap
svgedit.Utilities.snapToAngle = function(x1,y1,x2,y2) {
	var snap = Math.PI/4; // 45 degrees
	var dx = x2 - x1;
	var dy = y2 - y1;
	var angle = Math.atan2(dy,dx);
	var dist = Math.sqrt(dx * dx + dy * dy);
	var snapangle= Math.round(angle/snap)*snap;
	var x = x1 + dist*Math.cos(snapangle);
	var y = y1 + dist*Math.sin(snapangle);
	//console.log(x1,y1,x2,y2,x,y,angle)
	return {x:x, y:y, a:snapangle};
},

// Function: text2xml
// Cross-browser compatible method of converting a string to an XML tree
// found this function here: http://groups.google.com/group/jquery-dev/browse_thread/thread/c6d11387c580a77f
svgedit.Utilities.text2xml = function(sXML) {
	if(sXML.indexOf('<svg:svg') >= 0) {
		sXML = sXML.replace(/<(\/?)svg:/g, '<$1').replace('xmlns:svg', 'xmlns');
	}

	var out;
	try{
		var dXML = (window.DOMParser)?new DOMParser():new ActiveXObject("Microsoft.XMLDOM");
		dXML.async = false;
	} catch(e){
		throw new Error("XML Parser could not be instantiated");
	};
	try{
		if(dXML.loadXML) out = (dXML.loadXML(sXML))?dXML:false;
		else out = dXML.parseFromString(sXML, "text/xml");
	}
	catch(e){ throw new Error("Error parsing XML string"); };
	return out;
};

// Function: bboxToObj
// Converts a SVGRect into an object.
//
// Parameters:
// bbox - a SVGRect
//
// Returns:
// An object with properties names x, y, width, height.
svgedit.Utilities.bboxToObj = function(bbox) {
	return {
		x: bbox.x,
		y: bbox.y,
		width: bbox.width,
		height: bbox.height
	}
};

// Function: walkTree
// Walks the tree and executes the callback on each element in a top-down fashion
//
// Parameters:
// elem - DOM element to traverse
// cbFn - Callback function to run on each element
svgedit.Utilities.walkTree = function(elem, cbFn){
	if (elem && elem.nodeType == 1) {
		cbFn(elem);
		var i = elem.childNodes.length;
		while (i--) {
			svgedit.Utilities.walkTree(elem.childNodes.item(i), cbFn);
		}
	}
};

// Function: walkTreePost
// Walks the tree and executes the callback on each element in a depth-first fashion
// TODO: FIXME: Shouldn't this be calling walkTreePost?
//
// Parameters:
// elem - DOM element to traverse
// cbFn - Callback function to run on each element
svgedit.Utilities.walkTreePost = function(elem, cbFn) {
	if (elem && elem.nodeType == 1) {
		var i = elem.childNodes.length;
		while (i--) {
			svgedit.Utilities.walkTree(elem.childNodes.item(i), cbFn);
		}
		cbFn(elem);
	}
};

// Function: getUrlFromAttr
// Extracts the URL from the url(...) syntax of some attributes.
// Three variants:
// 	* <circle fill="url(someFile.svg#foo)" />
//  * <circle fill="url('someFile.svg#foo')" />
//  * <circle fill='url("someFile.svg#foo")' />
//
// Parameters:
// attrVal - The attribute value as a string
//
// Returns:
// String with just the URL, like someFile.svg#foo
svgedit.Utilities.getUrlFromAttr = function(attrVal) {
	if (attrVal) {
		// url("#somegrad")
		if (attrVal.indexOf('url("') === 0) {
			return attrVal.substring(5,attrVal.indexOf('"',6));
		}
		// url('#somegrad')
		else if (attrVal.indexOf("url('") === 0) {
			return attrVal.substring(5,attrVal.indexOf("'",6));
		}
		else if (attrVal.indexOf("url(") === 0) {
			return attrVal.substring(4,attrVal.indexOf(')'));
		}
	}
	return null;
};

})();<commit_msg>Fix missing conversion from Utils. to svgedit.Utilities.<commit_after>/**
 * SVG-edit Utilities
 *
 * Licensed under the Apache License, Version 2
 *
 * Copyright(c) 2010 Alexis Deveria
 * Copyright(c) 2010 Jeff Schiller
 */

// Dependencies:
// 1) jQuery

(function() {

if (window.svgedit == undefined) {
	window.svgedit = {};
}

svgedit.Utilities = {
};

// String used to encode base64.
svgedit.Utilities._keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";

// Function: svgedit.Utilities.toXml
// Converts characters in a string to XML-friendly entities.
//
// Example: "&" becomes "&"
//
// Parameters:
// str - The string to be converted
//
// Returns:
// The converted string
svgedit.Utilities.toXml = function(str) {
	return $('<p/>').text(str).html();
};

// Function: svgedit.Utilities.fromXml
// Converts XML entities in a string to single characters.
// Example: "&" becomes "&"
//
// Parameters:
// str - The string to be converted
//
// Returns:
// The converted string
svgedit.Utilities.fromXml = function(str) {
	return $('<p/>').html(str).text();
};

// This code was written by Tyler Akins and has been placed in the
// public domain.  It would be nice if you left this header intact.
// Base64 code from Tyler Akins -- http://rumkin.com

// schiller: Removed string concatenation in favour of Array.join() optimization,
//           also precalculate the size of the array needed.

// Function: svgedit.Utilities.encode64
// Converts a string to base64
svgedit.Utilities.encode64 = function(input) {
	// base64 strings are 4/3 larger than the original string
//	input = svgedit.Utilities.encodeUTF8(input); // convert non-ASCII characters
	input = svgedit.Utilities.convertToXMLReferences(input);
	if(window.btoa) return window.btoa(input); // Use native if available
	var _keyStr = svgedit.Utilities._keyStr;
	var output = new Array( Math.floor( (input.length + 2) / 3 ) * 4 );
	var chr1, chr2, chr3;
	var enc1, enc2, enc3, enc4;
	var i = 0, p = 0;

	do {
		chr1 = input.charCodeAt(i++);
		chr2 = input.charCodeAt(i++);
		chr3 = input.charCodeAt(i++);

		enc1 = chr1 >> 2;
		enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
		enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
		enc4 = chr3 & 63;

		if (isNaN(chr2)) {
			enc3 = enc4 = 64;
		} else if (isNaN(chr3)) {
			enc4 = 64;
		}

		output[p++] = _keyStr.charAt(enc1);
		output[p++] = _keyStr.charAt(enc2);
		output[p++] = _keyStr.charAt(enc3);
		output[p++] = _keyStr.charAt(enc4);
	} while (i < input.length);

	return output.join('');
};

// Function: svgedit.Utilities.decode64
// Converts a string from base64
svgedit.Utilities.decode64 = function(input) {
	if(window.atob) return window.atob(input);
	var output = "";
	var chr1, chr2, chr3 = "";
	var enc1, enc2, enc3, enc4 = "";
	var i = 0;

	 // remove all characters that are not A-Z, a-z, 0-9, +, /, or =
	 input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");

	 do {
		enc1 = _keyStr.indexOf(input.charAt(i++));
		enc2 = _keyStr.indexOf(input.charAt(i++));
		enc3 = _keyStr.indexOf(input.charAt(i++));
		enc4 = _keyStr.indexOf(input.charAt(i++));

		chr1 = (enc1 << 2) | (enc2 >> 4);
		chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
		chr3 = ((enc3 & 3) << 6) | enc4;

		output = output + String.fromCharCode(chr1);

		if (enc3 != 64) {
		   output = output + String.fromCharCode(chr2);
		}
		if (enc4 != 64) {
		   output = output + String.fromCharCode(chr3);
		}

		chr1 = chr2 = chr3 = "";
		enc1 = enc2 = enc3 = enc4 = "";

	 } while (i < input.length);
	 return unescape(output);
};

// Currently not being used, so commented out for now
// based on http://phpjs.org/functions/utf8_encode:577
// codedread:does not seem to work with webkit-based browsers on OSX
// 		"encodeUTF8": function(input) {
// 			//return unescape(encodeURIComponent(input)); //may or may not work
// 			var output = '';
// 			for (var n = 0; n < input.length; n++){
// 				var c = input.charCodeAt(n);
// 				if (c < 128) {
// 					output += input[n];
// 				}
// 				else if (c > 127) {
// 					if (c < 2048){
// 						output += String.fromCharCode((c >> 6) | 192);
// 					}
// 					else {
// 						output += String.fromCharCode((c >> 12) | 224) + String.fromCharCode((c >> 6) & 63 | 128);
// 					}
// 					output += String.fromCharCode((c & 63) | 128);
// 				}
// 			}
// 			return output;
// 		},

// Function: svgedit.Utilities.convertToXMLReferences
// Converts a string to use XML references
svgedit.Utilities.convertToXMLReferences = function(input) {
	var output = '';
	for (var n = 0; n < input.length; n++){
		var c = input.charCodeAt(n);
		if (c < 128) {
			output += input[n];
		}
		else if(c > 127) {
			output += ("&#" + c + ";");
		}
	}
	return output;
};

// Function: rectsIntersect
// Check if two rectangles (BBoxes objects) intersect each other
//
// Paramaters:
// r1 - The first BBox-like object
// r2 - The second BBox-like object
//
// Returns:
// Boolean that's true if rectangles intersect
svgedit.Utilities.rectsIntersect = function(r1, r2) {
	return r2.x < (r1.x+r1.width) &&
		(r2.x+r2.width) > r1.x &&
		r2.y < (r1.y+r1.height) &&
		(r2.y+r2.height) > r1.y;
};

// Function: snapToAngle
// Returns a 45 degree angle coordinate associated with the two given
// coordinates
//
// Parameters:
// x1 - First coordinate's x value
// x2 - Second coordinate's x value
// y1 - First coordinate's y value
// y2 - Second coordinate's y value
//
// Returns:
// Object with the following values:
// x - The angle-snapped x value
// y - The angle-snapped y value
// snapangle - The angle at which to snap
svgedit.Utilities.snapToAngle = function(x1,y1,x2,y2) {
	var snap = Math.PI/4; // 45 degrees
	var dx = x2 - x1;
	var dy = y2 - y1;
	var angle = Math.atan2(dy,dx);
	var dist = Math.sqrt(dx * dx + dy * dy);
	var snapangle= Math.round(angle/snap)*snap;
	var x = x1 + dist*Math.cos(snapangle);
	var y = y1 + dist*Math.sin(snapangle);
	//console.log(x1,y1,x2,y2,x,y,angle)
	return {x:x, y:y, a:snapangle};
},

// Function: text2xml
// Cross-browser compatible method of converting a string to an XML tree
// found this function here: http://groups.google.com/group/jquery-dev/browse_thread/thread/c6d11387c580a77f
svgedit.Utilities.text2xml = function(sXML) {
	if(sXML.indexOf('<svg:svg') >= 0) {
		sXML = sXML.replace(/<(\/?)svg:/g, '<$1').replace('xmlns:svg', 'xmlns');
	}

	var out;
	try{
		var dXML = (window.DOMParser)?new DOMParser():new ActiveXObject("Microsoft.XMLDOM");
		dXML.async = false;
	} catch(e){
		throw new Error("XML Parser could not be instantiated");
	};
	try{
		if(dXML.loadXML) out = (dXML.loadXML(sXML))?dXML:false;
		else out = dXML.parseFromString(sXML, "text/xml");
	}
	catch(e){ throw new Error("Error parsing XML string"); };
	return out;
};

// Function: bboxToObj
// Converts a SVGRect into an object.
//
// Parameters:
// bbox - a SVGRect
//
// Returns:
// An object with properties names x, y, width, height.
svgedit.Utilities.bboxToObj = function(bbox) {
	return {
		x: bbox.x,
		y: bbox.y,
		width: bbox.width,
		height: bbox.height
	}
};

// Function: walkTree
// Walks the tree and executes the callback on each element in a top-down fashion
//
// Parameters:
// elem - DOM element to traverse
// cbFn - Callback function to run on each element
svgedit.Utilities.walkTree = function(elem, cbFn){
	if (elem && elem.nodeType == 1) {
		cbFn(elem);
		var i = elem.childNodes.length;
		while (i--) {
			svgedit.Utilities.walkTree(elem.childNodes.item(i), cbFn);
		}
	}
};

// Function: walkTreePost
// Walks the tree and executes the callback on each element in a depth-first fashion
// TODO: FIXME: Shouldn't this be calling walkTreePost?
//
// Parameters:
// elem - DOM element to traverse
// cbFn - Callback function to run on each element
svgedit.Utilities.walkTreePost = function(elem, cbFn) {
	if (elem && elem.nodeType == 1) {
		var i = elem.childNodes.length;
		while (i--) {
			svgedit.Utilities.walkTree(elem.childNodes.item(i), cbFn);
		}
		cbFn(elem);
	}
};

// Function: getUrlFromAttr
// Extracts the URL from the url(...) syntax of some attributes.
// Three variants:
// 	* <circle fill="url(someFile.svg#foo)" />
//  * <circle fill="url('someFile.svg#foo')" />
//  * <circle fill='url("someFile.svg#foo")' />
//
// Parameters:
// attrVal - The attribute value as a string
//
// Returns:
// String with just the URL, like someFile.svg#foo
svgedit.Utilities.getUrlFromAttr = function(attrVal) {
	if (attrVal) {
		// url("#somegrad")
		if (attrVal.indexOf('url("') === 0) {
			return attrVal.substring(5,attrVal.indexOf('"',6));
		}
		// url('#somegrad')
		else if (attrVal.indexOf("url('") === 0) {
			return attrVal.substring(5,attrVal.indexOf("'",6));
		}
		else if (attrVal.indexOf("url(") === 0) {
			return attrVal.substring(4,attrVal.indexOf(')'));
		}
	}
	return null;
};

})();<|endoftext|>


<commit_before>.pragma library
// TODO make this a utility singleton

/*
function findRoot(item) {
    if(item.objectName === "QakNode" && item.isRoot)
        return item
    else
        return findRoot(item.parent)
}
*/

var __math_abs = Math.abs
var __math_random = Math.random
var __math_floor = Math.floor
var __math_sqrt = Math.sqrt
var __math_max = Math.max
var __math_round = Math.round

function loopData(object,callback) {
    if(object !== undefined && object !== null) {
        if('data' in object) {
            var data = object.data
            for(var i in data) {
                callback(data[i])
                loopData(data[i],callback)
            }
        }
    }
}

function loopChildren(object,callback) {
    if(object !== undefined && object !== null) {
        var children = object.children, i
        for(i in children) {
            callback(children[i])
            loopChildren(children[i],callback)
        }
    }
}

// These will loop Loader types as well as 'normal' QML objects
function loopDataDeep(object,callback) {
    if('item' in object && qtypeof(object) === "QQmlTimer")
        object = object.item
    if(object !== undefined && object !== null) {
        if('data' in object) {
            var data = object.data
            for(var i in data) {
                callback(data[i])
                loopDataDeep(data[i],callback)
            }
        }
    }
}

function loopChildrenDeep(object,callback) {
    if('item' in object && qtypeof(object) === "QQmlTimer")
        object = object.item
    if(object !== undefined && object !== null) {
        var children = object.children, i
        for(i in children) {
            callback(children[i])
            loopChildrenDeep(children[i],callback)
        }
    }
}

function loopParent(object,callback) {
    if(object !== undefined && object !== null) {
        var parent = object.parent
        if(parent) {
            callback(parent)
            if(parent.parent)
                loopParent(parent.parent,callback)
        }
    }
}

function remap(oldValue, oldMin, oldMax, newMin, newMax) {
    if(oldMin === newMin && oldMax === newMax)
        return oldValue
    // Linear conversion
    // NewValue = (((OldValue - OldMin) * (NewMax - NewMin)) / (OldMax - OldMin)) + NewMin
    return (((oldValue - oldMin) * (newMax - newMin)) / (oldMax - oldMin)) + newMin
}

function interpolate(x0, x1, alpha) {
    return (x0 * (1 - alpha) + alpha * x1)
}

function lerp(x0, x1, alpha) {
    return interpolate(x0,x1,alpha)
}

//Normalizes any number to an arbitrary range
//by assuming the range wraps around when going below min or above max
// NOTE untested
function normalize( value, start, end )
{
    console.warn('Utility','using untested function normalize')
    var width       = end - start
    var offsetValue = value - start // value relative to 0

    return ( offsetValue - ( __math_floor( offsetValue / width ) * width ) ) + start
    // + start to reset back to start of original range
}

// NOTE untested
function normalize0to360(degrees) {
    console.warn('Utility','using untested function normalize0to360')
    degrees = degrees % 360
    if (degrees < 0)
        degrees += 360
    return degrees
}

// Oscillate e.g. "wave" or "ping-pong" between min and max value
// Integers only
function oscillate(value, min, max) {
    var range = max - min
    return min + __math_abs(((value + range) % (range * 2)) - range)
}

/**
 * Returns a random number between min and max
 */
function randomRangeArbitary (min, max) {
    return __math_random() * (max - min) + min
}

/**
 * Returns a random integer between min and max
 * Using Math.round() will give you a non-uniform distribution!
 */
function randomRangeInt (min, max) {
    return __math_floor(__math_random() * (max - min + 1)) + min
}

function startsWith (haystack, needle) {
    return haystack.lastIndexOf(needle, 0) === 0
}

function endsWith (haystack, needle) {
    return needle === haystack.substr(0 - needle.length)
}

function objectSize(obj) {
    if(isObject(obj)) {
        var size = 0, key
        for (key in obj) {
            if (key in obj) size++
        }
        return size
    }
    return 0
}

function clone(obj) {
    var copy

    // Handle the 3 simple types, and null or undefined
    if (null == obj || "object" != typeof obj) return obj

    // Handle Date
    if (obj instanceof Date) {
        copy = new Date()
        copy.setTime(obj.getTime())
        return copy
    }

    // Handle Array
    if (obj instanceof Array) {
        copy = []
        for (var i = 0, len = obj.length; i < len; i++) {
            copy[i] = clone(obj[i])
        }
        return copy
    }

    // Handle Object
    if (obj instanceof Object) {
        copy = {}
        for (var attr in obj) {
            if (obj.hasOwnProperty(attr)) copy[attr] = clone(obj[attr])
        }
        return copy
    }

    throw new Error("Unable to copy obj! Its type isn't supported.")
}

function dump(arr,level) {
    var dumped_text = ""
    if(!level) level = 0

    //The padding given at the beginning of the line.
    var level_padding = ""
    for(var j=0;j<level+1;j++) level_padding += "    "

    if(typeof(arr) == 'object') { //Array/Hashes/Objects
        for(var item in arr) {
            var value = arr[item]

            if(typeof(value) == 'object') { //If it is an array,
                dumped_text += level_padding + "'" + item + "' ...\n"
                dumped_text += dump(value,level+1)
            } else {
                dumped_text += level_padding + "'" + item + "' => \"" + value + "\" "+'('+typeof value+')'+"\n"
            }
        }
    } else { //Stings/Chars/Numbers etc.
        dumped_text = "===>"+arr+"<===("+typeof(arr)+")"
    }
    return dumped_text
}

function randomProperty(obj) {
    var keys = Object.keys(obj)
    return obj[keys[ keys.length * __math_random() << 0]]
}

function randomFromArray(arr) {
    var key = __math_floor(__math_random() * arr.length)
    return arr[key]
}

function shuffle(array) {
  var currentIndex = array.length, temporaryValue, randomIndex

  // While there remain elements to shuffle...
  while (0 !== currentIndex) {

    // Pick a remaining element...
    randomIndex = __math_floor(__math_random() * currentIndex)
    currentIndex -= 1

    // And swap it with the current element.
    temporaryValue = array[currentIndex]
    array[currentIndex] = array[randomIndex]
    array[randomIndex] = temporaryValue
  }

  return array
}

function extend(target, source) {
  target = target || {}
  for (var prop in source) {
    if (typeof source[prop] === 'object') {
      target[prop] = extend(target[prop], source[prop])
    } else {
      target[prop] = source[prop]
    }
  }
  return target
}

function pad(number, digits, padChar) {
    padChar = padChar || 0
    return new Array(__math_max(digits + 1 - String(number).length + 1, 0)).join(padChar) + number
}

function readDuration(text, wpm) {
    wpm = wpm || 160
    var wc = text.split(' ').length
    if(wc <= 2) wc = 3
    return __math_round((wc/wpm)*60*1000)
}

function countPad(str, padChar) {
    padChar = padChar || "0"
    var count = 0
    for (var i = 0, len = str.length; i < len; i++) {
        if(str[i] === padChar)
            count++
        else
            break
    }
    // NOTE fixes a string with all zeroes e.g.: '0000'
    if(count === str.length)
        count--
    return count
}

// Greatest Common Divisor
function gcd (a, b) {
    return (b === 0) ? a : gcd (b, a%b)
}

function distance(x1,y1,x2,y2) {
    if((x1 && 'x' in x1 && 'y' in x1) && (y1 && 'x' in y1 && 'y' in y1))
        return __math_sqrt( (x1.x-y1.x)*(x1.x-y1.x) + (x1.y-y1.y)*(x1.y-y1.y) )
    else
        return __math_sqrt( (x1-x2)*(x1-x2) + (y1-y2)*(y1-y2) )
}

function isObject(o) {
    return o !== null && typeof o === 'object'
}

function isInteger(value) {
  var x
  if (isNaN(value))
    return false
  x = parseFloat(value)
  return (x | 0) === x
}

// NOTE WARNING There is currently no official Qt way of getting the QML type of an object as a string - so this might break someday!
// Further more this won't take QML inheritance into account :(
function qtypeof(object) {

    if(object === null)
        return 'null'
    if(object === undefined)
        return 'undefined'

    var type = typeof object

    if(type === "object") {
        if('toString' in object && isFunction(object.toString)) {
            type = object.toString()
            //if(type.match(/.*_QMLTYPE_.*/i)) {
                type = type.replace(/_QMLTYPE_.*/i,'')
            //} else if(type.match(/QQuick.*/i)) {
                type = type.replace(/\(0x.*\)$/i,'')
                type = type.replace(/_QML_\d+$/i,'')
            //}
        }
    }
    return type
}

function isFunction(v) {
    return (typeof v === "function")
}

function isArray(a) {
    return (a instanceof Array)
}

function isEmpty(obj) {

    // null and undefined are "empty"
    if (obj === null) return true

    // Assume if it has a length property with a non-zero value
    // that that property is correct.
    if (obj.length > 0)    return false
    if (obj.length === 0)  return true

    // If it isn't an object at this point
    // it is empty, but it can't be anything *but* empty
    // Is it empty?  Depends on your application.
    if (typeof obj !== "object") return true

    // Otherwise, does it have any properties of its own?
    // Note that this doesn't handle
    // toString and valueOf enumeration bugs in IE < 9
    for (var key in obj) {
        if (hasOwnProperty.call(obj, key)) return false
    }

    return true
}

function isString(v) {
    return (typeof v === 'string' || v instanceof String)
}

function basename(path) {
    return path.split(/[\\/]/).pop()
}

// TODO optimize and test
// Dijkstra's algorithm
// https://github.com/andrewhayward/dijkstra
// https://raw.githubusercontent.com/andrewhayward/dijkstra/master/graph.js
// MIT Licence: https://github.com/andrewhayward/dijkstra/blob/master/LICENSE
// Node input format:
//  nodes = {
//      'name1': {
//          'name2': <weight>,
//          'name3': <weight>
//      },
//      'name2': {
//          'name1': <weight>,
//          'name3': <weight>
//      },
//      'name3': {
//          'name1': <weight>,
//          'name2': <weight>,
//          'name4': <weight>
//      },
//      'name4': {
//          'name1': <weight>,
//          'name2': <weight>
//      }
//  }
//
//  Example call: findShortestPath(nodes,'name1','name4')
//  Ouput: [ 'name1', 'name3', 'name4' ]
function findShortestPath(nodes,from,to) {

    var extractKeys = function (obj) {
        var keys = [], key
        for (key in obj) {
            //Object.prototype.hasOwnProperty.call(obj,key) && keys.push(key)
            if(Object.prototype.hasOwnProperty.call(obj,key))
                keys.push(key)
        }
        return keys
    }

    var sorter = function (a, b) {
        return parseFloat (a) - parseFloat (b)
    }

    var findPaths = function (map, start, end, infinity) {
        infinity = infinity || Infinity

        var costs = {},
            open = {'0': [start]},
            predecessors = {},
            keys

        var addToOpen = function (cost, vertex) {
            var key = "" + cost
            if (!open[key]) open[key] = []
            open[key].push(vertex)
        }

        costs[start] = 0

        while (open) {
            if(!(keys = extractKeys(open)).length) break

            keys.sort(sorter)

            var key = keys[0],
                bucket = open[key],
                node = bucket.shift(),
                currentCost = parseFloat(key),
                adjacentNodes = map[node] || {}

            if (!bucket.length) delete open[key]

            for (var vertex in adjacentNodes) {
                if (Object.prototype.hasOwnProperty.call(adjacentNodes, vertex)) {
                    var cost = adjacentNodes[vertex],
                        totalCost = cost + currentCost,
                        vertexCost = costs[vertex]

                    if ((vertexCost === undefined) || (vertexCost > totalCost)) {
                        costs[vertex] = totalCost
                        addToOpen(totalCost, vertex)
                        predecessors[vertex] = node
                    }
                }
            }
        }

        if (costs[end] === undefined) {
            return null
        } else {
            return predecessors
        }

    }

    var extractShortest = function (predecessors, end) {
        var nodes = [],
            u = end

        while (u) {
            nodes.push(u)
            u = predecessors[u]
        }

        nodes.reverse()
        return nodes
    }

    var findShortestPath = function (map, nodes) {
        var start = nodes.shift(),
            end,
            predecessors,
            path = [],
            shortest

        while (nodes.length) {
            end = nodes.shift()
            predecessors = findPaths(map, start, end)

            if (predecessors) {
                shortest = extractShortest(predecessors, end)
                if (nodes.length) {
                    path.push.apply(path, shortest.slice(0, -1))
                } else {
                    return path.concat(shortest)
                }
            } else {
                return null
            }

            start = end
        }
    }

    var toArray = function (list, offset) {
        try {
            return Array.prototype.slice.call(list, offset)
        } catch (e) {
            var a = []
            for (var i = offset || 0, l = list.length; i < l; ++i) {
                a.push(list[i])
            }
            return a
        }
    }

    var Graph = function (map) {
        this.map = map
    }

    Graph.prototype.findShortestPath = function (start, end) {
        if (Object.prototype.toString.call(start) === '[object Array]') {
            return findShortestPath(this.map, start)
        } else if (arguments.length === 2) {
            return findShortestPath(this.map, [start, end])
        } else {
            return findShortestPath(this.map, toArray(arguments))
        }
    }

    Graph.findShortestPath = function (map, start, end) {
        if (Object.prototype.toString.call(start) === '[object Array]') {
            return findShortestPath(map, start)
        } else if (arguments.length === 3) {
            return findShortestPath(map, [start, end])
        } else {
            return findShortestPath(map, toArray(arguments, 1))
        }
    }

    return Graph.findShortestPath(nodes,from,to)

}
<commit_msg>Added Aid.hasProperty check function<commit_after>.pragma library
// TODO make this a utility singleton

/*
function findRoot(item) {
    if(item.objectName === "QakNode" && item.isRoot)
        return item
    else
        return findRoot(item.parent)
}
*/

var __math_abs = Math.abs
var __math_random = Math.random
var __math_floor = Math.floor
var __math_sqrt = Math.sqrt
var __math_max = Math.max
var __math_round = Math.round

function loopData(object,callback) {
    if(object !== undefined && object !== null) {
        if('data' in object) {
            var data = object.data
            for(var i in data) {
                callback(data[i])
                loopData(data[i],callback)
            }
        }
    }
}

function loopChildren(object,callback) {
    if(object !== undefined && object !== null) {
        var children = object.children, i
        for(i in children) {
            callback(children[i])
            loopChildren(children[i],callback)
        }
    }
}

// These will loop Loader types as well as 'normal' QML objects
function loopDataDeep(object,callback) {
    if('item' in object && qtypeof(object) === "QQmlTimer")
        object = object.item
    if(object !== undefined && object !== null) {
        if('data' in object) {
            var data = object.data
            for(var i in data) {
                callback(data[i])
                loopDataDeep(data[i],callback)
            }
        }
    }
}

function loopChildrenDeep(object,callback) {
    if('item' in object && qtypeof(object) === "QQmlTimer")
        object = object.item
    if(object !== undefined && object !== null) {
        var children = object.children, i
        for(i in children) {
            callback(children[i])
            loopChildrenDeep(children[i],callback)
        }
    }
}

function loopParent(object,callback) {
    if(object !== undefined && object !== null) {
        var parent = object.parent
        if(parent) {
            callback(parent)
            if(parent.parent)
                loopParent(parent.parent,callback)
        }
    }
}

function remap(oldValue, oldMin, oldMax, newMin, newMax) {
    if(oldMin === newMin && oldMax === newMax)
        return oldValue
    // Linear conversion
    // NewValue = (((OldValue - OldMin) * (NewMax - NewMin)) / (OldMax - OldMin)) + NewMin
    return (((oldValue - oldMin) * (newMax - newMin)) / (oldMax - oldMin)) + newMin
}

function interpolate(x0, x1, alpha) {
    return (x0 * (1 - alpha) + alpha * x1)
}

function lerp(x0, x1, alpha) {
    return interpolate(x0,x1,alpha)
}

//Normalizes any number to an arbitrary range
//by assuming the range wraps around when going below min or above max
// NOTE untested
function normalize( value, start, end )
{
    console.warn('Utility','using untested function normalize')
    var width       = end - start
    var offsetValue = value - start // value relative to 0

    return ( offsetValue - ( __math_floor( offsetValue / width ) * width ) ) + start
    // + start to reset back to start of original range
}

// NOTE untested
function normalize0to360(degrees) {
    console.warn('Utility','using untested function normalize0to360')
    degrees = degrees % 360
    if (degrees < 0)
        degrees += 360
    return degrees
}

// Oscillate e.g. "wave" or "ping-pong" between min and max value
// Integers only
function oscillate(value, min, max) {
    var range = max - min
    return min + __math_abs(((value + range) % (range * 2)) - range)
}

/**
 * Returns a random number between min and max
 */
function randomRangeArbitary (min, max) {
    return __math_random() * (max - min) + min
}

/**
 * Returns a random integer between min and max
 * Using Math.round() will give you a non-uniform distribution!
 */
function randomRangeInt (min, max) {
    return __math_floor(__math_random() * (max - min + 1)) + min
}

function startsWith (haystack, needle) {
    return haystack.lastIndexOf(needle, 0) === 0
}

function endsWith (haystack, needle) {
    return needle === haystack.substr(0 - needle.length)
}

function objectSize(obj) {
    if(isObject(obj)) {
        var size = 0, key
        for (key in obj) {
            if (key in obj) size++
        }
        return size
    }
    return 0
}

function clone(obj) {
    var copy

    // Handle the 3 simple types, and null or undefined
    if (null == obj || "object" != typeof obj) return obj

    // Handle Date
    if (obj instanceof Date) {
        copy = new Date()
        copy.setTime(obj.getTime())
        return copy
    }

    // Handle Array
    if (obj instanceof Array) {
        copy = []
        for (var i = 0, len = obj.length; i < len; i++) {
            copy[i] = clone(obj[i])
        }
        return copy
    }

    // Handle Object
    if (obj instanceof Object) {
        copy = {}
        for (var attr in obj) {
            if (obj.hasOwnProperty(attr)) copy[attr] = clone(obj[attr])
        }
        return copy
    }

    throw new Error("Unable to copy obj! Its type isn't supported.")
}

function dump(arr,level) {
    var dumped_text = ""
    if(!level) level = 0

    //The padding given at the beginning of the line.
    var level_padding = ""
    for(var j=0;j<level+1;j++) level_padding += "    "

    if(typeof(arr) == 'object') { //Array/Hashes/Objects
        for(var item in arr) {
            var value = arr[item]

            if(typeof(value) == 'object') { //If it is an array,
                dumped_text += level_padding + "'" + item + "' ...\n"
                dumped_text += dump(value,level+1)
            } else {
                dumped_text += level_padding + "'" + item + "' => \"" + value + "\" "+'('+typeof value+')'+"\n"
            }
        }
    } else { //Stings/Chars/Numbers etc.
        dumped_text = "===>"+arr+"<===("+typeof(arr)+")"
    }
    return dumped_text
}

function randomProperty(obj) {
    var keys = Object.keys(obj)
    return obj[keys[ keys.length * __math_random() << 0]]
}

function randomFromArray(arr) {
    var key = __math_floor(__math_random() * arr.length)
    return arr[key]
}

function shuffle(array) {
  var currentIndex = array.length, temporaryValue, randomIndex

  // While there remain elements to shuffle...
  while (0 !== currentIndex) {

    // Pick a remaining element...
    randomIndex = __math_floor(__math_random() * currentIndex)
    currentIndex -= 1

    // And swap it with the current element.
    temporaryValue = array[currentIndex]
    array[currentIndex] = array[randomIndex]
    array[randomIndex] = temporaryValue
  }

  return array
}

function extend(target, source) {
  target = target || {}
  for (var prop in source) {
    if (typeof source[prop] === 'object') {
      target[prop] = extend(target[prop], source[prop])
    } else {
      target[prop] = source[prop]
    }
  }
  return target
}

function pad(number, digits, padChar) {
    padChar = padChar || 0
    return new Array(__math_max(digits + 1 - String(number).length + 1, 0)).join(padChar) + number
}

function readDuration(text, wpm) {
    wpm = wpm || 160
    var wc = text.split(' ').length
    if(wc <= 2) wc = 3
    return __math_round((wc/wpm)*60*1000)
}

function countPad(str, padChar) {
    padChar = padChar || "0"
    var count = 0
    for (var i = 0, len = str.length; i < len; i++) {
        if(str[i] === padChar)
            count++
        else
            break
    }
    // NOTE fixes a string with all zeroes e.g.: '0000'
    if(count === str.length)
        count--
    return count
}

// Greatest Common Divisor
function gcd (a, b) {
    return (b === 0) ? a : gcd (b, a%b)
}

function distance(x1,y1,x2,y2) {
    if((x1 && 'x' in x1 && 'y' in x1) && (y1 && 'x' in y1 && 'y' in y1))
        return __math_sqrt( (x1.x-y1.x)*(x1.x-y1.x) + (x1.y-y1.y)*(x1.y-y1.y) )
    else
        return __math_sqrt( (x1-x2)*(x1-x2) + (y1-y2)*(y1-y2) )
}

function isObject(o) {
    return o !== null && typeof o === 'object'
}

function hasProperty(o,prop) {
    return isObject(o) && isString(prop) && (prop in o)
}

function isInteger(value) {
  var x
  if (isNaN(value))
    return false
  x = parseFloat(value)
  return (x | 0) === x
}

// NOTE WARNING There is currently no official Qt way of getting the QML type of an object as a string - so this might break someday!
// Further more this won't take QML inheritance into account :(
function qtypeof(object) {

    if(object === null)
        return 'null'
    if(object === undefined)
        return 'undefined'

    var type = typeof object

    if(type === "object") {
        if('toString' in object && isFunction(object.toString)) {
            type = object.toString()
            //if(type.match(/.*_QMLTYPE_.*/i)) {
                type = type.replace(/_QMLTYPE_.*/i,'')
            //} else if(type.match(/QQuick.*/i)) {
                type = type.replace(/\(0x.*\)$/i,'')
                type = type.replace(/_QML_\d+$/i,'')
            //}
        }
    }
    return type
}

function isFunction(v) {
    return (typeof v === "function")
}

function isArray(a) {
    return (a instanceof Array)
}

function isEmpty(obj) {

    // null and undefined are "empty"
    if (obj === null) return true

    // Assume if it has a length property with a non-zero value
    // that that property is correct.
    if (obj.length > 0)    return false
    if (obj.length === 0)  return true

    // If it isn't an object at this point
    // it is empty, but it can't be anything *but* empty
    // Is it empty?  Depends on your application.
    if (typeof obj !== "object") return true

    // Otherwise, does it have any properties of its own?
    // Note that this doesn't handle
    // toString and valueOf enumeration bugs in IE < 9
    for (var key in obj) {
        if (hasOwnProperty.call(obj, key)) return false
    }

    return true
}

function isString(v) {
    return (typeof v === 'string' || v instanceof String)
}

function basename(path) {
    return path.split(/[\\/]/).pop()
}

// TODO optimize and test
// Dijkstra's algorithm
// https://github.com/andrewhayward/dijkstra
// https://raw.githubusercontent.com/andrewhayward/dijkstra/master/graph.js
// MIT Licence: https://github.com/andrewhayward/dijkstra/blob/master/LICENSE
// Node input format:
//  nodes = {
//      'name1': {
//          'name2': <weight>,
//          'name3': <weight>
//      },
//      'name2': {
//          'name1': <weight>,
//          'name3': <weight>
//      },
//      'name3': {
//          'name1': <weight>,
//          'name2': <weight>,
//          'name4': <weight>
//      },
//      'name4': {
//          'name1': <weight>,
//          'name2': <weight>
//      }
//  }
//
//  Example call: findShortestPath(nodes,'name1','name4')
//  Ouput: [ 'name1', 'name3', 'name4' ]
function findShortestPath(nodes,from,to) {

    var extractKeys = function (obj) {
        var keys = [], key
        for (key in obj) {
            //Object.prototype.hasOwnProperty.call(obj,key) && keys.push(key)
            if(Object.prototype.hasOwnProperty.call(obj,key))
                keys.push(key)
        }
        return keys
    }

    var sorter = function (a, b) {
        return parseFloat (a) - parseFloat (b)
    }

    var findPaths = function (map, start, end, infinity) {
        infinity = infinity || Infinity

        var costs = {},
            open = {'0': [start]},
            predecessors = {},
            keys

        var addToOpen = function (cost, vertex) {
            var key = "" + cost
            if (!open[key]) open[key] = []
            open[key].push(vertex)
        }

        costs[start] = 0

        while (open) {
            if(!(keys = extractKeys(open)).length) break

            keys.sort(sorter)

            var key = keys[0],
                bucket = open[key],
                node = bucket.shift(),
                currentCost = parseFloat(key),
                adjacentNodes = map[node] || {}

            if (!bucket.length) delete open[key]

            for (var vertex in adjacentNodes) {
                if (Object.prototype.hasOwnProperty.call(adjacentNodes, vertex)) {
                    var cost = adjacentNodes[vertex],
                        totalCost = cost + currentCost,
                        vertexCost = costs[vertex]

                    if ((vertexCost === undefined) || (vertexCost > totalCost)) {
                        costs[vertex] = totalCost
                        addToOpen(totalCost, vertex)
                        predecessors[vertex] = node
                    }
                }
            }
        }

        if (costs[end] === undefined) {
            return null
        } else {
            return predecessors
        }

    }

    var extractShortest = function (predecessors, end) {
        var nodes = [],
            u = end

        while (u) {
            nodes.push(u)
            u = predecessors[u]
        }

        nodes.reverse()
        return nodes
    }

    var findShortestPath = function (map, nodes) {
        var start = nodes.shift(),
            end,
            predecessors,
            path = [],
            shortest

        while (nodes.length) {
            end = nodes.shift()
            predecessors = findPaths(map, start, end)

            if (predecessors) {
                shortest = extractShortest(predecessors, end)
                if (nodes.length) {
                    path.push.apply(path, shortest.slice(0, -1))
                } else {
                    return path.concat(shortest)
                }
            } else {
                return null
            }

            start = end
        }
    }

    var toArray = function (list, offset) {
        try {
            return Array.prototype.slice.call(list, offset)
        } catch (e) {
            var a = []
            for (var i = offset || 0, l = list.length; i < l; ++i) {
                a.push(list[i])
            }
            return a
        }
    }

    var Graph = function (map) {
        this.map = map
    }

    Graph.prototype.findShortestPath = function (start, end) {
        if (Object.prototype.toString.call(start) === '[object Array]') {
            return findShortestPath(this.map, start)
        } else if (arguments.length === 2) {
            return findShortestPath(this.map, [start, end])
        } else {
            return findShortestPath(this.map, toArray(arguments))
        }
    }

    Graph.findShortestPath = function (map, start, end) {
        if (Object.prototype.toString.call(start) === '[object Array]') {
            return findShortestPath(map, start)
        } else if (arguments.length === 3) {
            return findShortestPath(map, [start, end])
        } else {
            return findShortestPath(map, toArray(arguments, 1))
        }
    }

    return Graph.findShortestPath(nodes,from,to)

}
<|endoftext|>


<commit_before>var execSync = require('child_process').execSync;
var join = require('path').join;
var _ = require('underscore');
var Q = require('q');
var debug = require('debug')('launchpad:local');
var mkdirp = require('mkdirp');

var instance = require('./instance');
var getBrowser = require('./browser');
var getVersion = require('./version');
var platform = require('./platform');

var normalize = function(browser) {
  return _.pick(browser, 'name', 'version', 'path', 'binPath');
};

function makeNixTmpDir() {
  return execSync('mktemp -d -t launchpad.XXXXXXX').toString().trim();
}

function makeWin32TmpDir() {
  var winTmpPath = process.env.TEMP || process.env.TMP ||
      (process.env.SystemRoot || process.env.windir) + '\\temp';
  var randomNumber = Math.floor(Math.random() * 9e7 + 1e7);
  var tmpdir = join(winTmpPath, 'launchpad.' + randomNumber);

  mkdirp.sync(tmpdir);
  return tmpdir;
}

var tmpdir = process.platform === "win32" ? makeWin32TmpDir() : makeNixTmpDir();

var cleanLaunch = {
  firefox: ['-no-remote', '-silent', '-p', 'launchpad-firefox'],
  opera: ['-nosession'],
  chrome: [
      // Disable built-in Google Translate service
    '--disable-features=TranslateUI',
    // Disable all chrome extensions
    '--disable-extensions',
    // Disable some extensions that aren't affected by --disable-extensions
    '--disable-component-extensions-with-background-pages',
    // Disable various background network services, including extension updating,
    //   safe browsing service, upgrade detector, translate, UMA
    '--disable-background-networking',
    // Disable syncing to a Google account
    '--disable-sync',
    // Disable reporting to UMA, but allows for collection
    '--metrics-recording-only',
    // Disable installation of default apps on first run
    '--disable-default-apps',
    // Mute any audio
    '--mute-audio',
    // Skip first run wizards
    '--no-first-run',
    // Disable backgrounding renders for occluded windows
    '--disable-backgrounding-occluded-windows',
    // Disable renderer process backgrounding
    '--disable-renderer-backgrounding',
    // Disable task throttling of timer tasks from background pages.
    '--disable-background-timer-throttling',
    '--user-data-dir=' + tmpdir
  ]
};

module.exports = function (settings, callback) {
	if (!callback) {
		callback = settings;
		settings = undefined;
	}

  debug('Initializing local launchers', settings);
  var api = function(url, options, callback) {
    var name = options.browser;

    debug('Launching browser', url, options);
    getBrowser(_.extend({ name: name }, platform[name])).then(function(browser) {
      if(browser === null) {
        return Q.reject(new Error('Browser ' + name + ' not available.'));
      }

      var args = [];
      if(options.clean) {
        if (cleanLaunch[name]) {
          args = args.concat(cleanLaunch[name]);
        }

        //Clean instances needs to be run with bin file
        browser.command = browser.binPath;

        // keep the tmpdir in options
        // in order to be cleaned after closing the browser
        browser.tmpdir = tmpdir;
        browser.clean = true;

        // The settings to be passed to childProcess.spawn
        // We need to have a detached clean process instance
        settings = {
          detached: true
        };
      } else {
        args = browser.args;
      }

      if(options.args) {
        args = args.concat(options.args);
      }

      // Convert the command if set (some browsers need some customization)
      if(browser.getCommand) {
        browser.command = browser.getCommand(browser, url, args, options);
        args = null;
      } else {
        args = args.concat(url);
      }

      debug('Starting Browser', browser.name);
      return Q.ninvoke(instance, 'start', browser.command, args, settings, browser);
    }).nodeify(callback);
  };

  api.browsers = function(callback) {
    var deferreds = _.map(platform, function(browser, name) {
      return getBrowser(_.extend({ name: name }, browser)).then(getVersion);
    });

    Q.all(deferreds).then(function(browsers) {
      debug('Listed all browsers (before normalization)', browsers);
      return _.map(_.compact(browsers), normalize);
    }).nodeify(callback);
  };

  _.each(platform, function(browser, name) {
    api[name] = function(url, options, callback) {
      if(!callback) {
        callback = options;
        options = {};
      }

      options.browser = name;

      return api(url, options, callback);
    };
  });

  callback(null, api);
};

module.exports.platform = platform;
<commit_msg>fix missing browser args (#121)<commit_after>var execSync = require('child_process').execSync;
var join = require('path').join;
var _ = require('underscore');
var Q = require('q');
var debug = require('debug')('launchpad:local');
var mkdirp = require('mkdirp');

var instance = require('./instance');
var getBrowser = require('./browser');
var getVersion = require('./version');
var platform = require('./platform');

var normalize = function(browser) {
  return _.pick(browser, 'name', 'version', 'path', 'binPath');
};

function makeNixTmpDir() {
  return execSync('mktemp -d -t launchpad.XXXXXXX').toString().trim();
}

function makeWin32TmpDir() {
  var winTmpPath = process.env.TEMP || process.env.TMP ||
      (process.env.SystemRoot || process.env.windir) + '\\temp';
  var randomNumber = Math.floor(Math.random() * 9e7 + 1e7);
  var tmpdir = join(winTmpPath, 'launchpad.' + randomNumber);

  mkdirp.sync(tmpdir);
  return tmpdir;
}

var tmpdir = process.platform === "win32" ? makeWin32TmpDir() : makeNixTmpDir();

var cleanLaunch = {
  firefox: ['-no-remote', '-silent', '-p', 'launchpad-firefox'],
  opera: ['-nosession'],
  chrome: [
      // Disable built-in Google Translate service
    '--disable-features=TranslateUI',
    // Disable all chrome extensions
    '--disable-extensions',
    // Disable some extensions that aren't affected by --disable-extensions
    '--disable-component-extensions-with-background-pages',
    // Disable various background network services, including extension updating,
    //   safe browsing service, upgrade detector, translate, UMA
    '--disable-background-networking',
    // Disable syncing to a Google account
    '--disable-sync',
    // Disable reporting to UMA, but allows for collection
    '--metrics-recording-only',
    // Disable installation of default apps on first run
    '--disable-default-apps',
    // Mute any audio
    '--mute-audio',
    // Skip first run wizards
    '--no-first-run',
    // Disable backgrounding renders for occluded windows
    '--disable-backgrounding-occluded-windows',
    // Disable renderer process backgrounding
    '--disable-renderer-backgrounding',
    // Disable task throttling of timer tasks from background pages.
    '--disable-background-timer-throttling',
    '--user-data-dir=' + tmpdir
  ]
};

module.exports = function (settings, callback) {
	if (!callback) {
		callback = settings;
		settings = undefined;
	}

  debug('Initializing local launchers', settings);
  var api = function(url, options, callback) {
    var name = options.browser;

    debug('Launching browser', url, options);
    getBrowser(_.extend({ name: name }, platform[name])).then(function(browser) {
      if(browser === null) {
        return Q.reject(new Error('Browser ' + name + ' not available.'));
      }

      var args = [];
      if(options.clean) {
        if (cleanLaunch[name]) {
          args = args.concat(cleanLaunch[name]);
        }

        //Clean instances needs to be run with bin file
        browser.command = browser.binPath;

        // keep the tmpdir in options
        // in order to be cleaned after closing the browser
        browser.tmpdir = tmpdir;
        browser.clean = true;

        // The settings to be passed to childProcess.spawn
        // We need to have a detached clean process instance
        settings = {
          detached: true
        };
      } else if (browser.args) {
        args = browser.args;
      }

      if(options.args) {
        args = args.concat(options.args);
      }

      // Convert the command if set (some browsers need some customization)
      if(browser.getCommand) {
        browser.command = browser.getCommand(browser, url, args, options);
        args = null;
      } else {
        args = args.concat(url);
      }

      debug('Starting Browser', browser.name);
      return Q.ninvoke(instance, 'start', browser.command, args, settings, browser);
    }).nodeify(callback);
  };

  api.browsers = function(callback) {
    var deferreds = _.map(platform, function(browser, name) {
      return getBrowser(_.extend({ name: name }, browser)).then(getVersion);
    });

    Q.all(deferreds).then(function(browsers) {
      debug('Listed all browsers (before normalization)', browsers);
      return _.map(_.compact(browsers), normalize);
    }).nodeify(callback);
  };

  _.each(platform, function(browser, name) {
    api[name] = function(url, options, callback) {
      if(!callback) {
        callback = options;
        options = {};
      }

      options.browser = name;

      return api(url, options, callback);
    };
  });

  callback(null, api);
};

module.exports.platform = platform;
<|endoftext|>


<commit_before>import {mainLizmap, mainEventDispatcher} from '../modules/Globals.js';

import GeoJSONReader from 'jsts/org/locationtech/jts/io/GeoJSONReader.js';
import GeoJSONWriter from 'jsts/org/locationtech/jts/io/GeoJSONWriter.js';
import BufferOp from 'jsts/org/locationtech/jts/operation/buffer/BufferOp.js';

export default class SelectionTool {

    constructor() {

        this._layers = [];
        this._allFeatureTypeSelected = [];

        this._bufferValue = 0;

        this._bufferLayer = new OpenLayers.Layer.Vector(
            'selectionBufferLayer', {
                styleMap: new OpenLayers.StyleMap({
                    fillColor: 'white',
                    fillOpacity: 0,
                    strokeColor: 'blue',
                    strokeOpacity: 1,
                    strokeWidth: 2,
                    strokeDashstyle: 'longdash'
                })
            });

        mainLizmap.lizmap3.map.addLayer(this._bufferLayer);

        this._geomOperator = 'intersects';

        this._newAddRemove = ['new', 'add', 'remove'];
        this._newAddRemoveSelected = this._newAddRemove[0];

        // Verifying WFS layers
        const featureTypes = mainLizmap.vectorLayerFeatureTypes;
        if (featureTypes.length === 0) {
            if (document.getElementById('button-selectiontool')) {
                document.getElementById('button-selectiontool').parentNode.remove();
            }
            return false;
        }

        const config = mainLizmap.config;
        const layersSorted = [];

        for (const attributeLayerName in config.attributeLayers) {
            if (config.attributeLayers.hasOwnProperty(attributeLayerName)) {
                for (const featureType of featureTypes) {
                    const lname = mainLizmap.getNameByTypeName(featureType.getElementsByTagName('Name')[0].textContent);

                    if (attributeLayerName === lname
                        && lname in config.layers
                        && config.layers[lname]['geometryType'] != 'none'
                        && config.layers[lname]['geometryType'] != 'unknown') {

                        layersSorted[config.attributeLayers[attributeLayerName].order] = {
                            name: lname,
                            title: config.layers[lname].title
                        };
                    }
                }
            }
        }

        for (const params of layersSorted) {
            if (params !== undefined) {
                this._layers.push(params);
                this._allFeatureTypeSelected.push(params.name);
            }
        }

        if (this._layers.length === 0) {
            if (document.getElementById('button-selectiontool')) {
                document.getElementById('button-selectiontool').parentNode.remove();
            }
            return false;
        }

        // Listen to digitizing tool to query a selection when tool is active and a feature (buffered or not) is drawn
        mainEventDispatcher.addListener(
            () => {
                if(this.isActive && mainLizmap.digitizing.featureDrawn){
                    // We only handle a single drawn feature currently
                    if (mainLizmap.digitizing.featureDrawn.length > 1){
                        mainLizmap.digitizing.drawLayer.destroyFeatures(mainLizmap.digitizing.drawLayer.features.shift());

                        mainLizmap.digitizing.saveFeatureDrawn();
                    }

                    const selectionFeature = mainLizmap.digitizing.featureDrawn[0];

                    if (selectionFeature) {
                        // Handle buffer if any
                        this._bufferLayer.destroyFeatures();
                        if (this._bufferValue > 0) {
                            // Reproject to project projection
                            selectionFeature.geometry.transform(mainLizmap.projection, mainLizmap.qgisProjectProjection);

                            const geoJSONParser = new OpenLayers.Format.GeoJSON();
                            const jstsGeoJSONReader = new GeoJSONReader();
                            const jstsGeoJSONWriter = new GeoJSONWriter();

                            // Use JSTS to get buffered geom
                            const jstsGeom = jstsGeoJSONReader.read(geoJSONParser.write(selectionFeature.geometry));
                            const jstsbBufferedGeom = BufferOp.bufferOp(jstsGeom, this._bufferValue);
                            const bufferedFeature = (geoJSONParser.read(jstsGeoJSONWriter.write(jstsbBufferedGeom)))[0];

                            // Reproject back to map projection
                            bufferedFeature.geometry.transform(mainLizmap.qgisProjectProjection, mainLizmap.projection);

                            // Draw buffer
                            this._bufferLayer.addFeatures(bufferedFeature);
                            this._bufferLayer.redraw(true);
                        }

                        for (const featureType of this.allFeatureTypeSelected) {
                            mainLizmap.lizmap3.selectLayerFeaturesFromSelectionFeature(featureType, this.featureDrawnBuffered || selectionFeature, this._geomOperator);
                        }
                    }
                }
            },
            ['digitizing.featureDrawn', 'digitizing.editionEnds']
        );

        // Change buffer visibility on digitizing.featureDrawnVisibility event
        mainEventDispatcher.addListener(
            () => {
                this._bufferLayer.setVisibility(mainLizmap.digitizing.featureDrawnVisibility);
            },
            ['digitizing.featureDrawnVisibility']
        );

        // Erase buffer on digitizing.erase event
        mainEventDispatcher.addListener(
            () => {
                this._bufferLayer.destroyFeatures();
            },
            ['digitizing.erase']
        );

        mainLizmap.lizmap3.events.on({
            minidockclosed: (event) => {
                if (event.id === 'selectiontool'){
                    mainLizmap.digitizing.toolSelected = 'deactivate';
                }
            }
        });
    }

    /**
     * Is selection tool active or not
     * @todo active state should be set on UI's events
     * @readonly
     * @memberof SelectionTool
     * @return {boolean}
     */
    get isActive() {
        return document.getElementById('button-selectiontool').parentElement.classList.contains('active')
    }

    get layers() {
        return this._layers;
    }

    get bufferLayer() {
        return this._bufferLayer;
    }

    get bufferValue() {
        return this._bufferValue;
    }

    set bufferValue(bufferValue) {
        this._bufferValue = isNaN(bufferValue) ? 0 : bufferValue;

        mainEventDispatcher.dispatch('selection.bufferValue');
    }

    get featureDrawnBuffered() {
        if (this._bufferLayer.features.length) {
            return this._bufferLayer.features[0];
        }
        return null;
    }

    // List of WFS format
    get exportFormats() {
        return mainLizmap.vectorLayerResultFormat.filter(
            format => !['GML2', 'GML3', 'GEOJSON'].includes(format.toUpperCase())
        );
    }

    // Selection is exportable if :
    // - one single feature type is selected in list
    // - there is at least one feature selected
    get isExportable(){
        return (this._allFeatureTypeSelected.length === 1 && this.selectedFeaturesCount);
    }

    get selectedFeaturesCount() {
        let count = 0;

        for (const featureType of this.allFeatureTypeSelected) {
            if (featureType in mainLizmap.config.layers &&
                'selectedFeatures' in mainLizmap.config.layers[featureType]
                && mainLizmap.config.layers[featureType]['selectedFeatures'].length) {
                count += mainLizmap.config.layers[featureType]['selectedFeatures'].length;
            }
        }

        return count;
    }

    get filterActive() {
        return mainLizmap.lizmap3.lizmapLayerFilterActive;
    }

    set filterActive(active) {
        mainLizmap.lizmap3.lizmapLayerFilterActive = active;
    }

    get filteredFeaturesCount() {
        let count = 0;

        for (const featureType of this.allFeatureTypeSelected) {
            if (featureType in mainLizmap.config.layers &&
                'filteredFeatures' in mainLizmap.config.layers[featureType]) {
                count += mainLizmap.config.layers[featureType]['filteredFeatures'].length;
            }
        }

        return count;
    }

    get allFeatureTypeSelected() {
        return this._allFeatureTypeSelected;
    }

    set allFeatureTypeSelected(featureType) {
        if (this._allFeatureTypeSelected !== featureType) {
            if (featureType === 'selectable-layers') {
                this._allFeatureTypeSelected = this.layers.map(layer => layer.name);
            } else if (featureType === 'selectable-visible-layers') {
                this._allFeatureTypeSelected = this.layers.map(layer => layer.name).filter(layerName => {
                    for (let index = 0; index < mainLizmap.lizmap3.map.layers.length; index++) {
                        if (mainLizmap.lizmap3.map.layers[index].visibility
                            && mainLizmap.lizmap3.map.layers[index].name === layerName) {
                            return true;
                        }
                    }
                });
            } else {
                this._allFeatureTypeSelected = [featureType];
            }

            mainEventDispatcher.dispatch('selectionTool.allFeatureTypeSelected');
        }
    }

    set geomOperator(geomOperator) {
        if (this._geomOperator !== geomOperator) {
            this._geomOperator = geomOperator;
        }
    }

    get newAddRemoveSelected() {
        return this._newAddRemoveSelected;
    }

    set newAddRemoveSelected(newAddRemove) {
        if (this._newAddRemove.includes(newAddRemove)) {
            this._newAddRemoveSelected = newAddRemove;

            mainEventDispatcher.dispatch('selectionTool.newAddRemoveSelected');
        }
    }

    disable() {
        if (this.isActive) {
            document.getElementById('button-selectiontool').click();
        }
    }

    unselect() {
        for (const featureType of this.allFeatureTypeSelected) {
            mainLizmap.lizmap3.events.triggerEvent('layerfeatureunselectall',
                {'featureType': featureType, 'updateDrawing': true}
            );
        }
        mainLizmap.digitizing.drawLayer.destroyFeatures();
        this._bufferLayer.destroyFeatures();
    }

    filter() {
        if (this.filteredFeaturesCount) {
            for (const featureType of this.allFeatureTypeSelected) {
                mainLizmap.lizmap3.events.triggerEvent('layerfeatureremovefilter',
                    {'featureType': featureType}
                );
            }
            this.filterActive = null;
        } else {
            for (const featureType of this.allFeatureTypeSelected) {
                if (featureType in mainLizmap.config.layers &&
                    'selectedFeatures' in mainLizmap.config.layers[featureType]
                    && mainLizmap.config.layers[featureType]['selectedFeatures'].length) {
                    this.filterActive = featureType;

                    mainLizmap.lizmap3.events.triggerEvent('layerfeaturefilterselected',
                        {'featureType': featureType}
                    );
                }
            }
        }
    }

    // Invert selection on a single layer
    invert(mfeatureType) {
        const featureType = mfeatureType ? mfeatureType : this.allFeatureTypeSelected[0];

        if (featureType in mainLizmap.config.layers &&
            'selectedFeatures' in mainLizmap.config.layers[featureType]
            && mainLizmap.config.layers[featureType]['selectedFeatures'].length) {

            // Get all features
            mainLizmap.lizmap3.getFeatureData(featureType, null, null, 'extent', false, null, null,
                (aName, aFilter, cFeatures) => {
                    const invertSelectionIds = [];
                    for (const feat of cFeatures) {
                        const fid = feat.id.split('.')[1];

                        if (!mainLizmap.config.layers[aName]['selectedFeatures'].includes(fid)) {
                            invertSelectionIds.push(fid);
                        }
                    }

                    mainLizmap.config.layers[featureType]['selectedFeatures'] = invertSelectionIds;

                    mainLizmap.lizmap3.events.triggerEvent('layerSelectionChanged',
                        {
                            'featureType': featureType,
                            'featureIds': '40',
                            'updateDrawing': true
                        }
                    );
                });
        }
    }

    export(format) {
        if (format === 'GML') {
            format = 'GML3';
        }
        mainLizmap.lizmap3.exportVectorLayer(this._allFeatureTypeSelected[0], format, false);
    }
}
<commit_msg>Handle selection tool<commit_after>import {mainLizmap, mainEventDispatcher} from '../modules/Globals.js';

import GeoJSONReader from 'jsts/org/locationtech/jts/io/GeoJSONReader.js';
import GeoJSONWriter from 'jsts/org/locationtech/jts/io/GeoJSONWriter.js';
import BufferOp from 'jsts/org/locationtech/jts/operation/buffer/BufferOp.js';

import GML3 from 'ol/format/GML3';
import GeoJSON from 'ol/format/GeoJSON';

import WFS from '../modules/WFS.js';

export default class SelectionTool {

    constructor() {

        this._layers = [];
        this._allFeatureTypeSelected = [];

        this._bufferValue = 0;

        this._bufferLayer = new OpenLayers.Layer.Vector(
            'selectionBufferLayer', {
                styleMap: new OpenLayers.StyleMap({
                    fillColor: 'white',
                    fillOpacity: 0,
                    strokeColor: 'blue',
                    strokeOpacity: 1,
                    strokeWidth: 2,
                    strokeDashstyle: 'longdash'
                })
            });

        mainLizmap.lizmap3.map.addLayer(this._bufferLayer);

        this._geomOperator = 'intersects';

        this._newAddRemove = ['new', 'add', 'remove'];
        this._newAddRemoveSelected = this._newAddRemove[0];

        // Verifying WFS layers
        const featureTypes = mainLizmap.vectorLayerFeatureTypes;
        if (featureTypes.length === 0) {
            if (document.getElementById('button-selectiontool')) {
                document.getElementById('button-selectiontool').parentNode.remove();
            }
            return false;
        }

        const config = mainLizmap.config;
        const layersSorted = [];

        for (const attributeLayerName in config.attributeLayers) {
            if (config.attributeLayers.hasOwnProperty(attributeLayerName)) {
                for (const featureType of featureTypes) {
                    const lname = mainLizmap.getNameByTypeName(featureType.getElementsByTagName('Name')[0].textContent);

                    if (attributeLayerName === lname
                        && lname in config.layers
                        && config.layers[lname]['geometryType'] != 'none'
                        && config.layers[lname]['geometryType'] != 'unknown') {

                        layersSorted[config.attributeLayers[attributeLayerName].order] = {
                            name: lname,
                            title: config.layers[lname].title
                        };
                    }
                }
            }
        }

        for (const params of layersSorted) {
            if (params !== undefined) {
                this._layers.push(params);
                this._allFeatureTypeSelected.push(params.name);
            }
        }

        if (this._layers.length === 0) {
            if (document.getElementById('button-selectiontool')) {
                document.getElementById('button-selectiontool').parentNode.remove();
            }
            return false;
        }

        // Listen to digitizing tool to query a selection when tool is active and a feature (buffered or not) is drawn
        mainEventDispatcher.addListener(
            () => {
                if(this.isActive && mainLizmap.digitizing.featureDrawn){
                    // We only handle a single drawn feature currently
                    if (mainLizmap.digitizing.featureDrawn.length > 1){
                        const lastFeature = mainLizmap.digitizing.featureDrawn[1];
                        mainLizmap.digitizing.drawLayer.getSource().clear();
                        mainLizmap.digitizing.drawLayer.getSource().addFeature(lastFeature);

                        mainLizmap.digitizing.saveFeatureDrawn();

                        // addFeature will provoke a new call of this callack
                        // so we return to avoid two calls
                        return;
                    }

                    const selectionFeature = mainLizmap.digitizing.featureDrawn[0];

                    if (selectionFeature) {
                        // Handle buffer if any
                        this._bufferLayer.destroyFeatures();
                        if (this._bufferValue > 0) {
                            // Reproject to project projection
                            selectionFeature.geometry.transform(mainLizmap.projection, mainLizmap.qgisProjectProjection);

                            const geoJSONParser = new OpenLayers.Format.GeoJSON();
                            const jstsGeoJSONReader = new GeoJSONReader();
                            const jstsGeoJSONWriter = new GeoJSONWriter();

                            // Use JSTS to get buffered geom
                            const jstsGeom = jstsGeoJSONReader.read(geoJSONParser.write(selectionFeature.geometry));
                            const jstsbBufferedGeom = BufferOp.bufferOp(jstsGeom, this._bufferValue);
                            const bufferedFeature = (geoJSONParser.read(jstsGeoJSONWriter.write(jstsbBufferedGeom)))[0];

                            // Reproject back to map projection
                            bufferedFeature.geometry.transform(mainLizmap.qgisProjectProjection, mainLizmap.projection);

                            // Draw buffer
                            this._bufferLayer.addFeatures(bufferedFeature);
                            this._bufferLayer.redraw(true);
                        }

                        for (const featureType of this.allFeatureTypeSelected) {
                            // mainLizmap.lizmap3.selectLayerFeaturesFromSelectionFeature(featureType, this.featureDrawnBuffered || selectionFeature, this._geomOperator);
                            const gml = new GML3({srsName: mainLizmap.qgisProjectProjection});

                            // TODO create a geometry collection from the selection draw?
                            const gmlNode = gml.writeGeometryNode(selectionFeature.getGeometry());

                            const serializer = new XMLSerializer();

                            let spatialFilter = this._geomOperator + `($geometry, geom_from_gml('${serializer.serializeToString(gmlNode)}'))`;

                            const lConfig = mainLizmap.config.layers[featureType];

                            let rFilter = lConfig?.request_params?.filter;
                            if( rFilter ){
                                rFilter = rFilter.replace( featureType + ':', '');
                                spatialFilter = rFilter + ' AND ' + spatialFilter;
                            }

                            // Add exp_filter, for example if set by another tool( filter module )
                            // Often 'filter' is not set because filtertoken is set instead
                            // But in this case, exp_filter must also been set and must be added
                            let eFilter = lConfig?.request_params?.exp_filter;
                            if( eFilter ){
                                spatialFilter = eFilter +' AND '+ spatialFilter;
                            }

                            // TODO: useful?
                            // let limitDataToBbox = config.options.limitDataToBbox === 'True';

                            // var getFeatureUrlData = lizMap.getVectorLayerWfsUrl( targetFeatureType, spatialFilter, null, null, limitDataToBbox );

                            const wfs = new WFS();
                            const wfsParams = {
                                TYPENAME: featureType,
                                EXP_FILTER: spatialFilter
                            };

                            wfs.getFeature(wfsParams).then(response => {
                                const features = (new GeoJSON()).readFeatures(response);

                                // Array of feature ids matching geometry condition
                                let featureIds = features.map(feature => feature.getId().split('.')[1]);

                                if (this.newAddRemoveSelected === 'add' ) { // Add to selection
                                    featureIds = config.layers[featureType]['selectedFeatures'].concat(featureIds);
                                    // Remove duplicates
                                    featureIds = [...new Set(featureIds)];
                                }else if (this.newAddRemoveSelected === 'remove' ) { // Remove from selection
                                    const toRemove = new Set(featureIds);
                                    featureIds = config.layers[featureType]['selectedFeatures'].filter( x => !toRemove.has(x) );
                                }

                                mainLizmap.config.layers[featureType]['selectedFeatures'] = featureIds;
                                lizMap.events.triggerEvent("layerSelectionChanged",
                                    {
                                        'featureType': featureType,
                                        'featureIds': mainLizmap.config.layers[featureType]['selectedFeatures'],
                                        'updateDrawing': true
                                    }
                                );

                            });
                        }
                    }
                }
            },
            ['digitizing.featureDrawn', 'digitizing.editionEnds']
        );

        // Change buffer visibility on digitizing.featureDrawnVisibility event
        mainEventDispatcher.addListener(
            () => {
                this._bufferLayer.setVisibility(mainLizmap.digitizing.featureDrawnVisibility);
            },
            ['digitizing.featureDrawnVisibility']
        );

        // Erase buffer on digitizing.erase event
        mainEventDispatcher.addListener(
            () => {
                this._bufferLayer.destroyFeatures();
            },
            ['digitizing.erase']
        );

        mainLizmap.lizmap3.events.on({
            minidockclosed: (event) => {
                if (event.id === 'selectiontool'){
                    mainLizmap.digitizing.toolSelected = 'deactivate';
                }
            }
        });
    }

    /**
     * Is selection tool active or not
     * @todo active state should be set on UI's events
     * @readonly
     * @memberof SelectionTool
     * @return {boolean}
     */
    get isActive() {
        return document.getElementById('button-selectiontool').parentElement.classList.contains('active')
    }

    get layers() {
        return this._layers;
    }

    get bufferLayer() {
        return this._bufferLayer;
    }

    get bufferValue() {
        return this._bufferValue;
    }

    set bufferValue(bufferValue) {
        this._bufferValue = isNaN(bufferValue) ? 0 : bufferValue;

        mainEventDispatcher.dispatch('selection.bufferValue');
    }

    get featureDrawnBuffered() {
        if (this._bufferLayer.features.length) {
            return this._bufferLayer.features[0];
        }
        return null;
    }

    // List of WFS format
    get exportFormats() {
        return mainLizmap.vectorLayerResultFormat.filter(
            format => !['GML2', 'GML3', 'GEOJSON'].includes(format.toUpperCase())
        );
    }

    // Selection is exportable if :
    // - one single feature type is selected in list
    // - there is at least one feature selected
    get isExportable(){
        return (this._allFeatureTypeSelected.length === 1 && this.selectedFeaturesCount);
    }

    get selectedFeaturesCount() {
        let count = 0;

        for (const featureType of this.allFeatureTypeSelected) {
            if (featureType in mainLizmap.config.layers &&
                'selectedFeatures' in mainLizmap.config.layers[featureType]
                && mainLizmap.config.layers[featureType]['selectedFeatures'].length) {
                count += mainLizmap.config.layers[featureType]['selectedFeatures'].length;
            }
        }

        return count;
    }

    get filterActive() {
        return mainLizmap.lizmap3.lizmapLayerFilterActive;
    }

    set filterActive(active) {
        mainLizmap.lizmap3.lizmapLayerFilterActive = active;
    }

    get filteredFeaturesCount() {
        let count = 0;

        for (const featureType of this.allFeatureTypeSelected) {
            if (featureType in mainLizmap.config.layers &&
                'filteredFeatures' in mainLizmap.config.layers[featureType]) {
                count += mainLizmap.config.layers[featureType]['filteredFeatures'].length;
            }
        }

        return count;
    }

    get allFeatureTypeSelected() {
        return this._allFeatureTypeSelected;
    }

    set allFeatureTypeSelected(featureType) {
        if (this._allFeatureTypeSelected !== featureType) {
            if (featureType === 'selectable-layers') {
                this._allFeatureTypeSelected = this.layers.map(layer => layer.name);
            } else if (featureType === 'selectable-visible-layers') {
                this._allFeatureTypeSelected = this.layers.map(layer => layer.name).filter(layerName => {
                    for (let index = 0; index < mainLizmap.lizmap3.map.layers.length; index++) {
                        if (mainLizmap.lizmap3.map.layers[index].visibility
                            && mainLizmap.lizmap3.map.layers[index].name === layerName) {
                            return true;
                        }
                    }
                });
            } else {
                this._allFeatureTypeSelected = [featureType];
            }

            mainEventDispatcher.dispatch('selectionTool.allFeatureTypeSelected');
        }
    }

    set geomOperator(geomOperator) {
        if (this._geomOperator !== geomOperator) {
            this._geomOperator = geomOperator;
        }
    }

    get newAddRemoveSelected() {
        return this._newAddRemoveSelected;
    }

    set newAddRemoveSelected(newAddRemove) {
        if (this._newAddRemove.includes(newAddRemove)) {
            this._newAddRemoveSelected = newAddRemove;

            mainEventDispatcher.dispatch('selectionTool.newAddRemoveSelected');
        }
    }

    disable() {
        if (this.isActive) {
            document.getElementById('button-selectiontool').click();
        }
    }

    unselect() {
        for (const featureType of this.allFeatureTypeSelected) {
            mainLizmap.lizmap3.events.triggerEvent('layerfeatureunselectall',
                {'featureType': featureType, 'updateDrawing': true}
            );
        }
        mainLizmap.digitizing.drawLayer.destroyFeatures();
        this._bufferLayer.destroyFeatures();
    }

    filter() {
        if (this.filteredFeaturesCount) {
            for (const featureType of this.allFeatureTypeSelected) {
                mainLizmap.lizmap3.events.triggerEvent('layerfeatureremovefilter',
                    {'featureType': featureType}
                );
            }
            this.filterActive = null;
        } else {
            for (const featureType of this.allFeatureTypeSelected) {
                if (featureType in mainLizmap.config.layers &&
                    'selectedFeatures' in mainLizmap.config.layers[featureType]
                    && mainLizmap.config.layers[featureType]['selectedFeatures'].length) {
                    this.filterActive = featureType;

                    mainLizmap.lizmap3.events.triggerEvent('layerfeaturefilterselected',
                        {'featureType': featureType}
                    );
                }
            }
        }
    }

    // Invert selection on a single layer
    invert(mfeatureType) {
        const featureType = mfeatureType ? mfeatureType : this.allFeatureTypeSelected[0];

        if (featureType in mainLizmap.config.layers &&
            'selectedFeatures' in mainLizmap.config.layers[featureType]
            && mainLizmap.config.layers[featureType]['selectedFeatures'].length) {

            // Get all features
            mainLizmap.lizmap3.getFeatureData(featureType, null, null, 'extent', false, null, null,
                (aName, aFilter, cFeatures) => {
                    const invertSelectionIds = [];
                    for (const feat of cFeatures) {
                        const fid = feat.id.split('.')[1];

                        if (!mainLizmap.config.layers[aName]['selectedFeatures'].includes(fid)) {
                            invertSelectionIds.push(fid);
                        }
                    }

                    mainLizmap.config.layers[featureType]['selectedFeatures'] = invertSelectionIds;

                    mainLizmap.lizmap3.events.triggerEvent('layerSelectionChanged',
                        {
                            'featureType': featureType,
                            'featureIds': '40',
                            'updateDrawing': true
                        }
                    );
                });
        }
    }

    export(format) {
        if (format === 'GML') {
            format = 'GML3';
        }
        mainLizmap.lizmap3.exportVectorLayer(this._allFeatureTypeSelected[0], format, false);
    }
}
<|endoftext|>