mirror of
https://gitlab.linphone.org/BC/public/linphone-desktop.git
synced 2026-01-17 11:28:07 +00:00
653 lines
16 KiB
JavaScript
653 lines
16 KiB
JavaScript
// =============================================================================
|
|
// Contains many common helpers.
|
|
// =============================================================================
|
|
|
|
.pragma library
|
|
|
|
.import QtQuick 2.7 as QtQuick
|
|
|
|
.import 'port-tools.js' as PortTools
|
|
.import 'uri-tools.js' as UriTools
|
|
|
|
// =============================================================================
|
|
// Constants.
|
|
// =============================================================================
|
|
|
|
var PORT_REGEX = PortTools.PORT_REGEX
|
|
var PORT_RANGE_REGEX = PortTools.PORT_RANGE_REGEX
|
|
|
|
var SCHEME_REGEX = new RegExp('^[^:]+:')
|
|
|
|
// =============================================================================
|
|
// QML helpers.
|
|
// =============================================================================
|
|
|
|
function buildDialogUri (component) {
|
|
return 'qrc:/ui/modules/Common/Dialog/' + component + '.qml'
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
// Destroy timeout.
|
|
function clearTimeout (timer) {
|
|
timer.stop() // NECESSARY.
|
|
timer.destroy()
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
function createObject (source, parent, options) {
|
|
if (options && options.isString) {
|
|
var object = Qt.createQmlObject(source, parent)
|
|
|
|
var properties = options && options.properties
|
|
if (properties) {
|
|
for (var key in properties) {
|
|
object[key] = properties[key]
|
|
}
|
|
}
|
|
|
|
return object
|
|
}
|
|
|
|
var component = Qt.createComponent(source)
|
|
if (component.status !== QtQuick.Component.Ready) {
|
|
console.debug('Component not ready.')
|
|
if (component.status === QtQuick.Component.Error) {
|
|
console.debug('Error: ' + component.errorString())
|
|
}
|
|
return // Error.
|
|
}
|
|
|
|
var object = component.createObject(parent, (options && options.properties) || {})
|
|
if (!object) {
|
|
console.debug('Error: unable to create dynamic object.')
|
|
}
|
|
|
|
return object
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
function encodeTextToQmlRichFormat (text, options) {
|
|
var images = ''
|
|
|
|
if (!options) {
|
|
options = {}
|
|
}
|
|
|
|
var formattedText = execAll(UriTools.URI_REGEX, text, function (str, valid) {
|
|
if (!valid) {
|
|
return unscapeHtml(str)
|
|
}
|
|
|
|
var uri = startsWith(str, 'www.') ? 'http://' + str : str
|
|
|
|
var ext = getExtension(str)
|
|
if (includes([ 'jpg', 'jpeg', 'gif', 'png', 'svg' ], ext)) {
|
|
images += '<a href="' + uri + '"><img' + (
|
|
options.imagesWidth != null
|
|
? ' width="' + options.imagesWidth + '"'
|
|
: ''
|
|
) + (
|
|
options.imagesHeight != null
|
|
? ' height="' + options.imagesHeight + '"'
|
|
: ''
|
|
) + ' src="' + str + '" /></a>'
|
|
}
|
|
|
|
return '<a href="' + uri + '">' + unscapeHtml(str) + '</a>'
|
|
}).join('')
|
|
if (images.length > 0) {
|
|
images = '<div>' + images + '</div>'
|
|
}
|
|
|
|
return images.concat('<p style="white-space:pre-wrap;">' + formattedText + '</p>')
|
|
}
|
|
|
|
function extractFirstUri (str) {
|
|
var res = str.match(UriTools.URI_REGEX)
|
|
return res == null || startsWith(res[0], 'www')
|
|
? undefined
|
|
: res[0]
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
function getSystemPathFromUri (uri) {
|
|
var str = uri.toString()
|
|
if (startsWith(str, 'file://')) {
|
|
str = str.substring(7)
|
|
|
|
// Absolute path.
|
|
if (str.charAt(0) === '/') {
|
|
return runOnWindows() ? str.substring(1) : str
|
|
}
|
|
}
|
|
|
|
return str
|
|
}
|
|
|
|
function getUriFromSystemPath (path) {
|
|
if (path.startsWith('file://')) {
|
|
return path
|
|
}
|
|
|
|
if (runOnWindows()) {
|
|
return 'file://' + (/^\w:/.exec(path) ? '/' : '') + path
|
|
}
|
|
|
|
return 'file://' + path
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
// Returns the top (root) parent of one object.
|
|
function getTopParent (object, useFakeParent) {
|
|
function _getTopParent (object, useFakeParent) {
|
|
return (useFakeParent && object.$parent) || object.parent
|
|
}
|
|
|
|
var parent = _getTopParent(object, useFakeParent)
|
|
var p
|
|
while ((p = _getTopParent(parent, useFakeParent)) != null) {
|
|
parent = p
|
|
}
|
|
|
|
return parent
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
// Load by default a window in the ui/views folder.
|
|
// If options.isString is equals to true, a marshalling component can
|
|
// be used.
|
|
//
|
|
// Supported options: isString, exitHandler, properties.
|
|
//
|
|
// If exitHandler is used, window must implement exitStatus signal.
|
|
function openWindow (window, parent, options) {
|
|
var object = createObject(window, parent, options)
|
|
|
|
object.closing.connect(object.destroy.bind(object))
|
|
|
|
if (options && options.exitHandler) {
|
|
object.exitStatus.connect(
|
|
// Bind to access parent properties.
|
|
options.exitHandler.bind(parent)
|
|
)
|
|
}
|
|
|
|
object.show()
|
|
|
|
return object
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
function resolveImageUri (name) {
|
|
return name
|
|
? 'image://internal/' + removeScheme(Qt.resolvedUrl('/assets/images/' + name + '.svg'))
|
|
: ''
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
|
|
function runOnWindows () {
|
|
var os = Qt.platform.os
|
|
return os === 'windows' || os === 'winrt'
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
// Test if a point is in a item.
|
|
//
|
|
// `source` is the item that generated the point.
|
|
// `target` is the item to test.
|
|
// `point` is the point to test.
|
|
function pointIsInItem (source, target, point) {
|
|
point = source.mapToItem(target.parent, point.x, point.y)
|
|
|
|
return (
|
|
point.x >= target.x &&
|
|
point.y >= target.y &&
|
|
point.x < target.x + target.width &&
|
|
point.y < target.y + target.height
|
|
)
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
// Test the type of a qml object.
|
|
// Warning: this function is probably not portable
|
|
// on new versions of Qt.
|
|
//
|
|
// So, if you want to use it on a specific `className`, please to add
|
|
// a test in `test_qmlTypeof_data` of `utils.spec.qml`.
|
|
function qmlTypeof (object, className) {
|
|
var str = object.toString()
|
|
|
|
return (
|
|
str.indexOf(className + '(') == 0 ||
|
|
str.indexOf(className + '_QML') == 0
|
|
)
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
function removeScheme (url) {
|
|
return url.toString().replace(SCHEME_REGEX, '')
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
// A copy of `Window.setTimeout` from js.
|
|
// delay is in milliseconds.
|
|
function setTimeout (parent, delay, cb) {
|
|
var timer = new (function (parent) {
|
|
return Qt.createQmlObject('import QtQml 2.2; Timer { }', parent)
|
|
})(parent)
|
|
|
|
timer.interval = delay
|
|
timer.repeat = false
|
|
timer.triggered.connect(cb)
|
|
timer.start()
|
|
|
|
return timer
|
|
}
|
|
|
|
// =============================================================================
|
|
// GENERIC.
|
|
// =============================================================================
|
|
|
|
function _computeOptimizedCb (func, context) {
|
|
return context
|
|
? (function () {
|
|
return func.apply(context, arguments)
|
|
}) : func
|
|
}
|
|
|
|
function _indexFinder (array, cb, context) {
|
|
var length = array.length
|
|
|
|
for (var i = 0; i < length; i++) {
|
|
if (cb(array[i], i, array)) {
|
|
return i
|
|
}
|
|
}
|
|
|
|
return -1
|
|
}
|
|
|
|
function _keyFinder (obj, cb, context) {
|
|
var keys = Object.keys(obj)
|
|
var length = keys.length
|
|
|
|
for (var i = 0; i < length; i++) {
|
|
var key = keys[i]
|
|
if (cb(obj[key], key, obj)) {
|
|
return key
|
|
}
|
|
}
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
// Basic assert function.
|
|
function assert (condition, message) {
|
|
if (!condition) {
|
|
throw new Error('Assert: ' + message)
|
|
}
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
function basename (str) {
|
|
if (!str) {
|
|
return ''
|
|
}
|
|
|
|
if (runOnWindows()) {
|
|
str = str.replace(/\\/g, '/')
|
|
}
|
|
|
|
var str2 = str
|
|
var length = str2.length - 1
|
|
|
|
if (str2[length] === '/') {
|
|
str2 = str2.substring(0, length)
|
|
}
|
|
|
|
return str2.slice(str2.lastIndexOf('/') + 1)
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
function capitalizeFirstLetter (str) {
|
|
return str.charAt(0).toUpperCase() + str.slice(1)
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
function dirname (str) {
|
|
if (!str) {
|
|
return ''
|
|
}
|
|
|
|
if (runOnWindows()) {
|
|
str = str.replace(/\\/g, '/')
|
|
}
|
|
|
|
var str2 = str
|
|
var length = str2.length - 1
|
|
|
|
if (str2[length] === '/') {
|
|
str2 = str2.substring(0, length)
|
|
}
|
|
|
|
return str2.slice(0, str2.lastIndexOf('/') + 1)
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
function execAll (regex, text, cb) {
|
|
var index = 0
|
|
var arr = []
|
|
var match
|
|
|
|
if (!cb) {
|
|
cb = function (text) {
|
|
return text
|
|
}
|
|
}
|
|
|
|
while ((match = regex.exec(text))) {
|
|
var curIndex = match.index
|
|
var matchStr = match[0]
|
|
|
|
if (curIndex > index) {
|
|
arr.push(cb(text.substring(index, curIndex), false))
|
|
}
|
|
|
|
arr.push(cb(matchStr, true))
|
|
|
|
index = curIndex + matchStr.length
|
|
}
|
|
|
|
var length = text.length
|
|
if (index < length) {
|
|
arr.push(cb(text.substring(index, length)))
|
|
}
|
|
|
|
return arr
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
function extractProperties (obj, pattern) {
|
|
if (!pattern) {
|
|
return {}
|
|
}
|
|
|
|
var obj2 = {}
|
|
pattern.forEach(function (property) {
|
|
obj2[property] = obj[property]
|
|
})
|
|
|
|
return obj2
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
// Returns an array from an `object` or `array` argument.
|
|
function ensureArray (obj) {
|
|
if (isArray(obj)) {
|
|
return obj
|
|
}
|
|
|
|
var keys = Object.keys(obj)
|
|
var length = keys.length
|
|
var values = Array(length)
|
|
|
|
for (var i = 0; i < length; i++) {
|
|
values[i] = obj[keys[i]]
|
|
}
|
|
|
|
return values
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
function escapeQuotes (str) {
|
|
return str != null
|
|
? str.replace(/([^'\\]*(?:\\.[^'\\]*)*)'/g, '$1\\\'')
|
|
: ''
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
// Get the first matching value in a array or object.
|
|
// The matching value is obtained if `cb` returns true.
|
|
function find (obj, cb, context) {
|
|
cb = _computeOptimizedCb(cb, context)
|
|
|
|
var finder = isArray(obj) ? _indexFinder : _keyFinder
|
|
var key = finder(obj, cb, context)
|
|
|
|
return key != null && key !== -1 ? obj[key] : null
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
function findIndex (array, cb, context) {
|
|
cb = _computeOptimizedCb(cb, context)
|
|
|
|
var key = _indexFinder(array, cb, context)
|
|
return key != null && key !== -1 ? key : null
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
function formatElapsedTime (seconds) {
|
|
seconds = parseInt(seconds, 10)
|
|
|
|
var h = Math.floor(seconds / 3600)
|
|
var m = Math.floor((seconds - h * 3600) / 60)
|
|
var s = seconds - h * 3600 - m * 60
|
|
|
|
if (h < 10 && h > 0) {
|
|
h = '0' + h
|
|
}
|
|
|
|
if (m < 10) {
|
|
m = '0' + m
|
|
}
|
|
|
|
if (s < 10) {
|
|
s = '0' + s
|
|
}
|
|
|
|
return (h === 0 ? '' : h + ':') + m + ':' + s
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
function formatSize (size) {
|
|
var units = ['KB', 'MB', 'GB', 'TB']
|
|
var unit = 'B'
|
|
|
|
if (size == null) {
|
|
size = 0
|
|
}
|
|
|
|
var length = units.length
|
|
for (var i = 0; size >= 1024 && i < length; i++) {
|
|
unit = units[i]
|
|
size /= 1024
|
|
}
|
|
|
|
return parseFloat(size.toFixed(2)) + unit
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
// Generate a random number in the [min, max[ interval.
|
|
// Uniform distrib.
|
|
function genRandomNumber (min, max) {
|
|
return Math.random() * (max - min) + min
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
// Generate a random number between a set of intervals.
|
|
// The `intervals` param must be orderer like this:
|
|
// `[ [ 1, 4 ], [ 8, 16 ], [ 22, 25 ] ]`
|
|
function genRandomNumberBetweenIntervals (intervals) {
|
|
if (intervals.length === 1) {
|
|
return genRandomNumber(intervals[0][0], intervals[0][1])
|
|
}
|
|
|
|
// Compute the intervals size.
|
|
var size = 0
|
|
intervals.forEach(function (interval) {
|
|
size += interval[1] - interval[0]
|
|
})
|
|
|
|
// Generate a value in the interval: `[0, size[`
|
|
var n = genRandomNumber(0, size)
|
|
|
|
// Map the value in the right interval.
|
|
n += intervals[0][0]
|
|
for (var i = 0; i < intervals.length - 1; i++) {
|
|
if (n < intervals[i][1]) {
|
|
break
|
|
}
|
|
|
|
n += intervals[i + 1][0] - intervals[i][1]
|
|
}
|
|
|
|
return n
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
// Returns the extension of a filename.
|
|
function getExtension (str) {
|
|
var index = str.lastIndexOf('.')
|
|
|
|
if (index === -1) {
|
|
return ''
|
|
}
|
|
|
|
return str.slice(index + 1)
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
// Test if a value is included in an array or object.
|
|
function includes (obj, value, startIndex) {
|
|
obj = ensureArray(obj)
|
|
|
|
if (startIndex == null) {
|
|
startIndex = 0
|
|
}
|
|
|
|
var length = obj.length
|
|
|
|
for (var i = startIndex; i < length; i++) {
|
|
if (
|
|
value === obj[i] ||
|
|
// Check `NaN`.
|
|
(value !== value && obj[i] !== obj[i])
|
|
) {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
function isArray (array) {
|
|
return (array instanceof Array)
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
function isFunction (func) {
|
|
return typeof func === 'function'
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
function isInteger (integer) {
|
|
return integer === parseInt(integer, 10)
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
function isObject (object) {
|
|
return object !== null && typeof object === 'object'
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
function isString (string) {
|
|
return typeof string === 'string' || string instanceof String
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
// Convert a snake_case string to a lowerCamelCase string.
|
|
function snakeToCamel (s) {
|
|
return s.replace(/(\_\w)/g, function (matches) {
|
|
return matches[1].toUpperCase()
|
|
})
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
// Test if a string starts by a given string.
|
|
function startsWith (str, searchStr) {
|
|
if (searchStr == null) {
|
|
searchStr = ''
|
|
}
|
|
|
|
return str.slice(0, searchStr.length) === searchStr
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
// Invoke a `cb` function with each value of the interval: `[0, n[`.
|
|
// Return a mapped array created with the returned values of `cb`.
|
|
function times (n, cb, context) {
|
|
var arr = Array(Math.max(0, n))
|
|
cb = _computeOptimizedCb(cb, context, 1)
|
|
|
|
for (var i = 0; i < n; i++) {
|
|
arr[i] = cb(i)
|
|
}
|
|
|
|
return arr
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
function unscapeHtml (str) {
|
|
return str.replace(/&/g, '&')
|
|
.replace(/</g, '\u2063<')
|
|
.replace(/>/g, '\u2063>')
|
|
.replace(/"/g, '"')
|
|
.replace(/'/g, ''')
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
function write (fileName, text) {
|
|
// TODO: Deal with async.
|
|
var request = new XMLHttpRequest()
|
|
request.open('PUT', getUriFromSystemPath(fileName), false)
|
|
request.send(text)
|
|
}
|