API Docs for: 2.0.0

public/src/SAGE2_DisplayUI.js

// SAGE2 is available for use under the SAGE2 Software License
//
// University of Illinois at Chicago's Electronic Visualization Laboratory (EVL)
// and University of Hawai'i at Manoa's Laboratory for Advanced Visualization and
// Applications (LAVA)
//
// See full text, terms and conditions in the LICENSE.txt included file
//
// Copyright (c) 2014-15

"use strict";

/**
 * Web user interface for SAGE2
 *
 * @module client
 * @submodule SAGE2DisplayUI
 */

/* global viewOnlyMode */

/**
 * User interface drawn using Canvas2D
 *
 * @class SAGE2DisplayUI
 * @constructor
 */
function SAGE2DisplayUI() {
	this.config = null;
	this.wsio = null;
	this.scale = 1.0;
	this.logoAspect = 3.47828052509;
	this.logoLoaded = false;
	this.fileDrop = false;
	this.fileUpload = false;
	this.uploadPercent = 0;
	this.fileDropFontSize = 12;
	this.applications = {};
	this.appCount = 20;
	this.partitions = {};
	this.ptnCount = 0;
	this.mediaStreamIcon = null;
	this.pointerX = 0;
	this.pointerY = 0;
	this.scrollTimeId = null;
}

/**
 * Initialize the object
 *
 * @method init
 * @param config {Object} display configuration object
 * @param wsio {Object} WebsocktIO object
 */
SAGE2DisplayUI.prototype.init = function(config, wsio) {
	this.svgLoadedFunc = this.svgLoaded.bind(this);

	this.config = config;
	this.wsio   = wsio;

	this.mediaStreamIcon = document.createElement('canvas');
	this.mediaStreamIcon.width  = 512;
	this.mediaStreamIcon.height = 512;

	var applicationsDiv = document.getElementById('applicationsDiv');
	var logo = document.createElement('img');
	logo.style.opacity  = 0.4;
	logo.style.position = "absolute";
	logo.style.left     = "50%";
	logo.style.top      = "50%";
	logo.style.webkitTransform = "translate(-50%, -50%)";
	logo.style.mozTransform    = "translate(-50%, -50%)";
	logo.style.transform       = "translate(-50%, -50%)";
	if ((this.config.totalWidth / this.config.totalHeight) <= this.logoAspect) {
		logo.style.width  = "75%";
	} else {
		logo.style.height = "75%";
	}
	// If bacground watermark defined
	if (this.config.background.watermark !== undefined && this.config.background.watermark.svg !== undefined) {
		logo.src = this.config.background.watermark.svg;
	} else {
		logo.src = "images/sage2.svg";
	}
	applicationsDiv.appendChild(logo);
};

/**
 * Draw the UI
 *
 * @method draw
 */
SAGE2DisplayUI.prototype.draw = function() {
	var sage2UI = document.getElementById('sage2UICanvas');
	var ctx = sage2UI.getContext('2d');

	ctx.clearRect(0, 0, sage2UI.width, sage2UI.height);

	// tiled display layout
	var i;
	ctx.lineWidth = 2;
	ctx.strokeStyle = "rgba(86, 86, 86, 1.0)";
	var stepX = sage2UI.width / this.config.layout.columns;
	var stepY = sage2UI.height / this.config.layout.rows;
	ctx.beginPath();
	for (i = 1; i < this.config.layout.columns; i++) {
		ctx.moveTo(i * stepX, 0);
		ctx.lineTo(i * stepX, sage2UI.height);
	}
	for (i = 1; i < this.config.layout.rows; i++) {
		ctx.moveTo(0, i * stepY);
		ctx.lineTo(sage2UI.width, i * stepY);
	}
	ctx.closePath();
	ctx.stroke();

	// file drop overlay
	if (this.fileDrop === true) {
		ctx.fillStyle = "rgba(255, 255, 255, 0.7)";
		ctx.fillRect(0, 0, sage2UI.width, sage2UI.height);

		var txt = "Drop multimedia files here";
		ctx.font = this.fileDropFontSize + "px Verdana";

		var textBoxWidth = Math.round(sage2UI.width * 0.75);
		var lines = this.textLineCount(ctx, txt, textBoxWidth);
		var lineHeight = this.fileDropFontSize * 1.2;
		var textBoxHeight = lineHeight * lines;

		var textBoxX = (sage2UI.width - textBoxWidth) / 2;
		var textBoxY = (sage2UI.height - textBoxHeight) / 2;
		var textBoxRadius = this.fileDropFontSize * 0.5;
		ctx.textAlign = "center";
		ctx.fillStyle = "rgba(86, 86, 86, 0.7)";
		this.drawRoundedRect(ctx, textBoxX, textBoxY, textBoxWidth, textBoxHeight, textBoxRadius, true, false);

		var textStartX = sage2UI.width / 2 + this.fileDropFontSize * 0.175;
		var textStartY = sage2UI.height / 2 - ((lines - 1) / 2) * lineHeight + this.fileDropFontSize * 0.333;
		ctx.fillStyle = "rgba(255, 255, 255, 0.7)";
		this.wrapText(ctx, txt, textStartX, textStartY, textBoxWidth, lineHeight);
	}

	// file upload overlay
	if (this.fileUpload === true) {
		ctx.fillStyle = "rgba(255, 255, 255, 0.7)";
		ctx.fillRect(0, 0, sage2UI.width, sage2UI.height);

		var progressWidth = Math.round(sage2UI.width * 0.75);
		var progressHeight = progressWidth * 0.07;
		var progressX = (sage2UI.width - progressWidth) / 2;
		var progressY = (sage2UI.height - progressHeight) / 2;
		var progressRadius = progressHeight * 0.5;

		ctx.strokeStyle = "rgba(30, 30, 30, 0.85)";
		ctx.strokeWidth = 2;
		this.drawRoundedRect(ctx, progressX, progressY, progressWidth, progressHeight, progressRadius, false, true);

		var percentWidth = Math.round(progressWidth * this.uploadPercent);
		if (percentWidth > progressHeight) {
			ctx.fillStyle = "rgba(86, 86, 86, 0.85)";
			this.drawRoundedRect(ctx, progressX, progressY, percentWidth, progressHeight, progressRadius, true, false);
		}
	}
};

