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*/ 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 46 /** 47 * @return {undefined} 48 */ 49 function updateEnabledState() { 50 if (sessionConstraints.getState(gui.CommonConstraints.EDIT.REVIEW_MODE) === true) { 51 isEnabled = /**@type{!boolean}*/(sessionContext.isLocalCursorWithinOwnAnnotation()); 52 } else { 53 isEnabled = true; 54 } 55 } 56 57 /** 58 * @param {!ops.OdtCursor} cursor 59 * @return {undefined} 60 */ 61 function onCursorEvent(cursor) { 62 if (cursor.getMemberId() === inputMemberId) { 63 updateEnabledState(); 64 } 65 } 66 67 /** 68 * @return {!boolean} 69 */ 70 this.isEnabled = function () { 71 return isEnabled; 72 }; 73 74 /** 75 * Rounds to the first step within the paragraph 76 * @param {!number} step 77 * @return {!boolean} 78 */ 79 function roundUp(step) { 80 return step === ops.OdtStepsTranslator.NEXT_STEP; 81 } 82 83 /** 84 * @param {!string} data 85 * @return {undefined} 86 */ 87 this.paste = function (data) { 88 if (!isEnabled) { 89 return; 90 } 91 92 var originalCursorPosition = odtDocument.getCursorPosition(inputMemberId), 93 cursorNode = odtDocument.getCursor(inputMemberId).getNode(), 94 originalParagraph = /**@type{!Element}*/(odtDocument.getParagraphElement(cursorNode)), 95 paragraphStyle = originalParagraph.getAttributeNS(textns, "style-name") || "", 96 /**@type{number}*/ 97 cursorPosition = originalCursorPosition, 98 operations = [], 99 currentParagraphStartPosition = odtDocument.convertDomPointToCursorStep(originalParagraph, 0, roundUp), 100 paragraphs; 101 102 paragraphs = data.replace(/\r/g, "").split("\n"); 103 paragraphs.forEach(function (text) { 104 var insertTextOp = new ops.OpInsertText(), 105 splitParagraphOp = new ops.OpSplitParagraph(); 106 107 insertTextOp.init({ 108 memberid: inputMemberId, 109 position: cursorPosition, 110 text: text, 111 moveCursor: true 112 }); 113 operations.push(insertTextOp); 114 cursorPosition += text.length; 115 116 splitParagraphOp.init({ 117 memberid: inputMemberId, 118 position: cursorPosition, 119 paragraphStyleName: paragraphStyle, 120 sourceParagraphPosition: currentParagraphStartPosition, 121 moveCursor: true 122 }); 123 operations.push(splitParagraphOp); 124 cursorPosition += 1; // Splitting a paragraph introduces 1 walkable position, bumping the cursor forward 125 currentParagraphStartPosition = cursorPosition; // Reset the source paragraph to the newly created one 126 }); 127 128 // Discard the last split paragraph op as unnecessary. 129 // Reasoning through the scenarios, this produces the most intuitive behaviour: 130 // 1. Paste a single line - No line split should be added 131 // 2. Paste two lines - Only one paragraph split is necessary per new paragraph. As pasting MUST occur within an 132 // existing paragraph, only a single split should occur. 133 operations.pop(); 134 135 session.enqueue(operations); 136 }; 137 138 /** 139 * @param {!function(!Error=)} callback, passing an error object in case of error 140 * @return {undefined} 141 */ 142 this.destroy = function (callback) { 143 odtDocument.unsubscribe(ops.Document.signalCursorMoved, onCursorEvent); 144 sessionConstraints.unsubscribe(gui.CommonConstraints.EDIT.REVIEW_MODE, updateEnabledState); 145 callback(); 146 }; 147 148 function init() { 149 odtDocument.subscribe(ops.Document.signalCursorMoved, onCursorEvent); 150 sessionConstraints.subscribe(gui.CommonConstraints.EDIT.REVIEW_MODE, updateEnabledState); 151 updateEnabledState(); 152 } 153 init(); 154 }; 155