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