/**
 * Callback when the browser is resize, adjust the position of UI elements
 *
 * @method resize
 * @param ratio {Number} scale factor
 */
SAGE2DisplayUI.prototype.resize = function(ratio) {
	var displayUI        = document.getElementById('displayUIDiv');
	var sage2UI          = document.getElementById('sage2UICanvas');
	var applicationsDiv  = document.getElementById('applicationsDiv');
	var partitionsDiv 	 = document.getElementById('partitionsDiv');

	// Extra scaling factor
	ratio = ratio || 1.0;
	var menuScale = 1.0;

	// var winWidth = window.innerWidth * ratio;
	// if (window.innerWidth < 856) {
	// 	menuScale = window.innerWidth / 856;
	// }

	// Not icon menu bar in view-only mode
	if (viewOnlyMode) {
		menuScale = 0;
	}

	// window width minus padding
	var freeWidth   = window.innerWidth  - 26;
	//  bottom margin, and bottom buttons
	//  height scaled by ratio (like half the screen height)
	var sage2Aspect = this.config.totalWidth / this.config.totalHeight;

	// Calculate new sizes
	var drawWidth  = Math.floor(freeWidth * 1);
	var drawHeight = Math.floor(freeWidth * 1 / sage2Aspect);

	if ((drawHeight / window.innerHeight) < ratio) {
		// the UI is already smaller than needed
		ratio = 1.0;
		drawWidth  = Math.floor(freeWidth * ratio);
		drawHeight = Math.floor(freeWidth * ratio / sage2Aspect);
	}

	// height minus the button labels and the buttons and the top menubar
	var freeHeight  = (window.innerHeight * ratio) - 24 - (86 * menuScale) - 40;
	if (freeHeight < 100) {
		freeHeight = 100;
	}

	// Check if it fits
	if (drawHeight >= freeHeight) {
		drawHeight = Math.floor(freeHeight);
		drawWidth  = Math.floor(drawHeight * sage2Aspect);
	}

	displayUI.style.marginLeft = parseInt((freeWidth - drawWidth) / 2 + 10, 10) + "px";

	var minDim = Math.min(drawWidth, drawHeight);
	this.fileDropFontSize = Math.round(minDim * 0.075);
	this.scale = drawWidth / this.config.totalWidth;

	sage2UI.width  = drawWidth;
	sage2UI.height = drawHeight;
	applicationsDiv.style.width  = drawWidth  + "px";
	applicationsDiv.style.height = drawHeight + "px";
	partitionsDiv.style.width    = drawWidth  + "px";
	partitionsDiv.style.height   = drawHeight + "px";
	displayUI.style.height = (drawHeight + 5) + "px";

	// adjust the top menubar width
	var mainMenuBar = document.getElementById('mainMenuBar');
	mainMenuBar.style.width = window.innerWidth + "px";
	$$("toplayout").adjust();

	this.resizeAppWindows();
	this.resizePartitionWindows();

	this.draw();
};

