1 /**
  2  * Copyright (C) 2012 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 core, ops, gui, runtime*/
 26 
 27 /**
 28  * @class
 29  * A cursor is a dom node that visually represents a cursor in a DOM tree.
 30  * It should stay synchronized with the selection in the document. When
 31  * there is only one collapsed selection range, a cursor should be shown at
 32  * that point.
 33  *
 34  * Putting the cursor in the DOM tree modifies the DOM, so care should be taken
 35  * to keep the selection consistent. If e.g. a selection is drawn over the
 36  * cursor, and the cursor is updated to the selection, the cursor is removed
 37  * from the DOM because the selection is not collapsed. This means that the
 38  * offsets of the selection may have to be changed.
 39  *
 40  * When the selection is collapsed, the cursor is placed after the point of the
 41  * selection and the selection will stay valid. However, if the cursor was
 42  * placed in the DOM tree and was counted in the offset, the offset in the
 43  * selection should be decreased by one.
 44  *
 45  * Even when the selection allows for a cursor, it might be desireable to hide
 46  * the cursor by not letting it be part of the DOM.
 47  *
 48  * @constructor
 49  * @param {!string} memberId The memberid this cursor is assigned to
 50  * @param {!ops.Document} document The document in which the cursor is placed
 51  */
 52 ops.OdtCursor = function OdtCursor(memberId, document) {
 53     "use strict";
 54     var self = this,
 55         validSelectionTypes = {},
 56         selectionType,
 57         /**@type{!core.Cursor}*/
 58         cursor,
 59         events = new core.EventNotifier([ops.OdtCursor.signalCursorUpdated]);
 60 
 61     /**
 62      * Remove the cursor from the document
 63      * @return {undefined}
 64      */
 65     this.removeFromDocument = function () {
 66         // TODO: find out if nodeAfterCursor, textNodeIncrease need to be dealt with in any way
 67         cursor.remove();
 68     };
 69 
 70     /**
 71      * Subscribe to cursor update events.
 72      *
 73      * The update event called whenever the cursor is moved around manually.
 74      * @param {!string} eventid
 75      * @param {!Function} cb
 76      */
 77     this.subscribe = function (eventid, cb) {
 78         events.subscribe(eventid, cb);
 79     };
 80 
 81     /**
 82      * Unsubscribe from cursor events
 83      * @param {!string} eventid
 84      * @param {!Function} cb
 85      */
 86     this.unsubscribe = function (eventid, cb) {
 87         events.unsubscribe(eventid, cb);
 88     };
 89 
 90     /**
 91      * Obtain the memberid the cursor is assigned to.
 92      * @return {string}
 93      */
 94     this.getMemberId = function () {
 95         return memberId;
 96     };
 97     /**
 98      * Obtain the node representing the cursor.
 99      * @return {!Element}
100      */
101     this.getNode = function () {
102         return cursor.getNode();
103     };
104     /**
105      * Obtain the node representing the selection start point.
106      * If a 0-length range is selected (e.g., by clicking without
107      * dragging),, this will return the exact same node as getNode
108      * @return {!Element}
109      */
110     this.getAnchorNode = function () {
111         return cursor.getAnchorNode();
112     };
113     /**
114      * Obtain the currently selected range to which the cursor corresponds.
115      * @return {!Range}
116      */
117     this.getSelectedRange = function () {
118         return cursor.getSelectedRange();
119     };
120     /** Set the given range as the selected range for this cursor
121      * @param {!Range} range,
122      * @param {boolean} isForwardSelection
123      * @return {undefined}
124      */
125     this.setSelectedRange = function (range, isForwardSelection) {
126         cursor.setSelectedRange(range, isForwardSelection);
127         events.emit(ops.OdtCursor.signalCursorUpdated, self);
128     };
129     /**
130      * Returns if the selection of this cursor has the
131      * same direction as the direction of the range
132      * @return {boolean}
133      */
134     this.hasForwardSelection = function () {
135         return cursor.hasForwardSelection();
136     };
137     /**
138      * Obtain the document to which the cursor corresponds.
139      * @return {!ops.Document}
140      */
141     this.getDocument = function () {
142         return document;
143     };
144 
145     /**
146      * Gets the current selection type.
147      * @return {!string}
148      */
149     this.getSelectionType = function () {
150         return selectionType;
151     };
152 
153     /**
154      * Sets the current selection type to the given value.
155      * @param {!string} value
156      * @return {undefined}
157      */
158     this.setSelectionType = function (value) {
159         if (validSelectionTypes.hasOwnProperty(value)) {
160             selectionType = value;
161         } else {
162             runtime.log("Invalid selection type: " + value);
163         }
164     };
165 
166     /**
167      * Reset selection type to default.
168      * @return {undefined}
169      */
170     this.resetSelectionType = function () {
171         self.setSelectionType(ops.OdtCursor.RangeSelection);
172     };
173 
174     function init() {
175         cursor = new core.Cursor(document.getDOMDocument(), memberId);
176 
177         validSelectionTypes[ops.OdtCursor.RangeSelection] = true;
178         validSelectionTypes[ops.OdtCursor.RegionSelection] = true;
179         self.resetSelectionType();
180     }
181 
182     init();
183 };
184 
185 /**@const
186    @type {!string} */
187 ops.OdtCursor.RangeSelection = 'Range';
188 /**@const
189    @type {!string} */
190 ops.OdtCursor.RegionSelection = 'Region';
191 /**@const
192  @type {!string} */
193 ops.OdtCursor.signalCursorUpdated = "cursorUpdated";
194