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, core, gui, odf, ops, Node*/ 26 27 /** 28 * @constructor 29 * @implements {core.Destroyable} 30 * @param {!ops.Session} session 31 * @param {!gui.SessionConstraints} sessionConstraints 32 * @param {!string} inputMemberId 33 */ 34 gui.AnnotationController = function AnnotationController(session, sessionConstraints, inputMemberId) { 35 "use strict"; 36 37 var odtDocument = session.getOdtDocument(), 38 isAnnotatable = false, 39 eventNotifier = new core.EventNotifier([gui.AnnotationController.annotatableChanged]), 40 odfUtils = odf.OdfUtils, 41 /**@const*/ 42 NEXT = core.StepDirection.NEXT; 43 44 /** 45 * @return {undefined} 46 */ 47 function updatedCachedValues() { 48 var cursor = odtDocument.getCursor(inputMemberId), 49 cursorNode = cursor && cursor.getNode(), 50 newIsAnnotatable = false; 51 if (cursorNode) { 52 newIsAnnotatable = !odfUtils.isWithinAnnotation(cursorNode, odtDocument.getRootNode()); 53 } 54 55 if (newIsAnnotatable !== isAnnotatable) { 56 isAnnotatable = newIsAnnotatable; 57 eventNotifier.emit(gui.AnnotationController.annotatableChanged, isAnnotatable); 58 } 59 } 60 61 /** 62 * @param {!ops.OdtCursor} cursor 63 * @return {undefined} 64 */ 65 function onCursorAdded(cursor) { 66 if (cursor.getMemberId() === inputMemberId) { 67 updatedCachedValues(); 68 } 69 } 70 71 /** 72 * @param {!string} memberId 73 * @return {undefined} 74 */ 75 function onCursorRemoved(memberId) { 76 if (memberId === inputMemberId) { 77 updatedCachedValues(); 78 } 79 } 80 81 /** 82 * @param {!ops.OdtCursor} cursor 83 * @return {undefined} 84 */ 85 function onCursorMoved(cursor) { 86 if (cursor.getMemberId() === inputMemberId) { 87 updatedCachedValues(); 88 } 89 } 90 91 /** 92 * @return {!boolean} 93 */ 94 this.isAnnotatable = function () { 95 return isAnnotatable; 96 }; 97 98 /** 99 * Adds an annotation to the document based on the current selection 100 * @return {undefined} 101 */ 102 this.addAnnotation = function () { 103 var op = new ops.OpAddAnnotation(), 104 selection = odtDocument.getCursorSelection(inputMemberId), 105 length = selection.length, 106 position = selection.position; 107 108 if (!isAnnotatable) { 109 return; 110 } 111 112 position = length >= 0 ? position : position + length; 113 length = Math.abs(length); 114 115 op.init({ 116 memberid: inputMemberId, 117 position: position, 118 length: length, 119 name: inputMemberId + Date.now() 120 }); 121 session.enqueue([op]); 122 }; 123 124 125 /** 126 * @param {!Element} annotationNode 127 * @return {undefined} 128 */ 129 this.removeAnnotation = function(annotationNode) { 130 var startStep, endStep, op, moveCursor, 131 currentUserName = odtDocument.getMember(inputMemberId).getProperties().fullName; 132 133 if (sessionConstraints.getState(gui.CommonConstraints.EDIT.ANNOTATIONS.ONLY_DELETE_OWN) === true) { 134 if (currentUserName !== odfUtils.getAnnotationCreator(annotationNode)) { 135 return; 136 } 137 } 138 139 // round up to get the first step within the annotation node 140 startStep = odtDocument.convertDomPointToCursorStep(annotationNode, 0, NEXT); 141 // Will report the last walkable step within the annotation 142 endStep = odtDocument.convertDomPointToCursorStep(annotationNode, annotationNode.childNodes.length); 143 144 op = new ops.OpRemoveAnnotation(); 145 op.init({ 146 memberid: inputMemberId, 147 position: startStep, 148 length: endStep - startStep 149 }); 150 moveCursor = new ops.OpMoveCursor(); 151 moveCursor.init({ 152 memberid: inputMemberId, 153 position: startStep > 0 ? startStep - 1 : startStep, // Last position just before the annotation starts 154 length: 0 155 }); 156 session.enqueue([op, moveCursor]); 157 }; 158 159 /** 160 * @param {!string} eventid 161 * @param {!Function} cb 162 * @return {undefined} 163 */ 164 this.subscribe = function (eventid, cb) { 165 eventNotifier.subscribe(eventid, cb); 166 }; 167 168 /** 169 * @param {!string} eventid 170 * @param {!Function} cb 171 * @return {undefined} 172 */ 173 this.unsubscribe = function (eventid, cb) { 174 eventNotifier.unsubscribe(eventid, cb); 175 }; 176 177 /** 178 * @param {!function(!Error=)} callback, passing an error object in case of error 179 * @return {undefined} 180 */ 181 this.destroy = function(callback) { 182 odtDocument.unsubscribe(ops.Document.signalCursorAdded, onCursorAdded); 183 odtDocument.unsubscribe(ops.Document.signalCursorRemoved, onCursorRemoved); 184 odtDocument.unsubscribe(ops.Document.signalCursorMoved, onCursorMoved); 185 callback(); 186 }; 187 188 function init() { 189 sessionConstraints.registerConstraint(gui.CommonConstraints.EDIT.ANNOTATIONS.ONLY_DELETE_OWN); 190 191 odtDocument.subscribe(ops.Document.signalCursorAdded, onCursorAdded); 192 odtDocument.subscribe(ops.Document.signalCursorRemoved, onCursorRemoved); 193 odtDocument.subscribe(ops.Document.signalCursorMoved, onCursorMoved); 194 updatedCachedValues(); 195 } 196 197 init(); 198 }; 199 200 /**@const*/gui.AnnotationController.annotatableChanged = "annotatable/changed"; 201