SAGE2DisplayUI.prototype.resizeAppWindows = function(event) {
	var key;
	for (key in this.applications) {
		var appWindow = document.getElementById(key);
		var appWindowTitle = document.getElementById(key + "_title");
		var appWindowArea  = document.getElementById(key + "_area");

		appWindow.style.width  = Math.round(this.applications[key].width * this.scale) + "px";
		appWindow.style.height = Math.round((this.applications[key].height + this.config.ui.titleBarHeight) * this.scale) + "px";
		appWindow.style.left   = Math.round(this.applications[key].left * this.scale) + "px";
		appWindow.style.top    = Math.round(this.applications[key].top * this.scale) + "px";

		appWindowTitle.style.width  = Math.round(this.applications[key].width * this.scale) + "px";
		appWindowTitle.style.height = Math.round(this.config.ui.titleBarHeight * this.scale) + "px";

		appWindowArea.style.top    = Math.round(this.config.ui.titleBarHeight * this.scale) + "px";
		appWindowArea.style.width  = Math.round(this.applications[key].width * this.scale) + "px";
		appWindowArea.style.height = Math.round(this.applications[key].height * this.scale) + "px";

		if (this.applications[key].sticky === true) {
			var windowIconPinned = document.getElementById(key + "_iconPinned");
			var windowIconPinout = document.getElementById(key + "_iconPinout");
			windowIconPinned.style.height = Math.round(this.config.ui.titleBarHeight * this.scale) + "px";
			windowIconPinout.style.height = Math.round(this.config.ui.titleBarHeight * this.scale) + "px";
		}
	}
};

SAGE2DisplayUI.prototype.resizePartitionWindows = function(event) {
	var key;
	for (key in this.partitions) {
		var ptnWindow = document.getElementById(key);
		var ptnWindowTitle = document.getElementById(key + "_title");
		var ptnWindowArea  = document.getElementById(key + "_area");

		ptnWindow.style.width  = Math.round(this.partitions[key].width * this.scale) + "px";
		ptnWindow.style.height = Math.round((this.partitions[key].height + this.config.ui.titleBarHeight) * this.scale) + "px";
		ptnWindow.style.left   = Math.round(this.partitions[key].left * this.scale) + "px";
		ptnWindow.style.top    = Math.round(this.partitions[key].top * this.scale) + "px";

		ptnWindowTitle.style.width  = Math.round(this.partitions[key].width * this.scale) + "px";
		ptnWindowTitle.style.height = Math.round(this.config.ui.titleBarHeight * this.scale) + "px";

		ptnWindowArea.style.top    = Math.round(this.config.ui.titleBarHeight * this.scale) + "px";
		ptnWindowArea.style.width  = Math.round(this.partitions[key].width * this.scale) + "px";
		ptnWindowArea.style.height = Math.round(this.partitions[key].height * this.scale) + "px";
	}
};

SAGE2DisplayUI.prototype.svgLoaded = function(event) {
	this.logoLoaded = true;
	this.resize();
};

/**
 * Generate an image icon for media stream apps
 *
 * @method generateMediaStreamIcon
 * @param title {String} title of application
 * @param color {String} color for background of icon
 */
