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