/* This file contains proprietary software owned by Motorola Mobility, Inc.
No rights, expressed or implied, whatsoever to this software are provided by Motorola Mobility, Inc. hereunder.
(c) Copyright 2011 Motorola Mobility, Inc. All Rights Reserved.
*/ //////////////////////////////////////////////////////////////////////// // var Montage = require("montage/core/core").Montage, Component = require("montage/ui/component").Component, NJUtils = require("js/lib/NJUtils").NJUtils, World = require("js/lib/drawing/world").World; var ClipboardController = exports.ClipboardController = Montage.create(Component, { hasTemplate: { value: false }, deserializedFromTemplate: { value: function() { document.body.addEventListener("copy", this, false); document.body.addEventListener("cut", this, false); document.body.addEventListener("paste", this, false); //ninja menu events this.eventManager.addEventListener("executeCut", this, false); this.eventManager.addEventListener("executeCopy", this, false); this.eventManager.addEventListener("executePaste", this, false); } }, clipboardOperationsAgent:{//appropriate agent instant required for execution of cut/copy/paste value: null }, //count how many times pasted //used to move multiple pastes of same copy pasteCounter:{ value: 0 }, copiedObjects:{ value: {} }, _copyFlag:{ value:false }, copyFlag:{ get:function(){return this._copyFlag;}, set:function(value){this._copyFlag = value;} }, _newCopyFlag:{ value:true }, newCopyFlag:{ get:function(){return this._newCopyFlag;}, set:function(value){this._newCopyFlag = value;} }, handleExecuteCopy:{ value: function(){document.execCommand('copy',false,null);} }, handleExecuteCut:{ value: function(){document.execCommand('cut',false,null);} }, handleExecutePaste:{ value: function(){document.execCommand('paste',false,null);} }, handleCopy:{ value:function(clipboardEvent){ if(!this.application.ninja.currentDocument || (this.application.ninja.currentDocument && this.application.ninja.currentDocument.currentView === "code")){ return; }//for design view only // Don't do anything if an input or other control is focused if(document.activeElement.nodeName !== "BODY") { return; } this.copy(clipboardEvent); clipboardEvent.preventDefault(); } }, handleCut:{ value:function(clipboardEvent){ if(this.application.ninja.currentDocument.currentView === "code") return; // Don't do anything if an input or other control is focused if(document.activeElement.nodeName !== "BODY") { return; } this.cut(clipboardEvent); clipboardEvent.preventDefault(); } }, handlePaste:{ value:function(clipboardEvent){ var clipboardData = clipboardEvent.clipboardData, ninjaData = clipboardData.getData("ninja"), htmlData = clipboardData.getData("text/html"), textData = clipboardData.getData("text/plain"), i=0, imageMime, imageData, imageElement; if(!this.application.ninja.currentDocument || (this.application.ninja.currentDocument && this.application.ninja.currentDocument.currentView === "code")){ return; }//for design view only // Don't do anything if an input or other control is focused if(document.activeElement.nodeName !== "BODY") { return; } //TODO: return if stage is not focussed this.pasteCounter++; if(ninjaData){ if(this.copiedObjects.copy){ try{ this.pasteFromCopy(); }catch(e){ console.log(""+e.stack); } } else if(this.copiedObjects.cut){ try{ this.pasteFromCut(); }catch(e){ console.log(""+e.stack); } } } else{ //handle image blobs if(clipboardData.items && (clipboardData.items.length > 0)){ for(i=0; i < clipboardData.items.length; i++ ){ if((clipboardData.items[i].kind === "file") && (clipboardData.items[i].type.indexOf("image") === 0)){//example type -> "image/png" imageMime = clipboardData.items[i].type; imageData = clipboardData.items[i].getAsFile(); try{ imageElement = this.generateImageElement(imageData); }catch(e){ console.log(""+e.stack); } this.application.ninja.selectionController.selectElements(imageElement); this.application.ninja.currentDocument.model.needsSave = true; } } } try{ this.pasteFromExternalSource(htmlData, textData); }catch(e){ console.log(""+e.stack); } } clipboardEvent.preventDefault(); } }, /* parameters: */ copy:{ value: function(clipboardEvent){ var j=0, htmlToClipboard = "", ninjaClipboardObj = {}, textToClipboard = ""; this.copiedObjects = {}; this.pasteCounter = 0; this.copiedObjects["copy"] = []; if(clipboardEvent){ for(j=0; j < this.application.ninja.selectedElements.length; j++){//copying from stage this.copiedObjects.copy.push(this.application.ninja.selectedElements[j]); if(this.application.ninja.selectedElements[j].tagName === "CANVAS"){ if(!ninjaClipboardObj.canvas){ ninjaClipboardObj.canvas = true; } }else{ htmlToClipboard = htmlToClipboard + this.serializeHTMLElement(this.application.ninja.selectedElements[j]); if(!ninjaClipboardObj.plainHtml){ ninjaClipboardObj.plainHtml = true; } textToClipboard = textToClipboard + this.getText(this.application.ninja.selectedElements[j]) + " "; } } //set clipboard data clipboardEvent.clipboardData.setData('ninja', ''+ JSON.stringify(ninjaClipboardObj)); clipboardEvent.clipboardData.setData('text/html', '' + htmlToClipboard + ''); clipboardEvent.clipboardData.setData('text/plain', textToClipboard); } else{ //TODO: custom copy/paste, ex: css, animation, materials } } }, pasteFromCopy:{//todo: change to appropriate name value:function(){ var i=0, j=0, pastedElements = [],//array of te pastes clones - for selection node = null, styles = null, copiedElement = null; //TODO: cleanse HTML for(j=0; j< this.copiedObjects.copy.length; j++){ copiedElement = this.copiedObjects.copy[j]; styles = null; if (copiedElement.tagName === "CANVAS"){ //clone copied canvas var canvas = this.cloneCanvas(copiedElement); pastedElements.push(canvas); } else { node = copiedElement.cloneNode(true); if(copiedElement.ownerDocument.defaultView.getComputedStyle(copiedElement).getPropertyValue("position") === "absolute"){ styles = {}; styles.top = this.application.ninja.elementMediator.getProperty(copiedElement, "top", parseInt); styles.left = this.application.ninja.elementMediator.getProperty(copiedElement, "left", parseInt); styles.position = "absolute"; }else{ styles = null; } this.pastePositioned(node, styles); pastedElements.push(node); } } NJevent("elementAdded", pastedElements); this.application.ninja.currentDocument.model.needsSave = true; } }, pasteFromCut:{ value:function(){ var i=0, j=0, node = null, canvas = null, styles=null, pastedElements = [];//array of te pastes clones - for selection for(j=0; j< this.copiedObjects.cut.length; j++){ node = this.deserializeHtmlString(this.copiedObjects.cut[j].outerhtml)[0]; if (node.tagName === "CANVAS"){ //paste canvas canvas = this.generateNewCanvas(this.copiedObjects.cut[j].outerhtml, this.copiedObjects.cut[j].styles, this.copiedObjects.cut[j].className, this.copiedObjects.cut[j].worldJson); pastedElements.push(canvas); node = null; } else if((node.nodeType === 3) || (node.tagName === "A")){//TextNode node = null; } else { this.pastePositioned(node, this.copiedObjects.cut[j].styles, false/*fromCopy*/); pastedElements.push(node); } } NJevent("elementAdded", pastedElements); this.application.ninja.currentDocument.model.needsSave = true; } }, //paste from external applicaitons pasteFromExternalSource:{//todo: change to pasteNinja, pasteHTML, etc value: function(htmlData, textData){ var i=0, j=0, pasteDataObject=null, pastedElements = null, node = null, nodeList = null, styles = null, divWrapper = null, spanWrapper = null, metaEl = null, self = this; if(htmlData){ //TODO: cleanse HTML htmlData.replace(/["+ textData +"")[0]; styles = {"position":"absolute", "top":"100px", "left":"100px"}; this.pastePositioned(node, styles); } } }, cut:{ value:function(clipboardEvent){ var j=0, htmlToClipboard = "", ninjaClipboardObj = {}, textToClipboard = "", elObj = null; this.copiedObjects = {}; this.pasteCounter = 0; this.copiedObjects["cut"] = []; if(clipboardEvent){ for(j=0; j < this.application.ninja.selectedElements.length; j++){//copying from stage elObj = {}; elObj["outerhtml"] = this.application.ninja.selectedElements[j].outerHTML; if(this.application.ninja.selectedElements[j].tagName === "CANVAS"){ elObj["styles"] = this.getDominantStyles(this.application.ninja.selectedElements[j], true); if(!ninjaClipboardObj.canvas){ ninjaClipboardObj.canvas = true; } elObj["worldJson"] = this.application.ninja.selectedElements[j].elementModel.shapeModel ? this.application.ninja.selectedElements[j].elementModel.shapeModel.GLWorld.exportJSON(): null; elObj["className"] = this.application.ninja.selectedElements[j].className; }else{ elObj["styles"] = this.getDominantStyles(this.application.ninja.selectedElements[j], false); htmlToClipboard = htmlToClipboard + this.serializeHTMLElement(this.application.ninja.selectedElements[j]); if(!ninjaClipboardObj.plainHtml){ ninjaClipboardObj.plainHtml = true; } textToClipboard = textToClipboard + this.getText(this.application.ninja.selectedElements[j]) + " "; } this.copiedObjects.cut.push(elObj); } //set clipboard data clipboardEvent.clipboardData.setData('ninja', ''+ JSON.stringify(ninjaClipboardObj)); clipboardEvent.clipboardData.setData('text/html', '' + htmlToClipboard + ''); clipboardEvent.clipboardData.setData('text/plain', textToClipboard); } else{ //TODO: custom copy/paste, ex: css, animation, materials } this.application.ninja.elementMediator.removeElements(this.application.ninja.selectedElements); clipboardEvent.preventDefault(); } }, deserializeHtmlString:{ value:function(htmlString){ var doc = (this.application.ninja.currentDocument.currentView === "design") ? this.application.ninja.currentDocument.model.views.design.document : document, clipboardHelper=doc.createElement("div"), nodeList = null; clipboardHelper.innerHTML = htmlString; nodeList = clipboardHelper.childNodes; clipboardHelper = null; //for garbage collection return nodeList; } }, serializeHTMLElement:{ value: function(elem){ var computedStyles = null, originalStyleAttr = null, computedStylesStr = "", i=0, stylePropertyName="", outerHtml = ""; originalStyleAttr = elem.getAttribute("style");//preserve the current styles elem.removeAttribute("style"); //build the computed style attribute computedStyles = elem.ownerDocument.defaultView.getComputedStyle(elem); //todo: consider cleaning up the position data [or making position:relative with 0,0] from the computed styles, // so that the object is pasted onto expernal applicaitons [like gmail] with no offset for (i = 0; i < computedStyles.length; i++) { stylePropertyName = computedStyles[i]; computedStylesStr = computedStylesStr + stylePropertyName + ":" + computedStyles.getPropertyValue(stylePropertyName) + ";"; } elem.setAttribute("style", computedStylesStr); outerHtml = elem.outerHTML; elem.setAttribute("style", originalStyleAttr);//reset style after copying to clipboard return outerHtml; } }, cloneCanvas:{ value: function(sourceCanvas){ var canvas, styles, world, worldData; canvas = document.application.njUtils.make("canvas", sourceCanvas.className, this.application.ninja.currentDocument); canvas.width = sourceCanvas.width; canvas.height = sourceCanvas.height; //end - clone copied canvas if (!canvas.getAttribute( "data-RDGE-id" )) canvas.setAttribute( "data-RDGE-id", NJUtils.generateRandom() ); if(sourceCanvas.ownerDocument.defaultView.getComputedStyle(sourceCanvas).getPropertyValue("position") === "absolute"){ styles = canvas.elementModel.data || {}; styles.top = "" + (this.application.ninja.elementMediator.getProperty(sourceCanvas, "top", parseInt) + (25 * this.pasteCounter))+"px"; styles.left = "" + (this.application.ninja.elementMediator.getProperty(sourceCanvas, "left", parseInt) + (25 * this.pasteCounter)) + "px"; }else{ styles = null; } this.application.ninja.elementMediator.addElements(canvas, styles, false); worldData = sourceCanvas.elementModel.shapeModel ? sourceCanvas.elementModel.shapeModel.GLWorld.exportJSON(): null; if(worldData) { var jObj; var index = worldData.indexOf( ';' ); if ((worldData[0] === 'v') && (index < 24)) { // JSON format. separate the version info from the JSON info var jStr = worldData.substr( index+1 ); jObj = JSON.parse( jStr ); world = new World(canvas, jObj.webGL); canvas.elementModel.shapeModel.GLWorld = world; canvas.elementModel.shapeModel.useWebGl = jObj.webGL; world.importJSON(jObj); this.application.ninja.currentDocument.model.webGlHelper.buildShapeModel( canvas.elementModel, world ); } } return canvas; } }, generateNewCanvas: { value: function(outerhtml, styles, className, worldJson){ var canvas, newCanvasStyles, world, worldData; canvas = document.application.njUtils.make("canvas", className, this.application.ninja.currentDocument); canvas.width = styles.width; canvas.height = styles.height; if (!canvas.getAttribute( "data-RDGE-id" )) canvas.setAttribute( "data-RDGE-id", NJUtils.generateRandom() ); this.pastePositioned(canvas, styles, false/*from copy*/); worldData = worldJson; if(worldData) { var jObj; var index = worldData.indexOf( ';' ); if ((worldData[0] === 'v') && (index < 24)) { // JSON format. separate the version info from the JSON info var jStr = worldData.substr( index+1 ); jObj = JSON.parse( jStr ); world = new World(canvas, jObj.webGL); canvas.elementModel.shapeModel.GLWorld = world; canvas.elementModel.shapeModel.useWebGl = jObj.webGL; world.importJSON(jObj); this.application.ninja.currentDocument.model.webGlHelper.buildShapeModel( canvas.elementModel, world ); } } return canvas; } }, serializeCanvas:{ value:function(sourceCanvas){ } }, copyMontageComponents:{ value: function(){ } }, pasteImage:{ value:function(imageData){ } }, pasteMontageComponents:{ value: function(){ } }, getText:{ value: function(element){ var nodeList = element.getElementsByTagName("*"), allText = "", i=0; for(i=0; i < nodeList.length; i++){ if(nodeList[i].nodeType === 3){//text node allText = allText + nodeList[i].innerText + " "; } } } }, pastePositioned:{ value: function(element, styles, fromCopy){// for now can wok for both in-place and centered paste var modObject = [], x,y, newX, newY, counter; if((typeof fromCopy === "undefined") || (fromCopy && fromCopy === true)){ counter = this.pasteCounter; }else{ counter = this.pasteCounter - 1; } x = styles ? ("" + styles.left + "px") : "100px"; y = styles ? ("" + styles.top + "px") : "100px"; newX = styles ? ("" + (styles.left + (25 * counter)) + "px") : "100px"; newY = styles ? ("" + (styles.top + (25 * counter)) + "px") : "100px"; if(!styles || (styles && !styles.position)){ this.application.ninja.elementMediator.addElements(element, null, false); }else if(styles && (styles.position === "absolute")){ this.application.ninja.elementMediator.addElements(element, {"top" : newY, "left" : newX}, false);//displace } } }, getDominantStyles:{ value: function(el, isCanvas){ var styles = {}; styles.top = this.application.ninja.elementMediator.getProperty(el, "top", parseInt); styles.left = this.application.ninja.elementMediator.getProperty(el, "left", parseInt); if(el.ownerDocument.defaultView.getComputedStyle(el).getPropertyValue("position") === "absolute"){ styles.position = "absolute"; } if(isCanvas){ styles.width = (el.getAttribute("width") ? el.getAttribute("width") : null); styles.height = (el.getAttribute("height") ? el.getAttribute("height") : null); } return styles; } }, //todo: this will be moved to a seperate api generateImageElement:{ value: function(imageBlob){ var reader = new FileReader(), file = reader.readAsArrayBuffer(imageBlob), url, uri, dir, save, counter, tempName, element, rules, fileName, fileNameOverride, rootUrl = this.application.ninja.coreIoApi.rootUrl+escape((this.application.ninja.documentController.documentHackReference.root.split(this.application.ninja.coreIoApi.cloudData.root)[1])), rootUri = this.application.ninja.documentController.documentHackReference.root; reader.fileName = imageBlob.name, reader.fileType = imageBlob.type, reader.rootUrl = rootUrl, reader.rootUri = rootUri; reader.onload = function (e) { if (this.application.ninja.coreIoApi.directoryExists({uri: e.currentTarget.rootUri+'images'}).status === 204) { uri = e.currentTarget.rootUri+'images'; url = e.currentTarget.rootUrl+'images'; } else if (this.application.ninja.coreIoApi.directoryExists({uri: e.currentTarget.rootUri+'img'}).status === 204) { uri = e.currentTarget.rootUri+'img'; url = e.currentTarget.rootUrl+'img'; } else { dir = this.application.ninja.coreIoApi.createDirectory({uri: e.currentTarget.rootUri+'images'}); if (dir.success && dir.status === 201) { uri = e.currentTarget.rootUri+'images'; url = e.currentTarget.rootUrl+'images'; } else { //TODO: HANDLE ERROR ON CREATING FOLDER } } // //// //fileName is undefined while pasting image from clipboard fileNameOverride = e.currentTarget.fileName ? e.currentTarget.fileName : ("image." + e.currentTarget.fileType.substring((e.currentTarget.fileType.indexOf("/")+1), e.currentTarget.fileType.length));//like image.png //// if (this.application.ninja.coreIoApi.fileExists({uri: uri+'/'+fileNameOverride}).status === 404) { save = this.application.ninja.coreIoApi.createFile({uri: uri+'/'+fileNameOverride, contents: e.currentTarget.result, contentType: e.currentTarget.fileType}); fileName = fileNameOverride; } else { counter = 1; tempName = fileNameOverride.split('.'+(fileNameOverride.split('.')[fileNameOverride.split('.').length-1]))[0]; tempName += '_'+counter+'.'+(fileNameOverride.split('.')[fileNameOverride.split('.').length-1]); while (this.application.ninja.coreIoApi.fileExists({uri: uri+'/'+tempName}).status !== 404) { counter++; tempName = fileNameOverride.split('.'+(fileNameOverride.split('.')[fileNameOverride.split('.').length-1]))[0]; tempName += '_'+counter+'.'+(fileNameOverride.split('.')[fileNameOverride.split('.').length-1]); } save = this.application.ninja.coreIoApi.createFile({uri: uri+'/'+tempName, contents: e.currentTarget.result, contentType: e.currentTarget.fileType}); fileName = tempName; } if (save && save.success && save.status === 201) { var self = this; // if (e.currentTarget.fileType.indexOf('svg') !== -1) { element = NJUtils.make('embed', null, this.application.ninja.currentDocument);//TODO: Verify this is proper element.type = 'image/svg+xml'; element.src = url+'/'+fileName; } else { element = NJUtils.make('image', null, this.application.ninja.currentDocument); element.src = url+'/'+fileName; } //Adding element once it is loaded element.onload = function () { element.onload = null; self.application.ninja.elementMediator.addElements(element, rules, true); }; //Setting rules of element rules = { 'position': 'absolute', 'top' : '100px', 'left' : '100px' }; // self.application.ninja.elementMediator.addElements(element, rules, false); } else { //TODO: HANDLE ERROR ON SAVING FILE TO BE ADDED AS ELEMENT } }.bind(this); return element; } } });