SAGE2DisplayUI.prototype.generateMediaStreamIcon = function(title, color) {
	var msiCtx = this.mediaStreamIcon.getContext('2d');
	msiCtx.clearRect(0, 0, this.mediaStreamIcon.width, this.mediaStreamIcon.height);
	var size = this.mediaStreamIcon.width;
	var mid = size / 2;
	var x, y, w, h;
	var radius;

	x = mid - (size * 0.1);
	y = mid + (size * 0.2125);
	w = size * 0.2;
	h = size * 0.15;
	msiCtx.fillStyle = "rgba(150, 150, 150, 1.0)";
	msiCtx.fillRect(x, y, w, h);

	radius = 0.035 * size;
	x = mid - (size * 0.2);
	y = mid + (size * 0.3125);
	w = size * 0.4;
	h = size * 0.1;
	msiCtx.fillStyle = "rgba(150, 150, 150, 1.0)";
	this.drawRoundedRect(msiCtx, x, y, w, h, radius, true, false);

	var strokeWidth = 0.0209 * size;
	radius = 0.035 * size;
	x = mid - (size * 0.5)    + (strokeWidth / 2);
	y = mid - (size * 0.4125) + (strokeWidth / 2);
	w = size - strokeWidth;
	h = w * 0.625;
	if (color) {
		msiCtx.fillStyle = color;
	} else {
		msiCtx.fillStyle = "rgba(150, 180, 220, 1.0)";
	}
	msiCtx.lineWidth = strokeWidth;
	msiCtx.strokeStyle = "rgba(150, 150, 150, 1.0)";
	this.drawRoundedRect(msiCtx, x, y, w, h, radius, true, true);

	var mediaTextSize = size * 0.1;
	var mediaTextW = (size - 2 * strokeWidth) * 0.9;
	msiCtx.font = mediaTextSize + "px Verdana";
	msiCtx.fillStyle = "rgba(0, 0, 0, 1.0)";
	var mediaTextLines = this.textLineCount(msiCtx, title, mediaTextW);
	var mediaTextLineHeight = mediaTextSize * 1.2;
	var mediaTextH = mediaTextLineHeight * mediaTextLines;
	var mediaTextX = (x + w / 2) - (mediaTextW / 2);
	var mediaTextY = (y + h / 2) - (mediaTextH / 2);
	msiCtx.fillStyle = "rgba(255, 255, 255, 0.4)";
	this.drawRoundedRect(msiCtx, mediaTextX, mediaTextY, mediaTextW, mediaTextH, radius, true, false);


	mediaTextX = (x + w / 2) + mediaTextSize * 0.175;
	mediaTextY = (y + h / 2) - ((mediaTextLines - 1) / 2) * mediaTextLineHeight + mediaTextSize * 0.333;
	msiCtx.textAlign = "center";
	msiCtx.fillStyle = "rgba(0, 0, 0, 1.0)";
	this.wrapText(msiCtx, title, mediaTextX, mediaTextY, mediaTextW, mediaTextLineHeight);

	return this.mediaStreamIcon.toDataURL("image/png");
};

/**
 * Update the upload progress bar
 *
 * @method setUploadPercent
 * @param percent {Number} progress [0.0 - 1.0]
 */
SAGE2DisplayUI.prototype.setUploadPercent = function(percent) {
	this.uploadPercent = percent; // [0.0 - 1.0]   (not 0 - 100)
};

/**
 * Add an application with its icon and draw
 *
 * @method addAppWindow
 * @param data {Object} contains .icon image of the application
 */
SAGE2DisplayUI.prototype.addAppWindow = function(data) {
	var applicationsDiv  = document.getElementById('applicationsDiv');

	var appWindow = document.createElement('div');
	appWindow.id = data.id;
	appWindow.className    = "appWindow";
	appWindow.style.width  = Math.round(data.width * this.scale) + "px";
	appWindow.style.height = Math.round((data.height + this.config.ui.titleBarHeight) * this.scale) + "px";
	appWindow.style.left   = Math.round(data.left * this.scale) + "px";
	appWindow.style.top    = Math.round(data.top * this.scale) + "px";
	appWindow.style.zIndex = this.appCount + 2;

	var appWindowTitle = document.createElement('div');
	appWindowTitle.id = data.id + "_title";
	appWindowTitle.className    = "appWindowTitle";
	appWindowTitle.style.left   = "0px";
	appWindowTitle.style.top    = "0px";
	appWindowTitle.style.width  = Math.round(data.width * this.scale) + "px";
	appWindowTitle.style.height = Math.round(this.config.ui.titleBarHeight * this.scale) + "px";

	var appWindowArea = document.createElement('div');
	appWindowArea.id = data.id + "_area";
	appWindowArea.className    = "appWindowArea";
	appWindowArea.style.left   = "0px";
	appWindowArea.style.top    = Math.round(this.config.ui.titleBarHeight * this.scale) + "px";
	appWindowArea.style.width  = Math.round(data.width * this.scale) + "px";
	appWindowArea.style.height = Math.round(data.height * this.scale) + "px";

	var appIcon = document.createElement('img');
	appIcon.id = data.id + "_icon";
	appIcon.className = "appWindowIcon";
	appIcon.onerror = function(event) {
		setTimeout(function() {
			if (data.icon) {
				if (data.icon.startsWith('data:image')) {
					appIcon.src = data.icon;
				} else {
					appIcon.src = data.icon + "_512.jpg";
				}
			} else {
				appIcon.src = "/images/unknownapp_512.jpg";
			}
		}, 1000);
	};

	if (data.icon) {
		if (data.icon.startsWith('data:image')) {
			appIcon.src = data.icon;
		} else {
			appIcon.src = data.icon + "_512.jpg";
		}
	} else if (data.application === "media_stream" || data.application === "media_block_stream") {
		appIcon.src = this.generateMediaStreamIcon(data.title, data.color);
	} else {
		// appIcon.src = "images/blank.png";
		appIcon.src = "images/unknownapp_512.png";
	}

	if (data.sticky === true) {
		var windowIconPinned = document.createElement("img");
		windowIconPinned.id  = data.id + "_iconPinned";
		windowIconPinned.className = "invertedIcon";
		windowIconPinned.src = "images/ui/window-pinnedUI.svg";
		windowIconPinned.style.height = Math.round(this.config.ui.titleBarHeight * this.scale) + "px";
		windowIconPinned.style.position = "absolute";
		windowIconPinned.style.left    = "0px";
		windowIconPinned.style.display  = "none";
		appWindowTitle.appendChild(windowIconPinned);

		var windowIconPinout = document.createElement("img");
		windowIconPinout.id  = data.id + "_iconPinout";
		windowIconPinout.className = "invertedIcon";
		windowIconPinout.src = "images/ui/window-pinoutUI.svg";
		windowIconPinout.style.height = Math.round(this.config.ui.titleBarHeight * this.scale) + "px";
		windowIconPinout.style.position = "absolute";
		windowIconPinout.style.left    = "0px";
		windowIconPinout.style.display  = "none";
		appWindowTitle.appendChild(windowIconPinout);
	}
	appWindowArea.appendChild(appIcon);
	appWindow.appendChild(appWindowTitle);
	appWindow.appendChild(appWindowArea);
	applicationsDiv.appendChild(appWindow);

	this.appCount++;
	this.applications[data.id] = data;
};


