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 if (length === 0) { 113 length = undefined; 114 } else { 115 position = length >= 0 ? position : position + length; 116 length = Math.abs(length); 117 } 118 119 op.init({ 120 memberid: inputMemberId, 121 position: position, 122 length: length, 123 name: inputMemberId + Date.now() 124 }); 125 session.enqueue([op]); 126 }; 127 128 129 /** 130 * @param {!Element} annotationNode 131 * @return {undefined} 132 */ 133 this.removeAnnotation = function(annotationNode) { 134 var startStep, endStep, op, moveCursor, 135 currentUserName = odtDocument.getMember(inputMemberId).getProperties().fullName; 136 137 if (sessionConstraints.getState(gui.CommonConstraints.EDIT.ANNOTATIONS.ONLY_DELETE_OWN) === true) { 138 if (currentUserName !== odfUtils.getAnnotationCreator(annotationNode)) { 139 return; 140 } 141 } 142 143 // round up to get the first step within the annotation node 144 startStep = odtDocument.convertDomPointToCursorStep(annotationNode, 0, NEXT); 145 // Will report the last walkable step within the annotation 146 endStep = odtDocument.convertDomPointToCursorStep(annotationNode, annotationNode.childNodes.length); 147 148 op = new ops.OpRemoveAnnotation(); 149 op.init({ 150 memberid: inputMemberId, 151 position: startStep, 152 length: endStep - startStep 153 }); 154 moveCursor = new ops.OpMoveCursor(); 155 moveCursor.init({ 156 memberid: inputMemberId, 157 position: startStep > 0 ? startStep - 1 : startStep, // Last position just before the annotation starts 158 length: 0 159 }); 160 session.enqueue([op, moveCursor]); 161 }; 162 163 /** 164 * @param {!string} eventid 165 * @param {!Function} cb 166 * @return {undefined} 167 */ 168 this.subscribe = function (eventid, cb) { 169 eventNotifier.subscribe(eventid, cb); 170 }; 171 172 /** 173 * @param {!string} eventid 174 * @param {!Function} cb 175 * @return {undefined} 176 */ 177 this.unsubscribe = function (eventid, cb) { 178 eventNotifier.unsubscribe(eventid, cb); 179 }; 180 181 /** 182 * @param {!function(!Error=)} callback, passing an error object in case of error 183 * @return {undefined} 184 */ 185 this.destroy = function(callback) { 186 odtDocument.unsubscribe(ops.Document.signalCursorAdded, onCursorAdded); 187 odtDocument.unsubscribe(ops.Document.signalCursorRemoved, onCursorRemoved); 188 odtDocument.unsubscribe(ops.Document.signalCursorMoved, onCursorMoved); 189 callback(); 190 }; 191 192 function init() { 193 sessionConstraints.registerConstraint(gui.CommonConstraints.EDIT.ANNOTATIONS.ONLY_DELETE_OWN); 194 195 odtDocument.subscribe(ops.Document.signalCursorAdded, onCursorAdded); 196 odtDocument.subscribe(ops.Document.signalCursorRemoved, onCursorRemoved); 197 odtDocument.subscribe(ops.Document.signalCursorMoved, onCursorMoved); 198 updatedCachedValues(); 199 } 200 201 init(); 202 }; 203 204 /**@const*/gui.AnnotationController.annotatableChanged = "annotatable/changed"; 205