1 /** 2 * Copyright (C) 2013 KO GmbH <copyright@kogmbh.com> 3 * 4 * @licstart 5 * This file is part of WebODF. 6 * 7 * WebODF is free software: you can redistribute it and/or modify it 8 * under the terms of the GNU Affero General Public License (GNU AGPL) 9 * as published by the Free Software Foundation, either version 3 of 10 * the License, or (at your option) any later version. 11 * 12 * WebODF is distributed in the hope that it will be useful, but 13 * WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 * GNU Affero General Public License for more details. 16 * 17 * You should have received a copy of the GNU Affero General Public License 18 * along with WebODF. If not, see <http://www.gnu.org/licenses/>. 19 * @licend 20 * 21 * @source: http://www.webodf.org/ 22 * @source: https://github.com/kogmbh/WebODF/ 23 */ 24 25 /*global runtime, gui, ops, odf, core*/ 26 27 /** 28 * Provides a method to paste text at the current cursor 29 * position, and processes the input string to understand 30 * special structuring such as paragraph splits. 31 * @implements {core.Destroyable} 32 * @param {!ops.Session} session 33 * @param {!gui.SessionConstraints} sessionConstraints 34 * @param {!gui.SessionContext} sessionContext 35 * @param {!string} inputMemberId 36 * @constructor 37 */ 38 gui.PasteController = function PasteController(session, sessionConstraints, sessionContext, inputMemberId) { 39 "use strict"; 40 41 var odtDocument = session.getOdtDocument(), 42 isEnabled = false, 43 /**@const*/ 44 textns = odf.Namespaces.textns, 45 /**@const*/ 46 NEXT = core.StepDirection.NEXT, 47 odfUtils = odf.OdfUtils; 48 49 /** 50 * @return {undefined} 51 */ 52 function updateEnabledState() { 53 if (sessionConstraints.getState(gui.CommonConstraints.EDIT.REVIEW_MODE) === true) { 54 isEnabled = /**@type{!boolean}*/(sessionContext.isLocalCursorWithinOwnAnnotation()); 55 } else { 56 isEnabled = true; 57 } 58 } 59 60 /** 61 * @param {!ops.OdtCursor} cursor 62 * @return {undefined} 63 */ 64 function onCursorEvent(cursor) { 65 if (cursor.getMemberId() === inputMemberId) { 66 updateEnabledState(); 67 } 68 } 69 70 /** 71 * @return {!boolean} 72 */ 73 this.isEnabled = function () { 74 return isEnabled; 75 }; 76 77 /** 78 * @param {!string} data 79 * @return {undefined} 80 */ 81 this.paste = function (data) { 82 if (!isEnabled) { 83 return; 84 } 85 86 var originalCursorPosition = odtDocument.getCursorPosition(inputMemberId), 87 cursorNode = odtDocument.getCursor(inputMemberId).getNode(), 88 originalParagraph = /**@type{!Element}*/(odfUtils.getParagraphElement(cursorNode)), 89 paragraphStyle = originalParagraph.getAttributeNS(textns, "style-name") || "", 90 /**@type{number}*/ 91 cursorPosition = originalCursorPosition, 92 operations = [], 93 currentParagraphStartPosition = odtDocument.convertDomPointToCursorStep(originalParagraph, 0, NEXT), 94 paragraphs; 95 96 paragraphs = data.replace(/\r/g, "").split("\n"); 97 paragraphs.forEach(function (text) { 98 var insertTextOp = new ops.OpInsertText(), 99 splitParagraphOp = new ops.OpSplitParagraph(); 100 101 insertTextOp.init({ 102 memberid: inputMemberId, 103 position: cursorPosition, 104 text: text, 105 moveCursor: true 106 }); 107 operations.push(insertTextOp); 108 cursorPosition += text.length; 109 110 splitParagraphOp.init({ 111 memberid: inputMemberId, 112 position: cursorPosition, 113 paragraphStyleName: paragraphStyle, 114 sourceParagraphPosition: currentParagraphStartPosition, 115 moveCursor: true 116 }); 117 operations.push(splitParagraphOp); 118 cursorPosition += 1; // Splitting a paragraph introduces 1 walkable position, bumping the cursor forward 119 currentParagraphStartPosition = cursorPosition; // Reset the source paragraph to the newly created one 120 }); 121 122 // Discard the last split paragraph op as unnecessary. 123 // Reasoning through the scenarios, this produces the most intuitive behaviour: 124 // 1. Paste a single line - No line split should be added 125 // 2. Paste two lines - Only one paragraph split is necessary per new paragraph. As pasting MUST occur within an 126 // existing paragraph, only a single split should occur. 127 operations.pop(); 128 129 session.enqueue(operations); 130 }; 131 132 /** 133 * @param {!function(!Error=)} callback, passing an error object in case of error 134 * @return {undefined} 135 */ 136 this.destroy = function (callback) { 137 odtDocument.unsubscribe(ops.Document.signalCursorMoved, onCursorEvent); 138 sessionConstraints.unsubscribe(gui.CommonConstraints.EDIT.REVIEW_MODE, updateEnabledState); 139 callback(); 140 }; 141 142 function init() { 143 odtDocument.subscribe(ops.Document.signalCursorMoved, onCursorEvent); 144 sessionConstraints.subscribe(gui.CommonConstraints.EDIT.REVIEW_MODE, updateEnabledState); 145 updateEnabledState(); 146 } 147 init(); 148 }; 149