SAGE2DisplayUI.prototype.showStickyPin = function(data) {
	if (data.sticky !== true) {
		return;
	}
	var windowIconPinned = document.getElementById(data.id + "_iconPinned");
	var windowIconPinout = document.getElementById(data.id + "_iconPinout");
	if (data.pinned === true) {
		windowIconPinned.style.display = "block";
		windowIconPinout.style.display = "none";
	} else {
		windowIconPinned.style.display = "none";
		windowIconPinout.style.display = "block";
	}
};

SAGE2DisplayUI.prototype.hideStickyPin = function(data) {
	if (data.sticky !== true) {
		return;
	}
	var windowIconPinned = document.getElementById(data.id + "_iconPinned");
	var windowIconPinout = document.getElementById(data.id + "_iconPinout");
	windowIconPinned.style.display = "none";
	windowIconPinout.style.display = "none";
};

/**
 * Add ui element showing partition
 *
 * @method addPartitionBorder
 * @param data {Object} - contains partition information
 */
SAGE2DisplayUI.prototype.addPartitionBorder = function(data) {
	var partitionsDiv  = document.getElementById('partitionsDiv');

	var ptnWindow = document.createElement('div');
	ptnWindow.id = data.id;
	ptnWindow.className    = "ptnWindow";
	ptnWindow.style.width  = Math.round(data.width * this.scale) + "px";
	ptnWindow.style.height = Math.round((data.height + this.config.ui.titleBarHeight) * this.scale) + "px";
	ptnWindow.style.left   = Math.round(data.left * this.scale) + "px";
	ptnWindow.style.top    = Math.round(data.top * this.scale) + "px";
	ptnWindow.style.zIndex = 1;

	var ptnWindowTitle = document.createElement('div');
	ptnWindowTitle.id = data.id + "_title";
	ptnWindowTitle.className    = "ptnWindowTitle";
	ptnWindowTitle.style.backgroundColor = data.color;
	ptnWindowTitle.style.left   = "0px";
	ptnWindowTitle.style.top    = "0px";
	ptnWindowTitle.style.width  = Math.round(data.width * this.scale) + "px";
	ptnWindowTitle.style.height = Math.round(this.config.ui.titleBarHeight * this.scale) + "px";

	var ptnWindowArea = document.createElement('div');
	ptnWindowArea.id = data.id + "_area";
	ptnWindowArea.className    = "ptnWindowArea";
	ptnWindowArea.style.backgroundColor = "rgba(1, 1, 1, 0.25)";
	ptnWindowArea.style.left   = "0px";
	ptnWindowArea.style.top    = Math.round(this.config.ui.titleBarHeight * this.scale) + "px";
	ptnWindowArea.style.width  = Math.round(data.width * this.scale) + "px";
	ptnWindowArea.style.height = Math.round(data.height * this.scale) + "px";

	ptnWindow.appendChild(ptnWindowTitle);
	ptnWindow.appendChild(ptnWindowArea);
	partitionsDiv.appendChild(ptnWindow);

	this.ptnCount++;
	this.partitions[data.id] = data;
};

