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