/**
 * Reorder the application list and draw
 *
 * @method updateItemOrder
 * @param order {Object} contains the application ids and zIndex
 */
SAGE2DisplayUI.prototype.updateItemOrder = function(order) {
	var key;
	for (key in order) {
		if (this.applications.hasOwnProperty(key)) {
			var appWindow = document.getElementById(key);
			appWindow.style.zIndex = order[key];
		}
	}
};

/**
 * Move an application and redraw
 *
 * @method setItemPosition
 * @param position_data {Object}  oject with .elemId .elemLeft .elemTop .elemWidth .elemHeight fields
 */
SAGE2DisplayUI.prototype.setItemPosition = function(position_data) {
	this.applications[position_data.elemId].left = position_data.elemLeft;
	this.applications[position_data.elemId].top  = position_data.elemTop;

	var appWindow = document.getElementById(position_data.elemId);

	appWindow.style.left = Math.round(position_data.elemLeft * this.scale) + "px";
	appWindow.style.top = Math.round(position_data.elemTop * this.scale) + "px";
};

/**
 * Move and scale an application and redraw
 *
 * @method setItemPositionAndSize
 * @param position_data {Object}  oject with .elemId .elemLeft .elemTop .elemWidth .elemHeight fields
 */
SAGE2DisplayUI.prototype.setItemPositionAndSize = function(position_data) {
	this.applications[position_data.elemId].left   = position_data.elemLeft;
	this.applications[position_data.elemId].top    = position_data.elemTop;
	this.applications[position_data.elemId].width  = position_data.elemWidth;
	this.applications[position_data.elemId].height = position_data.elemHeight;

	var appWindow = document.getElementById(position_data.elemId);
	var appWindowTitle = document.getElementById(position_data.elemId + "_title");
	var appWindowArea = document.getElementById(position_data.elemId + "_area");

	appWindow.style.width = Math.round(position_data.elemWidth * this.scale) + "px";
	appWindow.style.height = Math.round((position_data.elemHeight + this.config.ui.titleBarHeight) * this.scale) + "px";
	appWindow.style.left = Math.round(position_data.elemLeft * this.scale) + "px";
	appWindow.style.top = Math.round(position_data.elemTop * this.scale) + "px";

	appWindowTitle.style.width = Math.round(position_data.elemWidth * this.scale) + "px";

	appWindowArea.style.width = Math.round(position_data.elemWidth * this.scale) + "px";
	appWindowArea.style.height = Math.round(position_data.elemHeight * this.scale) + "px";
};

/**
 * Move and scale a partition and redraw
 *
 * @method setPartitionPositionAndSize
 * @param data {Object}  oject with .id .left .top .width .height fields
 */
SAGE2DisplayUI.prototype.setPartitionPositionAndSize = function(data) {
	this.partitions[data.id] = data;

	var ptnWindow = document.getElementById(data.id);
	var ptnWindowTitle = document.getElementById(data.id + "_title");
	var ptnWindowArea = document.getElementById(data.id + "_area");

	ptnWindow.style.width = Math.round(data.width * this.scale) + "px";
	ptnWindow.style.height = Math.round((data.height + this.config.ui.titleBarHeight) * this.scale) + "px";
	ptnWindow.style.left = Math.round(data.left * this.scale) + "px";
	ptnWindow.style.top = Math.round(data.top * this.scale) + "px";

	ptnWindowTitle.style.width = Math.round(data.width * this.scale) + "px";

	ptnWindowArea.style.width = Math.round(data.width * this.scale) + "px";
	ptnWindowArea.style.height = Math.round(data.height * this.scale) + "px";
};


/**
 * Delete an application and draw
 *
 * @method deleteApp
 * @param id {String} application id
 */
SAGE2DisplayUI.prototype.deleteApp = function(id) {
	var applicationsDiv  = document.getElementById('applicationsDiv');
	var appWindow = document.getElementById(id);
	applicationsDiv.removeChild(appWindow);

	delete this.applications[id];
};

/**
 * Delete a partition and draw
 *
 * @method deletePartition
 * @param id {String} partition id
 */
SAGE2DisplayUI.prototype.deletePartition = function(id) {
	var partitionsDiv  = document.getElementById('partitionsDiv');
	var ptnWindow = document.getElementById(id);
	partitionsDiv.removeChild(ptnWindow);

	delete this.partitions[id];
};

SAGE2DisplayUI.prototype.updateHighlightedPartition = function(data) {
	// for (var p in this.partitions) {
	// 	var ptnElem = document.getElementById(p + "_area");


	// 	ptnElem.style.backgroundColor = "rgba(1, 1, 1, 0.25)";
	// 	ptnElem.style.border = "1px solid #a5a5a5";
	// }

	// // if a value was passed, highlight this value
	// if (id) {
	// 	let highlighted = document.getElementById(id + "_area");

	// 	highlighted.style.backgroundColor = "rgba(1, 1, 1, 0.5)";
	// 	highlighted.style.border = "6px solid #fff723";
	// }

	if (!data) {
		for (var p in this.partitions) {
			var ptnElem = document.getElementById(p + "_area");

			ptnElem.style.backgroundColor = "rgba(1, 1, 1, 0.25)";
			ptnElem.style.border = "1px solid #a5a5a5";
		}
	} else {
		var highlighted = document.getElementById(data.id + "_area");

		if (this.partitions.hasOwnProperty(data.id) && highlighted) {

			if (data.highlight) {
				highlighted.style.backgroundColor = "rgba(1, 1, 1, 0.5)";
				highlighted.style.border = "6px solid #fff723";
			} else {
				highlighted.style.backgroundColor = "rgba(1, 1, 1, 0.25)";
				highlighted.style.border = "1px solid #a5a5a5";
			}
		}
	}
};

SAGE2DisplayUI.prototype.setPartitionColor = function (data) {
	if (data && data.id) {
		var ptnWindowTitle = document.getElementById(data.id + "_title");

		if (ptnWindowTitle) {
			ptnWindowTitle.style.backgroundColor = data.color;
		}
	}
};

/**
 * Draw a rounded rectangle
 *
 * @method drawRoundedRect
 * @param ctx {Object} canvas context
 * @param x {Number} position x
 * @param y {Number} position y
 * @param width {Number}  width
 * @param height {Number} height
 * @param radius {Number} radius of corner
 * @param fillFlag {Bool} whether to fill or not
 * @param strokeFlag {Bool} whether to stroke or not
 */
SAGE2DisplayUI.prototype.drawRoundedRect = function(ctx, x, y, width, height, radius, fillFlag, strokeFlag) {
	ctx.beginPath();
	ctx.moveTo(x + radius, y);
	ctx.lineTo(x + width - radius, y);
	ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
	ctx.lineTo(x + width, y + height - radius);
	ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
	ctx.lineTo(x + radius, y + height);
	ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
	ctx.lineTo(x, y + radius);
	ctx.quadraticCurveTo(x, y, x + radius, y);
	ctx.closePath();
	if (fillFlag === true) {
		ctx.fill();
	}
	if (strokeFlag === true) {
		ctx.stroke();
	}
};

/**
 * Count the number of lines for a given maximum width
 *
 * @method textLineCount
 * @param ctx {Object} canvas context
 * @param text {String} text to be drawn
 * @param maxWidth {Number} maximum width
 */
SAGE2DisplayUI.prototype.textLineCount = function(ctx, text, maxWidth) {
	var words = text.split(" ");
	var line  = "";
	var count = 1;

	for (var n = 0; n < words.length; n++) {
		var testLine = line + words[n] + " ";
		var testWidth = ctx.measureText(testLine).width;
		if (testWidth > maxWidth && n > 0) {
			line = words[n] + ' ';
			count++;
		} else {
			line = testLine;
		}
	}
	return count;
};

/**
 * Draw some text, and wrap it over multiple lines if necessary
 *
 * @method wrapText
 * @param ctx {Object} canvas context
 * @param text {String} text to be drawn
 * @param x {Number} position x
 * @param y {Number} position y
 * @param maxWidth {Number} maximum width
 * @param lineHeight {Number} line height
 */
SAGE2DisplayUI.prototype.wrapText = function(ctx, text, x, y, maxWidth, lineHeight) {
	var words = text.split(" ");
	var line  = "";

	for (var n = 0; n < words.length; n++) {
		var testLine  = line + words[n] + " ";
		var testWidth = ctx.measureText(testLine).width;
		if (testWidth > maxWidth && n > 0) {
			ctx.fillText(line, x, y);
			line = words[n] + ' ';
			y += lineHeight;
		} else {
			line = testLine;
		}
	}
	ctx.fillText(line, x, y);
};

/**
 * Handler for mouse up
 *
 * @method pointerPress
 * @param btn {String} mouse button name (left, right, middle)
 */
SAGE2DisplayUI.prototype.pointerPress = function(btn) {
	if (btn !== "right") {
		this.wsio.emit('pointerPress', {button: btn});
	}
};

/**
 * Handler for mouse up
 *
 * @method pointerRelease
 * @param btn {String} mouse button name (left, right, middle)
 */
SAGE2DisplayUI.prototype.pointerRelease = function(btn) {
	if (btn !== "right") {
		this.wsio.emit('pointerRelease', {button: btn});
	}
};


function underElement(elem, pageX, pageY) {
	var elemPosition   = {top: elem.offsetTop, left: elem.offsetLeft};
	var elemPosition2  = {
		top:  elemPosition.top  + elem.clientHeight,
		left: elemPosition.left + elem.clientWidth
	};
	return ((pageX > elemPosition.left && pageX < elemPosition2.left) &&
			(pageY  > elemPosition.top  && pageY < elemPosition2.top));
}


/**
 * Highlight the top most application under the cursor
 *
 * @method highlightApplication
 * @param x {Number} x value
 * @param y {Number} y value
 */
SAGE2DisplayUI.prototype.highlightApplication = function(x, y) {
	var topApp   = null;
	var topLevel = -1;
	for (var a in this.applications) {
		var app = document.getElementById(this.applications[a].id);
		if (app) {
			var isapp = underElement(app, x, y);
			if (isapp) {
				var zi = parseInt(app.style.zIndex, 10);
				if (zi >= topLevel) {
					topLevel = zi;
					topApp = app;
				}
			}
			// remove decoration
			app.className = "appWindow";
			var area = document.getElementById(this.applications[a].id + "_title");
			area.className = "appWindowTitle";
		}
	}
	// Once we checked all the application, we can draw the top one
	// with decoration
	if (topApp) {
		// Since we have only one class, we dont have to use classList
		topApp.className = "appWindowHover";
		var title = document.getElementById(topApp.id + "_title");
		title.className = "appWindowTitleHover";
	}
};

/**
 * Handler for mouse move
 *
 * @method pointerMove
 * @param x {Number} x value
 * @param y {Number} y value
 */
SAGE2DisplayUI.prototype.pointerMove = function(x, y) {
	if (this.pointerX === x && this.pointerY === y) {
		return;
	}
	this.pointerX = x;
	this.pointerY = y;
	var globalX = this.pointerX / this.scale;
	var globalY = this.pointerY / this.scale;
	this.wsio.emit('pointerPosition', {pointerX: globalX, pointerY: globalY});
};

/**
 * Handler for scrolling
 *
 * @method pointerScroll
 * @param value {Number} scroll amount
 */
SAGE2DisplayUI.prototype.pointerScroll = function(x, y, value) {
	if (this.scrollTimeId === null) {
		this.pointerMove(x, y);
		this.wsio.emit('pointerScrollStart');
	} else {
		clearTimeout(this.scrollTimeId);
	}
	this.wsio.emit('pointerScroll', {wheelDelta: value});

	var _this = this;
	this.scrollTimeId = setTimeout(function() {
		_this.wsio.emit('pointerScrollEnd');
		_this.scrollTimeId = null;
	}, 500);
};

/**
 * Handler for double click
 *
 * @method pointerDblClick
 */
SAGE2DisplayUI.prototype.pointerDblClick = function() {
	this.wsio.emit('pointerDblClick');
};

/**
 * Handler for key down
 *
 * @method keyDown
 * @param keyCode {Number} character code
 */
SAGE2DisplayUI.prototype.keyDown = function(x, y, keyCode) {
	if (keyCode !== 27) { // not ESC key
		this.pointerMove(x, y);
		this.wsio.emit('keyDown', {code: keyCode});
		if (keyCode === 9) { // tab is a special case - must emulate keyPress event
			this.wsio.emit('keyPress', {code: keyCode, character: String.fromCharCode(keyCode)});
		}
		// if a special key - prevent default (otherwise let continue to keyPress)
		if (keyCode <= 7 || (keyCode >= 10 && keyCode <= 15) || keyCode === 32 ||
			(keyCode >= 47 && keyCode <= 90) || (keyCode >= 94 && keyCode <= 111) ||
			keyCode >= 146) {
			return false;
		}
	}
	return true;
};

/**
 * Handler for key up
 *
 * @method keyUp
 * @param keyCode {Number} character code
 */
SAGE2DisplayUI.prototype.keyUp = function(x, y, keyCode) {
	if (keyCode !== 27) { // not ESC key
		this.pointerMove(x, y);
		this.wsio.emit('keyUp', {code: keyCode});
	}
	return true;
};

/**
 * Handler for key press
 *
 * @method keyPress
 * @param charCode {Number} character code
 */
SAGE2DisplayUI.prototype.keyPress = function(x, y, charCode) {
	this.pointerMove(x, y);
	this.wsio.emit('keyPress', {code: charCode, character: String.fromCharCode(charCode)});
	return true;
};